diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs
index af0f67fa8098..8ec3bf52aba4 100644
--- a/crates/swc_html_minifier/src/lib.rs
+++ b/crates/swc_html_minifier/src/lib.rs
@@ -608,17 +608,15 @@ impl Minifier<'_> {
}
}
- fn is_default_attribute_value(
- &self,
- namespace: Namespace,
- tag_name: &JsWord,
- attribute: &Attribute,
- ) -> bool {
- let attribute_value = attribute.value.as_ref().unwrap();
+ fn is_default_attribute_value(&self, element: &Element, attribute: &Attribute) -> bool {
+ let attribute_value = match &attribute.value {
+ Some(value) => value,
+ _ => return false,
+ };
- match namespace {
+ match element.namespace {
Namespace::HTML | Namespace::SVG => {
- match *tag_name {
+ match element.tag_name {
js_word!("html") => match attribute.name {
js_word!("xmlns") => {
if &*attribute_value.trim().to_ascii_lowercase()
@@ -671,7 +669,7 @@ impl Minifier<'_> {
_ => {}
}
- let default_attributes = if namespace == Namespace::HTML {
+ let default_attributes = if element.namespace == Namespace::HTML {
&HTML_ELEMENTS_AND_ATTRIBUTES
} else {
&SVG_ELEMENTS_AND_ATTRIBUTES
@@ -691,7 +689,7 @@ impl Minifier<'_> {
};
let normalized_value = attribute_value.trim();
- let attributes = match default_attributes.get(tag_name) {
+ let attributes = match default_attributes.get(&element.tag_name) {
Some(element) => element,
None => return false,
};
@@ -715,15 +713,15 @@ impl Minifier<'_> {
// It it safe to remove svg redundant attributes, they used for
// styling
- if namespace == Namespace::SVG {
+ if element.namespace == Namespace::SVG {
return true;
}
// It it safe to remove redundant attributes for metadata
// elements
- if namespace == Namespace::HTML
+ if element.namespace == Namespace::HTML
&& matches!(
- *tag_name,
+ element.tag_name,
js_word!("base")
| js_word!("link")
| js_word!("noscript")
@@ -747,8 +745,8 @@ impl Minifier<'_> {
_ => {
matches!(
(
- namespace,
- tag_name,
+ element.namespace,
+ &element.tag_name,
&attribute.name,
attribute_value.to_ascii_lowercase().trim()
),
@@ -798,9 +796,9 @@ impl Minifier<'_> {
!matches!(self.options.collapse_whitespaces, CollapseWhitespaces::None)
}
- fn is_custom_element(&self, tag_name: &JsWord) -> bool {
+ fn is_custom_element(&self, element: &Element) -> bool {
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
- match *tag_name {
+ match element.tag_name {
js_word!("annotation-xml")
| js_word!("color-profile")
| js_word!("font-face")
@@ -809,13 +807,16 @@ impl Minifier<'_> {
| js_word!("font-face-format")
| js_word!("font-face-name")
| js_word!("missing-glyph") => false,
- _ => matches!(tag_name.chars().next(), Some('a'..='z')) && tag_name.contains('-'),
+ _ => {
+ matches!(element.tag_name.chars().next(), Some('a'..='z'))
+ && element.tag_name.contains('-')
+ }
}
}
- fn get_display(&self, namespace: Namespace, tag_name: &JsWord) -> Display {
- match namespace {
- Namespace::HTML => match *tag_name {
+ fn get_display(&self, element: &Element) -> Display {
+ match element.namespace {
+ Namespace::HTML => match element.tag_name {
js_word!("area")
| js_word!("base")
| js_word!("basefont")
@@ -978,7 +979,7 @@ impl Minifier<'_> {
_ => Display::Inline,
},
- Namespace::SVG => match *tag_name {
+ Namespace::SVG => match element.tag_name {
js_word!("text") | js_word!("foreignObject") => Display::Block,
_ => Display::Inline,
},
@@ -986,8 +987,8 @@ impl Minifier<'_> {
}
}
- fn is_element_displayed(&self, namespace: Namespace, tag_name: &JsWord) -> bool {
- match namespace {
+ fn is_element_displayed(&self, element: &Element) -> bool {
+ match element.namespace {
Namespace::HTML => {
// https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#metadata_content
//
@@ -995,7 +996,7 @@ impl Minifier<'_> {
// `noscript` - can be displayed if JavaScript disabled
// `script` - can insert markup using `document.write`
!matches!(
- *tag_name,
+ element.tag_name,
js_word!("base")
| js_word!("command")
| js_word!("link")
@@ -1005,7 +1006,7 @@ impl Minifier<'_> {
| js_word!("template")
)
}
- Namespace::SVG => !matches!(*tag_name, js_word!("style")),
+ Namespace::SVG => !matches!(element.tag_name, js_word!("style")),
_ => true,
}
}
@@ -1080,7 +1081,7 @@ impl Minifier<'_> {
}
}
Some(Child::Element(element)) => {
- if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
+ if !self.is_element_displayed(element) && index >= 1 {
self.get_prev_displayed_node(children, index - 1)
} else if !element.children.is_empty() {
self.get_prev_displayed_node(&element.children, element.children.len() - 1)
@@ -1109,7 +1110,7 @@ impl Minifier<'_> {
}
}
Some(Child::Element(element)) => {
- if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
+ if !self.is_element_displayed(element) && index >= 1 {
self.get_last_displayed_text_node(children, index - 1)
} else if !element.children.is_empty() {
for index in (0..=element.children.len() - 1).rev() {
@@ -1140,7 +1141,7 @@ impl Minifier<'_> {
match next {
Some(Child::Comment(_)) => self.get_first_displayed_text_node(children, index + 1),
Some(Child::Element(element)) => {
- if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
+ if !self.is_element_displayed(element) && index >= 1 {
self.get_first_displayed_text_node(children, index - 1)
} else if !element.children.is_empty() {
for index in 0..=element.children.len() - 1 {
@@ -1170,9 +1171,7 @@ impl Minifier<'_> {
match next {
Some(Child::Comment(_)) => self.get_next_displayed_node(children, index + 1),
- Some(Child::Element(element))
- if !self.is_element_displayed(element.namespace, &element.tag_name) =>
- {
+ Some(Child::Element(element)) if !self.is_element_displayed(element) => {
self.get_next_displayed_node(children, index + 1)
}
Some(_) => next,
@@ -1180,11 +1179,7 @@ impl Minifier<'_> {
}
}
- fn get_whitespace_minification_for_tag(
- &self,
- namespace: Namespace,
- tag_name: &JsWord,
- ) -> WhitespaceMinificationMode {
+ fn get_whitespace_minification_for_tag(&self, element: &Element) -> WhitespaceMinificationMode {
let default_collapse = match self.options.collapse_whitespaces {
CollapseWhitespaces::All
| CollapseWhitespaces::Smart
@@ -1201,8 +1196,8 @@ impl Minifier<'_> {
| CollapseWhitespaces::None => false,
};
- match namespace {
- Namespace::HTML => match *tag_name {
+ match element.namespace {
+ Namespace::HTML => match element.tag_name {
js_word!("script") | js_word!("style") => WhitespaceMinificationMode {
collapse: false,
trim: !matches!(
@@ -1211,7 +1206,7 @@ impl Minifier<'_> {
),
},
_ => {
- if get_white_space(namespace, tag_name) == WhiteSpace::Pre {
+ if get_white_space(element.namespace, &element.tag_name) == WhiteSpace::Pre {
WhitespaceMinificationMode {
collapse: false,
trim: false,
@@ -1224,14 +1219,14 @@ impl Minifier<'_> {
}
}
},
- Namespace::SVG => match *tag_name {
+ Namespace::SVG => match element.tag_name {
js_word!("script") | js_word!("style") => WhitespaceMinificationMode {
collapse: false,
trim: true,
},
// https://svgwg.org/svg2-draft/render.html#Definitions
_ if matches!(
- *tag_name,
+ element.tag_name,
js_word!("a")
| js_word!("circle")
| js_word!("ellipse")
@@ -1310,7 +1305,7 @@ impl Minifier<'_> {
fn is_empty_metadata_element(&self, child: &Child) -> bool {
if let Child::Element(element) = child {
- if (!self.is_element_displayed(element.namespace, &element.tag_name)
+ if (!self.is_element_displayed(element)
|| (matches!(element.namespace, Namespace::HTML | Namespace::SVG)
&& element.tag_name == js_word!("script"))
|| (element.namespace == Namespace::HTML
@@ -1378,7 +1373,12 @@ impl Minifier<'_> {
true
}
}
- _ => true,
+ _ => !self.is_default_attribute_value(left, attribute),
+ })
+ .map(|mut attribute| {
+ self.minify_attribute(left, &mut attribute);
+
+ attribute
})
.collect::>();
@@ -1411,7 +1411,12 @@ impl Minifier<'_> {
true
}
}
- _ => true,
+ _ => !self.is_default_attribute_value(right, attribute),
+ })
+ .map(|mut attribute| {
+ self.minify_attribute(right, &mut attribute);
+
+ attribute
})
.collect::>();
@@ -1467,14 +1472,12 @@ impl Minifier<'_> {
return vec![];
}
- let (namespace, tag_name) = match &self.current_element {
- Some(element) => (element.namespace, &element.tag_name),
- _ => {
- unreachable!();
- }
+ let parent = match &self.current_element {
+ Some(element) => element,
+ _ => return children.to_vec(),
};
- let mode = self.get_whitespace_minification_for_tag(namespace, tag_name);
+ let mode = self.get_whitespace_minification_for_tag(parent);
let child_will_be_retained =
|child: &mut Child, prev_children: &mut Vec, next_children: &mut Vec| {
@@ -1495,15 +1498,16 @@ impl Minifier<'_> {
Child::Text(text) if text.data.is_empty() => false,
Child::Text(text)
if self.need_collapse_whitespace()
- && namespace == Namespace::HTML
- && matches!(*tag_name, js_word!("html") | js_word!("head"))
+ && parent.namespace == Namespace::HTML
+ && matches!(parent.tag_name, js_word!("html") | js_word!("head"))
&& text.data.chars().all(is_whitespace) =>
{
false
}
Child::Text(text)
if !self.descendant_of_pre
- && get_white_space(namespace, tag_name) == WhiteSpace::Normal
+ && get_white_space(parent.namespace, &parent.tag_name)
+ == WhiteSpace::Normal
&& matches!(
self.options.collapse_whitespaces,
CollapseWhitespaces::All
@@ -1530,11 +1534,7 @@ impl Minifier<'_> {
let prev = prev_children.last();
let prev_display = match prev {
- Some(Child::Element(Element {
- namespace,
- tag_name,
- ..
- })) => Some(self.get_display(*namespace, tag_name)),
+ Some(Child::Element(element)) => Some(self.get_display(element)),
Some(Child::Comment(_)) => match need_remove_metadata_whitespaces {
true => None,
_ => Some(Display::None),
@@ -1572,7 +1572,7 @@ impl Minifier<'_> {
// the behavior of spaces
let is_custom_element =
if let Some(Child::Element(element)) = &prev {
- self.is_custom_element(&element.tag_name)
+ self.is_custom_element(element)
} else {
false
};
@@ -1604,8 +1604,7 @@ impl Minifier<'_> {
}
}
_ => {
- let parent_display =
- self.get_display(namespace, tag_name);
+ let parent_display = self.get_display(parent);
match parent_display {
Display::Inline => {
@@ -1637,13 +1636,13 @@ impl Minifier<'_> {
// attribute. This includes text nodes.
// Also they can be used for custom logic
- if (namespace == Namespace::HTML
- && *tag_name == js_word!("template"))
- || self.is_custom_element(tag_name)
+ if (parent.namespace == Namespace::HTML
+ && parent.tag_name == js_word!("template"))
+ || self.is_custom_element(parent)
{
false
} else {
- let parent_display = self.get_display(namespace, tag_name);
+ let parent_display = self.get_display(parent);
match parent_display {
Display::Inline => {
@@ -1663,11 +1662,7 @@ impl Minifier<'_> {
let next = next_children.first();
let next_display = match next {
- Some(Child::Element(Element {
- namespace,
- tag_name,
- ..
- })) => Some(self.get_display(*namespace, tag_name)),
+ Some(Child::Element(element)) => Some(self.get_display(element)),
Some(Child::Comment(_)) => match need_remove_metadata_whitespaces {
true => None,
_ => Some(Display::None),
@@ -1716,8 +1711,7 @@ impl Minifier<'_> {
}
}
_ => {
- let parent_display =
- self.get_display(namespace, tag_name);
+ let parent_display = self.get_display(parent);
!matches!(parent_display, Display::Inline)
}
@@ -1726,13 +1720,13 @@ impl Minifier<'_> {
Some(_) => false,
None => {
// Template can be used in any place, so let's keep whitespaces
- let is_template = namespace == Namespace::HTML
- && *tag_name == js_word!("template");
+ let is_template = parent.namespace == Namespace::HTML
+ && parent.tag_name == js_word!("template");
if is_template {
false
} else {
- let parent_display = self.get_display(namespace, tag_name);
+ let parent_display = self.get_display(parent);
!matches!(parent_display, Display::Inline)
}
@@ -2352,167 +2346,14 @@ impl Minifier<'_> {
Some(minified)
}
-}
-
-impl VisitMut for Minifier<'_> {
- fn visit_mut_document(&mut self, n: &mut Document) {
- n.visit_mut_children_with(self);
-
- n.children
- .retain(|child| !matches!(child, Child::Comment(_) if self.options.remove_comments));
- }
-
- fn visit_mut_document_fragment(&mut self, n: &mut DocumentFragment) {
- n.children = self.minify_children(&mut n.children);
-
- n.visit_mut_children_with(self);
- }
-
- fn visit_mut_document_type(&mut self, n: &mut DocumentType) {
- n.visit_mut_children_with(self);
-
- if !self.options.force_set_html5_doctype {
- return;
- }
-
- n.name = Some(js_word!("html"));
- n.system_id = None;
- n.public_id = None;
- }
-
- fn visit_mut_child(&mut self, n: &mut Child) {
- n.visit_mut_children_with(self);
-
- self.current_element = None;
-
- if matches!(
- self.options.collapse_whitespaces,
- CollapseWhitespaces::Smart
- | CollapseWhitespaces::AdvancedConservative
- | CollapseWhitespaces::OnlyMetadata
- ) {
- match n {
- Child::Text(_) | Child::Element(_) => {
- self.latest_element = Some(n.clone());
- }
- _ => {}
- }
- }
- }
-
- fn visit_mut_element(&mut self, n: &mut Element) {
- // Don't copy children to save memory
- self.current_element = Some(Element {
- span: Default::default(),
- tag_name: n.tag_name.clone(),
- namespace: n.namespace,
- attributes: n.attributes.clone(),
- children: vec![],
- content: None,
- is_self_closing: n.is_self_closing,
- });
-
- let old_descendant_of_pre = self.descendant_of_pre;
-
- if self.need_collapse_whitespace() && !old_descendant_of_pre {
- self.descendant_of_pre = get_white_space(n.namespace, &n.tag_name) == WhiteSpace::Pre;
- }
-
- n.children = self.minify_children(&mut n.children);
-
- n.visit_mut_children_with(self);
-
- // Remove all leading and trailing whitespaces for the `body` element
- if n.namespace == Namespace::HTML
- && n.tag_name == js_word!("body")
- && self.need_collapse_whitespace()
- {
- self.remove_leading_and_trailing_whitespaces(&mut n.children, true, true);
- }
-
- if self.need_collapse_whitespace() {
- self.descendant_of_pre = old_descendant_of_pre;
- }
-
- let mut remove_list = vec![];
-
- for (i, i1) in n.attributes.iter().enumerate() {
- if i1.value.is_some() {
- if self.options.remove_redundant_attributes != RemoveRedundantAttributes::None
- && self.is_default_attribute_value(n.namespace, &n.tag_name, i1)
- {
- remove_list.push(i);
-
- continue;
- }
-
- if self.options.remove_empty_attributes {
- let value = i1.value.as_ref().unwrap();
-
- if (matches!(i1.name, js_word!("id")) && value.is_empty())
- || (matches!(i1.name, js_word!("class") | js_word!("style"))
- && value.is_empty())
- || self.is_event_handler_attribute(i1) && value.is_empty()
- {
- remove_list.push(i);
-
- continue;
- }
- }
- }
-
- for (j, j1) in n.attributes.iter().enumerate() {
- if i < j && i1.name == j1.name {
- remove_list.push(j);
- }
- }
- }
-
- // Fast path. We don't face real duplicates in most cases.
- if !remove_list.is_empty() {
- let new = take(&mut n.attributes)
- .into_iter()
- .enumerate()
- .filter_map(|(idx, value)| {
- if remove_list.contains(&idx) {
- None
- } else {
- Some(value)
- }
- })
- .collect::>();
-
- n.attributes = new;
- }
-
- if let Some(attribute_name_counter) = &self.attribute_name_counter {
- n.attributes.sort_by(|a, b| {
- let ordeing = attribute_name_counter
- .get(&b.name)
- .cmp(&attribute_name_counter.get(&a.name));
-
- match ordeing {
- Ordering::Equal => b.name.cmp(&a.name),
- _ => ordeing,
- }
- });
- }
- }
-
- fn visit_mut_attribute(&mut self, n: &mut Attribute) {
- n.visit_mut_children_with(self);
+ fn minify_attribute(&self, element: &Element, n: &mut Attribute) {
if let Some(value) = &n.value {
- let current_element = match &self.current_element {
- Some(current_element) => current_element,
- _ => return,
- };
-
if value.is_empty() {
if (self.options.collapse_boolean_attributes
- && self.is_boolean_attribute(current_element, n))
+ && self.is_boolean_attribute(element, n))
|| (self.options.normalize_attributes
- && self.is_crossorigin_attribute(current_element, n)
+ && self.is_crossorigin_attribute(element, n)
&& value.is_empty())
{
n.value = None;
@@ -2521,11 +2362,7 @@ impl VisitMut for Minifier<'_> {
return;
}
- match (
- current_element.namespace,
- ¤t_element.tag_name,
- &n.name,
- ) {
+ match (element.namespace, &element.tag_name, &n.name) {
(Namespace::HTML, &js_word!("iframe"), &js_word!("srcdoc")) => {
if let Some(minified) = self.minify_html(
value.to_string(),
@@ -2545,13 +2382,13 @@ impl VisitMut for Minifier<'_> {
n.value = Some(value.trim().to_ascii_lowercase().into());
}
_ if self.options.normalize_attributes
- && self.is_crossorigin_attribute(current_element, n)
+ && self.is_crossorigin_attribute(element, n)
&& value.to_ascii_lowercase() == js_word!("anonymous") =>
{
n.value = None;
}
_ if self.options.collapse_boolean_attributes
- && self.is_boolean_attribute(current_element, n) =>
+ && self.is_boolean_attribute(element, n) =>
{
n.value = None;
}
@@ -2575,14 +2412,14 @@ impl VisitMut for Minifier<'_> {
}
}
_ if self.options.normalize_attributes
- && current_element.namespace == Namespace::HTML
+ && element.namespace == Namespace::HTML
&& n.name == js_word!("contenteditable")
&& n.value == Some(js_word!("true")) =>
{
n.value = Some(js_word!(""));
}
_ if self.options.normalize_attributes
- && self.is_semicolon_separated_attribute(current_element, n) =>
+ && self.is_semicolon_separated_attribute(element, n) =>
{
n.value = Some(
value
@@ -2596,7 +2433,7 @@ impl VisitMut for Minifier<'_> {
_ if self.options.normalize_attributes
&& n.name == js_word!("content")
&& self.element_has_attribute_with_value(
- current_element,
+ element,
&js_word!("http-equiv"),
&[js_word!("content-security-policy")],
) =>
@@ -2623,7 +2460,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(value.into());
}
_ if self.options.sort_space_separated_attribute_values
- && self.is_attribute_value_unordered_set(current_element, n) =>
+ && self.is_attribute_value_unordered_set(element, n) =>
{
let mut values = value.split_whitespace().collect::>();
@@ -2632,7 +2469,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(values.join(" ").into());
}
_ if self.options.normalize_attributes
- && self.is_space_separated_attribute(current_element, n) =>
+ && self.is_space_separated_attribute(element, n) =>
{
n.value = Some(
value
@@ -2642,7 +2479,7 @@ impl VisitMut for Minifier<'_> {
.into(),
);
}
- _ if self.is_comma_separated_attribute(current_element, n) => {
+ _ if self.is_comma_separated_attribute(element, n) => {
let mut value = value.to_string();
if self.options.normalize_attributes {
@@ -2678,7 +2515,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(value.into());
}
}
- _ if self.is_trimable_separated_attribute(current_element, n) => {
+ _ if self.is_trimable_separated_attribute(element, n) => {
let mut value = value.to_string();
let fallback = |n: &mut Attribute| {
@@ -2697,9 +2534,7 @@ impl VisitMut for Minifier<'_> {
} else {
fallback(n);
}
- } else if self.need_minify_js()
- && self.is_javascript_url_element(current_element)
- {
+ } else if self.need_minify_js() && self.is_javascript_url_element(element) {
if value.trim().to_lowercase().starts_with("javascript:") {
value = value.trim().chars().skip(11).collect();
@@ -2759,6 +2594,163 @@ impl VisitMut for Minifier<'_> {
}
}
}
+}
+
+impl VisitMut for Minifier<'_> {
+ fn visit_mut_document(&mut self, n: &mut Document) {
+ n.visit_mut_children_with(self);
+
+ n.children
+ .retain(|child| !matches!(child, Child::Comment(_) if self.options.remove_comments));
+ }
+
+ fn visit_mut_document_fragment(&mut self, n: &mut DocumentFragment) {
+ n.children = self.minify_children(&mut n.children);
+
+ n.visit_mut_children_with(self);
+ }
+
+ fn visit_mut_document_type(&mut self, n: &mut DocumentType) {
+ n.visit_mut_children_with(self);
+
+ if !self.options.force_set_html5_doctype {
+ return;
+ }
+
+ n.name = Some(js_word!("html"));
+ n.system_id = None;
+ n.public_id = None;
+ }
+
+ fn visit_mut_child(&mut self, n: &mut Child) {
+ n.visit_mut_children_with(self);
+
+ self.current_element = None;
+
+ if matches!(
+ self.options.collapse_whitespaces,
+ CollapseWhitespaces::Smart
+ | CollapseWhitespaces::AdvancedConservative
+ | CollapseWhitespaces::OnlyMetadata
+ ) {
+ match n {
+ Child::Text(_) | Child::Element(_) => {
+ self.latest_element = Some(n.clone());
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn visit_mut_element(&mut self, n: &mut Element) {
+ // Don't copy children to save memory
+ self.current_element = Some(Element {
+ span: Default::default(),
+ tag_name: n.tag_name.clone(),
+ namespace: n.namespace,
+ attributes: n.attributes.clone(),
+ children: vec![],
+ content: None,
+ is_self_closing: n.is_self_closing,
+ });
+
+ let old_descendant_of_pre = self.descendant_of_pre;
+
+ if self.need_collapse_whitespace() && !old_descendant_of_pre {
+ self.descendant_of_pre = get_white_space(n.namespace, &n.tag_name) == WhiteSpace::Pre;
+ }
+
+ n.children = self.minify_children(&mut n.children);
+
+ n.visit_mut_children_with(self);
+
+ // Remove all leading and trailing whitespaces for the `body` element
+ if n.namespace == Namespace::HTML
+ && n.tag_name == js_word!("body")
+ && self.need_collapse_whitespace()
+ {
+ self.remove_leading_and_trailing_whitespaces(&mut n.children, true, true);
+ }
+
+ if self.need_collapse_whitespace() {
+ self.descendant_of_pre = old_descendant_of_pre;
+ }
+
+ let mut remove_list = vec![];
+
+ for (i, i1) in n.attributes.iter().enumerate() {
+ if i1.value.is_some() {
+ if self.options.remove_redundant_attributes != RemoveRedundantAttributes::None
+ && self.is_default_attribute_value(n, i1)
+ {
+ remove_list.push(i);
+
+ continue;
+ }
+
+ if self.options.remove_empty_attributes {
+ let value = i1.value.as_ref().unwrap();
+
+ if (matches!(i1.name, js_word!("id")) && value.is_empty())
+ || (matches!(i1.name, js_word!("class") | js_word!("style"))
+ && value.is_empty())
+ || self.is_event_handler_attribute(i1) && value.is_empty()
+ {
+ remove_list.push(i);
+
+ continue;
+ }
+ }
+ }
+
+ for (j, j1) in n.attributes.iter().enumerate() {
+ if i < j && i1.name == j1.name {
+ remove_list.push(j);
+ }
+ }
+ }
+
+ // Fast path. We don't face real duplicates in most cases.
+ if !remove_list.is_empty() {
+ let new = take(&mut n.attributes)
+ .into_iter()
+ .enumerate()
+ .filter_map(|(idx, value)| {
+ if remove_list.contains(&idx) {
+ None
+ } else {
+ Some(value)
+ }
+ })
+ .collect::>();
+
+ n.attributes = new;
+ }
+
+ if let Some(attribute_name_counter) = &self.attribute_name_counter {
+ n.attributes.sort_by(|a, b| {
+ let ordeing = attribute_name_counter
+ .get(&b.name)
+ .cmp(&attribute_name_counter.get(&a.name));
+
+ match ordeing {
+ Ordering::Equal => b.name.cmp(&a.name),
+ _ => ordeing,
+ }
+ });
+ }
+ }
+
+ fn visit_mut_attribute(&mut self, n: &mut Attribute) {
+ n.visit_mut_children_with(self);
+
+ let element = match &self.current_element {
+ Some(current_element) => current_element,
+ _ => return,
+ };
+
+ self.minify_attribute(element, n);
+ }
fn visit_mut_text(&mut self, n: &mut Text) {
n.visit_mut_children_with(self);
diff --git a/crates/swc_html_minifier/tests/fixture/attribute/script-type/output.min.html b/crates/swc_html_minifier/tests/fixture/attribute/script-type/output.min.html
index f7c39847dad9..9b94c93741ed 100644
--- a/crates/swc_html_minifier/tests/fixture/attribute/script-type/output.min.html
+++ b/crates/swc_html_minifier/tests/fixture/attribute/script-type/output.min.html
@@ -3,4 +3,4 @@
test
\n
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/crates/swc_html_minifier/tests/fixture/element/script-group/input.html b/crates/swc_html_minifier/tests/fixture/element/script-group/input.html
index 218581957bae..6fa23d5a6f3c 100644
--- a/crates/swc_html_minifier/tests/fixture/element/script-group/input.html
+++ b/crates/swc_html_minifier/tests/fixture/element/script-group/input.html
@@ -69,5 +69,17 @@
breaker
+breaker
+
+
+
+breaker
+
+
+
+breaker
+
+
+