Skip to content

Commit

Permalink
feat(html/minifier): Merge script tags (#6273)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Oct 28, 2022
1 parent 30b3596 commit 02f8d31
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 65 deletions.
144 changes: 89 additions & 55 deletions crates/swc_html_minifier/src/lib.rs
Expand Up @@ -578,6 +578,36 @@ impl Minifier<'_> {
})
}

fn is_type_text_javascript(&self, value: &JsWord) -> bool {
let value = value.trim().to_ascii_lowercase();
let value = if let Some(next) = value.split(';').next() {
next
} else {
&value
};

match value {
// Legacy JavaScript MIME types
"application/javascript"
| "application/ecmascript"
| "application/x-ecmascript"
| "application/x-javascript"
| "text/ecmascript"
| "text/javascript1.0"
| "text/javascript1.1"
| "text/javascript1.2"
| "text/javascript1.3"
| "text/javascript1.4"
| "text/javascript1.5"
| "text/jscript"
| "text/livescript"
| "text/x-ecmascript"
| "text/x-javascript" => true,
"text/javascript" => true,
_ => false,
}
}

fn is_default_attribute_value(
&self,
namespace: Namespace,
Expand Down Expand Up @@ -606,47 +636,22 @@ impl Minifier<'_> {
}
_ => {}
},
js_word!("script") => {
match attribute.name {
js_word!("type") => {
let value = if let Some(next) = attribute_value.split(';').next() {
next
} else {
attribute_value
};

match value {
// Legacy JavaScript MIME types
"application/javascript"
| "application/ecmascript"
| "application/x-ecmascript"
| "application/x-javascript"
| "text/ecmascript"
| "text/javascript1.0"
| "text/javascript1.1"
| "text/javascript1.2"
| "text/javascript1.3"
| "text/javascript1.4"
| "text/javascript1.5"
| "text/jscript"
| "text/livescript"
| "text/x-ecmascript"
| "text/x-javascript" => return true,
"text/javascript" => return true,
_ => {}
}
js_word!("script") => match attribute.name {
js_word!("type") => {
if self.is_type_text_javascript(attribute_value) {
return true;
}
js_word!("language") => {
match &*attribute_value.trim().to_ascii_lowercase() {
"javascript" | "javascript1.2" | "javascript1.3"
| "javascript1.4" | "javascript1.5" | "javascript1.6"
| "javascript1.7" => return true,
_ => {}
}
}
js_word!("language") => {
match &*attribute_value.trim().to_ascii_lowercase() {
"javascript" | "javascript1.2" | "javascript1.3"
| "javascript1.4" | "javascript1.5" | "javascript1.6"
| "javascript1.7" => return true,
_ => {}
}
_ => {}
}
}
_ => {}
},
js_word!("link") => {
if attribute.name == js_word!("type")
&& &*attribute_value.trim().to_ascii_lowercase() == "text/css"
Expand Down Expand Up @@ -1336,21 +1341,33 @@ impl Minifier<'_> {

fn allow_elements_to_merge(&self, left: Option<&Child>, right: &Element) -> bool {
if let Some(Child::Element(left)) = left {
if matches!(left.namespace, Namespace::HTML | Namespace::SVG)
let is_style_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)
&& left.tag_name == js_word!("style")
&& matches!(right.namespace, Namespace::HTML | Namespace::SVG)
&& right.tag_name == js_word!("style")
{
&& right.tag_name == js_word!("style");
let is_script_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)
&& left.tag_name == js_word!("script")
&& matches!(right.namespace, Namespace::HTML | Namespace::SVG)
&& right.tag_name == js_word!("script");

if is_style_tag || is_script_tag {
let mut need_skip = false;

let mut left_attributes = left
.attributes
.clone()
.into_iter()
.filter(|attribute| match attribute.name {
js_word!("src") if is_script_tag => {
need_skip = true;

true
}
js_word!("type") => {
if let Some(value) = &attribute.value {
if value.trim().to_ascii_lowercase() == "text/css" {
if (is_style_tag && value.trim().to_ascii_lowercase() == "text/css")
|| is_script_tag && self.is_type_text_javascript(value)
{
false
} else {
need_skip = true;
Expand All @@ -1374,9 +1391,16 @@ impl Minifier<'_> {
.clone()
.into_iter()
.filter(|attribute| match attribute.name {
js_word!("src") if is_script_tag => {
need_skip = true;

true
}
js_word!("type") => {
if let Some(value) = &attribute.value {
if value.trim().to_ascii_lowercase() == "text/css" {
if (is_style_tag && value.trim().to_ascii_lowercase() == "text/css")
|| (is_script_tag && self.is_type_text_javascript(value))
{
false
} else {
need_skip = true;
Expand Down Expand Up @@ -1406,18 +1430,31 @@ impl Minifier<'_> {
}

fn merge_text_children(&self, left: &Element, right: &Element) -> Vec<Child> {
let is_script_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)
&& left.tag_name == js_word!("script")
&& matches!(right.namespace, Namespace::HTML | Namespace::SVG)
&& right.tag_name == js_word!("script");

let data = left.children.iter().chain(right.children.iter()).fold(
String::new(),
|mut acc, child| match child {
Child::Text(text) => {
Child::Text(text) if text.data.len() > 0 => {
acc.push_str(&text.data);

if is_script_tag {
acc.push(';');
}

acc
}
_ => acc,
},
);

if data.is_empty() {
return vec![];
}

vec![Child::Text(Text {
span: DUMMY_SP,
data: data.into(),
Expand Down Expand Up @@ -2004,7 +2041,9 @@ impl Minifier<'_> {
}

let minified = match String::from_utf8(buf) {
Ok(minified) => minified,
// Avoid generating the sequence "</script" in JS code
// TODO move it to ecma codegen under the option?
Ok(minified) => minified.replace("</script>", "<\\/script>"),
_ => return None,
};

Expand Down Expand Up @@ -2751,19 +2790,14 @@ impl VisitMut for Minifier<'_> {
Some(js_word!("module")) if self.need_minify_js() => {
text_type = Some(MinifierType::JsModule);
}
Some(
js_word!("text/javascript")
| js_word!("text/ecmascript")
| js_word!("text/jscript")
| js_word!("application/javascript")
| js_word!("application/x-javascript")
| js_word!("application/ecmascript"),
)
| None
if self.need_minify_js() =>
Some(value)
if self.need_minify_js() && self.is_type_text_javascript(&value) =>
{
text_type = Some(MinifierType::JsScript);
}
None if self.need_minify_js() => {
text_type = Some(MinifierType::JsScript);
}
Some(
js_word!("application/json")
| js_word!("application/ld+json")
Expand Down
@@ -1,6 +1,6 @@
<!doctype html><script defer>console.log()</script><script>console.log()</script><script>console.log()</script><script type=module>console.log()</script><script type=module>console.log()</script><script>window.jQuery||document.write('<script src="jquery.js"></script>')</script><script type=text/html>
<!doctype html><script defer>console.log()</script><script>console.log();console.log()</script><script type=module>console.log()</script><script type=module>console.log()</script><script>window.jQuery||document.write('<script src="jquery.js"><\/script>')</script><script type=text/html>
<div>
test
</div>
<!-- aa -->\n
</script><script type="">alert(1)</script><script type=modules>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script type=module src=app.mjs></script><script nomodule defer src=classic-app-bundle.js></script><script>alert(1)</script><script type=text/vbscript>MsgBox("foo bar")</script><script type="">MsgBox("foo bar")</script><script type=;;;;;>MsgBox("foo bar")</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script></script>
</script><script type="">alert(1)</script><script type=modules>alert(1)</script><script>alert(1);alert(1)</script><script type=module src=app.mjs></script><script nomodule defer src=classic-app-bundle.js></script><script>alert(1)</script><script type=text/vbscript>MsgBox("foo bar")</script><script type="">MsgBox("foo bar")</script><script type=;;;;;>MsgBox("foo bar")</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script>alert(1)</script><script></script>
Expand Up @@ -8,7 +8,7 @@


<div>baz</div>
<script></script><script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(10)</script>
<!-- @preserve foo -->
<!-- @copyright foo -->
Expand Down
Expand Up @@ -10,7 +10,7 @@
<!-- foo --><div>baz</div><!-- bar
moo -->
<script></script><script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(10)</script>
<!-- @preserve foo -->
<!-- @copyright foo -->
Expand Down
Expand Up @@ -2,7 +2,7 @@


<div>baz</div>
<script></script><script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(1)</script><style>p{color:red}</style><script>alert(8)</script>
<script>alert(10)</script>
<!--! test -->
<!-- ko if: someExpressionGoesHere --><li>test</li><!-- /ko -->
@@ -0,0 +1,73 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div>breaker</div>
<script>var a = "test";console.log(a)</script>
<script>var b = "test";console.log(b)</script>


<div>breaker</div>
<script type="text/javascript">var a = "test";console.log(a);</script>
<script type="application/javascript">var b = "test";console.log(b);</script>


<div>breaker</div>
<script type="application/javascript">var a = "test";console.log(a)</script>
<script>var b = "test";console.log(b)</script>


<div>breaker</div>
<script type="application/javascript;version=1.8">var a = "test";console.log(a)</script>
<script>var b = "test";console.log(b)</script>


<div>breaker</div>
<script>var a = "test";console.log(a)</script>
<script crossorigin="use-credentials">var b = "test";console.log(b)</script>


<div>breaker</div>
<script type="text/javascript" crossorigin="use-credentials">var a = "test";console.log(a)</script>
<script crossorigin="use-credentials">var b = "test";console.log(b)</script>


<div>breaker</div>
<script>
(function test() {
let test = "1";
console.log(test);
})();
</script>
<script>
let test = "1";
console.log(test);
</script>


<div>breaker</div>
<script type="module">var a = "test";console.log(a)</script>
<script>var b = "test";console.log(b)</script>


<div>breaker</div>
<script type="module">var a = "test";console.log(a)</script>
<script type="module">var b = "test";console.log(b)</script>


<div>breaker</div>
<script>var a = "test";console.log(a)</script>
<script type="unknown">var b = "test";console.log(b)</script>


<div>breaker</div>
<script src="test.js"></script>
<script>var b = "test";console.log(b)</script>

<div>breaker</div>
<script></script>

</body>
</html>
@@ -0,0 +1,44 @@
<!doctype html><html lang=en><title>Document</title><div>breaker</div>
<script>var o="test";console.log(o);var e="test";console.log(e)</script>


<div>breaker</div>
<script>var o="test";console.log(o);var e="test";console.log(e)</script>


<div>breaker</div>
<script>var o="test";console.log(o);var e="test";console.log(e)</script>


<div>breaker</div>
<script>var o="test";console.log(o);var e="test";console.log(e)</script>


<div>breaker</div>
<script>var o="test";console.log(o)</script><script crossorigin=use-credentials>var o="test";console.log(o)</script>


<div>breaker</div>
<script crossorigin=use-credentials>var o="test";console.log(o);var e="test";console.log(e)</script>


<div>breaker</div>
<script>(function o(){let o="1";console.log(o)})();let o="1";console.log(o)</script>


<div>breaker</div>
<script type=module>var o="test";console.log(o)</script><script>var o="test";console.log(o)</script>


<div>breaker</div>
<script type=module>var o="test";console.log(o)</script><script type=module>var o="test";console.log(o)</script>


<div>breaker</div>
<script>var o="test";console.log(o)</script><script type=unknown>var b = "test";console.log(b)</script>


<div>breaker</div>
<script src=test.js></script><script>var o="test";console.log(o)</script>

<div>breaker</div>
Expand Up @@ -183,5 +183,8 @@ <h2>Party coffee cake recipe</h2>
</math>
<script blocking="render a">console.log("block");</script>
<script>(function(test){ var test = "test" + Math.random() + test; var foo = 1; var bar = 2 + Math.random(); alert(foo + " " + bar); console.log(Math.random()) })("test")</script>
<script>window.jQuery || document.write('<script src="jquery.js"><\/script>')</script>
<div>test</div>
<script></script>
</body>
</html>

1 comment on commit 02f8d31

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 02f8d31 Previous: 8c1ac68 Ratio
es/full/bugs-1 354161 ns/iter (± 28230) 422553 ns/iter (± 35040) 0.84
es/full/minify/libraries/antd 1865115424 ns/iter (± 63451655) 2074136495 ns/iter (± 84790422) 0.90
es/full/minify/libraries/d3 429008668 ns/iter (± 11133572) 469995321 ns/iter (± 37946248) 0.91
es/full/minify/libraries/echarts 1570650146 ns/iter (± 38899866) 1715074849 ns/iter (± 111971530) 0.92
es/full/minify/libraries/jquery 99925110 ns/iter (± 1841293) 128041674 ns/iter (± 9408195) 0.78
es/full/minify/libraries/lodash 113938045 ns/iter (± 8793400) 155130241 ns/iter (± 21594048) 0.73
es/full/minify/libraries/moment 62702625 ns/iter (± 2262337) 91183362 ns/iter (± 10031001) 0.69
es/full/minify/libraries/react 20906086 ns/iter (± 983532) 28343139 ns/iter (± 3613934) 0.74
es/full/minify/libraries/terser 335456294 ns/iter (± 7702636) 450190118 ns/iter (± 101770297) 0.75
es/full/minify/libraries/three 576764323 ns/iter (± 13336693) 735889385 ns/iter (± 198894852) 0.78
es/full/minify/libraries/typescript 3462433450 ns/iter (± 77210381) 4401625514 ns/iter (± 274647832) 0.79
es/full/minify/libraries/victory 822593894 ns/iter (± 19122266) 1093032888 ns/iter (± 170596397) 0.75
es/full/minify/libraries/vue 165270185 ns/iter (± 7545555) 230265071 ns/iter (± 83991406) 0.72
es/full/codegen/es3 34581 ns/iter (± 458) 38632 ns/iter (± 6795) 0.90
es/full/codegen/es5 34445 ns/iter (± 1261) 39043 ns/iter (± 10430) 0.88
es/full/codegen/es2015 34563 ns/iter (± 657) 39171 ns/iter (± 3958) 0.88
es/full/codegen/es2016 34421 ns/iter (± 994) 39965 ns/iter (± 5447) 0.86
es/full/codegen/es2017 34017 ns/iter (± 1070) 38487 ns/iter (± 6334) 0.88
es/full/codegen/es2018 34418 ns/iter (± 1711) 38205 ns/iter (± 7458) 0.90
es/full/codegen/es2019 34451 ns/iter (± 1569) 39241 ns/iter (± 14438) 0.88
es/full/codegen/es2020 34519 ns/iter (± 437) 39217 ns/iter (± 3508) 0.88
es/full/all/es3 196319736 ns/iter (± 11486928) 264957445 ns/iter (± 83751900) 0.74
es/full/all/es5 183276300 ns/iter (± 10531859) 233424770 ns/iter (± 55331811) 0.79
es/full/all/es2015 147468855 ns/iter (± 5020165) 180627367 ns/iter (± 56300368) 0.82
es/full/all/es2016 144376424 ns/iter (± 14632678) 183157086 ns/iter (± 70558860) 0.79
es/full/all/es2017 148698989 ns/iter (± 7522574) 187879457 ns/iter (± 61924798) 0.79
es/full/all/es2018 140426073 ns/iter (± 5928379) 180862070 ns/iter (± 33282267) 0.78
es/full/all/es2019 149695966 ns/iter (± 10112648) 179318277 ns/iter (± 99456908) 0.83
es/full/all/es2020 138654535 ns/iter (± 6286155) 177289500 ns/iter (± 28671410) 0.78
es/full/parser 728183 ns/iter (± 90481) 807085 ns/iter (± 89320) 0.90
es/full/base/fixer 26325 ns/iter (± 410) 28651 ns/iter (± 2793) 0.92
es/full/base/resolver_and_hygiene 92630 ns/iter (± 2222) 101931 ns/iter (± 18043) 0.91
serialization of ast node 214 ns/iter (± 3) 226 ns/iter (± 15) 0.95
serialization of serde 215 ns/iter (± 4) 228 ns/iter (± 40) 0.94

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.