Change default CSS minifier optimising level and allow configuring it

This commit is contained in:
Wilson Lin 2023-02-16 19:23:03 +11:00
parent d4e0887465
commit ad21466d2c
11 changed files with 134 additions and 52 deletions

View File

@ -1,5 +1,10 @@
# minify-html changelog
## Pending
- Change the default CSS minifier optimisation level to 1, as higher levels may perform dangerous optimisations.
- Allow configuring the CSS minifier optimisation level.
## 0.10.8
- [Node.js] Fix assertion failure panic on invalid argument type.

View File

@ -31,6 +31,18 @@ struct Cli {
#[structopt(long)]
minify_css: bool,
/// Use optimisation level 1 for the CSS minifier.
#[structopt(long)]
minify_css_level_1: bool,
/// Use optimisation level 2 for the CSS minifier. May perform some dangerous optimisations.
#[structopt(long)]
minify_css_level_2: bool,
/// Use optimisation level 3 for the CSS minifier. May perform many dangerous optimisations.
#[structopt(long)]
minify_css_level_3: bool,
#[structopt(long)]
/// Do not minify DOCTYPEs. Minified DOCTYPEs may not be spec compliant.
do_not_minify_doctype: bool,
@ -92,6 +104,9 @@ fn main() {
keep_html_and_head_opening_tags: args.keep_html_and_head_opening_tags,
keep_spaces_between_attributes: args.keep_spaces_between_attributes,
minify_css: args.minify_css,
minify_css_level_1: args.minify_css_level_1,
minify_css_level_2: args.minify_css_level_2,
minify_css_level_3: args.minify_css_level_3,
minify_js: args.minify_js,
remove_bangs: args.remove_bangs,
remove_processing_instructions: args.remove_processing_instructions,

View File

@ -11,6 +11,9 @@ public class Configuration {
public final boolean keep_html_and_head_opening_tags;
public final boolean keep_spaces_between_attributes;
public final boolean minify_css;
public final boolean minify_css_level_1;
public final boolean minify_css_level_2;
public final boolean minify_css_level_3;
public final boolean minify_js;
public final boolean remove_bangs;
public final boolean remove_processing_instructions;
@ -23,6 +26,9 @@ public class Configuration {
boolean keep_html_and_head_opening_tags,
boolean keep_spaces_between_attributes,
boolean minify_css,
boolean minify_css_level_1,
boolean minify_css_level_2,
boolean minify_css_level_3,
boolean minify_js,
boolean remove_bangs,
boolean remove_processing_instructions
@ -34,6 +40,9 @@ public class Configuration {
this.keep_html_and_head_opening_tags = keep_html_and_head_opening_tags;
this.keep_spaces_between_attributes = keep_spaces_between_attributes;
this.minify_css = minify_css;
this.minify_css_level_1 = minify_css_level_1;
this.minify_css_level_2 = minify_css_level_2;
this.minify_css_level_3 = minify_css_level_3;
this.minify_js = minify_js;
this.remove_bangs = remove_bangs;
this.remove_processing_instructions = remove_processing_instructions;
@ -50,6 +59,9 @@ public class Configuration {
private boolean keep_html_and_head_opening_tags = false;
private boolean keep_spaces_between_attributes = false;
private boolean minify_css = false;
private boolean minify_css_level_1 = false;
private boolean minify_css_level_2 = false;
private boolean minify_css_level_3 = false;
private boolean minify_js = false;
private boolean remove_bangs = false;
private boolean remove_processing_instructions = false;
@ -89,6 +101,21 @@ public class Configuration {
return this;
}
public Builder setMinifyCssLevel1(boolean val) {
this.minify_css_level_1 = val;
return this;
}
public Builder setMinifyCssLevel2(boolean val) {
this.minify_css_level_2 = val;
return this;
}
public Builder setMinifyCssLevel3(boolean val) {
this.minify_css_level_3 = val;
return this;
}
public Builder setMinifyJs(boolean val) {
this.minify_js = val;
return this;
@ -114,6 +141,9 @@ public class Configuration {
this.keep_html_and_head_opening_tags,
this.keep_spaces_between_attributes,
this.minify_css,
this.minify_css_level_1,
this.minify_css_level_2,
this.minify_css_level_3,
this.minify_js,
this.remove_bangs,
this.remove_processing_instructions

12
nodejs/index.d.ts vendored
View File

@ -28,6 +28,18 @@ export function minify(
* If enabled, CSS in `<style>` tags and `style` attributes will be minified.
*/
minify_css?: boolean;
/**
* Use optimisation level 1 for the CSS minifier. This is currently the default, but may change in the future if higher levels become safe.
*/
minify_css_level_1?: boolean;
/**
* Use optimisation level 2 for the CSS minifier. This is mostly safe, but may perform some dangerous optimisations.
*/
minify_css_level_2?: boolean;
/**
* Use optimisation level 3 for the CSS minifier. This performs many dangerous optimisations, so ensure any input works with this level.
*/
minify_css_level_3?: boolean;
/** Remove all bangs. */
remove_bangs?: boolean;
/** Remove all processing_instructions. */

View File

@ -1,6 +1,14 @@
use neon::prelude::*;
use neon::types::buffer::TypedArray;
macro_rules! get_bool {
($cx:expr, $opt:expr, $name:literal) => {
$opt.get_opt::<JsBoolean, _, _>(&mut $cx, $name)?
.map(|v| v.value(&mut $cx))
.unwrap_or(false)
};
}
fn minify(mut cx: FunctionContext) -> JsResult<JsBuffer> {
let Ok(src) = cx.try_catch(|cx| cx.argument::<JsBuffer>(0)) else {
return cx.throw_type_error("the first argument is not a Buffer");
@ -9,46 +17,23 @@ fn minify(mut cx: FunctionContext) -> JsResult<JsBuffer> {
return cx.throw_type_error("the second argument is not an object");
};
let cfg = minify_html::Cfg {
do_not_minify_doctype: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "do_not_minify_doctype")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
ensure_spec_compliant_unquoted_attribute_values: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "ensure_spec_compliant_unquoted_attribute_values")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
keep_closing_tags: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "keep_closing_tags")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
keep_html_and_head_opening_tags: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "keep_html_and_head_opening_tags")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
keep_spaces_between_attributes: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "keep_spaces_between_attributes")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
keep_comments: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "keep_comments")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
minify_css: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "minify_css")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
minify_js: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "minify_js")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
remove_bangs: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "remove_bangs")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
remove_processing_instructions: opt
.get_opt::<JsBoolean, _, _>(&mut cx, "remove_processing_instructions")?
.map(|v| v.value(&mut cx))
.unwrap_or(false),
do_not_minify_doctype: get_bool!(cx, opt, "do_not_minify_doctype"),
ensure_spec_compliant_unquoted_attribute_values: get_bool!(
cx,
opt,
"ensure_spec_compliant_unquoted_attribute_values"
),
keep_closing_tags: get_bool!(cx, opt, "keep_closing_tags"),
keep_html_and_head_opening_tags: get_bool!(cx, opt, "keep_html_and_head_opening_tags"),
keep_spaces_between_attributes: get_bool!(cx, opt, "keep_spaces_between_attributes"),
keep_comments: get_bool!(cx, opt, "keep_comments"),
minify_css: get_bool!(cx, opt, "minify_css"),
minify_css_level_1: get_bool!(cx, opt, "minify_css_level_1"),
minify_css_level_2: get_bool!(cx, opt, "minify_css_level_2"),
minify_css_level_3: get_bool!(cx, opt, "minify_css_level_3"),
minify_js: get_bool!(cx, opt, "minify_js"),
remove_bangs: get_bool!(cx, opt, "remove_bangs"),
remove_processing_instructions: get_bool!(cx, opt, "remove_processing_instructions"),
};
let out = minify_html::minify(src.as_slice(&mut cx), &cfg);
Ok(JsBuffer::external(&mut cx, out))

View File

@ -25,6 +25,9 @@ fn minify(
keep_html_and_head_opening_tags: bool,
keep_spaces_between_attributes: bool,
minify_css: bool,
minify_css_level_1: bool,
minify_css_level_2: bool,
minify_css_level_3: bool,
minify_js: bool,
remove_bangs: bool,
remove_processing_instructions: bool,
@ -40,6 +43,9 @@ fn minify(
keep_html_and_head_opening_tags,
keep_spaces_between_attributes,
minify_css,
minify_css_level_1,
minify_css_level_2,
minify_css_level_3,
minify_js,
remove_bangs,
remove_processing_instructions,

View File

@ -36,6 +36,9 @@ methods! {
keep_html_and_head_opening_tags: get_cfg_hash_prop!(cfg_hash, "keep_html_and_head_opening_tags"),
keep_spaces_between_attributes: get_cfg_hash_prop!(cfg_hash, "keep_spaces_between_attributes"),
minify_css: get_cfg_hash_prop!(cfg_hash, "minify_css"),
minify_css_level_1: get_cfg_hash_prop!(cfg_hash, "minify_css_level_1"),
minify_css_level_2: get_cfg_hash_prop!(cfg_hash, "minify_css_level_2"),
minify_css_level_3: get_cfg_hash_prop!(cfg_hash, "minify_css_level_3"),
minify_js: get_cfg_hash_prop!(cfg_hash, "minify_js"),
remove_bangs: get_cfg_hash_prop!(cfg_hash, "remove_bangs"),
remove_processing_instructions: get_cfg_hash_prop!(cfg_hash, "remove_processing_instructions"),

View File

@ -50,8 +50,8 @@ impl Eq for AttrVal {}
#[derive(Eq, PartialEq, Debug)]
pub enum RcdataContentType {
Textarea,
Title,
Textarea,
Title,
}
// Derive Eq for testing.

View File

@ -14,9 +14,15 @@ pub struct Cfg {
pub keep_spaces_between_attributes: bool,
/// Keep all comments.
pub keep_comments: bool,
/// If enabled, CSS in `<style>` tags and `style` attributes are minified.
/// Minify CSS in `<style>` tags and `style` attributes using [https://github.com/Mnwa/css-minify](css-minify). By default, the optimisation level is 1 as specified by the CSS minifier, but this can be adjusted by the minify_css_level_* settings.
pub minify_css: bool,
/// If enabled, JavaScript in `<script>` tags are minified using
/// Use optimisation level 1 for the CSS minifier. This is currently the default, but may change in the future if higher levels become safe.
pub minify_css_level_1: bool,
/// Use optimisation level 2 for the CSS minifier. This is mostly safe, but may perform some dangerous optimisations.
pub minify_css_level_2: bool,
/// Use optimisation level 3 for the CSS minifier. This performs many dangerous optimisations, so ensure any input works with this level.
pub minify_css_level_3: bool,
/// Minify JavaScript in `<script>` tags using
/// [minify-js](https://github.com/wilsonzlin/minify-js).
///
/// Only `<script>` tags with a valid or no

View File

@ -6,7 +6,19 @@ use css_minify::optimizations::{Level, Minifier};
pub fn minify_css(cfg: &Cfg, out: &mut Vec<u8>, code: &[u8]) {
if cfg.minify_css {
let result = Minifier::default().minify(unsafe { from_utf8_unchecked(code) }, Level::Three);
let result = Minifier::default().minify(
unsafe { from_utf8_unchecked(code) },
if cfg.minify_css_level_1 {
Level::One
} else if cfg.minify_css_level_2 {
Level::Two
} else if cfg.minify_css_level_3 {
Level::Three
} else {
// Default to One, as other levels may perform dangerous optimisations.
Level::One
},
);
// TODO Collect error as warning.
if let Ok(min) = result {
if min.len() < code.len() {

View File

@ -1,4 +1,8 @@
use crate::{ast::RcdataContentType, tag::{TAG_TEXTAREA_END, TAG_TITLE_END}, entity::encode::encode_entities};
use crate::{
ast::RcdataContentType,
entity::encode::encode_entities,
tag::{TAG_TEXTAREA_END, TAG_TITLE_END},
};
pub fn minify_rcdata(out: &mut Vec<u8>, typ: RcdataContentType, text: &[u8]) {
// Encode entities, since they're still decoded by the browser.
@ -6,12 +10,16 @@ pub fn minify_rcdata(out: &mut Vec<u8>, typ: RcdataContentType, text: &[u8]) {
// Since the text has been decoded, there may be unintentional matches to end tags that we must escape.
let html = match typ {
RcdataContentType::Textarea => &*TAG_TEXTAREA_END,
RcdataContentType::Title => &*TAG_TITLE_END,
}.replace_all_bytes(&html, &[match typ {
RcdataContentType::Textarea => b"&LT/textarea".as_slice(),
RcdataContentType::Title => b"&LT/title".as_slice(),
}]);
RcdataContentType::Textarea => &*TAG_TEXTAREA_END,
RcdataContentType::Title => &*TAG_TITLE_END,
}
.replace_all_bytes(
&html,
&[match typ {
RcdataContentType::Textarea => b"&LT/textarea".as_slice(),
RcdataContentType::Title => b"&LT/title".as_slice(),
}],
);
out.extend_from_slice(&html);
}