Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bindings for dart #136

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions dart/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
30 changes: 30 additions & 0 deletions dart/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.

include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types

# analyzer:
# exclude:
# - path/to/excluded/files/**

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
5 changes: 5 additions & 0 deletions dart/example/minify_html_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:minify_html/minify_html.dart';

void main(List<String> args) {
final minified = minifyHtml("<p> Hello, world! </p>");
}
3 changes: 3 additions & 0 deletions dart/lib/minify_html.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library minify_html;

export 'src/minify_html.dart';
71 changes: 71 additions & 0 deletions dart/lib/src/bindings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// ignore_for_file: non_constant_identifier_names, camel_case_types

import 'dart:ffi';

import 'package:ffi/ffi.dart';
import 'package:minify_html/src/locator.dart';

typedef minify_html_native = Int32 Function(
Pointer<Utf8> input,
Uint32 length,
Bool do_not_minify_doctype,
Bool ensure_spec_compliant_unquoted_attribute_values,
Bool keep_closing_tags,
Bool keep_html_and_head_opening_tags,
Bool keep_spaces_between_attributes,
Bool keep_comments,
Bool minify_css,
Bool minify_css_level_1,
Bool minify_css_level_2,
Bool minify_css_level_3,
Bool minify_js,
Bool remove_bangs,
Bool remove_processing_instructions,
);

typedef get_last_result_native = Pointer<Utf8> Function();

typedef clear_last_result_native = Void Function();

class MinifyHtmlBindings {
late DynamicLibrary _library;

late int Function(
Pointer<Utf8> input,
int length,
bool do_not_minify_doctype,
bool ensure_spec_compliant_unquoted_attribute_values,
bool keep_closing_tags,
bool keep_html_and_head_opening_tags,
bool keep_spaces_between_attributes,
bool keep_comments,
bool minify_css,
bool minify_css_level_1,
bool minify_css_level_2,
bool minify_css_level_3,
bool minify_js,
bool remove_bangs,
bool remove_processing_instructions,
) minifyHtml;

late Pointer<Utf8> Function() getLastResult;

late void Function() clearLastResult;

MinifyHtmlBindings() {
_library = loadDynamicLibrary();

minifyHtml = _library
.lookup<NativeFunction<minify_html_native>>("minify_html")
.asFunction();
getLastResult = _library
.lookup<NativeFunction<get_last_result_native>>("get_last_result")
.asFunction();
clearLastResult = _library
.lookup<NativeFunction<clear_last_result_native>>("clear_last_result")
.asFunction();
}
}

MinifyHtmlBindings? _cachedBindings;
MinifyHtmlBindings get bindings => _cachedBindings ??= MinifyHtmlBindings();
76 changes: 76 additions & 0 deletions dart/lib/src/locator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'dart:ffi';
import 'dart:io';

/// Attempts to locate the MinifyHtml dynamic library.
///
/// Throws [MinifyHtmlLocatorError] if the dynamic library could not be found.
DynamicLibrary loadDynamicLibrary() {
m-haisham marked this conversation as resolved.
Show resolved Hide resolved
if (Platform.isIOS) {
return DynamicLibrary.process();
} else if (Platform.isMacOS) {
return _locateOrError(appleLib);
} else if (Platform.isWindows) {
return _locate(windowsLib) ?? DynamicLibrary.executable();
} else if (Platform.isLinux) {
return _locateOrError(linuxLib);
} else if (Platform.isAndroid) {
return DynamicLibrary.open(linuxLib);
m-haisham marked this conversation as resolved.
Show resolved Hide resolved
} else if (Platform.isFuchsia) {
throw MinifyHtmlLocatorError(
'MinifyHtml is currently not supported on Fuchsia.',
);
} else {
throw MinifyHtmlLocatorError(
'MinifyHtml is currently not supported on this platform.',
);
}
}

/// This error is thrown when the dynamic library could not be found.
class MinifyHtmlLocatorError extends Error {
final String message;

MinifyHtmlLocatorError(
this.message,
);

@override
String toString() => 'MinifyHtmlLocatorError: $message';
}

/// The command that can be used to set up this package.
const invocationString = 'dart run minifyhtml:setup';

/// The expected name of the MinifyHtml library when compiled for Apple devices.
const appleLib = 'libminifyhtml.dylib';

/// The expected name of the MinifyHtml library when compiled for Linux devices.
const linuxLib = 'libminifyhtml.so';

/// The expected name of the MinifyHtml library when compiled for Windows devices.
const windowsLib = 'minifyhtml.dll';

const _minifyhtmlToolDir = '.dart_tool/minifyhtml/';

DynamicLibrary? _locate(String libName) {
if (FileSystemEntity.isFileSync(libName)) {
return DynamicLibrary.open(libName);
}

final toolLib =
Directory.current.uri.resolve("$_minifyhtmlToolDir$libName").toFilePath();
if (FileSystemEntity.isFileSync(toolLib)) {
return DynamicLibrary.open(toolLib);
}

return null;
}

DynamicLibrary _locateOrError(String libName) {
final value = _locate(libName);
if (value != null) {
return value;
} else {
throw MinifyHtmlLocatorError('MinifyHtml library not found');
}
}
37 changes: 37 additions & 0 deletions dart/lib/src/minify_html.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:ffi/ffi.dart';
import 'package:minify_html/src/bindings.dart';
import 'package:minify_html/src/models.dart';
import 'package:minify_html/src/resource.dart';

String minifyHtml(String source, [Cfg? cfg]) {
cfg = cfg ??= Cfg();

final srcC = Utf8Resource(source);

try {
final length = bindings.minifyHtml(
srcC.unsafe(),
srcC.length,
cfg.doNotMinifyDoctype,
cfg.ensureSpecCompliantUnquotedAttributeValues,
cfg.keepClosingTags,
cfg.keepHtmlAndHeadOpeningTags,
cfg.keepSpacesBetweenAttributes,
cfg.keepComments,
cfg.minifyCss,
cfg.minifyCssLevel1,
cfg.minifyCssLevel2,
cfg.minifyCssLevel3,
cfg.minifyJs,
cfg.removeBangs,
cfg.removeProcessingInstructions,
);

final value = bindings.getLastResult().toDartString(length: length);
bindings.clearLastResult();

return value;
} finally {
srcC.free();
}
}
38 changes: 38 additions & 0 deletions dart/lib/src/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Cfg {
const Cfg({
this.doNotMinifyDoctype = false,
this.ensureSpecCompliantUnquotedAttributeValues = false,
this.keepClosingTags = false,
this.keepHtmlAndHeadOpeningTags = false,
this.keepSpacesBetweenAttributes = false,
this.keepComments = false,
this.minifyCss = false,
this.minifyCssLevel1 = false,
this.minifyCssLevel2 = false,
this.minifyCssLevel3 = false,
this.minifyJs = false,
this.removeBangs = false,
this.removeProcessingInstructions = false,
});

const Cfg.specCompliant()
: this(
doNotMinifyDoctype: true,
ensureSpecCompliantUnquotedAttributeValues: true,
keepSpacesBetweenAttributes: true,
);

final bool doNotMinifyDoctype;
final bool ensureSpecCompliantUnquotedAttributeValues;
final bool keepClosingTags;
final bool keepHtmlAndHeadOpeningTags;
final bool keepSpacesBetweenAttributes;
final bool keepComments;
final bool minifyCss;
final bool minifyCssLevel1;
final bool minifyCssLevel2;
final bool minifyCssLevel3;
final bool minifyJs;
final bool removeBangs;
final bool removeProcessingInstructions;
}
37 changes: 37 additions & 0 deletions dart/lib/src/resource.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';

final DynamicLibrary stdlib = DynamicLibrary.process();
final posixFree = stdlib.lookup<NativeFunction<Void Function(Pointer)>>("free");

class Utf8Resource implements Finalizable {
static final NativeFinalizer _finalizer = NativeFinalizer(posixFree);

/// [_cString] must never escape [Utf8Resource], otherwise the
/// [_finalizer] will run prematurely.
late final Pointer<Utf8> _cString;
late final int length;

Utf8Resource(String source) {
final units = utf8.encode(source);
length = units.length;

final Pointer<Uint8> result = malloc<Uint8>(units.length);
final Uint8List nativeString = result.asTypedList(units.length);
nativeString.setAll(0, units);
_cString = result.cast();

_finalizer.attach(this, _cString.cast(), detach: this);
}

void free() {
_finalizer.detach(this);
calloc.free(_cString);
}

/// Ensure this [Utf8Resource] stays in scope longer than the inner resource.
Pointer<Utf8> unsafe() => _cString;
}
2 changes: 2 additions & 0 deletions dart/native/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
12 changes: 12 additions & 0 deletions dart/native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "minify-html-native"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "minifyhtml"
crate-type = ["cdylib"]

[dependencies]
minify-html = { version = "0.10.8", path = "../../rust/main" }
63 changes: 63 additions & 0 deletions dart/native/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::{cell::RefCell, ptr, slice};

use minify_html::Cfg;

thread_local! {
static LAST_RESULT: RefCell<Option<Vec<u8>>> = RefCell::new(None);
}

#[no_mangle]
pub extern "C" fn minify_html(
input: *const u8,
length: usize,
do_not_minify_doctype: bool,
ensure_spec_compliant_unquoted_attribute_values: bool,
keep_closing_tags: bool,
keep_html_and_head_opening_tags: bool,
keep_spaces_between_attributes: bool,
keep_comments: bool,
minify_css: bool,
minify_css_level_1: bool,
minify_css_level_2: bool,
minify_css_level_3: bool,
minify_js: bool,
remove_bangs: bool,
remove_processing_instructions: bool,
) -> usize {
let src = unsafe { slice::from_raw_parts(input, length) };

let cfg = Cfg {
do_not_minify_doctype,
ensure_spec_compliant_unquoted_attribute_values,
keep_closing_tags,
keep_html_and_head_opening_tags,
keep_spaces_between_attributes,
keep_comments,
minify_css,
minify_css_level_1,
minify_css_level_2,
minify_css_level_3,
minify_js,
remove_bangs,
remove_processing_instructions,
};

let result = minify_html::minify(src, &cfg);
let len = result.len();

LAST_RESULT.with(|v| *v.borrow_mut() = Some(result));
len
}

#[no_mangle]
pub extern "C" fn get_last_result() -> *const u8 {
LAST_RESULT.with(|prev| match prev.borrow().as_ref() {
Some(bytes) => bytes.as_ptr(),
None => return ptr::null(),
})
}

#[no_mangle]
pub extern "C" fn clear_last_result() {
LAST_RESULT.with(|value| *value.borrow_mut() = None)
}
Loading