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