From 06770cff047055b9cea27970e7ce882d770257ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 11 Jan 2023 16:04:20 +0900 Subject: [PATCH] fix(es/minifier): Make AST compressor respect `toplevel` (#6775) **Related issue:** - Closes https://github.com/swc-project/swc/issues/4386. --- .../src/compress/optimize/inline.rs | 5 +++- crates/swc_ecma_minifier/src/program_data.rs | 5 ++++ .../tests/fixture/issues/4386/1/config.json | 4 ++++ .../tests/fixture/issues/4386/1/input.js | 24 +++++++++++++++++++ .../tests/fixture/issues/4386/1/output.js | 12 ++++++++++ .../tests/fixture/issues/4386/2/config.json | 4 ++++ .../tests/fixture/issues/4386/2/input.js | 24 +++++++++++++++++++ .../tests/fixture/issues/4386/2/output.js | 11 +++++++++ .../tests/fixture/projects/next/config.json | 4 ++++ .../src/simplify/dce/mod.rs | 8 +++++++ .../src/analyzer/ctx.rs | 3 +++ .../src/analyzer/mod.rs | 7 +++++- 12 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/1/config.json create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/1/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/1/output.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/2/config.json create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/2/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/4386/2/output.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/projects/next/config.json diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index 697ab5793a74..db0de461847d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs @@ -29,7 +29,7 @@ where &mut self, ident: &mut Ident, init: &mut Expr, - should_preserve: bool, + mut should_preserve: bool, can_drop: bool, ) { trace_op!( @@ -58,6 +58,7 @@ where if !usage.var_initialized { return; } + if self.data.top.used_arguments && usage.declared_as_fn_param { return; } @@ -95,6 +96,8 @@ where let is_inline_enabled = self.options.reduce_vars || self.options.collapse_vars || self.options.inline != 0; + should_preserve |= !self.options.top_level() && usage.is_top_level; + self.vars.inline_with_multi_replacer(init); // We inline arrays partially if it's pure (all elements are literal), and not diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index 0fd06fc63942..b9d9c36798c6 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -121,6 +121,9 @@ pub(crate) struct VarUsageInfo { pub(crate) pure_fn: bool, + /// Is the variable declared in top level? + pub(crate) is_top_level: bool, + /// `infects_to`. This should be renamed, but it will be done with another /// PR. (because it's hard to review) infects: Vec, @@ -168,6 +171,7 @@ impl Default for VarUsageInfo { used_in_non_child_fn: Default::default(), accessed_props: Default::default(), used_recursively: Default::default(), + is_top_level: Default::default(), } } } @@ -346,6 +350,7 @@ impl Storage for ProgramData { // } let v = self.vars.entry(i.to_id()).or_default(); + v.is_top_level |= ctx.is_top_level; if has_init && (v.declared || v.var_initialized) { #[cfg(feature = "debug")] diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/config.json new file mode 100644 index 000000000000..d6dd7e6ba8db --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/config.json @@ -0,0 +1,4 @@ +{ + "defaults": true, + "toplevel": false +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/input.js new file mode 100644 index 000000000000..b6863d220cdf --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/input.js @@ -0,0 +1,24 @@ +var application; +(() => { + var __webpack_require__ = {}; + (() => { + __webpack_require__.d = (exports, definition) => { + }; + })(); + (() => { + __webpack_require__.o = (obj, prop) => { }; + })(); + (() => { + __webpack_require__.r = (exports) => { + }; + })(); + var __webpack_exports__ = {}; + __webpack_require__.r(__webpack_exports__); + __webpack_require__.d(__webpack_exports__, { + "bootstrap": () => (bootstrap) + }); + function bootstrap() { + alert(); + } + application = __webpack_exports__; +})(); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/output.js new file mode 100644 index 000000000000..919d1c42bb31 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/1/output.js @@ -0,0 +1,12 @@ +var application; +(()=>{ + var __webpack_require__ = {}; + __webpack_require__.d = (exports, definition)=>{}, __webpack_require__.o = (obj, prop)=>{}, __webpack_require__.r = (exports)=>{}; + var __webpack_exports__ = {}; + function bootstrap() { + alert(); + } + __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, { + bootstrap: ()=>bootstrap + }), application = __webpack_exports__; +})(); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/config.json new file mode 100644 index 000000000000..0804f8f8b56f --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/config.json @@ -0,0 +1,4 @@ +{ + "defaults": true, + "toplevel": true +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/input.js new file mode 100644 index 000000000000..b6863d220cdf --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/input.js @@ -0,0 +1,24 @@ +var application; +(() => { + var __webpack_require__ = {}; + (() => { + __webpack_require__.d = (exports, definition) => { + }; + })(); + (() => { + __webpack_require__.o = (obj, prop) => { }; + })(); + (() => { + __webpack_require__.r = (exports) => { + }; + })(); + var __webpack_exports__ = {}; + __webpack_require__.r(__webpack_exports__); + __webpack_require__.d(__webpack_exports__, { + "bootstrap": () => (bootstrap) + }); + function bootstrap() { + alert(); + } + application = __webpack_exports__; +})(); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/output.js new file mode 100644 index 000000000000..da382758bb42 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/4386/2/output.js @@ -0,0 +1,11 @@ +(()=>{ + var __webpack_require__ = {}; + __webpack_require__.d = (exports, definition)=>{}, __webpack_require__.o = (obj, prop)=>{}, __webpack_require__.r = (exports)=>{}; + var __webpack_exports__ = {}; + function bootstrap() { + alert(); + } + __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, { + bootstrap: ()=>bootstrap + }); +})(); diff --git a/crates/swc_ecma_minifier/tests/fixture/projects/next/config.json b/crates/swc_ecma_minifier/tests/fixture/projects/next/config.json new file mode 100644 index 000000000000..0804f8f8b56f --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/projects/next/config.json @@ -0,0 +1,4 @@ +{ + "defaults": true, + "toplevel": true +} diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs index 128bb9ed7c7d..67e11bd1c5d2 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs @@ -633,6 +633,14 @@ impl TreeShaker { } else if !self.in_block_stmt { return false; } + + // Abort if the variable is declared on top level scope. + let ix = self.data.graph_ix.get_index_of(&name); + if let Some(ix) = ix { + if self.data.entries.contains(&(ix as u32)) { + return false; + } + } } if self.config.top_retain.contains(&name.0) { diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs index 6c72c99a9791..858c5f0bac97 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs @@ -21,6 +21,7 @@ where } #[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] pub struct Ctx { /// See [crate::marks::Marks] pub skip_standalone: bool, @@ -60,6 +61,8 @@ pub struct Ctx { pub inline_prevented: bool, pub is_op_assign: bool, + + pub is_top_level: bool, } pub(super) struct WithCtx<'a, S> diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index 927d064262a7..2daafe7a0574 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -79,7 +79,10 @@ where let mut child = UsageAnalyzer { data: Default::default(), marks: self.marks, - ctx: self.ctx, + ctx: Ctx { + is_top_level: false, + ..self.ctx + }, scope: Default::default(), used_recursively: self.used_recursively.clone(), }; @@ -849,6 +852,7 @@ where fn visit_module(&mut self, n: &Module) { let ctx = Ctx { skip_standalone: true, + is_top_level: true, ..self.ctx }; n.visit_children_with(&mut *self.with_ctx(ctx)) @@ -857,6 +861,7 @@ where fn visit_script(&mut self, n: &Script) { let ctx = Ctx { skip_standalone: true, + is_top_level: true, ..self.ctx }; n.visit_children_with(&mut *self.with_ctx(ctx))