diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml
index 25d4bf62734..14fecc2866a 100644
--- a/.github/workflows/cargo.yml
+++ b/.github/workflows/cargo.yml
@@ -167,6 +167,7 @@ jobs:
- swc_timer
- swc_visit
- swc_visit_macros
+ - swc_webpack_ast
- testing
- testing_macros
- wasm
diff --git a/Cargo.lock b/Cargo.lock
index 2611fc537d1..18ba77bce78 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3154,7 +3154,7 @@ dependencies = [
[[package]]
name = "swc_estree_compat"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"ahash",
"anyhow",
@@ -3359,6 +3359,29 @@ dependencies = [
"syn",
]
+[[package]]
+name = "swc_webpack_ast"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "rayon",
+ "serde_json",
+ "swc_atoms 0.2.9",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_parser",
+ "swc_ecma_transforms_base",
+ "swc_ecma_transforms_testing",
+ "swc_ecma_utils",
+ "swc_ecma_visit",
+ "swc_estree_ast",
+ "swc_estree_compat",
+ "swc_node_base",
+ "swc_timer",
+ "testing",
+ "tracing",
+]
+
[[package]]
name = "syn"
version = "1.0.65"
diff --git a/Cargo.toml b/Cargo.toml
index 312b906a617..fb7e349784f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ members = [
"crates/swc_plugin_testing",
"crates/swc_stylis",
"crates/swc_timer",
+ "crates/swc_webpack_ast",
"crates/wasm",
]
diff --git a/crates/swc_ecma_transforms_testing/src/lib.rs b/crates/swc_ecma_transforms_testing/src/lib.rs
index 15561e999a4..3a7965c29e8 100644
--- a/crates/swc_ecma_transforms_testing/src/lib.rs
+++ b/crates/swc_ecma_transforms_testing/src/lib.rs
@@ -685,7 +685,7 @@ fn test_fixture_inner
(
as_folder(::swc_ecma_utils::DropSpan {
preserve_ctxt: true,
}),
- "output.js",
+ "expected.js",
syntax,
&expected,
)?;
diff --git a/crates/swc_estree_compat/Cargo.toml b/crates/swc_estree_compat/Cargo.toml
index 51aff9639c5..1943373e015 100644
--- a/crates/swc_estree_compat/Cargo.toml
+++ b/crates/swc_estree_compat/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_estree_compat"
repository = "https://github.com/swc-project/swc.git"
-version = "0.3.0"
+version = "0.3.1"
[package.metadata.docs.rs]
all-features = true
diff --git a/crates/swc_estree_compat/src/babelify/module.rs b/crates/swc_estree_compat/src/babelify/module.rs
index 9148e38a72b..637fbf2d5d1 100644
--- a/crates/swc_estree_compat/src/babelify/module.rs
+++ b/crates/swc_estree_compat/src/babelify/module.rs
@@ -141,7 +141,7 @@ impl Babelify for ModuleItem {
type Output = ModuleItemOutput;
fn parallel(cnt: usize) -> bool {
- cnt >= 4
+ cnt >= 32
}
fn babelify(self, ctx: &Context) -> Self::Output {
diff --git a/crates/swc_estree_compat/src/babelify/pat.rs b/crates/swc_estree_compat/src/babelify/pat.rs
index 43408713f8c..929d4d0f127 100644
--- a/crates/swc_estree_compat/src/babelify/pat.rs
+++ b/crates/swc_estree_compat/src/babelify/pat.rs
@@ -55,7 +55,11 @@ impl From for ObjectPropVal {
fn from(pat: PatOutput) -> Self {
match pat {
PatOutput::Expr(e) => ObjectPropVal::Expr(e),
- other => ObjectPropVal::Pattern(other.into()),
+ PatOutput::Id(p) => ObjectPropVal::Pattern(PatternLike::Id(p)),
+ PatOutput::Array(p) => ObjectPropVal::Pattern(PatternLike::ArrayPat(p)),
+ PatOutput::Rest(p) => ObjectPropVal::Pattern(PatternLike::RestEl(p)),
+ PatOutput::Object(p) => ObjectPropVal::Pattern(PatternLike::ObjectPat(p)),
+ PatOutput::Assign(p) => ObjectPropVal::Pattern(PatternLike::AssignmentPat(p)),
}
}
}
@@ -119,7 +123,10 @@ impl From for Param {
match pat {
PatOutput::Id(i) => Param::Id(i),
PatOutput::Rest(r) => Param::Rest(r),
- other => other.into(),
+ PatOutput::Array(p) => Param::Pat(Pattern::Array(p)),
+ PatOutput::Object(p) => Param::Pat(Pattern::Object(p)),
+ PatOutput::Assign(p) => Param::Pat(Pattern::Assignment(p)),
+ PatOutput::Expr(p) => panic!("Cannot convert {:?} to Param", p),
}
}
}
diff --git a/crates/swc_webpack_ast/.gitignore b/crates/swc_webpack_ast/.gitignore
new file mode 100644
index 00000000000..4d6d587d619
--- /dev/null
+++ b/crates/swc_webpack_ast/.gitignore
@@ -0,0 +1 @@
+tests/**/output.json
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/Cargo.toml b/crates/swc_webpack_ast/Cargo.toml
new file mode 100644
index 00000000000..780fc14acda
--- /dev/null
+++ b/crates/swc_webpack_ast/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+authors = ["강동윤 "]
+description = "Webpack AST optimizer"
+edition = "2018"
+license = "Apache-2.0"
+name = "swc_webpack_ast"
+repository = "https://github.com/swc-project/swc.git"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.48"
+rayon = "1.5.1"
+serde_json = "1.0.72"
+swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
+swc_common = {version = "0.14.6", path = "../swc_common"}
+swc_ecma_ast = {version = "0.58.0", path = "../swc_ecma_ast"}
+swc_ecma_transforms_base = {version = "0.44.3", path = "../swc_ecma_transforms_base"}
+swc_ecma_utils = {version = "0.52.3", path = "../swc_ecma_utils"}
+swc_ecma_visit = {version = "0.44.0", path = "../swc_ecma_visit"}
+swc_estree_ast = {version = "0.2.0", path = "../swc_estree_ast"}
+swc_estree_compat = {version = "0.3.0", path = "../swc_estree_compat"}
+swc_timer = {version = "0.1.0", path = "../swc_timer"}
+tracing = "0.1.29"
+
+[dev-dependencies]
+swc_ecma_parser = {version = "0.78.9", path = "../swc_ecma_parser"}
+swc_ecma_transforms_testing = {version = "0.45.1", path = "../swc_ecma_transforms_testing"}
+swc_node_base = {version = "0.5.1", path = "../swc_node_base"}
+testing = {version = "0.15.2", path = "../testing"}
diff --git a/crates/swc_webpack_ast/benches/webpack_ast.rs b/crates/swc_webpack_ast/benches/webpack_ast.rs
new file mode 100644
index 00000000000..144d8f7d1aa
--- /dev/null
+++ b/crates/swc_webpack_ast/benches/webpack_ast.rs
@@ -0,0 +1,48 @@
+#![feature(test)]
+
+use std::path::Path;
+use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax};
+use test::Bencher;
+
+extern crate swc_node_base;
+extern crate test;
+
+/// this benchmark requires real input, which cannot be committed into git
+/// repository
+#[bench]
+#[cfg(not(all))]
+fn total(b: &mut Bencher) {
+ let input = Path::new("tests/fixture/real/input.js");
+
+ b.iter(|| {
+ testing::run_test(false, |cm, handler| {
+ let fm = cm.load_file(&input).unwrap();
+
+ let module = {
+ let mut p = Parser::new(
+ Syntax::Es(EsConfig {
+ jsx: true,
+ ..Default::default()
+ }),
+ StringInput::from(&*fm),
+ None,
+ );
+ let res = p
+ .parse_module()
+ .map_err(|e| e.into_diagnostic(&handler).emit());
+
+ for e in p.take_errors() {
+ e.into_diagnostic(&handler).emit()
+ }
+
+ res?
+ };
+
+ let s = swc_webpack_ast::webpack_ast(cm.clone(), fm.clone(), module).unwrap();
+ println!("{} bytes", s.len());
+
+ Ok(())
+ })
+ .unwrap();
+ });
+}
diff --git a/crates/swc_webpack_ast/scripts/bench.js b/crates/swc_webpack_ast/scripts/bench.js
new file mode 100644
index 00000000000..5a30d0900ab
--- /dev/null
+++ b/crates/swc_webpack_ast/scripts/bench.js
@@ -0,0 +1,66 @@
+
+
+
+const Benchmark = require('benchmark');
+const acorn = require("acorn");
+const jsx = require("acorn-jsx");
+const parser = acorn.Parser.extend(jsx());
+
+
+const fs = require('fs');
+const path = require('path');
+
+
+const src = fs.readFileSync(path.join(process.argv[2], "input.js"), 'utf8');
+const jsonStr = fs.readFileSync(path.join(process.argv[2], "output.json"), 'utf8');
+
+
+{
+ parser.parse(src, {
+ ecmaVersion: 2020,
+ ranges: true,
+ allowHashBang: true,
+ sourceType: 'module'
+ })
+}
+const suite = new Benchmark.Suite;
+
+suite
+ .add('acorn', () => {
+ parser.parse(src, {
+ ecmaVersion: 2020,
+ ranges: true,
+ allowHashBang: true,
+ sourceType: 'module'
+ })
+ })
+ .add({
+ name: 'acorn-real',
+ fn: (deferred) => {
+ fs.promises.readFile(path.join(process.argv[2], "input.js"), 'utf8')
+ .then((src) => {
+ parser.parse(src, {
+ ecmaVersion: 2020,
+ ranges: true,
+ allowHashBang: true,
+ sourceType: 'module'
+ });
+ deferred.resolve()
+ })
+ },
+ defer: true,
+ async: true,
+ queued: true
+ })
+ .add('json', () => {
+ JSON.parse(jsonStr)
+ })
+ .on('cycle', function (event) {
+ console.log(String(event.target));
+ })
+ .on('complete', function () {
+ console.log('Fastest is ' + this.filter('fastest').map('name'));
+ })
+ .run({
+ async: true
+ })
diff --git a/crates/swc_webpack_ast/src/lib.rs b/crates/swc_webpack_ast/src/lib.rs
new file mode 100644
index 00000000000..3ac9643c338
--- /dev/null
+++ b/crates/swc_webpack_ast/src/lib.rs
@@ -0,0 +1,45 @@
+use crate::reducer::ast_reducer;
+use anyhow::{Context, Error};
+use swc_common::{sync::Lrc, Mark, SourceFile, SourceMap};
+use swc_ecma_ast::*;
+use swc_ecma_transforms_base::resolver::resolver_with_mark;
+use swc_ecma_visit::VisitMutWith;
+use swc_estree_ast::flavor::Flavor;
+use swc_estree_compat::babelify::Babelify;
+use swc_timer::timer;
+
+pub mod reducer;
+
+/// `n` is expected to be pure (`resolver` is not applied)
+pub fn webpack_ast(
+ cm: Lrc,
+ fm: Lrc,
+ mut n: Module,
+) -> Result {
+ let _timer = timer!("webpack_ast");
+ let top_level_mark = Mark::fresh(Mark::root());
+
+ Flavor::Acorn.with(|| {
+ {
+ let _timer = timer!("resolver");
+ n.visit_mut_with(&mut resolver_with_mark(top_level_mark));
+ }
+ {
+ n.visit_mut_with(&mut ast_reducer(top_level_mark));
+ }
+
+ let ctx = swc_estree_compat::babelify::Context {
+ fm,
+ cm: cm.clone(),
+ comments: Default::default(),
+ };
+
+ let babel_ast = {
+ let _timer = timer!("babelify");
+ n.babelify(&ctx)
+ };
+
+ let _timer = timer!("acorn ast to json");
+ serde_json::to_string(&babel_ast).context("failed to serialize babel ast")
+ })
+}
diff --git a/crates/swc_webpack_ast/src/reducer.rs b/crates/swc_webpack_ast/src/reducer.rs
new file mode 100644
index 00000000000..adb022c8df4
--- /dev/null
+++ b/crates/swc_webpack_ast/src/reducer.rs
@@ -0,0 +1,1247 @@
+use std::sync::Arc;
+use swc_atoms::js_word;
+use swc_common::{collections::AHashSet, util::take::Take, Mark, SyntaxContext, DUMMY_SP};
+use swc_ecma_ast::*;
+use swc_ecma_utils::{ident::IdentLike, Id, StmtLike, StmtOrModuleItem};
+use swc_ecma_visit::{VisitMut, VisitMutWith};
+use swc_timer::timer;
+
+/// # Usage
+///
+/// This transform should be applied after applying resolver.
+///
+///
+/// # Preserved nodes
+///
+/// - import
+/// - export
+/// - all imported identifiers
+/// - `process.env.NODE_ENV`
+/// - `require`
+/// - `module`
+/// - `__webpack_*`
+/// - `import.meta`
+/// - `import()`
+/// - `define()`
+/// - `require.ensure`
+///
+///
+///
+/// # Example
+///
+/// ## Input
+///
+///```js
+/// import { a } from "x";
+/// import b from "y";
+///
+/// function d() {
+/// a.x.y(), console.log(b);
+/// require("z");
+/// module.hot.accept("x", x => { ... })
+/// }
+/// ```
+///
+/// ## Output
+///
+/// ```js
+/// import { a } from "x";
+/// import b from "y";
+///
+///
+/// a.x.y(); b;
+/// require("z")
+/// module.hot.accept("x", () => { })
+/// ```
+pub fn ast_reducer(top_level_mark: Mark) -> impl VisitMut {
+ Minimalizer {
+ top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
+ ..Default::default()
+ }
+}
+
+#[derive(Default)]
+struct ScopeData {
+ imported_ids: AHashSet,
+}
+
+impl ScopeData {
+ fn analyze(items: &[ModuleItem]) -> Self {
+ let mut imported_ids = AHashSet::default();
+
+ for item in items {
+ match item {
+ ModuleItem::ModuleDecl(ModuleDecl::Import(i)) => {
+ for s in &i.specifiers {
+ match s {
+ ImportSpecifier::Named(s) => {
+ imported_ids.insert(s.local.to_id());
+ }
+ ImportSpecifier::Default(s) => {
+ imported_ids.insert(s.local.to_id());
+ }
+ ImportSpecifier::Namespace(s) => {
+ imported_ids.insert(s.local.to_id());
+ }
+ }
+ }
+ }
+
+ _ => {}
+ }
+ }
+
+ ScopeData { imported_ids }
+ }
+
+ fn should_preserve(&self, i: &Ident) -> bool {
+ if self.imported_ids.contains(&i.to_id()) {
+ return true;
+ }
+
+ match &*i.sym {
+ "process" | "module" | "import" | "define" | "require" => {
+ return true;
+ }
+
+ _ => {
+ if i.sym.starts_with("__webpack_") {
+ return true;
+ }
+ }
+ }
+
+ false
+ }
+}
+
+#[derive(Clone, Default)]
+struct Minimalizer {
+ data: Arc,
+ top_level_ctxt: SyntaxContext,
+
+ var_decl_kind: Option,
+
+ can_remove_pat: bool,
+}
+
+impl Minimalizer {
+ fn flatten_stmt(&mut self, to: &mut Vec, item: &mut T)
+ where
+ T: StmtOrModuleItem + StmtLike + Take,
+ {
+ let item = item.take();
+
+ match item.try_into_stmt() {
+ Ok(stmt) => match stmt {
+ Stmt::Block(b) => {
+ to.extend(b.stmts.into_iter().map(T::from_stmt));
+ }
+
+ // Flatten a function declaration.
+ Stmt::Decl(Decl::Fn(fn_decl)) => {
+ let Function {
+ params,
+ decorators,
+ body,
+ ..
+ } = fn_decl.function;
+
+ if !decorators.is_empty() {
+ let mut s = Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: Box::new(Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs: decorators.into_iter().map(|d| d.expr).collect(),
+ })),
+ });
+ s.visit_mut_with(self);
+ to.push(T::from_stmt(s));
+ }
+
+ if !params.is_empty() {
+ let mut exprs = Vec::with_capacity(params.len());
+
+ for p in params {
+ exprs.extend(p.decorators.into_iter().map(|d| d.expr));
+
+ preserve_pat(&mut exprs, p.pat);
+ }
+
+ let mut s = Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: Box::new(Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ })),
+ });
+ s.visit_mut_with(self);
+ to.push(T::from_stmt(s));
+ }
+
+ if let Some(body) = body {
+ to.extend(body.stmts.into_iter().map(T::from_stmt));
+ }
+ }
+ Stmt::Try(ts) => {
+ to.extend(ts.block.stmts.into_iter().map(T::from_stmt));
+ if let Some(h) = ts.handler {
+ if let Some(p) = h.param {
+ let mut exprs = vec![];
+ preserve_pat(&mut exprs, p);
+
+ if !exprs.is_empty() {
+ to.push(T::from_stmt(Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: Box::new(Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ })),
+ })));
+ }
+ }
+ to.extend(h.body.stmts.into_iter().map(T::from_stmt));
+ }
+
+ if let Some(f) = ts.finalizer {
+ to.extend(f.stmts.into_iter().map(T::from_stmt));
+ }
+ }
+ Stmt::Decl(Decl::Var(d)) => {
+ let mut exprs = vec![];
+
+ for decl in d.decls {
+ preserve_pat(&mut exprs, decl.name);
+ exprs.extend(decl.init);
+ }
+
+ if !exprs.is_empty() {
+ let mut s = Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: Box::new(Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ })),
+ });
+ s.visit_mut_with(self);
+ to.push(T::from_stmt(s));
+ }
+ }
+ _ => {
+ to.push(T::from_stmt(stmt));
+ }
+ },
+ Err(item) => {
+ to.push(item);
+ }
+ }
+ }
+
+ fn visit_mut_stmt_likes(&mut self, stmts: &mut Vec)
+ where
+ T: StmtOrModuleItem + StmtLike + VisitMutWith + Take,
+ Vec: VisitMutWith,
+ {
+ stmts.visit_mut_children_with(self);
+
+ let mut new = Vec::with_capacity(stmts.len());
+ for stmt in stmts.iter_mut() {
+ self.flatten_stmt(&mut new, stmt);
+ }
+
+ // Remove empty statements
+ new.retain(|stmt| match StmtOrModuleItem::as_stmt(stmt) {
+ Ok(Stmt::Empty(..)) => return false,
+ Ok(Stmt::Expr(es)) => return !can_remove(&es.expr),
+ _ => true,
+ });
+
+ *stmts = new;
+ }
+
+ fn ignore_expr(&mut self, e: &mut Expr) {
+ match e {
+ Expr::Lit(..)
+ | Expr::This(..)
+ | Expr::Member(MemberExpr {
+ obj: ExprOrSuper::Super(..),
+ computed: false,
+ ..
+ })
+ | Expr::Yield(YieldExpr { arg: None, .. }) => {
+ e.take();
+ return;
+ }
+
+ Expr::Ident(i) => {
+ if !self.data.should_preserve(&*i) {
+ e.take();
+ }
+ return;
+ }
+
+ Expr::Member(MemberExpr {
+ obj: ExprOrSuper::Expr(obj),
+ prop,
+ computed,
+ ..
+ }) => {
+ self.ignore_expr(obj);
+ if *computed {
+ self.ignore_expr(prop);
+ }
+
+ match (obj.is_invalid(), prop.is_invalid()) {
+ (true, true) => {
+ e.take();
+ return;
+ }
+ (true, false) => {
+ if *computed {
+ *e = *prop.take();
+ } else {
+ e.take();
+ return;
+ }
+ }
+ (false, true) => {
+ *e = *obj.take();
+ }
+ (false, false) => {}
+ }
+ }
+
+ Expr::Array(a) => {
+ if a.elems.is_empty() {
+ e.take();
+ return;
+ }
+ }
+
+ Expr::Object(obj) => {
+ if obj.props.is_empty() {
+ e.take();
+ return;
+ }
+ }
+
+ Expr::Seq(seq) => {
+ // visit_mut_seq_expr handles the elements other than last one.
+ if let Some(e) = seq.exprs.last_mut() {
+ self.ignore_expr(&mut **e);
+ }
+ seq.exprs.retain(|e| !e.is_invalid());
+ if seq.exprs.is_empty() {
+ e.take();
+ return;
+ }
+ if seq.exprs.len() == 1 {
+ *e = *seq.exprs.pop().unwrap();
+ return;
+ }
+ }
+
+ _ => {}
+ }
+ }
+}
+
+impl VisitMut for Minimalizer {
+ fn visit_mut_arrow_expr(&mut self, e: &mut ArrowExpr) {
+ let old_can_remove_pat = self.can_remove_pat;
+ self.can_remove_pat = true;
+ e.params.visit_mut_with(self);
+ self.can_remove_pat = old_can_remove_pat;
+
+ e.body.visit_mut_with(self);
+
+ e.type_params.visit_mut_with(self);
+
+ e.return_type.visit_mut_with(self);
+ }
+
+ fn visit_mut_assign_pat_prop(&mut self, p: &mut AssignPatProp) {
+ p.visit_mut_children_with(self);
+
+ if let Some(v) = &mut p.value {
+ self.ignore_expr(&mut **v);
+ if v.is_invalid() {
+ p.value = None;
+ }
+ }
+ }
+
+ fn visit_mut_class_members(&mut self, v: &mut Vec) {
+ v.visit_mut_children_with(self);
+
+ v.retain(|m| {
+ match m {
+ ClassMember::ClassProp(p) => {
+ if !p.computed
+ && p.decorators.is_empty()
+ && can_remove(&p.key)
+ && p.value.as_deref().map(can_remove).unwrap_or(true)
+ {
+ return false;
+ }
+ }
+ ClassMember::Method(m) => {
+ if !m.key.is_computed()
+ && m.function.decorators.is_empty()
+ && m.function.params.is_empty()
+ && m.function
+ .body
+ .as_ref()
+ .map(|v| v.stmts.is_empty())
+ .unwrap_or(true)
+ {
+ return false;
+ }
+ }
+ _ => {}
+ }
+
+ true
+ });
+ }
+
+ /// Normalize expressions.
+ ///
+ /// - empty [Expr::Seq] => [Expr::Invalid]
+ fn visit_mut_expr(&mut self, e: &mut Expr) {
+ e.visit_mut_children_with(self);
+
+ match e {
+ Expr::Seq(seq) => {
+ if seq.exprs.is_empty() {
+ *e = null_expr();
+ return;
+ }
+
+ if seq.exprs.len() == 1 {
+ *e = *seq.exprs.pop().unwrap();
+ }
+ }
+ _ => {}
+ }
+
+ match e {
+ Expr::Paren(p) => {
+ *e = *p.expr.take();
+ }
+
+ Expr::Ident(i) => {
+ if self.data.should_preserve(&i) {
+ return;
+ }
+
+ *e = null_expr();
+ }
+
+ Expr::Member(MemberExpr {
+ obj: ExprOrSuper::Expr(obj),
+ computed: false,
+ ..
+ }) => {
+ if let Some(left) = left_most(&obj) {
+ if self.data.should_preserve(&left) {
+ return;
+ }
+ }
+ *e = *obj.take();
+ return;
+ }
+
+ Expr::OptChain(opt) => {
+ *e = *opt.expr.take();
+ }
+
+ Expr::Await(expr) => {
+ *e = *expr.arg.take();
+ }
+
+ Expr::Yield(expr) => {
+ if let Some(arg) = expr.arg.take() {
+ *e = *arg;
+ }
+ }
+
+ Expr::Unary(expr) => {
+ *e = *expr.arg.take();
+ }
+
+ Expr::Update(expr) => {
+ *e = *expr.arg.take();
+ }
+
+ Expr::TsAs(expr) => {
+ *e = *expr.expr.take();
+ }
+
+ Expr::TsConstAssertion(expr) => {
+ *e = *expr.expr.take();
+ }
+
+ Expr::TsTypeAssertion(expr) => {
+ *e = *expr.expr.take();
+ }
+
+ Expr::TsNonNull(expr) => {
+ *e = *expr.expr.take();
+ }
+
+ Expr::TaggedTpl(expr) => {
+ let mut exprs = Vec::with_capacity(expr.tpl.exprs.len() + 1);
+ exprs.push(expr.tag.take());
+ exprs.extend(expr.tpl.exprs.take());
+
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Tpl(expr) => {
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs: expr.exprs.take(),
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Assign(expr) => {
+ let mut exprs = Vec::with_capacity(2);
+ preserve_pat_or_expr(&mut exprs, expr.left.take());
+ exprs.push(expr.right.take());
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Seq(seq) => {
+ if seq.exprs.is_empty() {
+ *e = Expr::Invalid(Invalid { span: DUMMY_SP });
+ return;
+ }
+ }
+
+ Expr::Bin(expr) => {
+ let mut exprs = Vec::with_capacity(2);
+
+ exprs.push(expr.left.take());
+ exprs.push(expr.right.take());
+
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Cond(expr) => {
+ let mut exprs = Vec::with_capacity(3);
+
+ exprs.push(expr.test.take());
+ exprs.push(expr.cons.take());
+ exprs.push(expr.alt.take());
+
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Array(arr) => {
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs: arr
+ .elems
+ .take()
+ .into_iter()
+ .flatten()
+ .map(|elem| elem.expr)
+ .collect(),
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ Expr::Object(obj) => {
+ if obj.props.iter().all(|prop| match prop {
+ PropOrSpread::Spread(..) => true,
+ PropOrSpread::Prop(prop) => match &**prop {
+ Prop::Shorthand(..) | Prop::KeyValue(..) | Prop::Assign(..) => true,
+ _ => false,
+ },
+ }) {
+ let mut exprs = Vec::with_capacity(obj.props.len());
+
+ for prop in obj.props.take() {
+ match prop {
+ PropOrSpread::Spread(prop) => {
+ exprs.push(prop.expr);
+ }
+ PropOrSpread::Prop(prop) => match *prop {
+ Prop::Shorthand(i) => {
+ exprs.push(Box::new(Expr::Ident(i)));
+ }
+ Prop::KeyValue(p) => {
+ preserve_prop_name(&mut exprs, p.key);
+ exprs.push(p.value);
+ }
+ Prop::Assign(p) => {
+ exprs.push(Box::new(Expr::Ident(p.key)));
+ exprs.push(p.value);
+ }
+ _ => {
+ unreachable!()
+ }
+ },
+ }
+ }
+
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+ }
+
+ Expr::Call(CallExpr {
+ callee: ExprOrSuper::Expr(callee),
+ args,
+ ..
+ })
+ | Expr::New(NewExpr {
+ callee,
+ args: Some(args),
+ ..
+ }) => {
+ self.ignore_expr(callee);
+
+ if callee.is_invalid() {
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs: args.take().into_iter().map(|arg| arg.expr).collect(),
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+ }
+
+ Expr::JSXElement(el) => {
+ // Remove empty, non-component elements.
+ match &el.opening.name {
+ JSXElementName::Ident(name) => {
+ if name.sym.chars().next().unwrap().is_uppercase() {
+ if el.opening.attrs.is_empty() && el.children.is_empty() {
+ *e = Expr::Ident(name.clone());
+ return;
+ }
+
+ return;
+ }
+ }
+ _ => return,
+ }
+
+ if el.opening.attrs.is_empty() && el.children.is_empty() {
+ *e = null_expr();
+ return;
+ }
+ }
+
+ Expr::Arrow(ArrowExpr {
+ params,
+ body: BlockStmtOrExpr::Expr(body),
+ ..
+ }) => {
+ let mut exprs = vec![];
+ for p in params.take() {
+ preserve_pat(&mut exprs, p);
+ }
+
+ exprs.push(body.take());
+
+ let mut seq = Expr::Seq(SeqExpr {
+ span: DUMMY_SP,
+ exprs,
+ });
+
+ seq.visit_mut_with(self);
+
+ *e = seq;
+ }
+
+ // TODO:
+ // Expr::Class(_) => todo!(),
+ // Expr::MetaProp(_) => todo!(),
+ // Expr::JSXMember(_) => todo!(),
+ // Expr::JSXNamespacedName(_) => todo!(),
+ // Expr::JSXEmpty(_) => todo!(),
+ // Expr::JSXFragment(_) => todo!(),
+ // Expr::OptChain(_) => todo!(),
+ _ => {}
+ }
+ }
+
+ fn visit_mut_expr_or_spread(&mut self, expr: &mut ExprOrSpread) {
+ expr.spread = None;
+ expr.expr.visit_mut_with(self);
+ }
+
+ fn visit_mut_expr_or_spreads(&mut self, elems: &mut Vec) {
+ elems.visit_mut_children_with(self);
+
+ elems.retain(|e| !e.expr.is_invalid());
+ }
+
+ fn visit_mut_expr_stmt(&mut self, s: &mut ExprStmt) {
+ s.visit_mut_children_with(self);
+
+ self.ignore_expr(&mut s.expr);
+ }
+
+ fn visit_mut_exprs(&mut self, exprs: &mut Vec>) {
+ exprs.visit_mut_children_with(self);
+
+ exprs.retain(|e| !e.is_invalid());
+ }
+
+ fn visit_mut_function(&mut self, f: &mut Function) {
+ f.decorators.visit_mut_with(self);
+
+ let old_can_remove_pat = self.can_remove_pat;
+ self.can_remove_pat = true;
+ f.params.visit_mut_with(self);
+ self.can_remove_pat = old_can_remove_pat;
+
+ f.body.visit_mut_with(self);
+
+ f.type_params.visit_mut_with(self);
+
+ f.return_type.visit_mut_with(self);
+ }
+
+ fn visit_mut_jsx_attr_or_spreads(&mut self, attrs: &mut Vec) {
+ attrs.visit_mut_children_with(self);
+
+ attrs.retain(|attr| match attr {
+ JSXAttrOrSpread::JSXAttr(attr) => {
+ match &attr.name {
+ JSXAttrName::Ident(..) => {}
+ JSXAttrName::JSXNamespacedName(_) => {
+ // We don't handle this because no one uses it
+ return true;
+ }
+ }
+
+ // Remove jsx attributes
+ match &attr.value {
+ Some(v) => match v {
+ JSXAttrValue::Lit(_) => return false,
+ JSXAttrValue::JSXExprContainer(e) => match &e.expr {
+ JSXExpr::JSXEmptyExpr(_) => {
+ return true;
+ }
+ JSXExpr::Expr(e) => {
+ if can_remove(&e) {
+ return false;
+ }
+ }
+ },
+ JSXAttrValue::JSXElement(_) => {}
+ JSXAttrValue::JSXFragment(_) => {}
+ },
+ None => return false,
+ }
+
+ true
+ }
+ JSXAttrOrSpread::SpreadElement(s) => {
+ if can_remove(&s.expr) {
+ return false;
+ }
+
+ true
+ }
+ });
+ }
+
+ fn visit_mut_jsx_element(&mut self, el: &mut JSXElement) {
+ el.visit_mut_children_with(self);
+
+ // Remove empty, non-component elements.
+ match &el.opening.name {
+ JSXElementName::Ident(name) => {
+ if name.sym.chars().next().unwrap().is_uppercase() {
+ return;
+ }
+ }
+ _ => return,
+ }
+
+ if !el.opening.attrs.is_empty() {
+ return;
+ }
+
+ if el.children.len() == 1 {
+ match &mut el.children[0] {
+ JSXElementChild::JSXElement(c) => {
+ *el = *c.take();
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn visit_mut_jsx_element_children(&mut self, v: &mut Vec) {
+ v.visit_mut_children_with(self);
+
+ v.retain(|c| match c {
+ JSXElementChild::JSXText(_)
+ | JSXElementChild::JSXExprContainer(JSXExprContainer {
+ expr: JSXExpr::JSXEmptyExpr(..),
+ ..
+ }) => return false,
+ JSXElementChild::JSXExprContainer(JSXExprContainer {
+ expr: JSXExpr::Expr(expr),
+ ..
+ }) => return !can_remove(&expr),
+
+ JSXElementChild::JSXElement(el) => {
+ // Remove empty, non-component elements.
+ match &el.opening.name {
+ JSXElementName::Ident(name) => {
+ if name.sym.chars().next().unwrap().is_uppercase() {
+ return true;
+ }
+ }
+ _ => return true,
+ }
+
+ if el.opening.attrs.is_empty() && el.children.is_empty() {
+ return false;
+ }
+
+ true
+ }
+
+ _ => true,
+ })
+ }
+
+ fn visit_mut_jsx_expr(&mut self, e: &mut JSXExpr) {
+ e.visit_mut_children_with(self);
+
+ match e {
+ JSXExpr::JSXEmptyExpr(_) => {}
+ JSXExpr::Expr(expr) => {
+ if expr.is_invalid() {
+ *e = JSXExpr::JSXEmptyExpr(JSXEmptyExpr { span: DUMMY_SP });
+ }
+ }
+ }
+ }
+
+ fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
+ e.obj.visit_mut_with(self);
+
+ if e.computed {
+ e.prop.visit_mut_with(self);
+ }
+ }
+
+ fn visit_mut_module_items(&mut self, stmts: &mut Vec) {
+ let _timer = timer!("reduce ast");
+
+ self.data = Arc::new(ScopeData::analyze(&stmts));
+
+ self.visit_mut_stmt_likes(stmts);
+ }
+
+ fn visit_mut_object_pat_props(&mut self, props: &mut Vec) {
+ props.visit_mut_children_with(self);
+
+ props.retain(|prop| match prop {
+ ObjectPatProp::Rest(p) => {
+ if p.arg.is_invalid() {
+ return false;
+ }
+
+ true
+ }
+ ObjectPatProp::Assign(p) => {
+ if self.can_remove_pat {
+ if p.value.is_none() {
+ return false;
+ }
+ }
+
+ true
+ }
+
+ ObjectPatProp::KeyValue(p) => {
+ if p.value.is_invalid() {
+ return false;
+ }
+
+ true
+ }
+ });
+ }
+
+ fn visit_mut_opt_expr(&mut self, e: &mut Option>) {
+ e.visit_mut_children_with(self);
+
+ if let Some(Expr::Invalid(..)) = e.as_deref() {
+ e.take();
+ }
+ }
+
+ fn visit_mut_opt_expr_or_spread(&mut self, e: &mut Option) {
+ e.visit_mut_children_with(self);
+
+ if let Some(elem) = e {
+ if elem.expr.is_invalid() {
+ *e = None;
+ }
+ }
+ }
+
+ fn visit_mut_params(&mut self, ps: &mut Vec) {
+ ps.visit_mut_children_with(self);
+
+ ps.retain(|param| !param.pat.is_invalid());
+ }
+
+ fn visit_mut_pat(&mut self, pat: &mut Pat) {
+ // We don't need rest pattern.
+ match pat {
+ Pat::Rest(rest) => {
+ *pat = *rest.arg.take();
+ }
+ _ => {}
+ }
+
+ pat.visit_mut_children_with(self);
+
+ if !self.can_remove_pat {
+ return;
+ }
+
+ match pat {
+ Pat::Ident(p) => {
+ if p.id.span.ctxt != self.top_level_ctxt {
+ pat.take();
+ return;
+ }
+ }
+
+ Pat::Array(arr) => {
+ if arr.elems.is_empty() {
+ pat.take();
+ return;
+ }
+ }
+
+ Pat::Object(obj) => {
+ if obj.props.is_empty() {
+ pat.take();
+ return;
+ }
+ }
+
+ _ => {}
+ }
+ }
+
+ fn visit_mut_pats(&mut self, pats: &mut Vec) {
+ pats.visit_mut_children_with(self);
+
+ pats.retain(|pat| !pat.is_invalid());
+ }
+
+ fn visit_mut_prop(&mut self, p: &mut Prop) {
+ p.visit_mut_children_with(self);
+
+ match p {
+ Prop::Shorthand(i) => {
+ if !self.data.should_preserve(&*i) {
+ i.take();
+ return;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn visit_mut_prop_or_spreads(&mut self, props: &mut Vec) {
+ props.visit_mut_children_with(self);
+
+ props.retain(|p| match p {
+ PropOrSpread::Spread(..) => true,
+ PropOrSpread::Prop(p) => match &**p {
+ Prop::Shorthand(p) => {
+ if p.sym == js_word!("") {
+ return false;
+ }
+
+ true
+ }
+ _ => true,
+ },
+ })
+ }
+
+ fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
+ e.visit_mut_children_with(self);
+
+ let cnt = e.exprs.len();
+
+ for (idx, elem) in e.exprs.iter_mut().enumerate() {
+ if idx == cnt - 1 {
+ continue;
+ }
+
+ self.ignore_expr(&mut **elem);
+ }
+
+ e.exprs.retain(|e| !can_remove(&e));
+ }
+
+ /// Normalize statements.
+ ///
+ /// - Invalid [Stmt::Expr] => [Stmt::Empty]
+ /// - Empty [Stmt::Block] => [Stmt::Empty]
+ /// - Single-item [Stmt::Block] => the item
+ /// - Invalid [Stmt::Decl] => [Stmt::Empty]
+ /// - Useless stmt => [Stmt::Empty]
+ fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
+ match stmt {
+ Stmt::Debugger(_) | Stmt::Break(_) | Stmt::Continue(_) => {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+
+ Stmt::Return(s) => {
+ if let Some(arg) = s.arg.take() {
+ *stmt = Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: arg,
+ });
+ } else {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+ }
+ Stmt::Throw(s) => {
+ *stmt = Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: s.arg.take(),
+ });
+ }
+
+ _ => {}
+ }
+
+ stmt.visit_mut_children_with(self);
+
+ match stmt {
+ Stmt::Labeled(l) => {
+ *stmt = *l.body.take();
+ }
+
+ _ => {}
+ }
+
+ match stmt {
+ Stmt::Expr(e) => {
+ if e.expr.is_invalid() {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+ }
+
+ Stmt::Block(block) => {
+ if block.stmts.is_empty() {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+ if block.stmts.len() == 1 {
+ *stmt = block.stmts.take().into_iter().next().unwrap();
+ return;
+ }
+ }
+
+ Stmt::Decl(Decl::Var(var)) => {
+ if var.decls.is_empty() {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+ }
+
+ Stmt::If(is) => {
+ if let Some(alt) = &mut is.alt {
+ if alt.is_empty() {
+ is.alt = None;
+ }
+ }
+
+ //
+ if can_remove(&is.test) {
+ if is.cons.is_empty() && is.alt.is_none() {
+ *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
+ return;
+ }
+
+ if is.alt.is_none() {
+ *stmt = *is.cons.take();
+ return;
+ }
+ }
+ }
+
+ // TODO: Flatten loops
+ // TODO: Flatten try catch
+ _ => {}
+ }
+ }
+
+ fn visit_mut_stmts(&mut self, stmts: &mut Vec) {
+ self.visit_mut_stmt_likes(stmts);
+ }
+
+ fn visit_mut_var_decl(&mut self, var: &mut VarDecl) {
+ let old_var_decl_kind = self.var_decl_kind;
+ self.var_decl_kind = Some(var.kind);
+
+ var.visit_mut_children_with(self);
+
+ self.var_decl_kind = old_var_decl_kind;
+ }
+
+ fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
+ v.visit_mut_children_with(self);
+
+ if let Some(e) = &mut v.init {
+ self.ignore_expr(&mut **e);
+
+ if e.is_invalid() {
+ v.init = None;
+ }
+ }
+
+ if v.init.is_none() && matches!(self.var_decl_kind, Some(VarDeclKind::Const)) {
+ v.init = Some(Box::new(null_expr()));
+ }
+ }
+}
+
+fn preserve_pat_or_expr(exprs: &mut Vec>, p: PatOrExpr) {
+ match p {
+ PatOrExpr::Expr(e) => {
+ exprs.push(e);
+ }
+ PatOrExpr::Pat(p) => preserve_pat(exprs, *p),
+ }
+}
+
+fn preserve_pat(exprs: &mut Vec>, p: Pat) {
+ match p {
+ Pat::Ident(..) => {}
+ Pat::Array(p) => {
+ p.elems
+ .into_iter()
+ .flatten()
+ .for_each(|elem| preserve_pat(exprs, elem));
+ }
+ Pat::Rest(p) => preserve_pat(exprs, *p.arg),
+ Pat::Object(p) => p.props.into_iter().for_each(|p| {
+ preserve_obj_pat(exprs, p);
+ }),
+ Pat::Assign(p) => {
+ preserve_pat(exprs, *p.left);
+ exprs.push(p.right)
+ }
+ Pat::Invalid(_) => {}
+ Pat::Expr(e) => {
+ exprs.push(e);
+ }
+ }
+}
+
+fn preserve_obj_pat(exprs: &mut Vec>, p: ObjectPatProp) {
+ match p {
+ ObjectPatProp::KeyValue(p) => {
+ preserve_prop_name(exprs, p.key);
+ preserve_pat(exprs, *p.value);
+ }
+ ObjectPatProp::Assign(p) => {
+ exprs.extend(p.value);
+ }
+ ObjectPatProp::Rest(p) => preserve_pat(exprs, *p.arg),
+ }
+}
+
+fn preserve_prop_name(exprs: &mut Vec>, p: PropName) {
+ match p {
+ PropName::Computed(e) => {
+ exprs.push(e.expr);
+ }
+ _ => {}
+ }
+}
+
+fn left_most(e: &Expr) -> Option {
+ match e {
+ Expr::Ident(i) => Some(i.clone()),
+ Expr::Member(MemberExpr {
+ obj: ExprOrSuper::Expr(obj),
+ computed: false,
+ ..
+ }) => left_most(obj),
+
+ _ => None,
+ }
+}
+
+fn null_expr() -> Expr {
+ Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))
+}
+
+fn can_remove(e: &Expr) -> bool {
+ match e {
+ Expr::Invalid(..) => true,
+ Expr::Lit(..) => true,
+ Expr::Seq(seq) => seq.exprs.iter().all(|e| can_remove(e)),
+ _ => false,
+ }
+}
diff --git a/crates/swc_webpack_ast/tests/fixture.rs b/crates/swc_webpack_ast/tests/fixture.rs
new file mode 100644
index 00000000000..e4afa8326cb
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture.rs
@@ -0,0 +1,66 @@
+use std::{fs, path::PathBuf};
+use swc_common::{chain, Mark};
+use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax};
+use swc_ecma_transforms_base::resolver::resolver_with_mark;
+use swc_ecma_transforms_testing::test_fixture;
+use swc_ecma_visit::as_folder;
+use swc_webpack_ast::reducer::ast_reducer;
+
+#[testing::fixture("tests/fixture/**/input.js")]
+fn fixture(input: PathBuf) {
+ let output = input.parent().unwrap().join("output.js");
+
+ test_fixture(
+ Syntax::Es(EsConfig {
+ jsx: true,
+ ..Default::default()
+ }),
+ &|_| {
+ let top_level_mark = Mark::fresh(Mark::root());
+
+ chain!(
+ resolver_with_mark(top_level_mark),
+ as_folder(ast_reducer(top_level_mark))
+ )
+ },
+ &input,
+ &output,
+ );
+}
+
+#[testing::fixture("tests/fixture/**/input.js")]
+fn test_babelify(input: PathBuf) {
+ let output_path = input.parent().unwrap().join("output.json");
+
+ testing::run_test(false, |cm, handler| {
+ let fm = cm.load_file(&input).unwrap();
+
+ let module = {
+ let mut p = Parser::new(
+ Syntax::Es(EsConfig {
+ jsx: true,
+ ..Default::default()
+ }),
+ StringInput::from(&*fm),
+ None,
+ );
+ let res = p
+ .parse_module()
+ .map_err(|e| e.into_diagnostic(&handler).emit());
+
+ for e in p.take_errors() {
+ e.into_diagnostic(&handler).emit()
+ }
+
+ res?
+ };
+
+ let s = swc_webpack_ast::webpack_ast(cm.clone(), fm.clone(), module).unwrap();
+ println!("{} bytes", s.len());
+
+ fs::write(&output_path, s.as_bytes()).unwrap();
+
+ Ok(())
+ })
+ .unwrap();
+}
diff --git a/crates/swc_webpack_ast/tests/fixture/array-literal/input.js b/crates/swc_webpack_ast/tests/fixture/array-literal/input.js
new file mode 100644
index 00000000000..2de3bf8dc63
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/array-literal/input.js
@@ -0,0 +1,5 @@
+import { a } from "x";
+import b from "y";
+
+const arr = [a, 'foo', b(), 'bar'];
+console.log(arr);
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/array-literal/output.js b/crates/swc_webpack_ast/tests/fixture/array-literal/output.js
new file mode 100644
index 00000000000..8e28a5c9eac
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/array-literal/output.js
@@ -0,0 +1,3 @@
+import { a } from "x";
+import b from "y";
+a, b();
diff --git a/crates/swc_webpack_ast/tests/fixture/base/input.js b/crates/swc_webpack_ast/tests/fixture/base/input.js
new file mode 100644
index 00000000000..2c2297a099e
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/base/input.js
@@ -0,0 +1,10 @@
+import { a } from "x";
+import b from "y";
+
+function d() {
+ a.x.y(), console.log(b);
+ require("z");
+ module.hot.accept("x", x => {
+
+ })
+}
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/base/output.js b/crates/swc_webpack_ast/tests/fixture/base/output.js
new file mode 100644
index 00000000000..e5e7794c4ee
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/base/output.js
@@ -0,0 +1,6 @@
+import { a } from "x";
+import b from "y";
+a.x.y(), b;
+require("z");
+module.hot.accept("x", ()=>{
+});
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js
new file mode 100644
index 00000000000..1bc030298ef
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/input.js
@@ -0,0 +1,6 @@
+import y from 'y;'
+
+const obj = {
+ [1 + 1]: 2,
+ y,
+}
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js
new file mode 100644
index 00000000000..35f334603b8
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/1/output.js
@@ -0,0 +1,2 @@
+import y from "y;";
+y;
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js
new file mode 100644
index 00000000000..0767535e5fe
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/input.js
@@ -0,0 +1,9 @@
+import x from 'x;/'
+import y from 'y;'
+
+const obj = {
+ [1 + y]: x,
+ y,
+}
+
+console.log(obj)
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js
new file mode 100644
index 00000000000..9f4d3ef1964
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/2/output.js
@@ -0,0 +1,3 @@
+import x from "x;/";
+import y from "y;";
+y, x, y;
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js
new file mode 100644
index 00000000000..1bc6f2ff2c7
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/input.js
@@ -0,0 +1,9 @@
+import x from 'x;/'
+import y from 'y;'
+
+const obj = {
+ [1 + y]: 1,
+ x,
+}
+
+console.log(obj)
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js
new file mode 100644
index 00000000000..1b62ff0a003
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/bin-expr/3/output.js
@@ -0,0 +1,3 @@
+import x from "x;/";
+import y from "y;";
+y, x;
diff --git a/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js b/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js
new file mode 100644
index 00000000000..6e67216044f
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/class-decl/1/input.js
@@ -0,0 +1,8 @@
+import bar from 'bar';
+
+class Foo {
+ [bar]() { }
+}
+
+
+console.log(new Foo())
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js b/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js
new file mode 100644
index 00000000000..8f7fe4b2912
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/class-decl/1/output.js
@@ -0,0 +1,5 @@
+import bar from "bar";
+class Foo {
+ [bar]() {
+ }
+}
diff --git a/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js b/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js
new file mode 100644
index 00000000000..9ddd6ff68b5
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/new-expr/1/input.js
@@ -0,0 +1,6 @@
+import Bar from 'bar';
+
+class Foo extends Bar {
+}
+
+console.log(new Foo())
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js b/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js
new file mode 100644
index 00000000000..423cf3f8366
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/new-expr/1/output.js
@@ -0,0 +1,3 @@
+import Bar from "bar";
+class Foo extends Bar {
+}
diff --git a/crates/swc_webpack_ast/tests/fixture/object-literal/input.js b/crates/swc_webpack_ast/tests/fixture/object-literal/input.js
new file mode 100644
index 00000000000..5bb9a1fd868
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/object-literal/input.js
@@ -0,0 +1,12 @@
+import { a } from "x";
+import b from "y";
+
+const d = {};
+
+const obj = {
+ a,
+ [b]: 'foo',
+ c: 'bar',
+ d
+};
+console.log(obj);
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/object-literal/output.js b/crates/swc_webpack_ast/tests/fixture/object-literal/output.js
new file mode 100644
index 00000000000..aad75448c2c
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/object-literal/output.js
@@ -0,0 +1,3 @@
+import { a } from "x";
+import b from "y";
+a, b;
diff --git a/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js b/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js
new file mode 100644
index 00000000000..b195a9a22c4
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/object-patterns/input.js
@@ -0,0 +1,10 @@
+import { a } from "x";
+import b from "y";
+
+function d() {
+ a.x.y(), console.log(b);
+ require("z");
+ module.hot.accept("x", ({ a = 1, b: { foo }, c: { ...rest } }) => {
+
+ })
+}
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js b/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js
new file mode 100644
index 00000000000..e5e7794c4ee
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/object-patterns/output.js
@@ -0,0 +1,6 @@
+import { a } from "x";
+import b from "y";
+a.x.y(), b;
+require("z");
+module.hot.accept("x", ()=>{
+});
diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js
new file mode 100644
index 00000000000..91e0027e661
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/input.js
@@ -0,0 +1 @@
+const a = obj?.foo;
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/1/output.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/1/output.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js
new file mode 100644
index 00000000000..ed33c2fe5dd
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/input.js
@@ -0,0 +1,5 @@
+
+
+export const foo =
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js
new file mode 100644
index 00000000000..f3194e8e688
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/opt-chain/in-jsx/output.js
@@ -0,0 +1 @@
+export const foo = null;
diff --git a/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js b/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js
new file mode 100644
index 00000000000..bf9774c8926
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/try-catch/1/input.js
@@ -0,0 +1,19 @@
+export function foo (){
+ try {
+ const meta = this.meta();
+ // const { event, team, user } = this.props;
+ const b = team ? event.user : user;
+
+ if (meta && meta.p1) {
+ return meta.p1;
+ } else if (meta && meta.p2) {
+ return meta.p2;
+ } else if (meta && meta.p3) {
+ return meta.p3;
+ }
+
+ return b.p4 === u.u ? 'You' : b.u;
+ } catch (e) {
+ return 'You';
+ }
+}
\ No newline at end of file
diff --git a/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js b/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js
new file mode 100644
index 00000000000..0da5001a08e
--- /dev/null
+++ b/crates/swc_webpack_ast/tests/fixture/try-catch/1/output.js
@@ -0,0 +1,2 @@
+export function foo() {
+}
diff --git a/package.json b/package.json
index c86cdb0e57e..72279f986db 100644
--- a/package.json
+++ b/package.json
@@ -75,8 +75,10 @@
"@types/jest": "^26.0.23",
"@types/node": "^14.14.41",
"acorn": "^8.6.0",
+ "acorn-jsx": "^5.3.2",
"axios": "^0.21.1",
"babel-plugin-transform-node-env-inline": "^0.4.3",
+ "benchmark": "^2.1.4",
"class-validator": "^0.13.1",
"core-js": "^2.6.11",
"cross-env": "^7.0.3",
diff --git a/yarn.lock b/yarn.lock
index f6d8721e061..27557a24049 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1730,6 +1730,11 @@ acorn-globals@^6.0.0:
acorn "^7.1.1"
acorn-walk "^7.1.1"
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
acorn-walk@^7.1.1:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
@@ -1942,6 +1947,14 @@ before-after-hook@^2.1.0, before-after-hook@^2.2.0:
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
+benchmark@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
+ integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik=
+ dependencies:
+ lodash "^4.17.4"
+ platform "^1.3.3"
+
bl@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@@ -3625,7 +3638,7 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "^3.0.0"
-lodash@^4.17.21, lodash@^4.7.0:
+lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3972,6 +3985,11 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
+platform@^1.3.3:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
+ integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"