Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Pending

- Disable treeshake annotations (e.g., `/*#__PURE__*/`) in minified JavaScript output as they are only useful for bundlers, not inline scripts.
- Add `preserve_self_closing_on_unknown_tags` option to preserve self-closing syntax on unknown HTML elements.

## 0.18.1

Expand Down
2 changes: 1 addition & 1 deletion minify-html-common/src/spec/tag/whitespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static DEFAULT_SVG: &WhitespaceMinification = &WhitespaceMinification {
trim: true,
};

static HTML_TAG_WHITESPACE_MINIFICATION: Lazy<
pub static HTML_TAG_WHITESPACE_MINIFICATION: Lazy<
AHashMap<&'static [u8], &'static WhitespaceMinification>,
> = Lazy::new(|| {
let mut m = AHashMap::<&'static [u8], &'static WhitespaceMinification>::default();
Expand Down
2 changes: 2 additions & 0 deletions minify-html/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub struct Cfg {
pub preserve_brace_template_syntax: bool,
/// When `<%` is seen in content, all source code until the subsequent matching closing `%>` gets piped through untouched.
pub preserve_chevron_percent_template_syntax: bool,
/// Preserve self-closing syntax on unknown HTML elements (e.g. `<custom />`).
pub preserve_self_closing_on_unknown_tags: bool,
/// Remove all bangs.
pub remove_bangs: bool,
/// Remove all processing instructions.
Expand Down
5 changes: 3 additions & 2 deletions minify-html/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn minify(src: &[u8], cfg: &Cfg) -> Vec<u8> {
treat_brace_as_opaque: cfg.preserve_brace_template_syntax,
treat_chevron_percent_as_opaque: cfg.preserve_chevron_percent_template_syntax,
});
let parsed = parse_content(&mut code, Namespace::Html, EMPTY_SLICE, EMPTY_SLICE);
let parsed = parse_content(cfg, &mut code, Namespace::Html, EMPTY_SLICE, EMPTY_SLICE);
let mut out = Vec::with_capacity(src.len());
minify_content(
cfg,
Expand All @@ -58,7 +58,8 @@ pub fn minify(src: &[u8], cfg: &Cfg) -> Vec<u8> {

pub fn canonicalise<T: Write>(out: &mut T, src: &[u8]) -> std::io::Result<()> {
let mut code = Code::new(src);
let parsed = parse_content(&mut code, Namespace::Html, EMPTY_SLICE, EMPTY_SLICE);
let cfg = Cfg::new();
let parsed = parse_content(&cfg, &mut code, Namespace::Html, EMPTY_SLICE, EMPTY_SLICE);
for c in parsed.children {
c14n_serialise_ast(out, &c)?;
}
Expand Down
4 changes: 3 additions & 1 deletion minify-html/src/parse/content.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::ast::NodeData;
use crate::cfg::Cfg;
use crate::entity::decode::decode_entities;
use crate::parse::bang::parse_bang;
use crate::parse::comment::parse_comment;
Expand Down Expand Up @@ -169,6 +170,7 @@ pub struct ParsedContent {

// Use empty slice for `grandparent` or `parent` if none.
pub fn parse_content(
cfg: &Cfg,
code: &mut Code,
ns: Namespace,
grandparent: &[u8],
Expand Down Expand Up @@ -228,7 +230,7 @@ pub fn parse_content(
};
match typ {
Text => break,
OpeningTag => nodes.push(parse_element(code, ns, parent)),
OpeningTag => nodes.push(parse_element(cfg, code, ns, parent)),
ClosingTag => {
closing_tag_omitted = false;
break;
Expand Down
16 changes: 13 additions & 3 deletions minify-html/src/parse/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::ast::AttrVal;
use crate::ast::ElementClosingTag;
use crate::ast::NodeData;
use crate::ast::ScriptOrStyleLang;
use crate::cfg::Cfg;
use crate::entity::decode::decode_entities;
use crate::parse::content::parse_content;
use crate::parse::content::ParsedContent;
Expand All @@ -22,6 +23,7 @@ use minify_html_common::gen::codepoints::WHITESPACE_OR_SLASH_OR_EQUALS_OR_RIGHT_
use minify_html_common::spec::script::JAVASCRIPT_MIME_TYPES;
use minify_html_common::spec::tag::ns::Namespace;
use minify_html_common::spec::tag::void::VOID_TAGS;
use minify_html_common::spec::tag::whitespace::HTML_TAG_WHITESPACE_MINIFICATION;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::str::from_utf8;
Expand Down Expand Up @@ -131,7 +133,7 @@ pub fn parse_tag(code: &mut Code) -> ParsedTag {
}

// `<` must be next. `parent` should be an empty slice if it doesn't exist.
pub fn parse_element(code: &mut Code, ns: Namespace, parent: &[u8]) -> NodeData {
pub fn parse_element(cfg: &Cfg, code: &mut Code, ns: Namespace, parent: &[u8]) -> NodeData {
let ParsedTag {
name: elem_name,
attributes,
Expand All @@ -146,7 +148,15 @@ pub fn parse_element(code: &mut Code, ns: Namespace, parent: &[u8]) -> NodeData
};

// Only foreign elements can be self closed.
if self_closing && ns != Namespace::Html {
// However, if preserve_self_closing_on_unknown_tags is enabled,
// preserve self-closing syntax on unknown HTML elements as well.
let should_preserve_self_closing = cfg.preserve_self_closing_on_unknown_tags
&& !VOID_TAGS.contains(elem_name.as_slice())
&& HTML_TAG_WHITESPACE_MINIFICATION
.get(elem_name.as_slice())
.is_none();

if self_closing && (ns != Namespace::Html || should_preserve_self_closing) {
return NodeData::Element {
attributes,
children: Vec::new(),
Expand Down Expand Up @@ -183,7 +193,7 @@ pub fn parse_element(code: &mut Code, ns: Namespace, parent: &[u8]) -> NodeData
(_, b"style") => parse_style_content(code),
(Namespace::Html, b"textarea") => parse_textarea_content(code),
(Namespace::Html, b"title") => parse_title_content(code),
_ => parse_content(code, ns, parent, &elem_name),
_ => parse_content(cfg, code, ns, parent, &elem_name),
};

if !closing_tag_omitted {
Expand Down
4 changes: 3 additions & 1 deletion minify-html/src/parse/tests/element.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::ast::AttrVal;
use crate::ast::ElementClosingTag;
use crate::ast::NodeData;
use crate::cfg::Cfg;
use crate::parse::element::parse_element;
use crate::parse::element::parse_tag;
use crate::parse::element::ParsedTag;
Expand Down Expand Up @@ -52,7 +53,8 @@ fn test_parse_tag() {
#[test]
fn test_parse_element() {
let mut code = Code::new(br#"<a b=\"c\"></a>"#);
let elem = parse_element(&mut code, Namespace::Html, EMPTY_SLICE);
let cfg = Cfg::new();
let elem = parse_element(&cfg, &mut code, Namespace::Html, EMPTY_SLICE);
assert_eq!(elem, NodeData::Element {
attributes: {
let mut map = AHashMap::<Vec<u8>, AttrVal>::default();
Expand Down
18 changes: 18 additions & 0 deletions minify-html/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,21 @@ fn test_style_attr_minification() {
// `style` attributes are removed if fully minified away.
eval_with_css_min(br#"<div style=" /* */ "></div>"#, br#"<div></div>"#);
}

#[test]
fn test_preserve_self_closing_on_unknown_tags() {
let mut cfg = Cfg::new();
cfg.preserve_self_closing_on_unknown_tags = true;
eval_with_cfg(b"<custom />", b"<custom/>", &cfg);
eval_with_cfg(b"<custom class=\"foo\" />", b"<custom class=foo />", &cfg);
eval_with_cfg(b"<custom-element />", b"<custom-element/>", &cfg);
eval_with_cfg(b"<my-component />", b"<my-component/>", &cfg);
eval_with_cfg(b"<div />", b"<div>", &cfg);
eval_with_cfg(b"<div id=\"test\" />", b"<div id=test>", &cfg);
eval_with_cfg(b"<span />", b"<span>", &cfg);
eval_with_cfg(b"<input />", b"<input>", &cfg);
eval_with_cfg(b"<br />", b"<br>", &cfg);
eval_with_cfg(b"<img />", b"<img>", &cfg);
eval_with_cfg(b"<svg><path /></svg>", b"<svg><path/></svg>", &cfg);
eval_with_cfg(b"<svg><circle /></svg>", b"<svg><circle/></svg>", &cfg);
}