mirror of
https://github.com/swc-project/swc.git
synced 2024-10-05 04:39:06 +03:00
feat(html/minifier): Improve minification of attributes (#4625)
This commit is contained in:
parent
5a53bb771d
commit
5679b69600
@ -51,6 +51,8 @@ static BOOLEAN_ATTRIBUTES: &[&str] = &[
|
||||
|
||||
static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
|
||||
"onabort",
|
||||
"onautocomplete",
|
||||
"onautocompleteerror",
|
||||
"onauxclick",
|
||||
"onbeforematch",
|
||||
"oncancel",
|
||||
@ -80,9 +82,7 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousewheel",
|
||||
"onmousedown",
|
||||
"onmouseenter",
|
||||
"onmouseleave",
|
||||
@ -117,6 +117,9 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onafterprint",
|
||||
@ -140,12 +143,11 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
|
||||
"onpaste",
|
||||
"onreadystatechange",
|
||||
"onvisibilitychange",
|
||||
"onshow",
|
||||
"onsort",
|
||||
];
|
||||
|
||||
// TODO improve list - event handlers + remove multiple whitespace from class +
|
||||
// test for custom elements
|
||||
static ALLOW_TO_TRIM_ATTRIBUTES: &[&str] = &[
|
||||
"id",
|
||||
"class",
|
||||
"style",
|
||||
"tabindex",
|
||||
@ -158,7 +160,20 @@ static ALLOW_TO_TRIM_ATTRIBUTES: &[&str] = &[
|
||||
"colspan",
|
||||
];
|
||||
|
||||
struct Minifier {}
|
||||
static COMMA_SEPARATED_ATTRIBUTES: &[&str] = &["srcset", "sizes"];
|
||||
|
||||
static SPACE_SEPARATED_ATTRIBUTES: &[&str] = &[
|
||||
"class",
|
||||
"rel",
|
||||
"aria-describedby",
|
||||
"aria-labelledby",
|
||||
"aria-owns",
|
||||
"autocomplete",
|
||||
];
|
||||
|
||||
struct Minifier {
|
||||
current_element_namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl Minifier {
|
||||
fn is_boolean_attribute(&self, name: &str) -> bool {
|
||||
@ -169,8 +184,12 @@ impl Minifier {
|
||||
EVENT_HANDLER_ATTRIBUTES.contains(&name)
|
||||
}
|
||||
|
||||
fn allow_to_trim(&self, name: &str) -> bool {
|
||||
ALLOW_TO_TRIM_ATTRIBUTES.contains(&name)
|
||||
fn is_comma_separated_attribute(&self, name: &str) -> bool {
|
||||
COMMA_SEPARATED_ATTRIBUTES.contains(&name)
|
||||
}
|
||||
|
||||
fn is_space_separated_attribute(&self, name: &str) -> bool {
|
||||
SPACE_SEPARATED_ATTRIBUTES.contains(&name)
|
||||
}
|
||||
|
||||
fn is_default_attribute_value(
|
||||
@ -215,6 +234,12 @@ impl Minifier {
|
||||
)
|
||||
| (Namespace::HTML, "form", "method", "get")
|
||||
| (Namespace::HTML, "form", "target", "_self")
|
||||
| (
|
||||
Namespace::HTML,
|
||||
"form",
|
||||
"enctype",
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
| (Namespace::HTML, "input", "type", "text")
|
||||
| (Namespace::HTML, "input", "size", "20")
|
||||
| (Namespace::HTML, "track", "kind", "subtitles")
|
||||
@ -263,6 +288,12 @@ impl Minifier {
|
||||
| (Namespace::HTML, "base", "target", "_self")
|
||||
| (Namespace::HTML, "canvas", "height", "150")
|
||||
| (Namespace::HTML, "canvas", "width", "300")
|
||||
| (Namespace::HTML, "col", "span", "1")
|
||||
| (Namespace::HTML, "colgroup", "span", "1")
|
||||
| (Namespace::HTML, "td", "colspan", "1")
|
||||
| (Namespace::HTML, "td", "rowspan", "1")
|
||||
| (Namespace::HTML, "th", "colspan", "1")
|
||||
| (Namespace::HTML, "th", "rowspan", "1")
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,12 +306,22 @@ impl Minifier {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn allow_to_trim(&self, name: &str) -> bool {
|
||||
ALLOW_TO_TRIM_ATTRIBUTES.contains(&name) || EVENT_HANDLER_ATTRIBUTES.contains(&name)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for Minifier {
|
||||
fn visit_mut_element(&mut self, n: &mut Element) {
|
||||
let old_current_element_namespace = self.current_element_namespace.take();
|
||||
|
||||
self.current_element_namespace = Some(n.namespace);
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
self.current_element_namespace = old_current_element_namespace;
|
||||
|
||||
n.children.retain(|child| !matches!(child, Child::Comment(comment) if !self.is_conditional_comment(&comment.data)));
|
||||
|
||||
let mut already_seen: AHashSet<JsWord> = Default::default();
|
||||
@ -296,17 +337,22 @@ impl VisitMut for Minifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
let is_empty_value = (&*attribute.value.as_ref().unwrap()).trim().is_empty();
|
||||
|
||||
if self.is_default_attribute_value(
|
||||
n.namespace,
|
||||
&n.tag_name,
|
||||
&attribute.name,
|
||||
attribute.value.as_ref().unwrap(),
|
||||
) || (matches!(&*attribute.name, "id" | "class" | "style")
|
||||
&& (&*attribute.value.as_ref().unwrap()).trim().is_empty())
|
||||
) || (matches!(&*attribute.name, "id" | "class" | "style") && is_empty_value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_event_handler_attribute(&attribute.name) && is_empty_value {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
}
|
||||
@ -318,32 +364,59 @@ impl VisitMut for Minifier {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = n.value.as_ref().unwrap();
|
||||
let is_element_html_namespace = self.current_element_namespace == Some(Namespace::HTML);
|
||||
|
||||
match &n.name {
|
||||
name if self.is_boolean_attribute(name) => {
|
||||
n.value = None;
|
||||
}
|
||||
name if self.is_event_handler_attribute(name)
|
||||
&& value.to_lowercase().starts_with("javascript:") =>
|
||||
{
|
||||
let new_value: String = value.as_ref().chars().skip(11).collect();
|
||||
|
||||
n.value = Some(new_value.into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.is_boolean_attribute(&n.name) {
|
||||
if is_element_html_namespace && self.is_boolean_attribute(&n.name) {
|
||||
n.value = None;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let mut value: &str = &*(n.value.as_ref().unwrap().clone());
|
||||
let mut value = match &n.value {
|
||||
Some(value) => value.to_string(),
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
if is_element_html_namespace {
|
||||
if &n.name == "contenteditable" && value == "true" {
|
||||
n.value = Some("".into());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_comma_separated_attribute(&n.name) {
|
||||
let values = value.split(',');
|
||||
|
||||
let mut new_values = vec![];
|
||||
|
||||
for value in values {
|
||||
new_values.push(value.trim());
|
||||
}
|
||||
|
||||
n.value = Some(new_values.join(",").into());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_space_separated_attribute(&n.name) {
|
||||
value = value.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
|
||||
n.value = Some(value.into());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_to_trim(&n.name) {
|
||||
value = value.trim();
|
||||
value = value.trim().to_string();
|
||||
}
|
||||
|
||||
if self.is_event_handler_attribute(&n.name)
|
||||
&& value.to_lowercase().starts_with("javascript:")
|
||||
{
|
||||
value = value.chars().skip(11).collect();
|
||||
}
|
||||
|
||||
n.value = Some(value.into());
|
||||
@ -351,5 +424,7 @@ impl VisitMut for Minifier {
|
||||
}
|
||||
|
||||
pub fn minify(document: &mut Document) {
|
||||
document.visit_mut_with(&mut Minifier {});
|
||||
document.visit_mut_with(&mut Minifier {
|
||||
current_element_namespace: None,
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="13.html" id="rm13" aria-labelledby=" rm13 attr ">read more</a>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href=13.html id=rm13 aria-labelledby="rm13 attr">read more</a>
|
||||
|
||||
</body></html>
|
@ -21,5 +21,24 @@
|
||||
|
||||
|
||||
class1 class-23 ">foo bar baz</p>
|
||||
<p class=" foo bar
|
||||
qqq">foo bar baz</p>
|
||||
<p class="
|
||||
|
||||
|
||||
|
||||
foo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bar
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
">foo bar baz</p>
|
||||
</body>
|
||||
</html>
|
@ -5,11 +5,8 @@
|
||||
<p class="foo bar">foo bar baz</p>
|
||||
<p class=foo>foo bar baz</p>
|
||||
<p class=foo>foo bar baz</p>
|
||||
<p class="foo
|
||||
|
||||
|
||||
|
||||
|
||||
class1 class-23">foo bar baz</p>
|
||||
<p class="foo class1 class-23">foo bar baz</p>
|
||||
<p class="foo bar qqq">foo bar baz</p>
|
||||
<p class="foo bar">foo bar baz</p>
|
||||
|
||||
</body></html>
|
||||
|
@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<table width="200" border="1" align="center" cellpadding="4" cellspacing="0">
|
||||
<tr>
|
||||
<th colspan="1" rowspan="1" scope="col">Cell 1</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="test" colspan="1" rowspan="1" bgcolor="#FBF0DB">
|
||||
Cell 1
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,16 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<table width=200 border=1 align=center cellpadding=4 cellspacing=0>
|
||||
<tbody><tr>
|
||||
<th scope=col>Cell 1</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=test bgcolor=#FBF0DB>
|
||||
Cell 1
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
</body></html>
|
@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable="true">Stress reliever</div>
|
||||
<div contenteditable="">Stress reliever</div>
|
||||
<div contenteditable="false">Stress reliever</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable="">Stress reliever</div>
|
||||
<div contenteditable="">Stress reliever</div>
|
||||
<div contenteditable=false>Stress reliever</div>
|
||||
|
||||
</body></html>
|
@ -3,7 +3,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<p title=\n lang="" dir="">x</p>
|
||||
<p onclick="" ondblclick=" " onmousedown="" onmouseup="" onmouseover=" " onmousemove="" onmouseout="" onkeypress="
|
||||
" onkeydown="" onkeyup="">x</p>
|
||||
<p>x</p>
|
||||
|
||||
</body></html>
|
||||
|
@ -8,5 +8,12 @@
|
||||
<input type="text" onkeydown="javascript:myFunction()">
|
||||
</form>
|
||||
<div type="text" onmouseover="javascript:myFunction()">test</div>
|
||||
<div type="text" onmouseover=" javascript:myFunction() ">test</div>
|
||||
<div type="text" onmouseover="">test</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice"
|
||||
style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;">
|
||||
<circle onmouseover=" javascript:alert('test') " cx="50" cy="50" r="30" style="fill:url(#gradient)" />
|
||||
</svg>
|
||||
</body>
|
||||
</html>
|
@ -6,5 +6,10 @@
|
||||
<input onkeydown=myFunction()>
|
||||
</form>
|
||||
<div type=text onmouseover=myFunction()>test</div>
|
||||
<div type=text onmouseover=myFunction()>test</div>
|
||||
<div type=text>test</div>
|
||||
<svg xmlns=http://www.w3.org/2000/svg version=1.1 viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;">
|
||||
<circle onmouseover="alert('test')" cx=50 cy=50 r=30 style=fill:url(#gradient)></circle>
|
||||
</svg>
|
||||
|
||||
</body></html>
|
||||
|
@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="#" rel="value1 nofollow">Link</a>
|
||||
<a href="#" rel=" value1 nofollow ">Link</a>
|
||||
<a href="#" rel=" nofollow ">Link</a>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href=# rel="value1 nofollow">Link</a>
|
||||
<a href=# rel="value1 nofollow">Link</a>
|
||||
<a href=# rel=nofollow>Link</a>
|
||||
|
||||
</body></html>
|
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<img srcset="elva-fairy-480w.jpg 480w,
|
||||
elva-fairy-800w.jpg 800w"
|
||||
sizes="(max-width: 600px) 480px,
|
||||
800px"
|
||||
src="elva-fairy-800w.jpg"
|
||||
alt="Elva dressed as a fairy">
|
||||
|
||||
<img srcset="elva-fairy-320w.jpg,
|
||||
elva-fairy-480w.jpg 1.5x,
|
||||
elva-fairy-640w.jpg 2x"
|
||||
src="elva-fairy-640w.jpg"
|
||||
alt="Elva dressed as a fairy">
|
||||
|
||||
<img src="favicon72.png"
|
||||
alt="MDN logo"
|
||||
srcset=" favicon144.png 2x ">
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<img srcset="elva-fairy-480w.jpg 480w,elva-fairy-800w.jpg 800w" sizes="(max-width: 600px) 480px,800px" src=elva-fairy-800w.jpg alt="Elva dressed as a fairy">
|
||||
|
||||
<img srcset="elva-fairy-320w.jpg,elva-fairy-480w.jpg 1.5x,elva-fairy-640w.jpg 2x" src=elva-fairy-640w.jpg alt="Elva dressed as a fairy">
|
||||
|
||||
<img src=favicon72.png alt="MDN logo" srcset="favicon144.png 2x">
|
||||
|
||||
|
||||
</body></html>
|
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="" method="get" class="form-example" enctype="application/x-www-form-urlencoded">
|
||||
<div class="form-example">
|
||||
<label for="name">Enter your name: </label>
|
||||
<input type="text" name="name" id="name" required>
|
||||
</div>
|
||||
<div class="form-example">
|
||||
<label for="email">Enter your email: </label>
|
||||
<input type="email" name="email" id="email" required>
|
||||
</div>
|
||||
<div class="form-example">
|
||||
<input type="submit" value="Subscribe!">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="#" accept-charset="US-ASCII UTF-8">
|
||||
First name:
|
||||
<input type="text" name="fname">
|
||||
<br> Last name:
|
||||
<input type="text" name="lname">
|
||||
<br>
|
||||
<input type="submit" value="Submit">
|
||||
<input name="cc-number" id="cc-number" autocomplete="off">
|
||||
<input name="cc-number1" id="cc-number1" autocomplete="
|
||||
organization
|
||||
organization-title
|
||||
">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,31 @@
|
||||
<!doctype html><html lang=en><head>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="" class=form-example>
|
||||
<div class=form-example>
|
||||
<label for=name>Enter your name: </label>
|
||||
<input name=name id=name required>
|
||||
</div>
|
||||
<div class=form-example>
|
||||
<label for=email>Enter your email: </label>
|
||||
<input type=email name=email id=email required>
|
||||
</div>
|
||||
<div class=form-example>
|
||||
<input type=submit value=Subscribe!>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action=# accept-charset="US-ASCII UTF-8">
|
||||
First name:
|
||||
<input name=fname>
|
||||
<br> Last name:
|
||||
<input name=lname>
|
||||
<br>
|
||||
<input type=submit value=Submit>
|
||||
<input name=cc-number id=cc-number autocomplete=off>
|
||||
<input name=cc-number1 id=cc-number1 autocomplete="organization organization-title">
|
||||
</form>
|
||||
|
||||
|
||||
</body></html>
|
Loading…
Reference in New Issue
Block a user