Skip to content

Commit

Permalink
feat(es/minifier): Support script fully (#6455)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Nov 17, 2022
1 parent 7716f58 commit 4d7b920
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 63 deletions.
8 changes: 8 additions & 0 deletions crates/swc_ecma_minifier/src/analyzer/mod.rs
Expand Up @@ -1150,6 +1150,14 @@ where
n.visit_children_with(&mut *self.with_ctx(ctx))
}

fn visit_script(&mut self, n: &Script) {
let ctx = Ctx {
skip_standalone: true,
..self.ctx
};
n.visit_children_with(&mut *self.with_ctx(ctx))
}

fn visit_named_export(&mut self, n: &NamedExport) {
if n.src.is_some() {
return;
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_minifier/src/compress/mod.rs
Expand Up @@ -336,6 +336,10 @@ where
{
noop_visit_mut_type!();

fn visit_mut_script(&mut self, n: &mut Script) {
self.optimize_unit_repeatedly(n);
}

fn visit_mut_module(&mut self, n: &mut Module) {
self.optimize_unit_repeatedly(n);
}
Expand Down
93 changes: 92 additions & 1 deletion crates/swc_ecma_minifier/src/debug.rs
Expand Up @@ -66,7 +66,7 @@ where
///
/// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke(module: &Module) {
pub(crate) fn invoke_module(module: &Module) {
debug_assert_valid(module);

let _noop_sub = tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());
Expand Down Expand Up @@ -153,3 +153,94 @@ pub(crate) fn invoke(module: &Module) {
)
}
}

/// Invokes code using node.js.
///
/// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke_script(script: &Script) {
debug_assert_valid(script);

let _noop_sub = tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());

let should_run =
cfg!(debug_assertions) && cfg!(feature = "debug") && option_env!("SWC_RUN") == Some("1");
let should_check = cfg!(debug_assertions) && option_env!("SWC_CHECK") == Some("1");

if !should_run && !should_check {
return;
}

let script = script
.clone()
.fold_with(&mut hygiene())
.fold_with(&mut fixer(None));
let script = drop_span(script);

let mut buf = vec![];
let cm = Lrc::new(SourceMap::default());

{
let mut emitter = Emitter {
cfg: Default::default(),
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm, "\n", &mut buf, None)),
};

emitter.emit_script(&script).unwrap();
}

let code = String::from_utf8(buf).unwrap();

debug!("Validating with node.js:\n{}", code);

if should_check {
let mut child = Command::new("node")
.arg("-")
.arg("--check")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn node");

{
let child_stdin = child.stdin.as_mut().unwrap();
child_stdin
.write_all(code.as_bytes())
.expect("failed to write");
}

let output = child.wait_with_output().expect("failed to check syntax");

if !output.status.success() {
panic!(
"[SWC_CHECK] Failed to validate code:\n{}\n===== ===== ===== ===== =====\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
} else {
let output = Command::new("node")
.arg("-e")
.arg(&code)
.output()
.expect("[SWC_RUN] failed to validate code using `node`");
if !output.status.success() {
panic!(
"[SWC_RUN] Failed to validate code:\n{}\n===== ===== ===== ===== =====\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

tracing::info!(
"[SWC_RUN]\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout)
)
}
}
42 changes: 21 additions & 21 deletions crates/swc_ecma_minifier/src/lib.rs
Expand Up @@ -87,7 +87,7 @@ pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT *
pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);

pub fn optimize(
mut m: Program,
mut n: Program,
_cm: Lrc<SourceMap>,
comments: Option<&dyn Comments>,
mut timings: Option<&mut Timings>,
Expand All @@ -99,7 +99,7 @@ pub fn optimize(
let mut marks = Marks::new();
marks.unresolved_mark = extra.unresolved_mark;

debug_assert_valid(&m);
debug_assert_valid(&n);

if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
let _timer = timer!("inline global defs");
Expand All @@ -111,15 +111,15 @@ pub fn optimize(

if !defs.is_empty() {
let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
m.visit_mut_with(&mut global_defs::globals_defs(
n.visit_mut_with(&mut global_defs::globals_defs(
defs,
extra.unresolved_mark,
extra.top_level_mark,
));
}
}

let module_info = match &m {
let module_info = match &n {
Program::Script(_) => ModuleInfo::default(),
Program::Module(m) => ModuleInfo {
blackbox_imports: m
Expand Down Expand Up @@ -162,20 +162,20 @@ pub fn optimize(
if let Some(_options) = &options.compress {
let _timer = timer!("precompress");

m.visit_mut_with(&mut precompress_optimizer());
debug_assert_valid(&m);
n.visit_mut_with(&mut precompress_optimizer());
debug_assert_valid(&n);
}

if options.compress.is_some() {
m.visit_mut_with(&mut info_marker(
n.visit_mut_with(&mut info_marker(
options.compress.as_ref(),
comments,
marks,
extra.unresolved_mark,
));
debug_assert_valid(&m);
debug_assert_valid(&n);
}
m.visit_mut_with(&mut unique_scope());
n.visit_mut_with(&mut unique_scope());

if options.wrap {
// TODO: wrap_common_js
Expand All @@ -191,8 +191,8 @@ pub fn optimize(
}
if let Some(options) = &options.compress {
if options.unused {
perform_dce(&mut m, options, extra);
debug_assert_valid(&m);
perform_dce(&mut n, options, extra);
debug_assert_valid(&n);
}
}

Expand All @@ -207,7 +207,7 @@ pub fn optimize(
if options.rename && DISABLE_BUGGY_PASSES {
// toplevel.figure_out_scope(options.mangle);
// TODO: Pass `options.mangle` to name expander.
m.visit_mut_with(&mut name_expander());
n.visit_mut_with(&mut name_expander());
}

if let Some(ref mut t) = timings {
Expand All @@ -217,14 +217,14 @@ pub fn optimize(
{
let _timer = timer!("compress ast");

m.visit_mut_with(&mut compressor(&module_info, marks, options, &Minification))
n.visit_mut_with(&mut compressor(&module_info, marks, options, &Minification))
}

// Again, we don't need to validate ast

let _timer = timer!("postcompress");

m.visit_mut_with(&mut postcompress_optimizer(options));
n.visit_mut_with(&mut postcompress_optimizer(options));

let mut pass = 0;
loop {
Expand All @@ -241,7 +241,7 @@ pub fn optimize(
debug_infinite_loop: false,
},
);
m.visit_mut_with(&mut v);
n.visit_mut_with(&mut v);
if !v.changed() || options.passes <= pass {
break;
}
Expand All @@ -263,30 +263,30 @@ pub fn optimize(
let _timer = timer!("mangle names");
// TODO: base54.reset();

let preserved = idents_to_preserve(mangle.clone(), &m);
let preserved = idents_to_preserve(mangle.clone(), &n);

let chars = CharFreq::compute(
&m,
&n,
&preserved,
SyntaxContext::empty().apply_mark(marks.unresolved_mark),
)
.compile();

m.visit_mut_with(&mut name_mangler(mangle.clone(), preserved, chars));
n.visit_mut_with(&mut name_mangler(mangle.clone(), preserved, chars));

if let Some(property_mangle_options) = &mangle.props {
mangle_properties(&mut m, &module_info, property_mangle_options.clone(), chars);
mangle_properties(&mut n, &module_info, property_mangle_options.clone(), chars);
}
}

m.visit_mut_with(&mut merge_exports());
n.visit_mut_with(&mut merge_exports());

if let Some(ref mut t) = timings {
t.section("hygiene");
t.end_section();
}

m
n
}

fn perform_dce(m: &mut Program, options: &CompressOptions, extra: &ExtraOptions) {
Expand Down
17 changes: 14 additions & 3 deletions crates/swc_ecma_minifier/src/metadata/mod.rs
Expand Up @@ -191,12 +191,23 @@ impl VisitMut for InfoMarker<'_> {

fn visit_mut_lit(&mut self, _: &mut Lit) {}

fn visit_mut_module(&mut self, m: &mut Module) {
m.visit_mut_children_with(self);
fn visit_mut_script(&mut self, n: &mut Script) {
n.visit_mut_children_with(self);

if self.state.is_bundle {
tracing::info!("Running minifier in the bundle mode");
n.span = n.span.apply_mark(self.marks.bundle_of_standalone);
} else {
tracing::info!("Running minifier in the normal mode");
}
}

fn visit_mut_module(&mut self, n: &mut Module) {
n.visit_mut_children_with(self);

if self.state.is_bundle {
tracing::info!("Running minifier in the bundle mode");
m.span = m.span.apply_mark(self.marks.bundle_of_standalone);
n.span = n.span.apply_mark(self.marks.bundle_of_standalone);
} else {
tracing::info!("Running minifier in the normal mode");
}
Expand Down
7 changes: 7 additions & 0 deletions crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs
Expand Up @@ -121,6 +121,13 @@ impl Visit for Preserver {

fn visit_ident(&mut self, _: &Ident) {}

fn visit_script(&mut self, n: &Script) {
for n in n.body.iter() {
self.in_top_level = true;
n.visit_with(self);
}
}

fn visit_module_items(&mut self, n: &[ModuleItem]) {
for n in n {
self.in_top_level = true;
Expand Down
37 changes: 36 additions & 1 deletion crates/swc_ecma_minifier/src/util/unit.rs
Expand Up @@ -69,7 +69,42 @@ impl CompileUnit for Module {
{
self.visit_mut_with(&mut *visitor);

crate::debug::invoke(self);
crate::debug::invoke_module(self);
}

fn remove_mark(&mut self) -> Mark {
Mark::root()
}
}

impl CompileUnit for Script {
fn is_module() -> bool {
false
}

fn force_dump(&self) -> String {
let _noop_sub =
tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());

dump(
&self
.clone()
.fold_with(&mut fixer(None))
.fold_with(&mut hygiene())
.fold_with(&mut as_folder(DropSpan {
preserve_ctxt: false,
})),
true,
)
}

fn apply<V>(&mut self, visitor: &mut V)
where
V: VisitMut,
{
self.visit_mut_with(&mut *visitor);

crate::debug::invoke_script(self);
}

fn remove_mark(&mut self) -> Mark {
Expand Down
9 changes: 4 additions & 5 deletions crates/swc_ecma_transforms_base/src/rename/mod.rs
Expand Up @@ -242,12 +242,11 @@ where

if contains_eval(s, true) {
s.visit_mut_children_with(self);
return;
}

let map = self.get_map(s, false, true);
} else {
let map = self.get_map(s, false, true);

s.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
s.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
}
}
}

Expand Down
Expand Up @@ -8,5 +8,5 @@
<circle onmouseover='alert("test")' cx=50 cy=50 r=30 style=fill:url(#gradient) />
</svg>
<div type=text onmouseover=myFunction()>test</div>
<a href=https://datacadamia.com onclick="console.log(`Navigation to ${this.href} cancelled`);return false">Click me</a>
<a href=https://datacadamia.com onclick="console.log(`Navigation to ${this.href} cancelled`);return false">Click me</a>
<a href=https://datacadamia.com onclick="return console.log(`Navigation to ${this.href} cancelled`),!1">Click me</a>
<a href=https://datacadamia.com onclick="return console.log(`Navigation to ${this.href} cancelled`),!1">Click me</a>
@@ -1,6 +1,6 @@
<!doctype html><script defer>console.log()</script><script>console.log();console.log()</script><script type=module>console.log(),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(),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);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);alert(1);alert(1);alert(1);alert(1);alert(1);alert(1)</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),alert(1),alert(1),alert(1),alert(1),alert(1),alert(1)</script>

1 comment on commit 4d7b920

@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: 4d7b920 Previous: f7072db Ratio
es/full/bugs-1 366800 ns/iter (± 31418) 334805 ns/iter (± 18127) 1.10
es/full/minify/libraries/antd 2055494042 ns/iter (± 78757627) 1780667832 ns/iter (± 17173485) 1.15
es/full/minify/libraries/d3 477783305 ns/iter (± 18919141) 381082585 ns/iter (± 7200345) 1.25
es/full/minify/libraries/echarts 1779525769 ns/iter (± 46840644) 1547630428 ns/iter (± 22586360) 1.15
es/full/minify/libraries/jquery 118346314 ns/iter (± 7646826) 98626124 ns/iter (± 2971453) 1.20
es/full/minify/libraries/lodash 137623358 ns/iter (± 6206468) 116907486 ns/iter (± 3089676) 1.18
es/full/minify/libraries/moment 69789977 ns/iter (± 1720809) 58622385 ns/iter (± 2255961) 1.19
es/full/minify/libraries/react 23433969 ns/iter (± 1581414) 19450406 ns/iter (± 510833) 1.20
es/full/minify/libraries/terser 372333562 ns/iter (± 12267596) 289477464 ns/iter (± 7208734) 1.29
es/full/minify/libraries/three 649739339 ns/iter (± 19737265) 542243840 ns/iter (± 7665782) 1.20
es/full/minify/libraries/typescript 3799607078 ns/iter (± 61154067) 3245973174 ns/iter (± 22954304) 1.17
es/full/minify/libraries/victory 934895778 ns/iter (± 30254734) 789988605 ns/iter (± 10590936) 1.18
es/full/minify/libraries/vue 182105144 ns/iter (± 11566172) 148323945 ns/iter (± 3007185) 1.23
es/full/codegen/es3 34522 ns/iter (± 3486) 33414 ns/iter (± 1301) 1.03
es/full/codegen/es5 34487 ns/iter (± 5155) 33367 ns/iter (± 804) 1.03
es/full/codegen/es2015 35078 ns/iter (± 3853) 33589 ns/iter (± 1278) 1.04
es/full/codegen/es2016 34826 ns/iter (± 2698) 33485 ns/iter (± 874) 1.04
es/full/codegen/es2017 34187 ns/iter (± 2043) 33443 ns/iter (± 926) 1.02
es/full/codegen/es2018 34358 ns/iter (± 2770) 33490 ns/iter (± 1231) 1.03
es/full/codegen/es2019 34475 ns/iter (± 3493) 33659 ns/iter (± 996) 1.02
es/full/codegen/es2020 35314 ns/iter (± 3217) 33404 ns/iter (± 1846) 1.06
es/full/all/es3 223676981 ns/iter (± 15577815) 184563807 ns/iter (± 5806100) 1.21
es/full/all/es5 205150591 ns/iter (± 12701799) 174353068 ns/iter (± 5635998) 1.18
es/full/all/es2015 159631999 ns/iter (± 10881445) 139550268 ns/iter (± 2434944) 1.14
es/full/all/es2016 161724721 ns/iter (± 11560641) 138933989 ns/iter (± 3265699) 1.16
es/full/all/es2017 159447439 ns/iter (± 14104711) 137643683 ns/iter (± 2704993) 1.16
es/full/all/es2018 159974078 ns/iter (± 12840273) 136547949 ns/iter (± 3141469) 1.17
es/full/all/es2019 159551306 ns/iter (± 13664315) 136033205 ns/iter (± 2568960) 1.17
es/full/all/es2020 154461090 ns/iter (± 10076296) 130946022 ns/iter (± 2042192) 1.18
es/full/parser 772838 ns/iter (± 66860) 687876 ns/iter (± 29004) 1.12
es/full/base/fixer 28855 ns/iter (± 3848) 25829 ns/iter (± 857) 1.12
es/full/base/resolver_and_hygiene 99021 ns/iter (± 16403) 88804 ns/iter (± 3811) 1.12
serialization of ast node 220 ns/iter (± 26) 211 ns/iter (± 3) 1.04
serialization of serde 251 ns/iter (± 36) 218 ns/iter (± 5) 1.15

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

Please sign in to comment.