Use minify-js as JS minifier
This commit is contained in:
parent
fd60983516
commit
c7d0652fbc
14
README.md
14
README.md
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ authors = ["Wilson Lin <code@wilsonl.in>"]
|
|||
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"
|
||||
|
||||
|
|
|
@ -7,5 +7,5 @@ authors = ["Wilson Lin <code@wilsonl.in>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
|
||||
minify-html = { path = "../rust/main" }
|
||||
structopt = "0.3"
|
||||
|
|
|
@ -6,7 +6,7 @@ authors = ["Wilson Lin <code@wilsonl.in>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
minify-html = { path = "../rust/main", features = ["js-esbuild"] }
|
||||
minify-html = { path = "../rust/main" }
|
||||
jni = "0.14.0"
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use crate::tests::eval;
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
use crate::tests::{eval_with_css_min, eval_with_js_min};
|
||||
|
||||
#[test]
|
||||
|
@ -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>",
|
||||
|
@ -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>",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use aho_corasick::{AhoCorasickBuilder, MatchKind};
|
||||
use lazy_static::lazy_static;
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
use {
|
||||
crate::minify::css::MINIFY_CSS_TRANSFORM_OPTIONS, crate::minify::esbuild::minify_using_esbuild,
|
||||
};
|
||||
use aho_corasick::{AhoCorasickBuilder, MatchKind};
|
||||
use css_minify::optimizations::{Level, Minifier};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::common::gen::attrs::ATTRS;
|
||||
use crate::common::gen::codepoints::DIGIT;
|
||||
|
@ -312,19 +310,16 @@ 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(),
|
||||
);
|
||||
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);
|
||||
|
@ -333,6 +328,7 @@ pub fn minify_attr(
|
|||
value_raw_wrapped_min.pop();
|
||||
};
|
||||
value_raw = value_raw_wrapped_min;
|
||||
};
|
||||
}
|
||||
|
||||
// Make lowercase before checking against default value or JAVASCRIPT_MIME_TYPES.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
#[cfg(feature = "js-esbuild")]
|
||||
use {crossbeam::sync::WaitGroup, esbuild_rs::TransformOptions};
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
// TODO The use of WG is ugly and we don't want to be multi-threaded; wait for Rust port esbuild-transform-rs.
|
||||
pub fn minify_using_esbuild(out: &mut Vec<u8>, code: &[u8], transform_options: &TransformOptions) {
|
||||
let wg = WaitGroup::new();
|
||||
unsafe {
|
||||
let wg = wg.clone();
|
||||
// esbuild now officially handles escaping `</script` and `</style`.
|
||||
esbuild_rs::transform_direct_unmanaged(code, transform_options, move |result| {
|
||||
let min_code = result.code.as_str().trim().as_bytes();
|
||||
out.extend_from_slice(min_code);
|
||||
drop(wg);
|
||||
});
|
||||
};
|
||||
wg.wait();
|
||||
}
|
|
@ -1,38 +1,18 @@
|
|||
#[cfg(feature = "js-esbuild")]
|
||||
use {
|
||||
crate::minify::esbuild::minify_using_esbuild,
|
||||
esbuild_rs::{Charset, LegalComments, SourceMap, TransformOptions, TransformOptionsBuilder},
|
||||
lazy_static::lazy_static,
|
||||
std::sync::Arc,
|
||||
};
|
||||
|
||||
use crate::cfg::Cfg;
|
||||
use crate::common::whitespace::trimmed;
|
||||
use minify_js::minify as minifier;
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
lazy_static! {
|
||||
static ref TRANSFORM_OPTIONS: Arc<TransformOptions> = {
|
||||
let mut builder = TransformOptionsBuilder::new();
|
||||
builder.charset = Charset::UTF8;
|
||||
builder.legal_comments = LegalComments::None;
|
||||
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_js(_cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
|
||||
out.extend_from_slice(trimmed(code));
|
||||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
pub fn minify_js(cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
|
||||
if !cfg.minify_js {
|
||||
out.extend_from_slice(trimmed(code));
|
||||
} else {
|
||||
minify_using_esbuild(out, code, &TRANSFORM_OPTIONS.clone());
|
||||
if cfg.minify_js {
|
||||
let source = code.to_vec();
|
||||
// TODO Write to `out` directly, but only if we can guarantee that the length will never exceed the input.
|
||||
let mut output = Vec::new();
|
||||
let result = minifier(source, &mut output);
|
||||
// TODO Collect error as warning.
|
||||
if !result.is_err() && output.len() < code.len() {
|
||||
out.extend_from_slice(output.as_slice());
|
||||
return;
|
||||
};
|
||||
}
|
||||
out.extend_from_slice(trimmed(code));
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ pub mod content;
|
|||
pub mod css;
|
||||
pub mod doctype;
|
||||
pub mod element;
|
||||
pub mod esbuild;
|
||||
pub mod instruction;
|
||||
pub mod js;
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -8,14 +8,12 @@ pub fn eval_with_cfg(src: &'static [u8], expected: &'static [u8], cfg: &Cfg) {
|
|||
assert_eq!(from_utf8(&min).unwrap(), from_utf8(expected).unwrap(),);
|
||||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
pub fn eval_with_js_min(src: &'static [u8], expected: &'static [u8]) -> () {
|
||||
let mut cfg = Cfg::new();
|
||||
cfg.minify_js = true;
|
||||
eval_with_cfg(src, expected, &cfg);
|
||||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
pub fn eval_with_css_min(src: &'static [u8], expected: &'static [u8]) -> () {
|
||||
let mut cfg = Cfg::new();
|
||||
cfg.minify_css = true;
|
||||
|
@ -146,12 +144,11 @@ fn test_viewport_attr_minification() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
#[test]
|
||||
fn test_style_attr_minification() {
|
||||
eval_with_css_min(
|
||||
br#"<div style="color: yellow;"></div>"#,
|
||||
br#"<div style=color:#ff0></div>"#,
|
||||
br#"<div style=color:yellow></div>"#,
|
||||
);
|
||||
// `style` attributes are removed if fully minified away.
|
||||
eval_with_css_min(br#"<div style=" /* */ "></div>"#, br#"<div></div>"#);
|
||||
|
|
2
version
2
version
|
@ -92,7 +92,7 @@ if (
|
|||
}
|
||||
cmd("git", "pull");
|
||||
cmd("bash", "./prebuild.sh");
|
||||
cmd("cargo", "test", "--features", "js-esbuild", { workingDir: RUST_MAIN_DIR });
|
||||
cmd("cargo", "test", { workingDir: RUST_MAIN_DIR });
|
||||
cmd("cargo", "test", "--features", "js-esbuild", {
|
||||
workingDir: RUST_ONEPASS_DIR,
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue