diff --git a/ecmascript/transforms/src/optimization/simplify/dce/mod.rs b/ecmascript/transforms/src/optimization/simplify/dce/mod.rs index 7aa61ae9a22..1ea85cddb43 100644 --- a/ecmascript/transforms/src/optimization/simplify/dce/mod.rs +++ b/ecmascript/transforms/src/optimization/simplify/dce/mod.rs @@ -145,10 +145,7 @@ where let item = if preserved.contains(&idx) { item } else { - log::info!("Dce.should_include({})", idx); - if self.should_include(&item) { - log::info!("Preserving {}", idx); preserved.insert(idx); self.changed = true; item = self.fold_in_marking_phase(item) diff --git a/ecmascript/transforms/src/optimization/simplify/dce/module_decl.rs b/ecmascript/transforms/src/optimization/simplify/dce/module_decl.rs index a1e0a039004..c3fb5f65446 100644 --- a/ecmascript/transforms/src/optimization/simplify/dce/module_decl.rs +++ b/ecmascript/transforms/src/optimization/simplify/dce/module_decl.rs @@ -1,16 +1,13 @@ use super::Dce; -use swc_common::{Fold, FoldWith}; +use swc_common::Fold; use swc_ecma_ast::*; -use swc_ecma_utils::find_ids; impl Fold for Dce<'_> { - fn fold(&mut self, import: ImportDecl) -> ImportDecl { + fn fold(&mut self, mut import: ImportDecl) -> ImportDecl { if self.is_marked(import.span) { return import; } - let mut import: ImportDecl = import.fold_children(self); - // Side effect import if import.specifiers.is_empty() { import.span = import.span.apply_mark(self.config.used_mark); @@ -22,19 +19,11 @@ impl Fold for Dce<'_> { return import; } - // TODO: Drop unused imports. - // e.g) import { foo, bar } from './foo'; - // foo() + // Drop unused imports. + import.specifiers.retain(|s| self.should_include(&s)); - let ids: Vec = find_ids(&import.specifiers); - - for id in ids { - for c in &self.included { - if c.0 == id.sym && c.1 == id.span.ctxt() { - import.span = import.span.apply_mark(self.config.used_mark); - return import; - } - } + if !import.specifiers.is_empty() { + import.span = import.span.apply_mark(self.config.used_mark); } import diff --git a/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs b/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs index 532523c9b70..1326e4a0cfd 100644 --- a/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs +++ b/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs @@ -1,5 +1,6 @@ use super::Dce; use fxhash::FxHashSet; +use swc_atoms::JsWord; use swc_common::{Visit, VisitWith}; use swc_ecma_ast::*; use swc_ecma_utils::{ident::IdentLike, ExprExt, Id}; @@ -21,6 +22,18 @@ impl Dce<'_> { } } +impl SideEffectVisitor<'_> { + fn is_exported(&self, i: &JsWord) -> bool { + self.exports.is_none() + || self + .exports + .as_ref() + .unwrap() + .iter() + .any(|exported| exported.0 == *i) + } +} + pub(super) struct SideEffectVisitor<'a> { included: &'a mut FxHashSet, exports: Option<&'a [Id]>, @@ -198,3 +211,44 @@ impl Visit for SideEffectVisitor<'_> { self.found = true; } } + +impl Visit for SideEffectVisitor<'_> { + fn visit(&mut self, import: &ImportDecl) { + if self.found { + return; + } + + if import.specifiers.is_empty() { + self.found = true; + return; + } + + import.visit_children(self) + } +} + +impl Visit for SideEffectVisitor<'_> { + fn visit(&mut self, _: &ExportDecl) { + self.found = true + } +} + +impl Visit for SideEffectVisitor<'_> { + fn visit(&mut self, _: &ExportDefaultExpr) { + if self.is_exported(&js_word!("default")) { + self.found = true + } + } +} + +impl Visit for SideEffectVisitor<'_> { + fn visit(&mut self, _: &NamedExport) { + self.found = true + } +} + +impl Visit for SideEffectVisitor<'_> { + fn visit(&mut self, _: &ExportDefaultDecl) { + self.found = true; + } +} diff --git a/ecmascript/transforms/tests/optimization_simplify_dce.rs b/ecmascript/transforms/tests/optimization_simplify_dce.rs index f8997dd00f9..0032b71b8e4 100644 --- a/ecmascript/transforms/tests/optimization_simplify_dce.rs +++ b/ecmascript/transforms/tests/optimization_simplify_dce.rs @@ -3,8 +3,11 @@ #![feature(box_patterns)] #![feature(specialization)] -use swc_common::chain; -use swc_ecma_transforms::{optimization::simplify::dce::dce, resolver}; +use swc_common::{chain, SyntaxContext}; +use swc_ecma_transforms::{ + optimization::simplify::dce::{self, dce}, + resolver, +}; #[macro_use] mod common; @@ -21,6 +24,26 @@ macro_rules! to { }; } +fn used(ids: &[&str], src: &str, expected: &str) { + test_transform!( + Default::default(), + |_| chain!( + resolver(), + dce(dce::Config { + used: Some( + ids.into_iter() + .map(|&v| { (v.into(), SyntaxContext::empty()) }) + .collect() + ), + ..Default::default() + }) + ), + src, + expected, + false + ); +} + macro_rules! optimized_out { ($name:ident, $src:expr) => { to!($name, $src, ""); @@ -104,3 +127,39 @@ console.log(c);" optimized_out!(simple_const, "{const x = 1}"); noop!(assign_op, "x *= 2; use(x)"); + +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!(export_named, "export { x };"); + +noop!(export_named_from, "export {foo} from 'src';"); + +noop!( + import_default_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 }; " +); + +#[test] +fn export_named_unused() { + used(&["foo"], "export { foo, bat }", "export { foo }"); +} + +#[test] +fn export_default_expr_unused() { + used(&[], "export default 5;", ""); +} + +#[test] +fn export_default_expr_used() { + used(&["default"], "export default 5;", "export default 5;"); +}