Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add preserve_imports_with_side_effects option for simplify optimization #6962

Merged
merged 14 commits into from
Mar 5, 2023
67 changes: 57 additions & 10 deletions crates/swc/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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
);

Expand Down Expand Up @@ -1478,12 +1501,36 @@ pub struct OptimizerConfig {
pub globals: Option<GlobalPassOption>,

#[serde(default)]
pub simplify: BoolConfig<true>,
pub simplify: Option<SimplifyOption>,

#[serde(default)]
pub jsonify: Option<JsonifyOption>,
}

#[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 {
Expand Down
16 changes: 16 additions & 0 deletions crates/swc/tests/fixture/issues-6xxx/6771/input/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"jsc": {
"transform": {
"optimizer": {
"simplify": {
"preserveImportsWithSideEffects": false
},
"globals": {
"envs": {
"NODE_ENV_ALT": "true"
}
}
}
}
}
}
1 change: 1 addition & 0 deletions crates/swc/tests/fixture/issues-6xxx/6771/input/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import path from 'path';
Empty file.
1 change: 1 addition & 0 deletions crates/swc_bundler/src/bundler/optimize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ where
module_mark: None,
top_level: true,
top_retain: Default::default(),
preserve_imports_with_side_effects: true,
},
self.unresolved_mark,
)));
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ pub struct Config {

/// Declarations with a symbol in this set will be preserved.
pub top_retain: Vec<JsWord>,

/// If false, imports with side effects will be removed.
pub preserve_imports_with_side_effects: bool,
}

impl Default for Config {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 }; "
);