CSS minification using esbuild

This commit is contained in:
Wilson Lin 2021-01-08 00:26:02 +11:00
commit e4d8b14d0b
26 changed files with 187 additions and 63 deletions

View file

@ -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"

View file

@ -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>

View file

@ -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.

View file

@ -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),

View file

@ -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();

View file

@ -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",

View file

@ -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 {

View file

@ -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,
});
});
}

View file

@ -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>

View file

@ -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);
}
}
}

View file

@ -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(),
}
}

View file

@ -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
View file

@ -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;
/**

View file

@ -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,
}))
}

View file

@ -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": [

View file

@ -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"

View file

@ -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))),

View file

@ -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"]

View file

@ -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)

View file

@ -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,
}

View file

@ -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) => {}

View file

@ -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);

View file

@ -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>");
}

View file

@ -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,
});

View file

@ -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(())
}

View file

@ -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,
};