Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(html/minifier): sort unordered values of attributes and option for it #5035

Merged
merged 7 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 60 additions & 18 deletions crates/swc_html_minifier/src/lib.rs
Expand Up @@ -221,10 +221,10 @@ static COMMA_SEPARATED_SVG_ATTRIBUTES: &[(&str, &str)] = &[("style", "media")];

static SPACE_SEPARATED_GLOBAL_ATTRIBUTES: &[&str] = &[
"class",
"part",
"itemtype",
"itemref",
"itemprop",
"itemref",
"itemtype",
"part",
"accesskey",
"aria-describedby",
"aria-labelledby",
Expand All @@ -238,15 +238,16 @@ static SPACE_SEPARATED_HTML_ATTRIBUTES: &[(&str, &str)] = &[
("area", "ping"),
("link", "rel"),
("link", "sizes"),
("input", "autocomplete"),
("form", "autocomplete"),
("link", "blocking"),
("iframe", "sandbox"),
("td", "headers"),
("th", "headers"),
("output", "for"),
("link", "blocking"),
("script", "blocking"),
("style", "blocking"),
("input", "autocomplete"),
("form", "rel"),
("form", "autocomplete"),
];

enum CssMinificationMode {
Expand Down Expand Up @@ -331,6 +332,8 @@ struct Minifier {
minify_css: bool,
minify_additional_attributes: Option<Vec<(CachedRegex, MinifierType)>>,
minify_additional_scripts_content: Option<Vec<(CachedRegex, MinifierType)>>,

sort_space_separated_attribute_values: bool,
}

fn get_white_space(namespace: Namespace, tag_name: &str) -> WhiteSpace {
Expand Down Expand Up @@ -418,16 +421,40 @@ impl Minifier {
}
}

fn is_additional_minifier_attribute(&self, name: &str) -> Option<MinifierType> {
if let Some(minify_additional_attributes) = &self.minify_additional_attributes {
for item in minify_additional_attributes {
if item.0.is_match(name) {
return Some(item.1.clone());
}
}
fn is_attribute_value_unordered_set(&self, element: &Element, attribute_name: &str) -> bool {
if matches!(
attribute_name,
"class" | "part" | "itemprop" | "itemref" | "itemtype"
) {
return true;
}

None
match element.namespace {
Namespace::HTML => match &*element.tag_name {
"link" if attribute_name == "blocking" => true,
"script" if attribute_name == "blocking" => true,
"style" if attribute_name == "blocking" => true,
"output" if attribute_name == "for" => true,
"td" if attribute_name == "headers" => true,
"th" if attribute_name == "headers" => true,
"form" if attribute_name == "rel" => true,
"a" if attribute_name == "rel" => true,
"area" if attribute_name == "rel" => true,
"link" if attribute_name == "rel" => true,
"iframe" if attribute_name == "sandbox" => true,
"link"
if self.element_has_attribute_with_value(
element,
"rel",
&["icon", "apple-touch-icon", "apple-touch-icon-precomposed"],
) && attribute_name == "sizes" =>
{
true
}
_ => false,
},
_ => false,
}
}

fn element_has_attribute_with_value(
Expand Down Expand Up @@ -866,6 +893,18 @@ impl Minifier {
collapsed
}

fn is_additional_minifier_attribute(&self, name: &str) -> Option<MinifierType> {
if let Some(minify_additional_attributes) = &self.minify_additional_attributes {
for item in minify_additional_attributes {
if item.0.is_match(name) {
return Some(item.1.clone());
}
}
}

None
}

fn minify_children(&mut self, children: &mut Vec<Child>) {
let (namespace, tag_name) = match &self.current_element {
Some(element) => (element.namespace, &element.tag_name),
Expand Down Expand Up @@ -1472,6 +1511,7 @@ impl Minifier {
minify_css: self.minify_css,
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,
};

match document_or_document_fragment {
Expand Down Expand Up @@ -1735,12 +1775,12 @@ impl VisitMut for Minifier {
}
}

if &*n.name == "class" {
if self.sort_space_separated_attribute_values
&& self.is_attribute_value_unordered_set(current_element, &n.name)
{
let mut values = value.split_whitespace().collect::<Vec<_>>();

if &*n.name == "class" {
values.sort_unstable();
}
values.sort_unstable();

value = values.join(" ");
} else if self.is_event_handler_attribute(&n.name) {
Expand Down Expand Up @@ -2025,6 +2065,8 @@ fn create_minifier(context_element: Option<&Element>, options: &MinifyOptions) -
minify_css: options.minify_css,
minify_additional_attributes: options.minify_additional_attributes.clone(),
minify_additional_scripts_content: options.minify_additional_scripts_content.clone(),

sort_space_separated_attribute_values: options.sort_space_separated_attribute_values,
}
}

Expand Down
18 changes: 10 additions & 8 deletions crates/swc_html_minifier/src/option.rs
Expand Up @@ -49,14 +49,6 @@ pub struct MinifyOptions {
pub minify_json: bool,
#[serde(default = "true_by_default")]
pub minify_css: bool,
/// Allow to compress value of custom attributes,
/// i.e. `<div data-js="myFunction(100 * 2, 'foo' + 'bar')"></div>`
///
/// The first item is tag_name
/// The second is attribute name
/// The third is type of minifier
#[serde(default)]
pub minify_additional_attributes: Option<Vec<(CachedRegex, MinifierType)>>,
// Allow to compress value of custom script elements,
// i.e. `<script type="text/html"><div><!-- text --> <div data-foo="bar> Text </div></script>`
//
Expand All @@ -65,6 +57,16 @@ pub struct MinifyOptions {
// The third is type of minifier
#[serde(default)]
pub minify_additional_scripts_content: Option<Vec<(CachedRegex, MinifierType)>>,
/// Allow to compress value of custom attributes,
/// i.e. `<div data-js="myFunction(100 * 2, 'foo' + 'bar')"></div>`
///
/// The first item is tag_name
/// The second is attribute name
/// The third is type of minifier
pub minify_additional_attributes: Option<Vec<(CachedRegex, MinifierType)>>,
/// Sorting the values of `class`, `rel`, etc. of attributes
#[serde(default = "true_by_default")]
pub sort_space_separated_attribute_values: bool,
}

/// Implement default using serde.
Expand Down
@@ -1,2 +1,2 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=test.css media="screen and (min-width:1024px)"><a rel="foo bar baz"></a>
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=test.css media="screen and (min-width:1024px)"><a rel="bar baz foo"></a>
<div onclick='javascript:alert("test")'></div>
@@ -0,0 +1,3 @@
{
"sortSpaceSeparatedAttributeValues": false
}
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="b a d"></div>
</body>
</html>
@@ -0,0 +1 @@
<!doctype html><html lang=en><meta charset=UTF-8><title>Document</title><div class="b a d"></div>
Expand Up @@ -21,6 +21,12 @@
<td headers="phone">+45342323</td>
<td headers="addr">Rosevn 56,4300 Sandnes,Norway</td>
</tr>
<tr>
<td headers="name a ">John Doe</td>
<td headers="email a">someone@example.com</td>
<td headers="phone a">+45342323</td>
<td headers="addr b">Rosevn 56,4300 Sandnes,Norway</td>
</tr>
</table>
</body>
</html>
Expand Up @@ -11,4 +11,10 @@
<td headers=phone>+45342323</td>
<td headers=addr>Rosevn 56,4300 Sandnes,Norway</td>
</tr>
<tr>
<td headers="a name">John Doe</td>
<td headers="a email">someone@example.com</td>
<td headers="a phone">+45342323</td>
<td headers="addr b">Rosevn 56,4300 Sandnes,Norway</td>
</tr>
</table>
Expand Up @@ -11,7 +11,7 @@
in excellent condition<br>
<link itemprop=availability href=http://schema.org/InStock>In stock! Order now!
</span>
<dl itemscope itemtype="https://md.example.com/loco https://md.example.com/lighting">
<dl itemscope itemtype="https://md.example.com/lighting https://md.example.com/loco">
<dt>Name:
<dd itemprop="name name">Tank Locomotive (DB 80)
<dt>Product code:
Expand Down
Expand Up @@ -8,6 +8,20 @@
<link rel="stylesheet" href="c.css" type=" text/css ">
<link rel="stylesheet" href="d.css" type="">
<link rel="stylesheet" href="d.css" type="unknown/unknown">
<link href="default.css" rel="stylesheet" title="Default Style">
<link href="fancy.css" rel="stylesheet alternate" title="Fancy">
<link href="basic.css" rel="stylesheet alternate" title="Basic">
<link rel="stylesheet foo bar" href="d.css">
<link rel="stylesheet foo bar" href="d.css" blocking="render a">
<link rel="icon" href="demo_icon.gif" type="image/gif" sizes="32x32 16x16">
<link rel="apple-touch-icon-precomposed" sizes="512x512 114x114"
href="apple-icon-114.png" type="image/png">
<link rel="apple-touch-icon" sizes="512x512 114x114"
href="apple-icon-114.png" type="image/png">
<link rel="icon" href="/favicon.ico" sizes="any"><!-- 32×32 -->
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png"><!-- 180×180 -->
<link rel="manifest" href="/manifest.webmanifest">
</head>
<body>
<div>test</div>
Expand Down
@@ -1 +1 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css><link rel=stylesheet href=b.css><link rel=stylesheet href=c.css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><div>test</div>
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css><link rel=stylesheet href=b.css><link rel=stylesheet href=c.css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><link href=default.css rel=stylesheet title="Default Style"><link href=fancy.css rel="alternate stylesheet" title=Fancy><link href=basic.css rel="alternate stylesheet" title=Basic><link rel="bar foo stylesheet" href=d.css><link rel="bar foo stylesheet" href=d.css blocking="a render"><link rel=icon href=demo_icon.gif type=image/gif sizes="16x16 32x32"><link rel=apple-touch-icon-precomposed sizes="114x114 512x512" href=apple-icon-114.png type=image/png><link rel=apple-touch-icon sizes="114x114 512x512" href=apple-icon-114.png type=image/png><link rel=icon href=/favicon.ico sizes=any><link rel=icon href=/icon.svg type=image/svg+xml><link rel=apple-touch-icon href=/apple-touch-icon.png><link rel=manifest href=/manifest.webmanifest><div>test</div>
@@ -1,7 +1,7 @@
<!doctype html><html lang=en><title>Document</title><body><style>c-e::part(textspan){color:red}</style>

<template id=c-e-template>
<span part="textspan a b c">This text will be red</span>
<span part="a b c textspan">This text will be red</span>
</template>

<c-e></c-e>
Expand Up @@ -7,5 +7,9 @@
<a href="#" rel="value1 nofollow">Link</a>
<a href="#" rel=" value1 nofollow ">Link</a>
<a href="#" rel=" nofollow ">Link</a>
<form rel="noreferrer nofollow" action="mypage.php">
<input type="search" placeholder="search here" />
<input type="button" value="search" />
</form>
</body>
</html>
@@ -1,3 +1,7 @@
<!doctype html><html lang=en><title>Document</title><a href=# rel="value1 nofollow">Link</a>
<a href=# rel="value1 nofollow">Link</a>
<a href=# rel=nofollow>Link</a>
<!doctype html><html lang=en><title>Document</title><a href=# rel="nofollow value1">Link</a>
<a href=# rel="nofollow value1">Link</a>
<a href=# rel=nofollow>Link</a>
<form rel="nofollow noreferrer" action=mypage.php>
<input type=search placeholder="search here">
<input type=button value=search>
</form>
@@ -1 +1 @@
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv=X-UA-Compatible content="ie=edge"><title>Document</title><iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms" src=https://platform.twitter.com/widgets/tweet_button.html style=border:0;width:130px;height:20px></iframe>
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv=X-UA-Compatible content="ie=edge"><title>Document</title><iframe sandbox="allow-forms allow-popups allow-same-origin allow-scripts" src=https://platform.twitter.com/widgets/tweet_button.html style=border:0;width:130px;height:20px></iframe>
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Document</title>
</head>
<body>
<form oninput="result.value=parseInt(a.value)+parseInt(b.value)">
<input type="range" id="b" name="b" value="50" /> +
<input type="number" id="a" name="a" value="10" /> =
<output name="result" for="b a">60</output>
</form>
</body>
</html>
@@ -0,0 +1,5 @@
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><title>Document</title><form oninput="result.value=parseInt(a.value)+parseInt(b.value)">
<input type=range id=b name=b value=50> +
<input type=number id=a name=a value=10> =
<output name=result for="a b">60</output>
</form>
Expand Up @@ -181,5 +181,6 @@ <h2>Party coffee cake recipe</h2>
alert('test')
</script>
</math>
<script blocking="render a">console.log("block");</script>
</body>
</html>
Expand Up @@ -53,4 +53,5 @@

alert('test')
</script>
</math>
</math>
<script blocking="a render">console.log("block")</script>
Expand Up @@ -60,5 +60,6 @@
}
</style>
</math>
<style blocking="render a">a { color: red }</style>
</body>
</html>
Expand Up @@ -23,4 +23,5 @@
color: red
}
</style>
</math>
</math>
<style blocking="a render">a{color:red}</style>