feat(html/minifier): Sort attributes (#4784)

This commit is contained in:
Alexander Akait 2022-07-06 18:50:23 +03:00 committed by GitHub
parent 70bfbfba44
commit f813a60497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 130 additions and 5 deletions

View File

@ -1,10 +1,16 @@
#![deny(clippy::all)]
use std::cmp::Ordering;
use once_cell::sync::Lazy;
use serde_json::Value;
use swc_atoms::{js_word, JsWord};
use swc_cached::regex::CachedRegex;
use swc_common::{collections::AHashSet, sync::Lrc, FileName, FilePathMapping, Mark, SourceMap};
use swc_common::{
collections::{AHashMap, AHashSet},
sync::Lrc,
FileName, FilePathMapping, Mark, SourceMap,
};
use swc_html_ast::*;
use swc_html_parser::parser::ParserConfig;
use swc_html_visit::{VisitMut, VisitMutWith};
@ -334,6 +340,7 @@ struct Minifier {
minify_additional_scripts_content: Option<Vec<(CachedRegex, MinifierType)>>,
sort_space_separated_attribute_values: bool,
attribute_name_counter: Option<AHashMap<JsWord, usize>>,
}
fn get_white_space(namespace: Namespace, tag_name: &str) -> WhiteSpace {
@ -1755,6 +1762,7 @@ impl Minifier {
minify_additional_scripts_content: self.minify_additional_scripts_content.clone(),
minify_additional_attributes: self.minify_additional_attributes.clone(),
sort_space_separated_attribute_values: self.sort_space_separated_attribute_values,
sort_attributes: self.attribute_name_counter.is_some(),
};
match document_or_document_fragment {
@ -1921,6 +1929,19 @@ impl VisitMut for Minifier {
true
});
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) {
@ -2288,6 +2309,18 @@ impl VisitMut for Minifier {
}
}
struct AttributeNameCounter {
tree: AHashMap<JsWord, usize>,
}
impl VisitMut for AttributeNameCounter {
fn visit_mut_attribute(&mut self, n: &mut Attribute) {
n.visit_mut_children_with(self);
*self.tree.entry(n.name.clone()).or_insert(0) += 1;
}
}
fn create_minifier(context_element: Option<&Element>, options: &MinifyOptions) -> Minifier {
let mut current_element = None;
let mut is_pre = false;
@ -2312,10 +2345,9 @@ fn create_minifier(context_element: Option<&Element>, options: &MinifyOptions) -
collapse_whitespaces: options.collapse_whitespaces.clone(),
remove_empty_metedata_elements: options.remove_empty_metedata_elements,
remove_empty_attributes: options.remove_empty_attributes,
remove_redundant_attributes: options.remove_redundant_attributes,
collapse_boolean_attributes: options.collapse_boolean_attributes,
remove_redundant_attributes: options.remove_redundant_attributes,
normalize_attributes: options.normalize_attributes,
minify_js: options.minify_js,
@ -2325,21 +2357,42 @@ fn create_minifier(context_element: Option<&Element>, options: &MinifyOptions) -
minify_additional_scripts_content: options.minify_additional_scripts_content.clone(),
sort_space_separated_attribute_values: options.sort_space_separated_attribute_values,
attribute_name_counter: None,
}
}
pub fn minify_document(document: &mut Document, options: &MinifyOptions) {
let mut minifier = create_minifier(None, options);
if options.sort_attributes {
let mut attribute_name_counter = AttributeNameCounter {
tree: Default::default(),
};
document.visit_mut_with(&mut attribute_name_counter);
minifier.attribute_name_counter = Some(attribute_name_counter.tree);
}
document.visit_mut_with(&mut minifier);
}
pub fn minify_document_fragment(
document: &mut DocumentFragment,
document_fragment: &mut DocumentFragment,
context_element: &Element,
options: &MinifyOptions,
) {
let mut minifier = create_minifier(Some(context_element), options);
document.visit_mut_with(&mut minifier);
if options.sort_attributes {
let mut attribute_name_counter = AttributeNameCounter {
tree: Default::default(),
};
document_fragment.visit_mut_with(&mut attribute_name_counter);
minifier.attribute_name_counter = Some(attribute_name_counter.tree);
}
document_fragment.visit_mut_with(&mut minifier);
}

View File

@ -89,6 +89,8 @@ pub struct MinifyOptions {
/// Sorting the values of `class`, `rel`, etc. of attributes
#[serde(default = "true_by_default")]
pub sort_space_separated_attribute_values: bool,
#[serde(default)]
pub sort_attributes: bool,
}
/// Implement default using serde.

View File

@ -0,0 +1,3 @@
{
"sortAttributes": true
}

View File

@ -0,0 +1,3 @@
<div b="1" a="2" c="3"></div>
<div c="3" a="2" b="1" ></div>
<div unknown="a" c="3"></div>

View File

@ -0,0 +1,3 @@
<div c=3 b=1 a=2></div>
<div c=3 b=1 a=2></div>
<div c=3 unknown=a></div>

View File

@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<title>Document</title>
</head>
<body>
<div class="foo" id="foo">Foo</div>
<div id="bar" class="bar">Bar</div>
<div style="color:red;" class="baz" tabindex="1" id="baz">Baz</div>
<div id="test"></div>
<div id="test"></div>
<div a b c d e f g></div>
<div c d e f g b></div>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!doctype html><title>Document</title><div class=foo id=foo>Foo</div>
<div id=bar class=bar>Bar</div>
<div style=color:red class=baz tabindex=1 id=baz>Baz</div>
<div id=test></div>
<div id=test></div>
<div a b c d e f g></div>
<div c d e f g b></div>

View File

@ -0,0 +1,3 @@
{
"sortAttributes": false
}

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link href="foo">
<link rel="bar" href="baz">
<link type="text/css" href="app.css" rel="stylesheet" async>
<script type="text/html" async></script>
</head>
<body>
<div b="2" a="1" c="3" foo bar="baz">test</div>
</body>
</html>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><title>Document</title><link href=foo><link rel=bar href=baz><link href=app.css rel=stylesheet async><script type=text/html async></script><div b=2 a=1 c=3 foo bar=baz>test</div>

View File

@ -0,0 +1,3 @@
{
"sortAttributes": true
}

View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link href="foo">
<link rel="bar" href="baz">
<link type="text/css" href="app.css" rel="stylesheet" async>
<script type="text/html" async></script>
<!--[if lt IE 9]><script b="a" type="module"></script><![endif]-->
</head>
<body>
<div b="2" a="1" c="3" foo bar="baz">test</div>
</body>
</html>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><title>Document</title><link href=foo><link href=baz rel=bar><link href=app.css rel=stylesheet async><script type=text/html async></script><!--[if lt IE 9]><script type=module b=a></script><![endif]--><div foo c=3 bar=baz b=2 a=1>test</div>