From 7baad47ed70893b3108890dd31e57bbeef68f3f8 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Mon, 27 Jun 2022 19:48:23 +0300 Subject: [PATCH 1/4] feat(ecma/parser): add the `allow_return_outside_function` option --- crates/swc_ecma_parser/src/lib.rs | 13 +++ crates/swc_ecma_parser/src/parser/stmt.rs | 2 +- crates/swc_html_minifier/src/lib.rs | 109 ++++++++++++++++-- .../attribute/javascript-prefix/input.html | 3 + .../javascript-prefix/output.min.html | 5 +- .../fixture/attribute/onclick/output.min.html | 2 +- 6 files changed, 124 insertions(+), 10 deletions(-) diff --git a/crates/swc_ecma_parser/src/lib.rs b/crates/swc_ecma_parser/src/lib.rs index e969a6fa6264..91c488d47c5c 100644 --- a/crates/swc_ecma_parser/src/lib.rs +++ b/crates/swc_ecma_parser/src/lib.rs @@ -256,6 +256,16 @@ impl Syntax { } } + pub fn allow_return_outside_function(self) -> bool { + match self { + Syntax::Es(EsConfig { + allow_return_outside_function, + .. + }) => allow_return_outside_function, + Syntax::Typescript(_) => false, + } + } + pub(crate) fn early_errors(self) -> bool { match self { Syntax::Typescript(t) => !t.no_early_errors, @@ -315,6 +325,9 @@ pub struct EsConfig { #[serde(default, rename = "allowSuperOutsideMethod")] pub allow_super_outside_method: bool, + + #[serde(default, rename = "allowReturnOutsideFunction")] + pub allow_return_outside_function: bool, } /// Syntactic context. diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index f73c2b39e62e..42810ee461ff 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -562,7 +562,7 @@ impl<'a, I: Tokens> Parser { })) }); - if !self.ctx().in_function { + if !self.ctx().in_function && !self.input.syntax().allow_return_outside_function() { self.emit_err(span!(self, start), SyntaxError::ReturnNotAllowed); } diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index 769e4bb3d1fc..fb4c56171b46 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -1055,13 +1055,17 @@ impl Minifier { // TODO source map url output for JS and CSS? // TODO allow preserve comments - fn minify_js(&self, data: String, is_module: bool) -> Option { + fn minify_js(&self, data: String, is_module: bool, is_attribute: bool) -> Option { let mut errors: Vec<_> = vec![]; let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let fm = cm.new_source_file(FileName::Anon, data); - let syntax = swc_ecma_parser::Syntax::Es(Default::default()); + let syntax = swc_ecma_parser::Syntax::Es(swc_ecma_parser::EsConfig { + allow_return_outside_function: !is_module && is_attribute, + ..Default::default() + }); + let target = swc_ecma_ast::EsVersion::latest(); let mut program = if is_module { match swc_ecma_parser::parse_file_as_module(&fm, syntax, target, None, &mut errors) { @@ -1075,6 +1079,13 @@ impl Minifier { } }; + println!("{:?}", errors); + + // Avoid compress potential invalid JS + if !errors.is_empty() { + return None; + } + let unresolved_mark = Mark::new(); let top_level_mark = Mark::new(); @@ -1083,11 +1094,6 @@ impl Minifier { &mut swc_ecma_transforms_base::resolver(unresolved_mark, top_level_mark, false), ); - // Avoid compress potential invalid JS - if !errors.is_empty() { - return None; - } - let options = swc_ecma_minifier::option::MinifyOptions { compress: Some(swc_ecma_minifier::option::CompressOptions { ecma: target, @@ -1659,6 +1665,89 @@ impl VisitMut for Minifier { value = values.join(" "); } else if self.minify_js && self.is_event_handler_attribute(&n.name) { value = match self.minify_js(value.clone(), false) { + } else if self.is_comma_separated_attribute(current_element, &n.name) { + let is_sizes = matches!(&*n.name, "sizes" | "imagesizes"); + + let mut new_values = vec![]; + + for value in value.trim().split(',') { + if is_sizes { + let trimmed = value.trim(); + + match self.minify_sizes(trimmed) { + Some(minified) => { + new_values.push(minified); + } + _ => { + new_values.push(trimmed.to_string()); + } + }; + } else { + new_values.push(value.trim().to_string()); + } + } + + value = new_values.join(","); + + if self.minify_css && &*n.name == "media" && !value.is_empty() { + if let Some(minified) = + self.minify_css(value.clone(), CssMinificationMode::MediaQueryList) + { + value = minified; + } + } + } else if self.is_trimable_separated_attribute(current_element, &n.name) { + value = value.trim().to_string(); + + if self.minify_css && &*n.name == "style" && !value.is_empty() { + if let Some(minified) = + self.minify_css(value.clone(), CssMinificationMode::ListOfDeclarations) + { + value = minified; + } + } + } else if current_element.namespace == Namespace::HTML + && &n.name == "contenteditable" + && value == "true" + { + n.value = Some(js_word!("")); + + return; + } else if &n.name == "content" + && self.element_has_attribute_with_value( + current_element, + "http-equiv", + &["content-security-policy"], + ) + { + let values = value.trim().split(';'); + + let mut new_values = vec![]; + + for value in values { + new_values.push( + value + .trim() + .split(' ') + .filter(|s| !s.is_empty()) + .collect::>() + .join(" "), + ); + } + + value = new_values.join(";"); + + if value.ends_with(';') { + value.pop(); + } + } else if self.is_event_handler_attribute(&n.name) { + value = value.trim().into(); + + if value.trim().to_lowercase().starts_with("javascript:") { + value = value.chars().skip(11).collect(); + } + + value = match self.minify_js(value.clone(), false, true) { Some(minified) => minified, _ => value, }; @@ -1682,6 +1771,8 @@ impl VisitMut for Minifier { match minifier_type { Some(MinifierType::JsScript) if self.minify_js => { value = match self.minify_js(value.clone(), false) { + Some(MinifierType::Js) if self.minify_js => { + value = match self.minify_js(value.clone(), false, true) { Some(minified) => minified, _ => value, }; @@ -1819,6 +1910,8 @@ impl VisitMut for Minifier { match text_type { Some(MinifierType::JsScript) => { let minified = match self.minify_js(n.data.to_string(), false) { + Some(TextChildrenType::Script) => { + let minified = match self.minify_js(n.data.to_string(), false, false) { Some(minified) => minified, None => return, }; @@ -1827,6 +1920,8 @@ impl VisitMut for Minifier { } Some(MinifierType::JsModule) => { let minified = match self.minify_js(n.data.to_string(), true) { + Some(TextChildrenType::Module) => { + let minified = match self.minify_js(n.data.to_string(), true, false) { Some(minified) => minified, None => return, }; diff --git a/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/input.html b/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/input.html index b346389aa5c9..567f610501f0 100644 --- a/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/input.html +++ b/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/input.html @@ -15,5 +15,8 @@ style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;"> +
test
+ Click me + Click me \ No newline at end of file diff --git a/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/output.min.html b/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/output.min.html index cca9e1eefac3..b90baa91fc16 100644 --- a/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/output.min.html +++ b/crates/swc_html_minifier/tests/fixture/attribute/javascript-prefix/output.min.html @@ -6,4 +6,7 @@
test
- \ No newline at end of file + +
test
+ Click me + Click me \ No newline at end of file diff --git a/crates/swc_html_minifier/tests/fixture/attribute/onclick/output.min.html b/crates/swc_html_minifier/tests/fixture/attribute/onclick/output.min.html index 6bd2a105a686..b495c04ed9c5 100644 --- a/crates/swc_html_minifier/tests/fixture/attribute/onclick/output.min.html +++ b/crates/swc_html_minifier/tests/fixture/attribute/onclick/output.min.html @@ -1,2 +1,2 @@ -DocumentGoogle +DocumentGoogle Google \ No newline at end of file From 2a7021bbc8190a66118f692346517abe6bf271c1 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 28 Jun 2022 19:26:11 +0300 Subject: [PATCH 2/4] chore: remove log --- crates/swc_html_minifier/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index fb4c56171b46..bd8e4298fb0b 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -1079,8 +1079,6 @@ impl Minifier { } }; - println!("{:?}", errors); - // Avoid compress potential invalid JS if !errors.is_empty() { return None; From 5158b357ce8b573bb3059ffb43ba08f42d685cbf Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 28 Jun 2022 19:32:25 +0300 Subject: [PATCH 3/4] refactor: rebase --- crates/swc_html_minifier/src/lib.rs | 92 +---------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index bd8e4298fb0b..a637f4da8bca 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -1661,90 +1661,7 @@ impl VisitMut for Minifier { } value = values.join(" "); - } else if self.minify_js && self.is_event_handler_attribute(&n.name) { - value = match self.minify_js(value.clone(), false) { - } else if self.is_comma_separated_attribute(current_element, &n.name) { - let is_sizes = matches!(&*n.name, "sizes" | "imagesizes"); - - let mut new_values = vec![]; - - for value in value.trim().split(',') { - if is_sizes { - let trimmed = value.trim(); - - match self.minify_sizes(trimmed) { - Some(minified) => { - new_values.push(minified); - } - _ => { - new_values.push(trimmed.to_string()); - } - }; - } else { - new_values.push(value.trim().to_string()); - } - } - - value = new_values.join(","); - - if self.minify_css && &*n.name == "media" && !value.is_empty() { - if let Some(minified) = - self.minify_css(value.clone(), CssMinificationMode::MediaQueryList) - { - value = minified; - } - } - } else if self.is_trimable_separated_attribute(current_element, &n.name) { - value = value.trim().to_string(); - - if self.minify_css && &*n.name == "style" && !value.is_empty() { - if let Some(minified) = - self.minify_css(value.clone(), CssMinificationMode::ListOfDeclarations) - { - value = minified; - } - } - } else if current_element.namespace == Namespace::HTML - && &n.name == "contenteditable" - && value == "true" - { - n.value = Some(js_word!("")); - - return; - } else if &n.name == "content" - && self.element_has_attribute_with_value( - current_element, - "http-equiv", - &["content-security-policy"], - ) - { - let values = value.trim().split(';'); - - let mut new_values = vec![]; - - for value in values { - new_values.push( - value - .trim() - .split(' ') - .filter(|s| !s.is_empty()) - .collect::>() - .join(" "), - ); - } - - value = new_values.join(";"); - - if value.ends_with(';') { - value.pop(); - } } else if self.is_event_handler_attribute(&n.name) { - value = value.trim().into(); - - if value.trim().to_lowercase().starts_with("javascript:") { - value = value.chars().skip(11).collect(); - } - value = match self.minify_js(value.clone(), false, true) { Some(minified) => minified, _ => value, @@ -1768,15 +1685,13 @@ impl VisitMut for Minifier { match minifier_type { Some(MinifierType::JsScript) if self.minify_js => { - value = match self.minify_js(value.clone(), false) { - Some(MinifierType::Js) if self.minify_js => { value = match self.minify_js(value.clone(), false, true) { Some(minified) => minified, _ => value, }; } Some(MinifierType::JsModule) if self.minify_js => { - value = match self.minify_js(value.clone(), true) { + value = match self.minify_js(value.clone(), true, true) { Some(minified) => minified, _ => value, }; @@ -1907,8 +1822,6 @@ impl VisitMut for Minifier { match text_type { Some(MinifierType::JsScript) => { - let minified = match self.minify_js(n.data.to_string(), false) { - Some(TextChildrenType::Script) => { let minified = match self.minify_js(n.data.to_string(), false, false) { Some(minified) => minified, None => return, @@ -1917,8 +1830,6 @@ impl VisitMut for Minifier { n.data = minified.into(); } Some(MinifierType::JsModule) => { - let minified = match self.minify_js(n.data.to_string(), true) { - Some(TextChildrenType::Module) => { let minified = match self.minify_js(n.data.to_string(), true, false) { Some(minified) => minified, None => return, @@ -2030,7 +1941,6 @@ fn create_minifier(context_element: Option<&Element>, options: &MinifyOptions) - minify_js: options.minify_js, minify_json: options.minify_json, minify_css: options.minify_css, - minify_additional_attributes: options.minify_additional_attributes.clone(), minify_additional_scripts_content: options.minify_additional_scripts_content.clone(), } From 1a1b6c3cff72d29af8ae09b31a84a8d41f113f2c Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Wed, 29 Jun 2022 07:13:03 +0300 Subject: [PATCH 4/4] fix: visibility --- crates/swc_ecma_parser/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_parser/src/lib.rs b/crates/swc_ecma_parser/src/lib.rs index 91c488d47c5c..72c0e5a77dca 100644 --- a/crates/swc_ecma_parser/src/lib.rs +++ b/crates/swc_ecma_parser/src/lib.rs @@ -246,7 +246,7 @@ impl Syntax { } } - pub fn allow_super_outside_method(self) -> bool { + pub(crate) fn allow_super_outside_method(self) -> bool { match self { Syntax::Es(EsConfig { allow_super_outside_method, @@ -256,7 +256,7 @@ impl Syntax { } } - pub fn allow_return_outside_function(self) -> bool { + pub(crate) fn allow_return_outside_function(self) -> bool { match self { Syntax::Es(EsConfig { allow_return_outside_function,