diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 265c2a2d4c7f..101729756af6 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -67,7 +67,11 @@ use swc_ecma_transforms::{ Assumptions, }; use swc_ecma_transforms_compat::es2015::regenerator; -use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap}; +use swc_ecma_transforms_optimization::{ + inline_globals2, + simplify::{dce::Config as DceConfig, Config as SimplifyConfig}, + GlobalExprMap, +}; use swc_ecma_visit::{Fold, VisitMutWith}; pub use crate::plugin::PluginConfig; @@ -451,10 +455,32 @@ impl Options { } }; - let enable_simplifier = optimizer - .as_ref() - .map(|v| v.simplify.into_bool()) - .unwrap_or_default(); + let simplifier_pass = { + if let Some(ref opts) = optimizer.as_ref().and_then(|o| o.simplify) { + match opts { + SimplifyOption::Bool(allow_simplify) => { + if *allow_simplify { + Either::Left(simplifier(top_level_mark, Default::default())) + } else { + Either::Right(noop()) + } + } + SimplifyOption::Json(cfg) => Either::Left(simplifier( + top_level_mark, + SimplifyConfig { + dce: DceConfig { + preserve_imports_with_side_effects: cfg + .preserve_imports_with_side_effects, + ..Default::default() + }, + ..Default::default() + }, + )), + } + } else { + Either::Right(noop()) + } + }; let optimization = { if let Some(opts) = optimizer.and_then(|o| o.globals) { @@ -471,10 +497,7 @@ impl Options { const_modules, optimization, Optional::new(export_default_from(), syntax.export_default_from()), - Optional::new( - simplifier(top_level_mark, Default::default()), - enable_simplifier - ), + simplifier_pass, json_parse_pass ); @@ -1478,12 +1501,36 @@ pub struct OptimizerConfig { pub globals: Option, #[serde(default)] - pub simplify: BoolConfig, + pub simplify: Option, #[serde(default)] pub jsonify: Option, } +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SimplifyOption { + Bool(bool), + Json(SimplifyJsonOption), +} + +impl Default for SimplifyOption { + fn default() -> Self { + SimplifyOption::Bool(true) + } +} + +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct SimplifyJsonOption { + #[serde(default = "default_preserve_imports_with_side_effects")] + pub preserve_imports_with_side_effects: bool, +} + +fn default_preserve_imports_with_side_effects() -> bool { + true +} + #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct JsonifyOption { diff --git a/crates/swc/tests/fixture/issues-6xxx/6771/input/.swcrc b/crates/swc/tests/fixture/issues-6xxx/6771/input/.swcrc new file mode 100644 index 000000000000..505327adc4c8 --- /dev/null +++ b/crates/swc/tests/fixture/issues-6xxx/6771/input/.swcrc @@ -0,0 +1,16 @@ +{ + "jsc": { + "transform": { + "optimizer": { + "simplify": { + "preserveImportsWithSideEffects": false + }, + "globals": { + "envs": { + "NODE_ENV_ALT": "true" + } + } + } + } + } +} diff --git a/crates/swc/tests/fixture/issues-6xxx/6771/input/index.js b/crates/swc/tests/fixture/issues-6xxx/6771/input/index.js new file mode 100644 index 000000000000..78d43db9807c --- /dev/null +++ b/crates/swc/tests/fixture/issues-6xxx/6771/input/index.js @@ -0,0 +1 @@ +import path from 'path'; diff --git a/crates/swc/tests/fixture/issues-6xxx/6771/output/index.js b/crates/swc/tests/fixture/issues-6xxx/6771/output/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/swc_bundler/src/bundler/optimize.rs b/crates/swc_bundler/src/bundler/optimize.rs index ea58fc3bf855..fd4998af6cd7 100644 --- a/crates/swc_bundler/src/bundler/optimize.rs +++ b/crates/swc_bundler/src/bundler/optimize.rs @@ -26,6 +26,7 @@ where module_mark: None, top_level: true, top_retain: Default::default(), + preserve_imports_with_side_effects: true, }, self.unresolved_mark, ))); diff --git a/crates/swc_ecma_minifier/src/lib.rs b/crates/swc_ecma_minifier/src/lib.rs index 6d26318c7971..7dff7819c391 100644 --- a/crates/swc_ecma_minifier/src/lib.rs +++ b/crates/swc_ecma_minifier/src/lib.rs @@ -304,6 +304,7 @@ fn perform_dce(m: &mut Program, options: &CompressOptions, extra: &ExtraOptions) module_mark: None, top_level: options.top_level(), top_retain: options.top_retain.clone(), + preserve_imports_with_side_effects: true, }, extra.unresolved_mark, ); 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 d495a27478c9..d74955f0263c 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs @@ -63,6 +63,9 @@ pub struct Config { /// Declarations with a symbol in this set will be preserved. pub top_retain: Vec, + + /// If false, imports with side effects will be removed. + pub preserve_imports_with_side_effects: bool, } impl Default for Config { @@ -71,6 +74,7 @@ impl Default for Config { module_mark: Default::default(), top_level: true, top_retain: Default::default(), + preserve_imports_with_side_effects: true, } } } @@ -911,7 +915,25 @@ impl VisitMut for TreeShaker { } fn visit_mut_module_item(&mut self, n: &mut ModuleItem) { - n.visit_mut_children_with(self); + match n { + ModuleItem::ModuleDecl(ModuleDecl::Import(i)) => { + let is_for_side_effect = i.specifiers.is_empty(); + + i.visit_mut_with(self); + + if !self.config.preserve_imports_with_side_effects + && !is_for_side_effect + && i.specifiers.is_empty() + { + debug!("Dropping an import because it's not used"); + self.changed = true; + *n = ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP })); + } + } + _ => { + n.visit_mut_children_with(self); + } + } debug_assert_valid(n); } diff --git a/crates/swc_ecma_transforms_optimization/tests/remove_imports_with_side_effects.rs b/crates/swc_ecma_transforms_optimization/tests/remove_imports_with_side_effects.rs new file mode 100644 index 000000000000..f40b6430bea6 --- /dev/null +++ b/crates/swc_ecma_transforms_optimization/tests/remove_imports_with_side_effects.rs @@ -0,0 +1,76 @@ +use swc_common::{chain, pass::Repeat, Mark}; +use swc_ecma_parser::{EsConfig, Syntax}; +use swc_ecma_transforms_base::resolver; +use swc_ecma_transforms_optimization::simplify::dce::{dce, Config}; +use swc_ecma_transforms_testing::test; +use swc_ecma_visit::Fold; + +fn tr() -> impl Fold { + Repeat::new(dce( + Config { + top_level: true, + preserve_imports_with_side_effects: false, + ..Default::default() + }, + Mark::new(), + )) +} + +macro_rules! to { + ($name:ident, $src:expr, $expected:expr) => { + test!( + Syntax::Es(EsConfig { + decorators: true, + ..Default::default() + }), + |_| chain!(resolver(Mark::new(), Mark::new(), false), tr()), + $name, + $src, + $expected + ); + }; +} + +macro_rules! optimized_out { + ($name:ident, $src:expr) => { + to!($name, $src, ""); + }; +} + +macro_rules! noop { + ($name:ident, $src:expr) => { + to!($name, $src, $src); + }; +} + +to!( + single_pass, + " + const a = 1; + + if (a) { + const b = 2; + } + ", + " + const a = 1; + if (a) {} + " +); + +optimized_out!(import_default_unused, "import foo from 'foo'"); + +optimized_out!(import_specific_unused, "import {foo} from 'foo'"); + +optimized_out!(import_mixed_unused, "import foo, { bar } from 'foo'"); + +noop!( + import_export_named, + "import foo from 'src'; export { foo };" +); + +to!( + import_unused_export_named, + "import foo, { bar } from 'src'; export { foo }; ", + "import foo from 'src'; export { foo }; " +);