Minify style attributes as well

This commit is contained in:
Wilson Lin 2021-08-07 18:59:54 +10:00
parent 4c0eb3ed28
commit 138de76de9
6 changed files with 65 additions and 28 deletions

View File

@ -1,6 +1,11 @@
use aho_corasick::{AhoCorasickBuilder, MatchKind};
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::gen::attrs::ATTRS;
use crate::gen::codepoints::DIGIT;
use crate::pattern::Replacer;
@ -8,6 +13,7 @@ use crate::spec::entity::encode::encode_entities;
use crate::spec::script::JAVASCRIPT_MIME_TYPES;
use crate::spec::tag::ns::Namespace;
use crate::whitespace::{collapse_whitespace, left_trim, right_trim};
use crate::Cfg;
fn build_double_quoted_replacer() -> Replacer {
let mut patterns = Vec::<Vec<u8>>::new();
@ -187,7 +193,13 @@ pub enum AttrMinified {
Value(AttrMinifiedValue),
}
pub fn minify_attr(ns: Namespace, tag: &[u8], name: &[u8], mut value_raw: Vec<u8>) -> AttrMinified {
pub fn minify_attr(
cfg: &Cfg,
ns: Namespace,
tag: &[u8],
name: &[u8],
mut value_raw: Vec<u8>,
) -> AttrMinified {
let attr_cfg = ATTRS.get(ns, tag, name);
let should_collapse_and_trim = attr_cfg.filter(|attr| attr.collapse_and_trim).is_some();
@ -203,6 +215,18 @@ pub fn minify_attr(ns: Namespace, tag: &[u8], name: &[u8], mut value_raw: Vec<u8
collapse_whitespace(&mut value_raw);
};
#[cfg(feature = "js-esbuild")]
if name == b"style" && cfg.minify_css {
let mut value_raw_min = Vec::new();
minify_using_esbuild(
&mut value_raw_min,
&value_raw,
&MINIFY_CSS_TRANSFORM_OPTIONS.clone(),
None,
);
value_raw = value_raw_min;
}
if (value_raw.is_empty() && redundant_if_empty)
|| default_value.filter(|dv| dv == &value_raw).is_some()
// TODO Cfg.

View File

@ -14,7 +14,7 @@ lazy_static! {
static ref STYLE_END: AhoCorasick = AhoCorasickBuilder::new()
.ascii_case_insensitive(true)
.build(&["</style"]);
static ref TRANSFORM_OPTIONS: Arc<TransformOptions> = {
pub static ref MINIFY_CSS_TRANSFORM_OPTIONS: Arc<TransformOptions> = {
let mut builder = TransformOptionsBuilder::new();
builder.loader = Loader::CSS;
builder.minify_identifiers = true;
@ -34,6 +34,11 @@ pub fn minify_css(cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
if !cfg.minify_css {
out.extend_from_slice(&code);
} else {
minify_using_esbuild(out, code, &TRANSFORM_OPTIONS.clone(), &STYLE_END);
minify_using_esbuild(
out,
code,
&MINIFY_CSS_TRANSFORM_OPTIONS.clone(),
Some(&STYLE_END),
);
}
}

View File

@ -46,7 +46,7 @@ pub fn minify_element(
let mut attrs_sorted = attributes.into_iter().collect::<Vec<_>>();
attrs_sorted.sort_unstable_by(|a, b| a.0.cmp(&b.0));
for (name, value) in attrs_sorted {
let min = minify_attr(ns, tag_name, &name, value);
let min = minify_attr(cfg, ns, tag_name, &name, value);
if let AttrMinified::Redundant = min {
continue;
};

View File

@ -3,38 +3,40 @@ use {aho_corasick::AhoCorasick, crossbeam::sync::WaitGroup, esbuild_rs::Transfor
#[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.
// `tag_to_escape` must be case insensitive.
// `tag_to_escape` must be case insensitive if provided.
pub fn minify_using_esbuild(
out: &mut Vec<u8>,
code: &[u8],
transform_options: &TransformOptions,
tag_to_escape: &'static AhoCorasick,
tag_to_escape: Option<&'static AhoCorasick>,
) {
let wg = WaitGroup::new();
unsafe {
let wg = wg.clone();
esbuild_rs::transform_direct_unmanaged(code, transform_options, move |result| {
// TODO (JS) Handle other forms:
// 1 < /script/.exec(a).length
// ` ${` ${a</script/} `} `
// // </script>
// /* </script>
// Considerations:
// - Need to parse strings (e.g. "", '', ``) so syntax within strings aren't mistakenly interpreted as code.
// - Need to be able to parse regex literals to determine string delimiters aren't actually characters in the regex.
// - Determining whether a slash is division or regex requires a full-blown JS parser to handle all cases (this is a well-known JS parsing problem).
// - `/</script` or `/</ script` are not valid JS so don't need to be handled.
// TODO (CSS) Are there other places that can have unintentional closing tags?
tag_to_escape.replace_all_with_bytes(
result.code.as_str().trim().as_bytes(),
out,
|_, orig, dst| {
dst.extend(b"<\\/");
// Keep original case.
dst.extend(&orig[2..]);
true
},
);
let min_code = result.code.as_str().trim().as_bytes();
match tag_to_escape {
None => out.extend_from_slice(min_code),
// TODO (JS) Handle other forms:
// 1 < /script/.exec(a).length
// ` ${` ${a</script/} `} `
// // </script>
// /* </script>
// Considerations:
// - Need to parse strings (e.g. "", '', ``) so syntax within strings aren't mistakenly interpreted as code.
// - Need to be able to parse regex literals to determine string delimiters aren't actually characters in the regex.
// - Determining whether a slash is division or regex requires a full-blown JS parser to handle all cases (this is a well-known JS parsing problem).
// - `/</script` or `/</ script` are not valid JS so don't need to be handled.
// TODO (CSS) Are there other places that can have unintentional closing tags?
Some(tag_to_escape) => {
tag_to_escape.replace_all_with_bytes(min_code, out, |_, orig, dst| {
dst.extend(b"<\\/");
// Keep original case.
dst.extend(&orig[2..]);
true
})
}
}
drop(wg);
});
};

View File

@ -33,6 +33,6 @@ pub fn minify_js(cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
if !cfg.minify_js {
out.extend_from_slice(&code);
} else {
minify_using_esbuild(out, code, &TRANSFORM_OPTIONS.clone(), &SCRIPT_END);
minify_using_esbuild(out, code, &TRANSFORM_OPTIONS.clone(), Some(&SCRIPT_END));
}
}

View File

@ -596,8 +596,14 @@ fn test_js_minification_unintentional_closing_tag() {
#[cfg(feature = "js-esbuild")]
#[test]
fn test_css_minification() {
// `<style>` contents.
eval_with_css_min(
b"<style>div { color: yellow }</style>",
b"<style>div{color:#ff0}</style>",
);
// `style` attributes.
eval_with_css_min(
br#"<div style="div { color: yellow }"></div>"#,
br#"<div style=div{color:#ff0}></div>"#,
);
}