feat(html/minifier): Compress default values of attributes (#4427)

This commit is contained in:
Alexander Akait 2022-04-26 10:12:32 +03:00 committed by GitHub
parent c7aa0fe86b
commit 4c699f2554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 304 additions and 43 deletions

View File

@ -47,16 +47,6 @@ static BOOLEAN_ATTRIBUTES: &[&str] = &[
"visible", "visible",
]; ];
static EXECUTABLE_SCRIPTS_MIME_TYPES: &[&str] = &[
"text/javascript",
"text/ecmascript",
"text/jscript",
"application/javascript",
"application/x-javascript",
"application/ecmascript",
"module",
];
struct Minifier {} struct Minifier {}
impl Minifier { impl Minifier {
@ -64,6 +54,99 @@ impl Minifier {
BOOLEAN_ATTRIBUTES.contains(&name) BOOLEAN_ATTRIBUTES.contains(&name)
} }
fn is_default_attribute_value(
&self,
namespace: Namespace,
tag_name: &str,
attribute_name: &str,
attribute_value: &str,
) -> bool {
matches!(
(
namespace,
tag_name,
attribute_name,
attribute_value.to_ascii_lowercase().trim()
),
(Namespace::HTML, "iframe", "height", "150")
| (Namespace::HTML, "iframe", "width", "300")
| (Namespace::HTML, "iframe", "frameborder", "1")
| (Namespace::HTML, "iframe", "loading", "eager")
| (Namespace::HTML, "iframe", "fetchpriority", "auto")
| (
Namespace::HTML,
"iframe",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (
Namespace::HTML,
"a",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (Namespace::HTML, "a", "target", "_self")
| (Namespace::HTML, "area", "target", "_self")
| (Namespace::HTML, "area", "shape", "rect")
| (
Namespace::HTML,
"area",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (Namespace::HTML, "form", "method", "get")
| (Namespace::HTML, "form", "target", "_self")
| (Namespace::HTML, "input", "type", "text")
| (Namespace::HTML, "input", "size", "20")
| (Namespace::HTML, "track", "kind", "subtitles")
| (Namespace::HTML, "textarea", "cols", "20")
| (Namespace::HTML, "textarea", "rows", "2")
| (Namespace::HTML, "textarea", "wrap", "sort")
| (Namespace::HTML, "progress", "max", "1")
| (Namespace::HTML, "meter", "min", "0")
| (Namespace::HTML, "img", "decoding", "auto")
| (Namespace::HTML, "img", "fetchpriority", "auto")
| (Namespace::HTML, "img", "loading", "eager")
| (
Namespace::HTML,
"img",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (Namespace::HTML, "link", "type", "text/css")
| (Namespace::HTML, "link", "fetchpriority", "auto")
| (
Namespace::HTML,
"link",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (Namespace::HTML, "style", "type", "text/css")
| (Namespace::HTML, "script", "type", "text/javascript")
| (Namespace::HTML, "script", "type", "text/ecmascript")
| (Namespace::HTML, "script", "type", "text/jscript")
| (Namespace::HTML, "script", "type", "application/javascript")
| (
Namespace::HTML,
"script",
"type",
"application/x-javascript"
)
| (Namespace::HTML, "script", "type", "application/ecmascript")
| (Namespace::HTML, "script", "fetchpriority", "auto")
| (
Namespace::HTML,
"script",
"referrerpolicy",
"strict-origin-when-cross-origin"
)
| (Namespace::HTML, "ol", "type", "1")
| (Namespace::HTML, "base", "target", "_self")
| (Namespace::HTML, "canvas", "height", "150")
| (Namespace::HTML, "canvas", "width", "300")
)
}
fn is_conditional_comment(&self, data: &str) -> bool { fn is_conditional_comment(&self, data: &str) -> bool {
let trimmed = data.trim(); let trimmed = data.trim();
@ -80,40 +163,23 @@ impl VisitMut for Minifier {
n.visit_mut_children_with(self); n.visit_mut_children_with(self);
n.children.retain(|child| !matches!(child, Child::Comment(comment) if !self.is_conditional_comment(&comment.data))); n.children.retain(|child| !matches!(child, Child::Comment(comment) if !self.is_conditional_comment(&comment.data)));
n.attributes.retain(|attribute| match &*attribute.name { n.attributes.retain(|attribute| {
"type" if attribute.value.is_none() {
if n.namespace == Namespace::HTML return true;
&& matches!(n.tag_name.as_ref(), "script")
&& (attribute.value.is_some()
&& attribute
.value
.as_ref()
.map(|v| {
EXECUTABLE_SCRIPTS_MIME_TYPES
.iter()
.any(|mime| v.eq_str_ignore_ascii_case(mime))
})
.unwrap_or_default()) =>
{
false
} }
"type"
if n.namespace == Namespace::HTML match &*attribute.name {
&& matches!(n.tag_name.as_ref(), "style" | "link") _ if self.is_default_attribute_value(
&& (attribute.value.is_some() n.namespace,
&& matches!( &n.tag_name,
attribute &attribute.name,
.value attribute.value.as_ref().unwrap(),
.as_ref() ) =>
.unwrap() {
.to_ascii_lowercase() false
.trim(), }
"text/css" _ => true,
)) =>
{
false
} }
_ => true,
}); });
} }

View File

@ -0,0 +1,13 @@
<!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">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<iframe id="test" src="test.html" frameborder="1" height="150" width="300" loading="eager" fetchpriority="auto" referrerpolicy="strict-origin-when-cross-origin"></iframe>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!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">
<meta http-equiv=X-UA-Compatible content="ie=edge">
<title>Document</title>
</head>
<body>
<iframe id=test src=test.html></iframe>
</body></html>

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<img src="test.png" alt="test" decoding="auto" loading="eager" referrerpolicy="strict-origin-when-cross-origin">
</body>
</html>

View File

@ -0,0 +1,7 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<img src=test.png alt=test>
</body></html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<input name="a" type="text">
<input name="b" type="TEXT">
<input name="c" type=" TEXT ">
<input name="d" size=" 20 ">
</body>
</html>

View File

@ -0,0 +1,10 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<input name=a>
<input name=b>
<input name=c>
<input name=d>
</body></html>

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<meter id="fuel"
min="0" max="100"
low="33" high="66" optimum="80"
value="50">
at 50/100
</meter>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<meter id=fuel max=100 low=33 high=66 optimum=80 value=50>
at 50/100
</meter>
</body></html>

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<ol type="1">
<li>Mix flour, baking powder, sugar, and salt.</li>
<li>In another bowl, mix eggs, milk, and oil.</li>
<li>Stir both mixtures together.</li>
<li>Fill muffin tray 3/4 full.</li>
<li>Bake for 20 minutes.</li>
</ol>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<ol>
<li>Mix flour, baking powder, sugar, and salt.</li>
<li>In another bowl, mix eggs, milk, and oil.</li>
<li>Stir both mixtures together.</li>
<li>Fill muffin tray 3/4 full.</li>
<li>Bake for 20 minutes.</li>
</ol>
</body></html>

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<progress max="1"></progress>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<progress></progress>
</body></html>

View File

@ -4,7 +4,7 @@
<script> <script>
console.log(); console.log();
</script> </script>
<script> <script type=module>
console.log(); console.log();
</script> </script>
</head><body></body></html> </head><body></body></html>

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<textarea name="test" id="test" cols="20" rows="2"></textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<textarea name=test id=test></textarea>
&lt;/&gt;
&lt;/&gt;</body></html>

View File

@ -0,0 +1,31 @@
<!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">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<video controls
src="/media/cc0-videos/friday.mp4">
<track default
kind="captions"
srclang="en"
src="/media/examples/friday.vtt" />
Sorry, your browser doesn't support embedded videos.
</video>
<video controls
src="/media/cc0-videos/friday.mp4">
<track default
kind="subtitles"
srclang="en"
src="/media/examples/friday.vtt" />
Sorry, your browser doesn't support embedded videos.
</video>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!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">
<meta http-equiv=X-UA-Compatible content="ie=edge">
<title>Document</title>
</head>
<body>
<video controls src=/media/cc0-videos/friday.mp4>
<track default kind=captions srclang=en src=/media/examples/friday.vtt>
Sorry, your browser doesn't support embedded videos.
</video>
<video controls src=/media/cc0-videos/friday.mp4>
<track default srclang=en src=/media/examples/friday.vtt>
Sorry, your browser doesn't support embedded videos.
</video>
</body></html>