From 4f55459b013eeee8374ee9587e7d7c71d16262ec Mon Sep 17 00:00:00 2001
From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com>
Date: Wed, 20 Jul 2022 10:23:58 +0300
Subject: [PATCH] fix(html/minifier): Don't break unknown attributes (#5256)
---
crates/swc_html_minifier/Cargo.toml | 2 +-
crates/swc_html_minifier/src/lib.rs | 73 +--
.../fixture/attribute/boolean/input.html | 7 +-
.../fixture/attribute/boolean/output.min.html | 7 +-
.../{src => data}/entities.json | 0
.../data/html_elements_and_attributes.json | 533 ++++++++++++++++++
.../svg_elements_and_attributes.json | 0
.../src/html_default_attributes.json | 217 -------
crates/swc_html_utils/src/lib.rs | 15 +-
9 files changed, 576 insertions(+), 278 deletions(-)
rename crates/swc_html_utils/{src => data}/entities.json (100%)
create mode 100644 crates/swc_html_utils/data/html_elements_and_attributes.json
rename crates/swc_html_utils/{src => data}/svg_elements_and_attributes.json (100%)
delete mode 100644 crates/swc_html_utils/src/html_default_attributes.json
diff --git a/crates/swc_html_minifier/Cargo.toml b/crates/swc_html_minifier/Cargo.toml
index 1070137745d..730e6ca938a 100644
--- a/crates/swc_html_minifier/Cargo.toml
+++ b/crates/swc_html_minifier/Cargo.toml
@@ -6,7 +6,7 @@ authors = [
description = "HTML minifier"
documentation = "https://rustdoc.swc.rs/swc_html_minifier/"
edition = "2021"
-include = ["Cargo.toml", "src/**/*.rs"]
+include = ["Cargo.toml", "src/**/*.rs", "data/**/*.json"]
license = "Apache-2.0"
name = "swc_html_minifier"
repository = "https://github.com/swc-project/swc.git"
diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs
index c7a5a0bbd69..976cb2a0829 100644
--- a/crates/swc_html_minifier/src/lib.rs
+++ b/crates/swc_html_minifier/src/lib.rs
@@ -14,7 +14,7 @@ use swc_common::{
};
use swc_html_ast::*;
use swc_html_parser::parser::ParserConfig;
-use swc_html_utils::{HTML_DEFAULT_ATTRIBUTES, SVG_ELEMENTS_AND_ATTRIBUTES};
+use swc_html_utils::{HTML_ELEMENTS_AND_ATTRIBUTES, SVG_ELEMENTS_AND_ATTRIBUTES};
use swc_html_visit::{VisitMut, VisitMutWith};
use crate::option::{
@@ -23,53 +23,6 @@ use crate::option::{
};
pub mod option;
-static HTML_BOOLEAN_ATTRIBUTES: &[&str] = &[
- "allowfullscreen",
- "async",
- "autofocus",
- "autoplay",
- "checked",
- "controls",
- "default",
- "defer",
- "disabled",
- "formnovalidate",
- "hidden",
- "inert",
- "ismap",
- "itemscope",
- "loop",
- "multiple",
- "muted",
- "nomodule",
- "novalidate",
- "open",
- "playsinline",
- "readonly",
- "required",
- "reversed",
- "selected",
- // Legacy
- "declare",
- "defaultchecked",
- "defaultmuted",
- "defaultselected",
- "enabled",
- "compact",
- "indeterminate",
- "sortable",
- "nohref",
- "noresize",
- "noshade",
- "truespeed",
- "typemustmatch",
- "nowrap",
- "visible",
- "pauseonexit",
- "scoped",
- "seamless",
-];
-
// Global attributes
static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
"onabort",
@@ -410,8 +363,24 @@ impl Minifier<'_> {
EVENT_HANDLER_ATTRIBUTES.contains(&name)
}
- fn is_boolean_attribute(&self, name: &str) -> bool {
- HTML_BOOLEAN_ATTRIBUTES.contains(&name)
+ fn is_boolean_attribute(&self, element: &Element, name: &str) -> bool {
+ if let Some(global_pseudo_element) = HTML_ELEMENTS_AND_ATTRIBUTES.get("*") {
+ if let Some(element) = global_pseudo_element.other.get(name) {
+ if element.boolean.is_some() && element.boolean.unwrap() {
+ return true;
+ }
+ }
+ }
+
+ if let Some(element) = HTML_ELEMENTS_AND_ATTRIBUTES.get(&*element.tag_name) {
+ if let Some(element) = element.other.get(name) {
+ if element.boolean.is_some() && element.boolean.unwrap() {
+ return true;
+ }
+ }
+ }
+
+ false
}
fn is_trimable_separated_attribute(&self, element: &Element, attribute_name: &str) -> bool {
@@ -600,7 +569,7 @@ impl Minifier<'_> {
}
let default_attributes = if namespace == Namespace::HTML {
- &HTML_DEFAULT_ATTRIBUTES
+ &HTML_ELEMENTS_AND_ATTRIBUTES
} else {
&SVG_ELEMENTS_AND_ATTRIBUTES
};
@@ -2030,7 +1999,7 @@ impl VisitMut for Minifier<'_> {
if self.options.collapse_boolean_attributes
&& current_element.namespace == Namespace::HTML
- && self.is_boolean_attribute(&n.name)
+ && self.is_boolean_attribute(current_element, &n.name)
{
n.value = None;
diff --git a/crates/swc_html_minifier/tests/fixture/attribute/boolean/input.html b/crates/swc_html_minifier/tests/fixture/attribute/boolean/input.html
index 87c1ce3444c..429c5e268d3 100644
--- a/crates/swc_html_minifier/tests/fixture/attribute/boolean/input.html
+++ b/crates/swc_html_minifier/tests/fixture/attribute/boolean/input.html
@@ -22,7 +22,12 @@
-
+
+
+
+
+
+
diff --git a/crates/swc_html_minifier/tests/fixture/attribute/boolean/output.min.html b/crates/swc_html_minifier/tests/fixture/attribute/boolean/output.min.html
index a2fad74a47a..0442cd1ef56 100644
--- a/crates/swc_html_minifier/tests/fixture/attribute/boolean/output.min.html
+++ b/crates/swc_html_minifier/tests/fixture/attribute/boolean/output.min.html
@@ -15,7 +15,12 @@
-
+
+
+
+
+
+
diff --git a/crates/swc_html_utils/src/entities.json b/crates/swc_html_utils/data/entities.json
similarity index 100%
rename from crates/swc_html_utils/src/entities.json
rename to crates/swc_html_utils/data/entities.json
diff --git a/crates/swc_html_utils/data/html_elements_and_attributes.json b/crates/swc_html_utils/data/html_elements_and_attributes.json
new file mode 100644
index 00000000000..ef9cca44018
--- /dev/null
+++ b/crates/swc_html_utils/data/html_elements_and_attributes.json
@@ -0,0 +1,533 @@
+{
+ "*": {
+ "autofocus": {
+ "boolean": true
+ },
+ "hidden": {
+ "boolean": true
+ },
+ "inert": {
+ "boolean": true
+ },
+ "itemscope": {
+ "boolean": true
+ }
+ },
+ "a": {
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ },
+ "target": {
+ "initial": "_self"
+ }
+ },
+ "abbr": {},
+ "applet": {},
+ "area": {
+ "nohref": {
+ "boolean": true
+ },
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ },
+ "shape": {
+ "initial": "rect"
+ },
+ "target": {
+ "initial": "_self"
+ }
+ },
+ "article": {},
+ "aside": {},
+ "audio": {
+ "autoplay": {
+ "boolean": true
+ },
+ "controls": {
+ "boolean": true
+ },
+ "disableremoteplayback": {
+ "boolean": true
+ },
+ "loop": {
+ "boolean": true
+ },
+ "muted": {
+ "boolean": true
+ }
+ },
+ "b": {},
+ "base": {
+ "target": {
+ "initial": "_self"
+ }
+ },
+ "basefont": {},
+ "bdi": {},
+ "bdo": {},
+ "bgsound": {},
+ "big": {},
+ "blockquote": {},
+ "body": {},
+ "br": {
+ "clear": {
+ "initial": "none"
+ }
+ },
+ "button": {
+ "disabled": {
+ "boolean": true
+ },
+ "formnovalidate": {
+ "boolean": true
+ },
+ "type": {
+ "initial": "submit"
+ }
+ },
+ "canvas": {
+ "height": {
+ "initial": "150"
+ },
+ "width": {
+ "initial": "300"
+ }
+ },
+ "caption": {},
+ "center": {},
+ "cite": {},
+ "code": {},
+ "col": {
+ "span": {
+ "initial": "1"
+ }
+ },
+ "colgroup": {
+ "span": {
+ "initial": "1"
+ }
+ },
+ "data": {},
+ "datalist": {},
+ "dd": {},
+ "del": {},
+ "details": {
+ "open": {
+ "boolean": true
+ }
+ },
+ "dfn": {},
+ "dialog": {
+ "open": {
+ "boolean": true
+ }
+ },
+ "dir": {
+ "compact": {
+ "boolean": true
+ }
+ },
+ "div": {},
+ "dl": {
+ "compact": {
+ "boolean": true
+ }
+ },
+ "dt": {},
+ "em": {},
+ "embed": {},
+ "fieldset": {
+ "disabled": {
+ "boolean": true
+ }
+ },
+ "font": {},
+ "footer": {},
+ "form": {
+ "enctype": {
+ "initial": "application/x-www-form-urlencoded"
+ },
+ "method": {
+ "initial": "get"
+ },
+ "novalidate": {
+ "boolean": true
+ },
+ "target": {
+ "initial": "_self"
+ }
+ },
+ "frame": {
+ "noresize": {
+ "boolean": true
+ }
+ },
+ "frameset": {},
+ "h1": {},
+ "h2": {},
+ "h3": {},
+ "h4": {},
+ "h5": {},
+ "h6": {},
+ "head": {},
+ "header": {},
+ "hgroup": {},
+ "hr": {
+ "align": {
+ "initial": "center"
+ },
+ "noshade": {
+ "booean": true
+ },
+ "width": {
+ "initial": "100%"
+ },
+ "size": {
+ "initial": "2"
+ }
+ },
+ "html": {
+ "xmlns": {
+ "initial": "http://www.w3.org/1999/xhtml"
+ },
+ "xmlns:xlink": {
+ "initial": "http://www.w3.org/1999/xlink"
+ }
+ },
+ "i": {},
+ "iframe": {
+ "allowtransparency": {
+ "boolean": true
+ },
+ "allowfullscreen": {
+ "boolean": true
+ },
+ "fetchpriority": {
+ "initial": "auto"
+ },
+ "frameborder": {
+ "initial": "1"
+ },
+ "height": {
+ "initial": "150"
+ },
+ "hspace": {
+ "initial": "0"
+ },
+ "loading": {
+ "initial": "eager"
+ },
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ },
+ "scrolling": {
+ "initial": "auto"
+ },
+ "seamless": {
+ "boolean": true
+ },
+ "vspace": {
+ "initial": "0"
+ },
+ "width": {
+ "initial": "300"
+ }
+ },
+ "image": {},
+ "img": {
+ "decoding": {
+ "initial": "auto"
+ },
+ "fetchpriority": {
+ "initial": "auto"
+ },
+ "ismap": {
+ "boolean": true
+ },
+ "loading": {
+ "initial": "eager"
+ },
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ }
+ },
+ "input": {
+ "checked": {
+ "boolean": true
+ },
+ "disabled": {
+ "boolean": true
+ },
+ "ismap": {
+ "boolean": true
+ },
+ "multiple": {
+ "boolean": true
+ },
+ "formnovalidate": {
+ "boolean": true
+ },
+ "indeterminate": {
+ "boolean": true
+ },
+ "readonly": {
+ "boolean": true
+ },
+ "required": {
+ "boolean": true
+ },
+ "size": {
+ "initial": "20"
+ },
+ "type": {
+ "initial": "text"
+ }
+ },
+ "ins": {},
+ "isindex": {},
+ "kbd": {},
+ "keygen": {},
+ "label": {},
+ "legend": {},
+ "li": {},
+ "link": {
+ "disabled": {
+ "boolean": true
+ },
+ "fetchpriority": {
+ "initial": "auto"
+ },
+ "media": {
+ "initial": "all"
+ },
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ },
+ "type": {
+ "initial": "text/css"
+ }
+ },
+ "listing": {},
+ "map": {},
+ "mark": {},
+ "marquee": {
+ "scrollamount": {
+ "initial": "6"
+ },
+ "scrolldelay": {
+ "initial": "85"
+ },
+ "truespeed": {
+ "boolean": true
+ }
+ },
+ "menu": {
+ "compact": {
+ "boolean": true
+ }
+ },
+ "meta": {},
+ "meter": {
+ "min": {
+ "initial": "0"
+ }
+ },
+ "nav": {},
+ "nobr": {},
+ "noembed": {},
+ "noframes": {},
+ "noscript": {},
+ "object": {
+ "declare": {
+ "boolean": true
+ },
+ "typemustmatch": {
+ "boolean": true
+ }
+ },
+ "ol": {
+ "compact": {
+ "boolean": true
+ },
+ "reversed": {
+ "boolean": true
+ },
+ "start": {
+ "initial": "1"
+ },
+ "type": {
+ "initial": "1"
+ }
+ },
+ "optgroup": {
+ "disabled": {
+ "boolean": true
+ }
+ },
+ "option": {
+ "disabled": {
+ "boolean": true
+ },
+ "selected": {
+ "boolean": true
+ }
+ },
+ "output": {},
+ "p": {},
+ "param": {},
+ "picture": {},
+ "plaintext": {},
+ "pre": {},
+ "progress": {
+ "max": {
+ "initial": "1"
+ }
+ },
+ "q": {},
+ "rb": {},
+ "rp": {},
+ "rt": {},
+ "rtc": {},
+ "ruby": {},
+ "s": {},
+ "samp": {},
+ "script": {
+ "async": {
+ "boolean": true
+ },
+ "charset": {
+ "initial": "utf-8"
+ },
+ "defer": {
+ "boolean": true
+ },
+ "fetchpriority": {
+ "initial": "auto"
+ },
+ "language": {
+ "initial": "javascript"
+ },
+ "nomodule": {
+ "boolean": true
+ },
+ "referrerpolicy": {
+ "initial": "strict-origin-when-cross-origin"
+ },
+ "type": {
+ "initial": "text/javascript"
+ }
+ },
+ "section": {},
+ "select": {
+ "disabled": {
+ "boolean": true
+ },
+ "multiple": {
+ "boolean": true
+ },
+ "required": {
+ "boolean": true
+ }
+ },
+ "slot": {},
+ "small": {},
+ "source": {},
+ "span": {},
+ "strike": {},
+ "strong": {},
+ "style": {
+ "scoped": {
+ "boolean": true
+ },
+ "type": {
+ "initial": "text/css"
+ }
+ },
+ "sub": {},
+ "sup": {},
+ "table": {},
+ "tbody": {},
+ "td": {
+ "colspan": {
+ "initial": "1"
+ },
+ "nowrap": {
+ "boolean": true
+ },
+ "rowspan": {
+ "initial": "1"
+ }
+ },
+ "template": {},
+ "textarea": {
+ "disabled": {
+ "boolean": true
+ },
+ "cols": {
+ "initial": "20"
+ },
+ "readonly": {
+ "boolean": true
+ },
+ "required": {
+ "boolean": true
+ },
+ "rows": {
+ "initial": "2"
+ },
+ "wrap": {
+ "initial": "sort"
+ }
+ },
+ "tfoot": {},
+ "th": {
+ "colspan": {
+ "initial": "1"
+ },
+ "rowspan": {
+ "initial": "1"
+ }
+ },
+ "thead": {},
+ "time": {},
+ "title": {},
+ "tr": {
+ "nowrap": {
+ "boolean": true
+ }
+ },
+ "track": {
+ "default": {
+ "boolean": true
+ },
+ "kind": {
+ "initial": "subtitles"
+ }
+ },
+ "tt": {},
+ "ul": {
+ "compact": {
+ "boolean": true
+ }
+ },
+ "var": {},
+ "video": {
+ "autoplay": {
+ "boolean": true
+ },
+ "controls": {
+ "boolean": true
+ },
+ "loop": {
+ "boolean": true
+ },
+ "muted": {
+ "boolean": true
+ },
+ "playsinline": {
+ "boolean": true
+ }
+ },
+ "wbr": {},
+ "xmp": {}
+}
\ No newline at end of file
diff --git a/crates/swc_html_utils/src/svg_elements_and_attributes.json b/crates/swc_html_utils/data/svg_elements_and_attributes.json
similarity index 100%
rename from crates/swc_html_utils/src/svg_elements_and_attributes.json
rename to crates/swc_html_utils/data/svg_elements_and_attributes.json
diff --git a/crates/swc_html_utils/src/html_default_attributes.json b/crates/swc_html_utils/src/html_default_attributes.json
deleted file mode 100644
index dd1a9af22fd..00000000000
--- a/crates/swc_html_utils/src/html_default_attributes.json
+++ /dev/null
@@ -1,217 +0,0 @@
-{
- "a": {
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- },
- "target": {
- "initial": "_self"
- }
- },
- "area": {
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- },
- "shape": {
- "initial": "rect"
- },
- "target": {
- "initial": "_self"
- }
- },
- "base": {
- "target": {
- "initial": "_self"
- }
- },
- "br": {
- "clear": {
- "initial": "none"
- }
- },
- "button": {
- "type": {
- "initial": "submit"
- }
- },
- "canvas": {
- "height": {
- "initial": "150"
- },
- "width": {
- "initial": "300"
- }
- },
- "col": {
- "span": {
- "initial": "1"
- }
- },
- "colgroup": {
- "span": {
- "initial": "1"
- }
- },
- "form": {
- "enctype": {
- "initial": "application/x-www-form-urlencoded"
- },
- "method": {
- "initial": "get"
- },
- "target": {
- "initial": "_self"
- }
- },
- "hr": {
- "align": {
- "initial": "center"
- },
- "width": {
- "initial": "100%"
- }
- },
- "html": {
- "xmlns": {
- "initial": "http://www.w3.org/1999/xhtml"
- },
- "xmlns:xlink": {
- "initial": "http://www.w3.org/1999/xlink"
- }
- },
- "iframe": {
- "fetchpriority": {
- "initial": "auto"
- },
- "frameborder": {
- "initial": "1"
- },
- "height": {
- "initial": "150"
- },
- "loading": {
- "initial": "eager"
- },
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- },
- "width": {
- "initial": "300"
- }
- },
- "img": {
- "decoding": {
- "initial": "auto"
- },
- "fetchpriority": {
- "initial": "auto"
- },
- "loading": {
- "initial": "eager"
- },
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- }
- },
- "input": {
- "size": {
- "initial": "20"
- },
- "type": {
- "initial": "text"
- }
- },
- "link": {
- "fetchpriority": {
- "initial": "auto"
- },
- "media": {
- "initial": "all"
- },
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- },
- "type": {
- "initial": "text/css"
- }
- },
- "marquee": {
- "scrollamount": {
- "initial": "6"
- },
- "scrolldelay": {
- "initial": "85"
- }
- },
- "meter": {
- "min": {
- "initial": "0"
- }
- },
- "ol": {
- "type": {
- "initial": "1"
- },
- "start": {
- "initial": "1"
- }
- },
- "progress": {
- "max": {
- "initial": "1"
- }
- },
- "script": {
- "charset": {
- "initial": "utf-8"
- },
- "fetchpriority": {
- "initial": "auto"
- },
- "language": {
- "initial": "javascript"
- },
- "referrerpolicy": {
- "initial": "strict-origin-when-cross-origin"
- },
- "type": {
- "initial": "text/javascript"
- }
- },
- "style": {
- "type": {
- "initial": "text/css"
- }
- },
- "td": {
- "colspan": {
- "initial": "1"
- },
- "rowspan": {
- "initial": "1"
- }
- },
- "textarea": {
- "cols": {
- "initial": "20"
- },
- "rows": {
- "initial": "2"
- },
- "wrap": {
- "initial": "sort"
- }
- },
- "th": {
- "colspan": {
- "initial": "1"
- },
- "rowspan": {
- "initial": "1"
- }
- },
- "track": {
- "kind": {
- "initial": "subtitles"
- }
- }
-}
diff --git a/crates/swc_html_utils/src/lib.rs b/crates/swc_html_utils/src/lib.rs
index bee5b6edab2..ba1b22e019d 100644
--- a/crates/swc_html_utils/src/lib.rs
+++ b/crates/swc_html_utils/src/lib.rs
@@ -8,8 +8,9 @@ pub struct Entity {
}
pub static HTML_ENTITIES: Lazy> = Lazy::new(|| {
- let entities: AHashMap = serde_json::from_str(include_str!("./entities.json"))
- .expect("failed to parse entities.json for html entities");
+ let entities: AHashMap =
+ serde_json::from_str(include_str!("../data/entities.json"))
+ .expect("failed to parse entities.json for html entities");
entities
});
@@ -21,6 +22,8 @@ pub struct AttributeInfo {
pub initial: Option,
#[serde(default)]
pub inherited: Option,
+ #[serde(default)]
+ pub boolean: Option,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -30,17 +33,17 @@ pub struct Element {
pub other: AHashMap,
}
-pub static HTML_DEFAULT_ATTRIBUTES: Lazy> = Lazy::new(|| {
+pub static HTML_ELEMENTS_AND_ATTRIBUTES: Lazy> = Lazy::new(|| {
let default_attributes: AHashMap =
- serde_json::from_str(include_str!("./html_default_attributes.json"))
- .expect("failed to parse html_default_attributes.json for default attributes");
+ serde_json::from_str(include_str!("../data/html_elements_and_attributes.json"))
+ .expect("failed to parse html_elements_and_attributes.json for default attributes");
default_attributes
});
pub static SVG_ELEMENTS_AND_ATTRIBUTES: Lazy> = Lazy::new(|| {
let svg_elements_and_attributes: AHashMap =
- serde_json::from_str(include_str!("./svg_elements_and_attributes.json"))
+ serde_json::from_str(include_str!("../data/svg_elements_and_attributes.json"))
.expect("failed to parse svg_elements_and_attributes.json for default attributes");
svg_elements_and_attributes