Skip to content

Commit

Permalink
Use minify-js as JS minifier
Browse files Browse the repository at this point in the history
  • Loading branch information
wilsonzlin committed Jun 21, 2022
1 parent fd60983 commit c7d0652
Show file tree
Hide file tree
Showing 18 changed files with 79 additions and 168 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A Rust HTML minifier meticulously optimised for speed and effectiveness, with bi
- Advanced minification strategy beats other minifiers while being much faster.
- Uses SIMD searching, direct tries, and lookup tables.
- Handles [invalid HTML](./notes/Parsing.md), with extensive testing and [fuzzing](./fuzz).
- Natively binds to [esbuild](https://github.com/wilsonzlin/esbuild-rs) for super fast JS and CSS minification.
- Uses [minify-js](https://github.com/wilsonzlin/minify-js) for super fast JS minification.

## Performance

Expand Down Expand Up @@ -55,13 +55,9 @@ minify-html --output /path/to/output.min.html --keep-closing-tags --minify-css /

```toml
[dependencies]
minify-html = { version = "0.8.1", features = ["js-esbuild"] }
minify-html = { version = "0.8.1" }
```

Building with the `js-esbuild` feature requires the Go compiler to be installed as well, to build the [JS and CSS minifier](https://github.com/wilsonzlin/esbuild-rs).

If the `js-esbuild` feature is not enabled, `cfg.minify_js` and `cfg.minify_css` will have no effect.

### Use

Check out the [docs](https://docs.rs/minify-html) for API and usage examples.
Expand Down Expand Up @@ -190,7 +186,11 @@ All [`Cfg` fields](https://docs.rs/minify-html/latest/minify_html/struct.Cfg.htm

## Minification

Note that some of the minification done can result in HTML that will not pass validation, but remain interpreted and rendered correctly by the browser; essentially, the laxness of the browser is taken advantage of for better minification. These can be turned off via the `Cfg` object.
Note that some of the minification done can result in HTML that will not pass validation, but remain interpreted and rendered correctly by the browser; essentially, the laxness of the browser is taken advantage of for better minification. To prevent this, refer to these configuration options:

- `do_not_minify_doctype`
- `ensure_spec_compliant_unquoted_attribute_values`
- `keep_spaces_between_attributes`

### Whitespace

Expand Down
2 changes: 1 addition & 1 deletion bench/runners/minify-html/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Wilson Lin <[email protected]>"]
edition = "2018"

[dependencies]
minify-html = { path = "../../../rust/main", features = ["js-esbuild"] }
minify-html = { path = "../../../rust/main" }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.44"

Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ authors = ["Wilson Lin <[email protected]>"]
edition = "2018"

[dependencies]
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
minify-html = { path = "../rust/main" }
structopt = "0.3"
2 changes: 1 addition & 1 deletion java/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Wilson Lin <[email protected]>"]
edition = "2018"

[dependencies]
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
minify-html = { path = "../rust/main" }
jni = "0.14.0"

[lib]
Expand Down
4 changes: 2 additions & 2 deletions nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export function createConfiguration (options: {
/** Keep all comments. */
keep_comments?: boolean;
/**
* If enabled, content in `<script>` tags with a JS or no [MIME type](https://mimesniff.spec.whatwg.org/#javascript-mime-type) will be minified using [esbuild-rs](https://github.com/wilsonzlin/esbuild-rs).
* If enabled, content in `<script>` tags with a JS or no [MIME type](https://mimesniff.spec.whatwg.org/#javascript-mime-type) will be minified using [minify-js](https://github.com/wilsonzlin/minify-js).
*/
minify_js?: boolean;
/**
* If enabled, CSS in `<style>` tags will be minified using [esbuild-rs](https://github.com/wilsonzlin/esbuild-rs).
* If enabled, CSS in `<style>` tags and `style` attributes will be minified.
*/
minify_css?: boolean;
/** Remove all bangs. */
Expand Down
6 changes: 1 addition & 5 deletions nodejs/native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ edition = "2018"
name = "minify_html_ffi"
crate-type = ["staticlib"]

[features]
core = ["minify-html"]
js = ["minify-html/js-esbuild"]

[build-dependencies]
cbindgen = "0.14"

[dependencies]
libc = "0.2"
minify-html = { path = "../../rust/main", optional = true }
minify-html = { path = "../../rust/main" }
2 changes: 1 addition & 1 deletion python/Cargo.js.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "minify_html"
crate-type = ["cdylib"]

[dependencies]
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
minify-html = { path = "../rust/main" }
[dependencies.pyo3]
version = "0.13.0"
features = ["extension-module"]
2 changes: 1 addition & 1 deletion ruby/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ name = "minify_html_ruby_lib"
crate-type = ["cdylib"]

[dependencies]
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
minify-html = { path = "../rust/main" }
rutie = "0.7.0"
40 changes: 15 additions & 25 deletions rust/common/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::tests::eval;

#[cfg(feature = "js-esbuild")]
use crate::tests::{eval_with_css_min, eval_with_js_min};

#[test]
Expand Down Expand Up @@ -460,25 +458,19 @@ fn test_processing_instructions() {
eval(b"av<?xml 1.0 ?>g", b"av<?xml 1.0 ?>g");
}

#[cfg(feature = "js-esbuild")]
#[test]
fn test_js_minification() {
eval_with_js_min(b"<script>let a = 1;</script>", b"<script>let a=1;</script>");
eval_with_js_min(b"<script>let a = 1;</script>", b"<script>let a=1</script>");
eval_with_js_min(
b"<script type=text/javascript>let a = 1;</script>",
b"<script>let a=1;</script>",
);
// `export` statements are not allowed inline.
eval_with_js_min(
b"<script type=module>let a = 1; export a;</script>",
b"<script type=module></script>",
b"<script>let a=1</script>",
);
eval_with_js_min(
br#"
<script>let a = 1;</script>
<script>let b = 2;</script>
"#,
b"<script>let a=1;</script><script>let b=2;</script>",
b"<script>let a=1</script><script>let b=2</script>",
);
eval_with_js_min(
b"<scRIPt type=text/plain> alert(1.00000); </scripT>",
Expand All @@ -491,39 +483,37 @@ fn test_js_minification() {
let a = 1;
</script>
"#,
b"<script>let a=1;</script>",
b"<script>let a=1</script>",
);
}

#[cfg(feature = "js-esbuild")]
/* TODO Reenable once unintentional script closing tag escaping is implemented in minify-js.
#[test]
fn test_js_minification_unintentional_closing_tag() {
eval_with_js_min(
br#"<script>let a = "</" + "script>";</script>"#,
br#"<script>let a="<\/script>";</script>"#,
);
// TODO Reenable once esbuild handles closing tags case insensitively (evanw/esbuild#1509).
// eval_with_js_min(
// br#"<script>let a = "</S" + "cRiPT>";</script>"#,
// br#"<script>let a="<\/ScRiPT>";</script>"#,
// );
eval_with_js_min(
br#"<script>let a = "</S" + "cRiPT>";</script>"#,
br#"<script>let a="<\/ScRiPT>";</script>"#,
);
eval_with_js_min(
br#"<script>let a = "\u003c/script>";</script>"#,
br#"<script>let a="<\/script>";</script>"#,
);
// TODO Reenable once esbuild handles closing tags case insensitively (evanw/esbuild#1509).
// eval_with_js_min(
// br#"<script>let a = "\u003c/scrIPt>";</script>"#,
// br#"<script>let a="<\/scrIPt>";</script>"#,
// );
eval_with_js_min(
br#"<script>let a = "\u003c/scrIPt>";</script>"#,
br#"<script>let a="<\/scrIPt>";</script>"#,
);
}
*/

#[cfg(feature = "js-esbuild")]
#[test]
fn test_style_element_minification() {
// `<style>` contents.
eval_with_css_min(
b"<style>div { color: yellow }</style>",
b"<style>div{color:#ff0}</style>",
b"<style>div{color:yellow}</style>",
);
}
8 changes: 2 additions & 6 deletions rust/main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@ include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
[badges]
maintenance = { status = "actively-developed" }

[features]
default = []
js-esbuild = ["crossbeam", "esbuild-rs"]

[dependencies]
aho-corasick = "0.7"
crossbeam = { version = "0.7", optional = true }
esbuild-rs = { version = "0.13.8", optional = true }
css-minify = "0.2.2"
minify-js = "0.1.0"
lazy_static = "1.4"
memchr = "2"
7 changes: 2 additions & 5 deletions rust/main/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ pub struct Cfg {
pub keep_spaces_between_attributes: bool,
/// Keep all comments.
pub keep_comments: bool,
/// If enabled, CSS in `<style>` tags are minified using
/// [esbuild-rs](https://github.com/wilsonzlin/esbuild-rs). The `js-esbuild` feature must be
/// enabled; otherwise, this value has no effect.
/// If enabled, CSS in `<style>` tags and `style` attributes are minified.
pub minify_css: bool,
/// If enabled, JavaScript in `<script>` tags are minified using
/// [esbuild-rs](https://github.com/wilsonzlin/esbuild-rs). The `js-esbuild` feature must be
/// enabled; otherwise, this value has no effect.
/// [minify-js](https://github.com/wilsonzlin/minify-js).
///
/// Only `<script>` tags with a valid or no
/// [MIME type](https://mimesniff.spec.whatwg.org/#javascript-mime-type) is considered to
Expand Down
42 changes: 19 additions & 23 deletions rust/main/src/minify/attr.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use std::str::from_utf8_unchecked;

use aho_corasick::{AhoCorasickBuilder, MatchKind};
use css_minify::optimizations::{Level, Minifier};
use lazy_static::lazy_static;

#[cfg(feature = "js-esbuild")]
use {
crate::minify::css::MINIFY_CSS_TRANSFORM_OPTIONS, crate::minify::esbuild::minify_using_esbuild,
};

use crate::common::gen::attrs::ATTRS;
use crate::common::gen::codepoints::DIGIT;
use crate::common::pattern::Replacer;
Expand Down Expand Up @@ -312,27 +310,25 @@ pub fn minify_attr(
};
};

#[cfg(feature = "js-esbuild")]
if name == b"style" && cfg.minify_css {
let mut value_raw_wrapped = Vec::with_capacity(value_raw.len() + 3);
let mut value_raw_wrapped = String::with_capacity(value_raw.len() + 3);
// TODO This isn't safe for invalid input e.g. `a}/*`.
value_raw_wrapped.extend_from_slice(b"x{");
value_raw_wrapped.extend_from_slice(&value_raw);
value_raw_wrapped.push(b'}');
let mut value_raw_wrapped_min = Vec::with_capacity(value_raw_wrapped.len());
minify_using_esbuild(
&mut value_raw_wrapped_min,
&value_raw_wrapped,
&MINIFY_CSS_TRANSFORM_OPTIONS.clone(),
);
// TODO If input was invalid, wrapper syntax may not exist anymore.
if value_raw_wrapped_min.starts_with(b"x{") {
value_raw_wrapped_min.drain(0..2);
};
if value_raw_wrapped_min.ends_with(b"}") {
value_raw_wrapped_min.pop();
value_raw_wrapped.push_str("x{");
value_raw_wrapped.push_str(unsafe { from_utf8_unchecked(&value_raw) });
value_raw_wrapped.push('}');
let result = Minifier::default().minify(&value_raw_wrapped, Level::Three);
// TODO Collect error as warning.
if let Ok(min) = result {
let mut value_raw_wrapped_min = min.into_bytes();
// TODO If input was invalid, wrapper syntax may not exist anymore.
if value_raw_wrapped_min.starts_with(b"x{") {
value_raw_wrapped_min.drain(0..2);
};
if value_raw_wrapped_min.ends_with(b"}") {
value_raw_wrapped_min.pop();
};
value_raw = value_raw_wrapped_min;
};
value_raw = value_raw_wrapped_min;
}

// Make lowercase before checking against default value or JAVASCRIPT_MIME_TYPES.
Expand Down
46 changes: 12 additions & 34 deletions rust/main/src/minify/css.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,19 @@
#[cfg(feature = "js-esbuild")]
use {
crate::minify::esbuild::minify_using_esbuild,
esbuild_rs::{
Charset, LegalComments, Loader, SourceMap, TransformOptions, TransformOptionsBuilder,
},
lazy_static::lazy_static,
std::sync::Arc,
};
use std::str::from_utf8_unchecked;

use crate::cfg::Cfg;
use crate::common::whitespace::trimmed;
use css_minify::optimizations::{Level, Minifier};

#[cfg(feature = "js-esbuild")]
lazy_static! {
pub static ref MINIFY_CSS_TRANSFORM_OPTIONS: Arc<TransformOptions> = {
let mut builder = TransformOptionsBuilder::new();
builder.charset = Charset::UTF8;
builder.legal_comments = LegalComments::None;
builder.loader = Loader::CSS;
builder.minify_identifiers = true;
builder.minify_syntax = true;
builder.minify_whitespace = true;
builder.source_map = SourceMap::None;
builder.build()
};
}

#[cfg(not(feature = "js-esbuild"))]
pub fn minify_css(_cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
out.extend_from_slice(trimmed(code));
}

#[cfg(feature = "js-esbuild")]
pub fn minify_css(cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
if !cfg.minify_css {
out.extend_from_slice(trimmed(code));
} else {
minify_using_esbuild(out, code, &MINIFY_CSS_TRANSFORM_OPTIONS.clone());
if cfg.minify_css {
let result = Minifier::default().minify(unsafe { from_utf8_unchecked(code) }, Level::Three);
// TODO Collect error as warning.
if let Ok(min) = result {
if min.len() < code.len() {
out.extend_from_slice(min.as_bytes());
return;
};
};
}
out.extend_from_slice(trimmed(code));
}
18 changes: 0 additions & 18 deletions rust/main/src/minify/esbuild.rs

This file was deleted.

Loading

0 comments on commit c7d0652

Please sign in to comment.