CSS minification using esbuild
This commit is contained in:
parent
ad7a24f9bd
commit
e4d8b14d0b
26 changed files with 187 additions and 63 deletions
|
|
@ -1,10 +1,10 @@
|
|||
[package]
|
||||
name = "minify-html"
|
||||
description = "Fast and smart HTML + JS minifier"
|
||||
description = "Extremely fast and smart HTML + JS + CSS minifier"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/wilsonzlin/minify-html"
|
||||
readme = "README.md"
|
||||
keywords = ["html", "compress", "minifier", "minify", "minification"]
|
||||
keywords = ["html", "compress", "minifier", "js", "css"]
|
||||
categories = ["compression", "command-line-utilities", "development-tools::build-utils", "web-programming"]
|
||||
repository = "https://github.com/wilsonzlin/minify-html.git"
|
||||
version = "0.3.12"
|
||||
|
|
@ -22,6 +22,6 @@ js-esbuild = ["crossbeam", "esbuild-rs"]
|
|||
[dependencies]
|
||||
aho-corasick = "0.7"
|
||||
crossbeam = { version = "0.7", optional = true }
|
||||
esbuild-rs = { version = "0.2.1", optional = true }
|
||||
esbuild-rs = { version = "0.8.30", optional = true }
|
||||
lazy_static = "1.4"
|
||||
memchr = "2"
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -6,7 +6,7 @@ Comes with native bindings to Node.js, Python, Java, and Ruby.
|
|||
- Advanced minification strategy beats other minifiers with only one pass.
|
||||
- Uses zero memory allocations, SIMD searching, direct tries, and lookup tables.
|
||||
- Well tested with a large test suite and extensive [fuzzing](./fuzz).
|
||||
- Natively binds to [esbuild](https://github.com/wilsonzlin/esbuild-rs) for super fast JS minification.
|
||||
- Natively binds to [esbuild](https://github.com/wilsonzlin/esbuild-rs) for super fast JS and CSS minification.
|
||||
|
||||
## Performance
|
||||
|
||||
|
|
@ -46,9 +46,9 @@ minify-html --src /path/to/src.html --out /path/to/output.min.html
|
|||
minify-html = { version = "0.3.12", features = ["js-esbuild"] }
|
||||
```
|
||||
|
||||
Building with the `js-esbuild` feature requires the Go compiler to be installed as well, to build the [JS minifier](https://github.com/wilsonzlin/esbuild-rs).
|
||||
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` will have no effect.
|
||||
If the `js-esbuild` feature is not enabled, `cfg.minify_js` and `cfg.minify_css` will have no effect.
|
||||
|
||||
##### Use
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ yarn add @minify-html/js
|
|||
```js
|
||||
const minifyHtml = require("@minify-html/js");
|
||||
|
||||
const cfg = minifyHtml.createConfiguration({ minifyJs: false });
|
||||
const cfg = minifyHtml.createConfiguration({ minifyJs: false, minifyCss: false });
|
||||
const minified = minifyHtml.minify("<p> Hello, world! </p>", cfg);
|
||||
|
||||
// Alternatively, minify in place to avoid copying.
|
||||
|
|
@ -97,7 +97,7 @@ minify-html is also available for TypeScript:
|
|||
import * as minifyHtml from "@minify-html/js";
|
||||
import * as fs from "fs";
|
||||
|
||||
const cfg = minifyHtml.createConfiguration({ minifyJs: false });
|
||||
const cfg = minifyHtml.createConfiguration({ minifyJs: false, minifyCss: false });
|
||||
const minified = minifyHtml.minify("<p> Hello, world! </p>", cfg);
|
||||
// Or alternatively:
|
||||
const minified = minifyHtml.minifyInPlace(fs.readFileSync("source.html"), cfg);
|
||||
|
|
@ -133,6 +133,7 @@ import in.wilsonl.minifyhtml.SyntaxException;
|
|||
|
||||
Configuration cfg = new Configuration.Builder()
|
||||
.setMinifyJs(false)
|
||||
.setMinifyCss(false)
|
||||
.build();
|
||||
|
||||
try {
|
||||
|
|
@ -165,7 +166,7 @@ Add the PyPI project as a dependency and install it using `pip` or `pipenv`.
|
|||
import minify_html
|
||||
|
||||
try:
|
||||
minified = minify_html.minify("<p> Hello, world! </p>", minify_js=False)
|
||||
minified = minify_html.minify("<p> Hello, world! </p>", minify_js=False, minify_css=False)
|
||||
except SyntaxError as e:
|
||||
print(e)
|
||||
```
|
||||
|
|
@ -188,7 +189,7 @@ Add the library as a dependency to `Gemfile` or `*.gemspec`.
|
|||
```ruby
|
||||
require 'minify_html'
|
||||
|
||||
print MinifyHtml.minify("<p> Hello, world! </p>", { :minify_js => false })
|
||||
print MinifyHtml.minify("<p> Hello, world! </p>", { :minify_js => false, :minify_css => false })
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ Since speed depends on the input, speed charts show performance relative to the
|
|||
|
||||
The settings used for each minifier can be found in [minifiers.js](./minifiers.js). Some settings to note:
|
||||
|
||||
- CSS minification is disabled for all, as minify-html currently does not support CSS minification (coming soon).
|
||||
- All minifiers are configured to use esbuild for JS minification asynchronously and in parallel, similar to how minify-html works.
|
||||
- `conservativeCollapse` is enabled for html-minifier as otherwise some whitespace would be unsafely removed with side effects. minify-html can safely remove whitespace with context if configured properly.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
const cleanCss = require('clean-css');
|
||||
const esbuild = require('esbuild');
|
||||
const htmlMinifier = require('html-minifier');
|
||||
const minifyHtml = require('@minify-html/js');
|
||||
const minimize = require('minimize');
|
||||
|
||||
const testJsMinification = process.env.HTML_ONLY !== '1';
|
||||
const testJsAndCssMinification = process.env.HTML_ONLY !== '1';
|
||||
|
||||
const jsMime = new Set([
|
||||
undefined,
|
||||
|
|
@ -46,7 +47,10 @@ class EsbuildAsync {
|
|||
}
|
||||
}
|
||||
|
||||
const minifyHtmlCfg = minifyHtml.createConfiguration({minifyJs: testJsMinification});
|
||||
const minifyHtmlCfg = minifyHtml.createConfiguration({
|
||||
minifyJs: testJsAndCssMinification,
|
||||
minifyCss: testJsAndCssMinification,
|
||||
});
|
||||
const htmlMinifierCfg = {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseInlineTagWhitespace: true,
|
||||
|
|
@ -59,7 +63,8 @@ const htmlMinifierCfg = {
|
|||
decodeEntities: true,
|
||||
ignoreCustomComments: [],
|
||||
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
||||
// This will be set to a function if `testJsMinification` is true.
|
||||
// These will be set later if `testJsAndCssMinification` is true.
|
||||
minifyCSS: false,
|
||||
minifyJS: false,
|
||||
processConditionalComments: true,
|
||||
removeAttributeQuotes: true,
|
||||
|
|
@ -75,32 +80,42 @@ const htmlMinifierCfg = {
|
|||
|
||||
module.exports = {
|
||||
'@minify-html/js': (_, buffer) => minifyHtml.minifyInPlace(Buffer.from(buffer), minifyHtmlCfg),
|
||||
'html-minifier': testJsMinification
|
||||
'html-minifier': testJsAndCssMinification
|
||||
? async (content) => {
|
||||
const js = new EsbuildAsync();
|
||||
const res = htmlMinifier.minify(content, {
|
||||
...htmlMinifierCfg,
|
||||
minifyCSS: true,
|
||||
minifyJS: code => js.queue(code),
|
||||
});
|
||||
return js.finalise(res);
|
||||
}
|
||||
: content => htmlMinifier.minify(content, htmlMinifierCfg),
|
||||
'minimize': testJsMinification
|
||||
'minimize': testJsAndCssMinification
|
||||
? async (content) => {
|
||||
const js = new EsbuildAsync();
|
||||
const plugins = [];
|
||||
if (testJsMinification) {
|
||||
plugins.push({
|
||||
id: 'esbuild',
|
||||
element: (node, next) => {
|
||||
if (node.type === 'text' && node.parent && node.parent.type === 'script' && jsMime.has(node.parent.attribs.type)) {
|
||||
node.data = js.queue(node.data);
|
||||
}
|
||||
next();
|
||||
const css = new cleanCss({
|
||||
level: 2,
|
||||
inline: false,
|
||||
rebase: false,
|
||||
});
|
||||
const res = new minimize({
|
||||
plugins: [
|
||||
{
|
||||
id: 'esbuild',
|
||||
element: (node, next) => {
|
||||
if (node.type === 'text' && node.parent) {
|
||||
if (node.parent.type === 'script' && jsMime.has(node.parent.attribs.type)) {
|
||||
node.data = js.queue(node.data);
|
||||
} else if (node.parent.type === 'style') {
|
||||
node.data = css.minify(node.data).styles;
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
const res = new minimize({plugins}).parse(content);
|
||||
],
|
||||
}).parse(content);
|
||||
return js.finalise(res);
|
||||
}
|
||||
: content => new minimize().parse(content),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ fn main() {
|
|||
let mut data = source.to_vec();
|
||||
in_place(&mut data, &Cfg {
|
||||
minify_js: false,
|
||||
minify_css: false,
|
||||
}).unwrap();
|
||||
};
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"benchmark": "2.1.4",
|
||||
"chart.js": "^2.9.3",
|
||||
"chartjs-node": "^1.7.1",
|
||||
"clean-css": "^4.2.3",
|
||||
"esbuild": "^0.6.5",
|
||||
"html-minifier": "4.0.0",
|
||||
"minimize": "2.2.0",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ struct Cli {
|
|||
out: Option<std::path::PathBuf>,
|
||||
#[structopt(long)]
|
||||
js: bool,
|
||||
#[structopt(long)]
|
||||
css: bool,
|
||||
}
|
||||
|
||||
macro_rules! io_expect {
|
||||
|
|
@ -38,6 +40,7 @@ fn main() {
|
|||
io_expect!(src_file.read_to_end(&mut code), "could not load source code");
|
||||
match with_friendly_error(&mut code, &Cfg {
|
||||
minify_js: args.js,
|
||||
minify_css: args.css,
|
||||
}) {
|
||||
Ok(out_len) => {
|
||||
let mut out_file: Box<dyn Write> = match args.out {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ fn main() {
|
|||
let mut mut_data: Vec<u8> = data.iter().map(|x| *x).collect();
|
||||
let _ = in_place(&mut mut_data, &Cfg {
|
||||
minify_js: false,
|
||||
minify_css: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<version>0.3.12</version>
|
||||
|
||||
<name>minify-html</name>
|
||||
<description>Fast and smart HTML + JS minifier</description>
|
||||
<description>Extremely fast and smart HTML + JS + CSS minifier</description>
|
||||
<url>https://github.com/wilsonzlin/minify-html</url>
|
||||
<developers>
|
||||
<developer>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ package in.wilsonl.minifyhtml;
|
|||
*/
|
||||
public class Configuration {
|
||||
private final boolean minifyJs;
|
||||
private final boolean minifyCss;
|
||||
|
||||
public Configuration(boolean minifyJs) {
|
||||
public Configuration(boolean minifyJs, boolean minifyCss) {
|
||||
this.minifyJs = minifyJs;
|
||||
this.minifyCss = minifyCss;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -15,14 +17,20 @@ public class Configuration {
|
|||
*/
|
||||
public static class Builder {
|
||||
private boolean minifyJs = false;
|
||||
private boolean minifyCss = false;
|
||||
|
||||
public Builder setMinifyJs(boolean minifyJs) {
|
||||
this.minifyJs = minifyJs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMinifyCss(boolean minifyCss) {
|
||||
this.minifyCss = minifyCss;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Configuration build() {
|
||||
return new Configuration(this.minifyJs);
|
||||
return new Configuration(this.minifyJs, this.minifyCss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ fn build_cfg(
|
|||
) -> Cfg {
|
||||
Cfg {
|
||||
minify_js: env.get_field(*obj, "minifyJs", "Z").unwrap().z().unwrap(),
|
||||
minify_css: env.get_field(*obj, "minifyCss", "Z").unwrap().z().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,19 @@ napi_value node_method_create_configuration(napi_env env, napi_callback_info inf
|
|||
return undefined;
|
||||
}
|
||||
|
||||
Cfg const* cfg = ffi_create_cfg(minify_js);
|
||||
// Get `minifyCss` property.
|
||||
napi_value minify_css_value;
|
||||
if (napi_get_named_property(env, obj_arg, "minifyCss", &minify_css_value) != napi_ok) {
|
||||
assert_ok(napi_throw_type_error(env, NULL, "Failed to get minifyCss property"));
|
||||
return undefined;
|
||||
}
|
||||
bool minify_css;
|
||||
if (napi_get_value_bool(env, minify_css_value, &minify_css) != napi_ok) {
|
||||
assert_ok(napi_throw_type_error(env, NULL, "Failed to get minifyCss boolean property"));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Cfg const* cfg = ffi_create_cfg(minify_js, minify_css);
|
||||
|
||||
napi_value js_cfg;
|
||||
if (napi_create_external(env, (void*) cfg, js_cfg_finalizer, NULL, &js_cfg) != napi_ok) {
|
||||
|
|
|
|||
6
nodejs/index.d.ts
vendored
6
nodejs/index.d.ts
vendored
|
|
@ -11,7 +11,11 @@ export function createConfiguration (options: {
|
|||
/**
|
||||
* 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).
|
||||
*/
|
||||
minifyJs: false;
|
||||
minifyJs: boolean;
|
||||
/**
|
||||
* If enabled, CSS in `<style>` tags will be minified using [esbuild-rs](https://github.com/wilsonzlin/esbuild-rs).
|
||||
*/
|
||||
minifyCss: boolean;
|
||||
}): Cfg;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ use std::{mem, ptr, slice};
|
|||
use minify_html::{Cfg, Error, in_place};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ffi_create_cfg(minify_js: bool) -> *const Cfg {
|
||||
pub extern "C" fn ffi_create_cfg(minify_js: bool, minify_css: bool) -> *const Cfg {
|
||||
Box::into_raw(Box::new(Cfg {
|
||||
minify_js,
|
||||
minify_css,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@minify-html/js",
|
||||
"version": "0.3.12",
|
||||
"description": "Fast and smart HTML + JS minifier",
|
||||
"description": "Extremely fast and smart HTML + JS + CSS minifier",
|
||||
"main": "index.node",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
publish = false
|
||||
name = "minify_html"
|
||||
description = "Fast and smart HTML + JS minifier"
|
||||
description = "Extremely fast and smart HTML + JS + CSS minifier"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/wilsonzlin/minify-html"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ use pyo3::exceptions::PySyntaxError;
|
|||
use pyo3::wrap_pyfunction;
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
#[pyfunction(py_args="*", minify_js="false")]
|
||||
fn minify(code: String, minify_js: bool) -> PyResult<String> {
|
||||
#[pyfunction(py_args="*", minify_js="false", minify_css="false")]
|
||||
fn minify(code: String, minify_js: bool, minify_css: bool) -> PyResult<String> {
|
||||
let mut code = code.into_bytes();
|
||||
match minify_html_native(&mut code, &Cfg {
|
||||
minify_js,
|
||||
minify_css,
|
||||
}) {
|
||||
Ok(out_len) => Ok(unsafe { from_utf8_unchecked(&code[0..out_len]).to_string() }),
|
||||
Err(Error { error_type, position }) => Err(PySyntaxError::new_err(format!("{} [Character {}]", error_type.message(), position))),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|||
spec.email = ["code@wilsonl.in"]
|
||||
spec.license = "MIT"
|
||||
spec.files = FileList["lib/*", "README.md"].to_a
|
||||
spec.summary = "Fast and smart HTML + JS minifier"
|
||||
spec.summary = "Extremely fast and smart HTML + JS + CSS minifier"
|
||||
spec.homepage = "https://github.com/wilsonzlin/minify_html"
|
||||
|
||||
spec.require_paths = ["lib"]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ methods! {
|
|||
.map(|h| h.at(&Symbol::new("minify_js")))
|
||||
.and_then(|e| e.try_convert_to::<Boolean>())
|
||||
.map_or(false, |v| v.to_bool()),
|
||||
minify_css: cfg_hash
|
||||
.map(|h| h.at(&Symbol::new("minify_css")))
|
||||
.and_then(|e| e.try_convert_to::<Boolean>())
|
||||
.map_or(false, |v| v.to_bool()),
|
||||
};
|
||||
|
||||
minify_html_native(&mut code, cfg)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,9 @@ pub struct Cfg {
|
|||
/// [MIME type](https://mimesniff.spec.whatwg.org/#javascript-mime-type) is considered to
|
||||
/// contain JavaScript, as per the specification.
|
||||
pub minify_js: 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.
|
||||
pub minify_css: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ mod unit;
|
|||
/// let mut code = b"<p> Hello, world! </p>".to_vec();
|
||||
/// let cfg = &Cfg {
|
||||
/// minify_js: false,
|
||||
/// minify_css: false,
|
||||
/// };
|
||||
/// match in_place(&mut code, cfg) {
|
||||
/// Ok(minified_len) => assert_eq!(&code, b"<p>Hello, world!d! </p>"),
|
||||
|
|
@ -68,6 +69,7 @@ pub fn in_place(code: &mut [u8], cfg: &Cfg) -> Result<usize, Error> {
|
|||
/// let mut code = "<p> Hello, world! </p>".to_string();
|
||||
/// let cfg = &Cfg {
|
||||
/// minify_js: false,
|
||||
/// minify_css: false,
|
||||
/// };
|
||||
/// match in_place_str(&mut code, cfg) {
|
||||
/// Ok(minified_len) => assert_eq!(&code, "<p>Hello, world!d! </p>"),
|
||||
|
|
@ -97,6 +99,7 @@ pub fn in_place_str<'s>(code: &'s mut str, cfg: &Cfg) -> Result<&'s str, Error>
|
|||
/// let mut code = b"<p> Hello, world! </p>".to_vec();
|
||||
/// let cfg = &Cfg {
|
||||
/// minify_js: false,
|
||||
/// minify_css: false,
|
||||
/// };
|
||||
/// match truncate(&mut code, cfg) {
|
||||
/// Ok(()) => assert_eq!(code, b"<p>Hello, world!".to_vec()),
|
||||
|
|
@ -129,6 +132,7 @@ pub fn truncate(code: &mut Vec<u8>, cfg: &Cfg) -> Result<(), Error> {
|
|||
/// let mut code: &[u8] = b"<p> Hello, world! </p>";
|
||||
/// let cfg = &Cfg {
|
||||
/// minify_js: false,
|
||||
/// minify_css: false,
|
||||
/// };
|
||||
/// match copy(&code, cfg) {
|
||||
/// Ok(minified) => {
|
||||
|
|
@ -167,6 +171,7 @@ pub fn copy(code: &[u8], cfg: &Cfg) -> Result<Vec<u8>, Error> {
|
|||
/// let mut code = b"<p></div>".to_vec();
|
||||
/// let cfg = &Cfg {
|
||||
/// minify_js: false,
|
||||
/// minify_css: false,
|
||||
/// };
|
||||
/// match with_friendly_error(&mut code, cfg) {
|
||||
/// Ok(minified_len) => {}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ pub enum MatchAction {
|
|||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
pub struct JsMinSection {
|
||||
pub struct EsbuildSection {
|
||||
pub src: ProcessorRange,
|
||||
pub result: TransformResult,
|
||||
}
|
||||
|
|
@ -65,9 +65,9 @@ pub struct Processor<'d> {
|
|||
// Index of the next unwritten space.
|
||||
write_next: usize,
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
script_wg: WaitGroup,
|
||||
esbuild_wg: WaitGroup,
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
script_results: Arc<Mutex<Vec<JsMinSection>>>,
|
||||
esbuild_results: Arc<Mutex<Vec<EsbuildSection>>>,
|
||||
}
|
||||
|
||||
impl<'d> Index<ProcessorRange> for Processor<'d> {
|
||||
|
|
@ -97,9 +97,9 @@ impl<'d> Processor<'d> {
|
|||
read_next: 0,
|
||||
code,
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
script_wg: WaitGroup::new(),
|
||||
esbuild_wg: WaitGroup::new(),
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
script_results: Arc::new(Mutex::new(Vec::new())),
|
||||
esbuild_results: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,8 +353,8 @@ impl<'d> Processor<'d> {
|
|||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
#[inline(always)]
|
||||
pub fn new_script_section(&self) -> (WaitGroup, Arc<Mutex<Vec<JsMinSection>>>) {
|
||||
(self.script_wg.clone(), self.script_results.clone())
|
||||
pub fn new_esbuild_section(&self) -> (WaitGroup, Arc<Mutex<Vec<EsbuildSection>>>) {
|
||||
(self.esbuild_wg.clone(), self.esbuild_results.clone())
|
||||
}
|
||||
|
||||
// Since we consume the Processor, we must provide a full Error with positions.
|
||||
|
|
@ -370,32 +370,32 @@ impl<'d> Processor<'d> {
|
|||
#[inline(always)]
|
||||
pub fn finish(self) -> Result<usize, Error> {
|
||||
debug_assert!(self.at_end());
|
||||
self.script_wg.wait();
|
||||
let mut results = Arc::try_unwrap(self.script_results)
|
||||
.unwrap_or_else(|_| panic!("failed to acquire script results"))
|
||||
self.esbuild_wg.wait();
|
||||
let mut results = Arc::try_unwrap(self.esbuild_results)
|
||||
.unwrap_or_else(|_| panic!("failed to acquire esbuild results"))
|
||||
.into_inner()
|
||||
.unwrap();
|
||||
results.sort_unstable_by_key(|r| r.src.start);
|
||||
// As we write minified JS code for sections from left to right, we will be shifting code
|
||||
// towards the left as previous source JS code sections shrink. We need to keep track of
|
||||
// As we write minified JS/CSS code for sections from left to right, we will be shifting code
|
||||
// towards the left as previous source JS/CSS code sections shrink. We need to keep track of
|
||||
// the write pointer after previous compaction.
|
||||
// If there are no script sections, then we get self.write_next which will be returned.
|
||||
let mut write_next = results.get(0).map_or(self.write_next, |r| r.src.start);
|
||||
for (i, JsMinSection { result, src }) in results.iter().enumerate() {
|
||||
// Resulting minified JS to write.
|
||||
for (i, EsbuildSection { result, src }) in results.iter().enumerate() {
|
||||
// Resulting minified JS/CSS to write.
|
||||
// TODO Verify.
|
||||
// TODO Rewrite these in esbuild fork so we don't have to do a memcpy and search+replace.
|
||||
let min_js = result.js.as_str().trim().replace("</script", "<\\/script");
|
||||
let js_len = if min_js.len() < src.len() {
|
||||
self.code[write_next..write_next + min_js.len()].copy_from_slice(min_js.as_bytes());
|
||||
min_js.len()
|
||||
let min_code = result.code.as_str().trim().replace("</script", "<\\/script");
|
||||
let min_len = if min_code.len() < src.len() {
|
||||
self.code[write_next..write_next + min_code.len()].copy_from_slice(min_code.as_bytes());
|
||||
min_code.len()
|
||||
} else {
|
||||
// If minified result is actually longer than source, then write source instead.
|
||||
// NOTE: We still need to write source as previous iterations may have shifted code down.
|
||||
self.code.copy_within(src.start..src.end, write_next);
|
||||
src.len()
|
||||
};
|
||||
let write_end = write_next + js_len;
|
||||
let write_end = write_next + min_len;
|
||||
let next_start = results.get(i + 1).map_or(self.write_next, |r| r.src.start);
|
||||
self.code.copy_within(src.end..next_start, write_end);
|
||||
write_next = write_end + (next_start - src.end);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ fn _eval_error(src: &'static [u8], expected: ErrorType, cfg: &super::Cfg) -> ()
|
|||
fn eval(src: &'static [u8], expected: &'static [u8]) -> () {
|
||||
_eval(src, expected, &super::Cfg {
|
||||
minify_js: false,
|
||||
minify_css: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ fn eval(src: &'static [u8], expected: &'static [u8]) -> () {
|
|||
fn eval_error(src: &'static [u8], expected: ErrorType) -> () {
|
||||
_eval_error(src, expected, &super::Cfg {
|
||||
minify_js: false,
|
||||
minify_css: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +45,16 @@ fn eval_error(src: &'static [u8], expected: ErrorType) -> () {
|
|||
fn eval_with_js_min(src: &'static [u8], expected: &'static [u8]) -> () {
|
||||
_eval(src, expected, &super::Cfg {
|
||||
minify_js: true,
|
||||
minify_css: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
fn eval_with_css_min(src: &'static [u8], expected: &'static [u8]) -> () {
|
||||
_eval(src, expected, &super::Cfg {
|
||||
minify_js: false,
|
||||
minify_css: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -402,3 +414,9 @@ fn test_js_minification() {
|
|||
"#, b"<script>let a=1;</script><script>let b=2;</script>");
|
||||
eval_with_js_min(b"<scRIPt type=text/plain> alert(1.00000); </scripT>", b"<script type=text/plain> alert(1.00000); </script>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
#[test]
|
||||
fn test_css_minification() {
|
||||
eval_with_css_min(b"<style>div { color: yellow }</style>", b"<style>div{color:#ff0}</style>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::proc::Processor;
|
|||
use {
|
||||
std::sync::Arc,
|
||||
esbuild_rs::{TransformOptionsBuilder, TransformOptions},
|
||||
crate::proc::JsMinSection,
|
||||
crate::proc::EsbuildSection,
|
||||
crate::proc::checkpoint::WriteCheckpoint,
|
||||
};
|
||||
|
||||
|
|
@ -36,14 +36,15 @@ pub fn process_script(proc: &mut Processor, cfg: &Cfg, js: bool) -> ProcessingRe
|
|||
proc.m(WhileNotSeq(&SCRIPT_END), Keep);
|
||||
// `process_tag` will require closing tag.
|
||||
|
||||
// TODO This is copied from style.rs.
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
if js && cfg.minify_js {
|
||||
let (wg, results) = proc.new_script_section();
|
||||
let (wg, results) = proc.new_esbuild_section();
|
||||
let src = start.written_range(proc);
|
||||
unsafe {
|
||||
esbuild_rs::transform_direct_unmanaged(&proc[src], &TRANSFORM_OPTIONS.clone(), move |result| {
|
||||
let mut guard = results.lock().unwrap();
|
||||
guard.push(JsMinSection {
|
||||
guard.push(EsbuildSection {
|
||||
src,
|
||||
result,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,16 +4,59 @@ use crate::err::ProcessingResult;
|
|||
use crate::proc::MatchAction::*;
|
||||
use crate::proc::MatchMode::*;
|
||||
use crate::proc::Processor;
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
use {
|
||||
std::sync::Arc,
|
||||
esbuild_rs::{Loader, TransformOptionsBuilder, TransformOptions},
|
||||
crate::proc::EsbuildSection,
|
||||
crate::proc::checkpoint::WriteCheckpoint,
|
||||
};
|
||||
use crate::Cfg;
|
||||
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
lazy_static! {
|
||||
static ref TRANSFORM_OPTIONS: Arc<TransformOptions> = {
|
||||
let mut builder = TransformOptionsBuilder::new();
|
||||
builder.loader = Loader::CSS;
|
||||
builder.minify_identifiers = true;
|
||||
builder.minify_syntax = true;
|
||||
builder.minify_whitespace = true;
|
||||
builder.build()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref STYLE_END: AhoCorasick = AhoCorasickBuilder::new().ascii_case_insensitive(true).build(&["</style"]);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn process_style(proc: &mut Processor) -> ProcessingResult<()> {
|
||||
pub fn process_style(proc: &mut Processor, cfg: &Cfg) -> ProcessingResult<()> {
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
let start = WriteCheckpoint::new(proc);
|
||||
proc.require_not_at_end()?;
|
||||
proc.m(WhileNotSeq(&STYLE_END), Keep);
|
||||
// `process_tag` will require closing tag.
|
||||
|
||||
// TODO This is copied from script.rs.
|
||||
#[cfg(feature = "js-esbuild")]
|
||||
if cfg.minify_css {
|
||||
let (wg, results) = proc.new_esbuild_section();
|
||||
let src = start.written_range(proc);
|
||||
unsafe {
|
||||
esbuild_rs::transform_direct_unmanaged(&proc[src], &TRANSFORM_OPTIONS.clone(), move |result| {
|
||||
let mut guard = results.lock().unwrap();
|
||||
guard.push(EsbuildSection {
|
||||
src,
|
||||
result,
|
||||
});
|
||||
// Drop Arc reference and Mutex guard before marking task as complete as it's possible proc::finish
|
||||
// waiting on WaitGroup will resume before Arc/Mutex is dropped after exiting this function.
|
||||
drop(guard);
|
||||
drop(results);
|
||||
drop(wg);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ pub fn process_tag(
|
|||
match tag_type {
|
||||
TagType::ScriptData => process_script(proc, cfg, false)?,
|
||||
TagType::ScriptJs => process_script(proc, cfg, true)?,
|
||||
TagType::Style => process_style(proc)?,
|
||||
TagType::Style => process_style(proc, cfg)?,
|
||||
_ => closing_tag_omitted = process_content(proc, cfg, child_ns, Some(tag_name))?.closing_tag_omitted,
|
||||
};
|
||||
|
||||
|
|
|
|||
Reference in a new issue