Skip to content

Commit

Permalink
feat(tree_shaking): shake unused import variables (#771)
Browse files Browse the repository at this point in the history
<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
  • Loading branch information
hyf0 committed Apr 6, 2024
1 parent c304a0d commit 6714dd0
Show file tree
Hide file tree
Showing 25 changed files with 324 additions and 302 deletions.
8 changes: 6 additions & 2 deletions crates/rolldown/src/ast_scanner/side_effect_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,12 @@ impl<'a> SideEffectDetector<'a> {
Statement::Declaration(decl) => self.detect_side_effect_of_decl(decl),
Statement::ExpressionStatement(expr) => self.detect_side_effect_of_expr(&expr.expression),
Statement::ModuleDeclaration(module_decl) => match &**module_decl {
oxc::ast::ast::ModuleDeclaration::ImportDeclaration(_)
| oxc::ast::ast::ModuleDeclaration::ExportAllDeclaration(_) => true,
oxc::ast::ast::ModuleDeclaration::ExportAllDeclaration(_) => true,
oxc::ast::ast::ModuleDeclaration::ImportDeclaration(_) => {
// We consider `import ...` has no side effect. However, `import ...` might be rewritten to other statements by the bundler.
// In that case, we will mark the statement as having side effect in link stage.
false
}
oxc::ast::ast::ModuleDeclaration::ExportDefaultDeclaration(default_decl) => {
match &default_decl.declaration {
oxc::ast::ast::ExportDefaultDeclarationKind::Expression(expr) => {
Expand Down
119 changes: 68 additions & 51 deletions crates/rolldown/src/stages/link_stage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,65 +215,82 @@ impl<'a> LinkStage<'a> {
stmt_infos.iter_mut().for_each(|stmt_info| {
stmt_info.import_records.iter().for_each(|rec_id| {
let rec = &importer.import_records[*rec_id];
let ModuleId::Normal(importee_id) = rec.resolved_module else {
return;
};
let importee_linking_info = &self.metas[importee_id];
match rec.kind {
ImportKind::Import => {
let is_reexport_all = importer.star_exports.contains(rec_id);
match importee_linking_info.wrap_kind {
WrapKind::None => {}
WrapKind::Cjs => {
if is_reexport_all {
// something like `__reExport(foo_exports, __toESM(require_bar()))`
// Reference to `require_bar`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toESM"));
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__reExport"));
stmt_info.referenced_symbols.push(importer.namespace_symbol);
} else {
// something like `var import_foo = __toESM(require_foo())`
match rec.resolved_module {
ModuleId::External(_) => {
// Make sure symbols from external modules are included and de_conflicted
stmt_info.side_effect = true;
}
ModuleId::Normal(importee_id) => {
let importee_linking_info = &self.metas[importee_id];
match rec.kind {
ImportKind::Import => {
let is_reexport_all = importer.star_exports.contains(rec_id);
match importee_linking_info.wrap_kind {
WrapKind::None => {}
WrapKind::Cjs => {
stmt_info.side_effect = true;
if is_reexport_all {
// Turn `export * from 'bar_cjs'` into `__reExport(foo_exports, __toESM(require_bar_cjs()))`
// Reference to `require_bar_cjs`
stmt_info
.referenced_symbols
.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toESM"));
stmt_info
.referenced_symbols
.push(self.runtime.resolve_symbol("__reExport"));
stmt_info.referenced_symbols.push(importer.namespace_symbol);
} else {
// Turn `import * as bar from 'bar_cjs'` into `var import_bar_cjs = __toESM(require_bar_cjs())`
// Turn `import { prop } from 'bar_cjs'; prop;` into `var import_bar_cjs = __toESM(require_bar_cjs()); import_bar_cjs.prop;`
// Reference to `require_bar_cjs`
stmt_info
.referenced_symbols
.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toESM"));
stmt_info.declared_symbols.push(rec.namespace_ref);
let importee = &self.module_table.normal_modules[importee_id];
symbols.lock().unwrap().get_mut(rec.namespace_ref).name =
format!("import_{}", &importee.repr_name).into();
}
}
WrapKind::Esm => {
stmt_info.side_effect = true;
// Turn `import ... from 'bar_esm'` into `init_bar_esm()`
// Reference to `init_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
if is_reexport_all && importee_linking_info.has_dynamic_exports {
// Turn `export * from 'bar_esm'` into `init_bar_esm();__reExport(foo_exports, bar_esm_exports);`
// something like `__reExport(foo_exports, other_exports)`
stmt_info
.referenced_symbols
.push(self.runtime.resolve_symbol("__reExport"));
stmt_info.referenced_symbols.push(importer.namespace_symbol);
let importee = &self.module_table.normal_modules[importee_id];
stmt_info.referenced_symbols.push(importee.namespace_symbol);
}
}
}
}
ImportKind::Require => match importee_linking_info.wrap_kind {
WrapKind::None => {}
WrapKind::Cjs => {
// something like `require_foo()`
// Reference to `require_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toESM"));
stmt_info.declared_symbols.push(rec.namespace_ref);
let importee = &self.module_table.normal_modules[importee_id];
symbols.lock().unwrap().get_mut(rec.namespace_ref).name =
format!("import_{}", &importee.repr_name).into();
}
}
WrapKind::Esm => {
// something like `init_foo()`
// Reference to `init_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
if is_reexport_all && importee_linking_info.has_dynamic_exports {
// something like `__reExport(foo_exports, other_exports)`
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__reExport"));
stmt_info.referenced_symbols.push(importer.namespace_symbol);
WrapKind::Esm => {
// something like `(init_foo(), toCommonJS(foo_exports))`
// Reference to `init_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toCommonJS"));
let importee = &self.module_table.normal_modules[importee_id];
stmt_info.referenced_symbols.push(importee.namespace_symbol);
}
}
},
ImportKind::DynamicImport => {}
}
}
ImportKind::Require => match importee_linking_info.wrap_kind {
WrapKind::None => {}
WrapKind::Cjs => {
// something like `require_foo()`
// Reference to `require_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
}
WrapKind::Esm => {
// something like `(init_foo(), toCommonJS(foo_exports))`
// Reference to `init_foo`
stmt_info.referenced_symbols.push(importee_linking_info.wrapper_ref.unwrap());
stmt_info.referenced_symbols.push(self.runtime.resolve_symbol("__toCommonJS"));
let importee = &self.module_table.normal_modules[importee_id];
stmt_info.referenced_symbols.push(importee.namespace_symbol);
}
},
ImportKind::DynamicImport => {}
}
});
});
Expand Down
2 changes: 2 additions & 0 deletions crates/rolldown/src/stages/link_stage/tree_shaking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ fn include_statement(ctx: &mut Context, module: &NormalModule, stmt_info_id: Stm
// include statements that are referenced by this statement
stmt_info.declared_symbols.iter().chain(stmt_info.referenced_symbols.iter()).for_each(
|symbol_ref| {
// Notice we also include `declared_symbols`. This for case that import statements declare new symbols, but they are not
// really declared by the module itself. We need to include them where they are really declared.
include_symbol(ctx, *symbol_ref);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ input_file: crates/rolldown/tests/esbuild/import_star/import_star_export_import_
## entry_js.mjs

```js
import { __export } from "./$runtime$.mjs";
// foo.js
var foo_ns = {};
__export(foo_ns, {
foo:() => foo$1
});
const foo$1 = 123;
// entry.js
let foo = 234;
console.log(foo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,6 @@ input_file: crates/rolldown/tests/esbuild/import_star/import_star_export_star_un
## entry_js.mjs

```js
import { __export } from "./$runtime$.mjs";
// foo.js
const foo$1 = 123;
// bar.js
var bar_ns = {};
__export(bar_ns, {
foo:() => foo$1
});
// entry.js
let foo = 234;
console.log(foo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ input_file: crates/rolldown/tests/esbuild/import_star/import_star_unused
## entry_js.mjs

```js
import { __export } from "./$runtime$.mjs";
// foo.js
var foo_ns = {};
__export(foo_ns, {
foo:() => foo$1
});
const foo$1 = 123;
// entry.js
let foo = 234;
console.log(foo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ input_file: crates/rolldown/tests/esbuild/import_star/other_file_import_export_s
## entry_js.mjs

```js
import { __export } from "./$runtime$.mjs";
// foo.js
var foo_ns = {};
__export(foo_ns, {
foo:() => foo,
ns:() => foo_ns
});
const foo = 123;
export { foo };
Expand Down
18 changes: 9 additions & 9 deletions crates/rolldown/tests/fixtures/basic/artifacts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ input_file: crates/rolldown/tests/fixtures/basic

```js
// shared.js
const a = 'shared.js';
const a$1 = 'shared.js';
// main.js
const a$1 = 'index.js';
console.log(a$1, a);
const a = 'index.js';
console.log(a, a$1);
//# sourceMappingURL=main.mjs.map
```
Expand All @@ -22,14 +22,14 @@ console.log(a$1, a);

- ../shared.js
(0:0-0:6) "const " --> (2:0-2:6) "\nconst"
(0:6-0:10) "a = " --> (2:6-2:10) " a ="
(0:10-2:13) "'shared.js'\n\nexport { a }" --> (2:10-5:0) " 'shared.js';\n\n// main.js"
(0:6-0:10) "a = " --> (2:6-2:12) " a$1 ="
(0:10-2:13) "'shared.js'\n\nexport { a }" --> (2:12-5:0) " 'shared.js';\n\n// main.js"
- ../main.js
(1:0-1:6) "\nconst" --> (5:0-5:6) "\nconst"
(1:6-1:10) " a =" --> (5:6-5:12) " a$1 ="
(1:10-2:0) " 'index.js'" --> (5:12-6:0) " 'index.js';"
(1:6-1:10) " a =" --> (5:6-5:10) " a ="
(1:10-2:0) " 'index.js'" --> (5:10-6:0) " 'index.js';"
(2:0-2:8) "\nconsole" --> (6:0-6:8) "\nconsole"
(2:8-2:12) ".log" --> (6:8-6:12) ".log"
(2:12-2:15) "(a," --> (6:12-6:17) "(a$1,"
(2:15-2:18) " a2" --> (6:17-6:19) " a"
(2:12-2:15) "(a," --> (6:12-6:15) "(a,"
(2:15-2:18) " a2" --> (6:15-6:19) " a$1"
(2:18-3:1) ")\n" --> (6:19-8:34) ");\n\n//# sourceMappingURL=main.mjs.map"
6 changes: 3 additions & 3 deletions crates/rolldown/tests/fixtures/basic_re_export/artifacts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ input_file: crates/rolldown/tests/fixtures/basic_re_export

```js
// a.js
const a = 'a.js';
const a$1 = 'a.js';
// main.js
const a$1 = 'index.js';
console.log(a$1, a);
const a = 'index.js';
console.log(a, a$1);
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ input_file: crates/rolldown/tests/fixtures/deconflict/basic
import { default as assert } from "assert";
// a.js
const a = 'a.js';
const a$1 = 'a.js';
// main.js
const a$1 = 'main.js';
assert.equal(a$1, 'main.js');
assert.equal(a, 'a.js');
const a = 'main.js';
assert.equal(a, 'main.js');
assert.equal(a$1, 'a.js');
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ input_file: crates/rolldown/tests/fixtures/deconflict/basic_scoped
import { default as assert } from "assert";
// a.js
const a = 'a.js';
const a$1 = 'a.js';
// main.js
const a$1 = 'main.js';
const a = 'main.js';
function foo(a$1$1) {
return [a$1$1, a$1, a];
return [a$1$1, a, a$1];
}
assert.deepEqual(foo('foo'), ['foo', 'main.js', 'a.js']);
Expand All @@ -27,21 +27,21 @@ assert.deepEqual(foo('foo'), ['foo', 'main.js', 'a.js']);

- ../a.js
(0:0-0:6) "const " --> (3:0-3:6) "\nconst"
(0:6-0:10) "a = " --> (3:6-3:10) " a ="
(0:10-3:1) "'a.js'\n\nexport { a }\n" --> (3:10-6:0) " 'a.js';\n\n// main.js"
(0:6-0:10) "a = " --> (3:6-3:12) " a$1 ="
(0:10-3:1) "'a.js'\n\nexport { a }\n" --> (3:12-6:0) " 'a.js';\n\n// main.js"
- ../main.js
(2:0-2:6) "\nconst" --> (6:0-6:6) "\nconst"
(2:6-2:10) " a =" --> (6:6-6:12) " a$1 ="
(2:10-5:0) " 'main.js'\n\n" --> (6:12-7:0) " 'main.js';"
(2:6-2:10) " a =" --> (6:6-6:10) " a ="
(2:10-5:0) " 'main.js'\n\n" --> (6:10-7:0) " 'main.js';"
(5:0-5:9) "\nfunction" --> (7:0-7:9) "\nfunction"
(5:9-5:13) " foo" --> (7:9-7:13) " foo"
(5:13-5:18) "(a$1)" --> (7:13-7:20) "(a$1$1)"
(5:18-6:2) " {\n " --> (7:20-8:0) " {"
(6:2-6:9) " return" --> (8:0-8:8) "\n\treturn"
(6:9-6:10) " " --> (8:8-8:9) " "
(6:10-6:15) "[a$1," --> (8:9-8:16) "[a$1$1,"
(6:15-6:18) " a," --> (8:16-8:21) " a$1,"
(6:18-6:22) " aJs" --> (8:21-8:23) " a"
(6:15-6:18) " a," --> (8:16-8:19) " a,"
(6:18-6:22) " aJs" --> (8:19-8:23) " a$1"
(6:22-7:1) "]\n" --> (8:23-9:0) "];"
(7:1-9:0) "}\n" --> (9:0-10:0) "\n}"
(9:0-9:7) "\nassert" --> (10:0-10:7) "\nassert"
Expand Down

0 comments on commit 6714dd0

Please sign in to comment.