Minify style attributes as well
This commit is contained in:
parent
4c0eb3ed28
commit
138de76de9
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>"#,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue