Skip to content

Commit

Permalink
feat: support tree shaking
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf0 committed Feb 20, 2024
1 parent d9ccdb7 commit 6e32999
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 166 deletions.
10 changes: 9 additions & 1 deletion crates/rolldown/src/bundler/ast_scanner/impl_visit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::sync::Arc;

use oxc::ast::{ast::IdentifierReference, Visit};
use oxc::{
ast::{ast::IdentifierReference, Visit},
codegen::{self, Codegen, CodegenOptions, Gen},
};
use rolldown_common::ImportKind;
use rolldown_error::BuildError;

Expand All @@ -19,6 +22,11 @@ impl<'ast> Visit<'ast> for AstScanner<'ast> {
fn visit_program(&mut self, program: &oxc::ast::ast::Program<'ast>) {
for (idx, stmt) in program.body.iter().enumerate() {
self.current_stmt_info.stmt_idx = Some(idx);
if cfg!(debug_assertions) {
let mut codegen = Codegen::<false>::new(0, CodegenOptions);
stmt.gen(&mut codegen, codegen::Context::default());
self.current_stmt_info.debug_label = Some(codegen.into_code());
}
self.visit_top_level_stmt(stmt);
self.result.stmt_infos.add_stmt_info(std::mem::take(&mut self.current_stmt_info));
}
Expand Down
74 changes: 57 additions & 17 deletions crates/rolldown/src/bundler/ast_scanner/side_effect_detector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, ops::Deref};

use oxc::{
ast::ast::{IdentifierReference, MemberExpression},
Expand Down Expand Up @@ -55,13 +55,15 @@ impl<'a> SideEffectDetector<'a> {
ClassElement::MethodDefinition(_) => false,
ClassElement::PropertyDefinition(def) => {
(match &def.key {
// FIXME: this is wrong, we should also always check the `def.value`.
oxc::ast::ast::PropertyKey::Identifier(_)
| oxc::ast::ast::PropertyKey::PrivateIdentifier(_) => false,
oxc::ast::ast::PropertyKey::Expression(expr) => self.detect_side_effect_of_expr(expr),
} || def.value.as_ref().is_some_and(|init| self.detect_side_effect_of_expr(init)))
}
ClassElement::AccessorProperty(def) => {
(match &def.key {
// FIXME: this is wrong, we should also always check the `def.value`.
oxc::ast::ast::PropertyKey::Identifier(_)
| oxc::ast::ast::PropertyKey::PrivateIdentifier(_) => false,
oxc::ast::ast::PropertyKey::Expression(expr) => self.detect_side_effect_of_expr(expr),
Expand Down Expand Up @@ -125,26 +127,64 @@ impl<'a> SideEffectDetector<'a> {
}
}

fn detect_side_effect_of_decl(&self, decl: &oxc::ast::ast::Declaration) -> bool {
use oxc::ast::ast::Declaration;
match decl {
Declaration::VariableDeclaration(var_decl) => var_decl
.declarations
.iter()
.any(|decl| decl.init.as_ref().is_some_and(|init| self.detect_side_effect_of_expr(init))),
Declaration::FunctionDeclaration(_) => false,
Declaration::ClassDeclaration(cls_decl) => self.detect_side_effect_of_class(cls_decl),
Declaration::UsingDeclaration(_) => todo!(),
Declaration::TSTypeAliasDeclaration(_)
| Declaration::TSInterfaceDeclaration(_)
| Declaration::TSEnumDeclaration(_)
| Declaration::TSModuleDeclaration(_)
| Declaration::TSImportEqualsDeclaration(_) => unreachable!("ts should be transpiled"),
}
}

pub fn detect_side_effect_of_stmt(&self, stmt: &oxc::ast::ast::Statement) -> bool {
use oxc::ast::ast::{Declaration, Statement};
use oxc::ast::ast::Statement;
match stmt {
Statement::Declaration(decl) => match decl {
Declaration::VariableDeclaration(var_decl) => var_decl
.declarations
.iter()
.any(|decl| decl.init.as_ref().is_some_and(|init| self.detect_side_effect_of_expr(init))),
Declaration::FunctionDeclaration(_) => false,
Declaration::ClassDeclaration(cls_decl) => self.detect_side_effect_of_class(cls_decl),
Declaration::UsingDeclaration(_) => todo!(),
Declaration::TSTypeAliasDeclaration(_)
| Declaration::TSInterfaceDeclaration(_)
| Declaration::TSEnumDeclaration(_)
| Declaration::TSModuleDeclaration(_)
| Declaration::TSImportEqualsDeclaration(_) => unreachable!("ts should be transpiled"),
},
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.deref() {
oxc::ast::ast::ModuleDeclaration::ImportDeclaration(_)
| oxc::ast::ast::ModuleDeclaration::ExportAllDeclaration(_) => true,
oxc::ast::ast::ModuleDeclaration::ExportDefaultDeclaration(default_decl) => {
match &default_decl.declaration {
oxc::ast::ast::ExportDefaultDeclarationKind::Expression(expr) => {
self.detect_side_effect_of_expr(expr)
}
oxc::ast::ast::ExportDefaultDeclarationKind::FunctionDeclaration(_) => false,
oxc::ast::ast::ExportDefaultDeclarationKind::ClassDeclaration(decl) => {
self.detect_side_effect_of_class(decl)
}
oxc::ast::ast::ExportDefaultDeclarationKind::TSInterfaceDeclaration(_)
| oxc::ast::ast::ExportDefaultDeclarationKind::TSEnumDeclaration(_) => {
unreachable!("ts should be transpiled")
}
}
}
oxc::ast::ast::ModuleDeclaration::ExportNamedDeclaration(named_decl) => {
if named_decl.source.is_some() {
// `export { ... } from '...'` is considered as side effect.
true
} else {
named_decl
.declaration
.as_ref()
.map_or(false, |decl| self.detect_side_effect_of_decl(decl))
}
}
oxc::ast::ast::ModuleDeclaration::TSExportAssignment(_)
| oxc::ast::ast::ModuleDeclaration::TSNamespaceExportDeclaration(_) => {
unreachable!("ts should be transpiled")
}
},
Statement::BlockStatement(_)
| Statement::ModuleDeclaration(_)
| Statement::BreakStatement(_)
| Statement::DebuggerStatement(_)
| Statement::DoWhileStatement(_)
Expand Down
2 changes: 1 addition & 1 deletion crates/rolldown/src/bundler/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl<T: FileSystem + Default + 'static> Bundler<T> {
async fn try_build(&mut self) -> BatchedResult<LinkStageOutput> {
let build_info = self.scan_inner().await?;

let link_stage = LinkStage::new(build_info);
let link_stage = LinkStage::new(build_info, &self.input_options);

Ok(link_stage.link())
}
Expand Down
226 changes: 118 additions & 108 deletions crates/rolldown/src/bundler/finalizer/impl_visit_mut_for_finalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,144 +29,154 @@ impl<'ast, 'me: 'ast> VisitMut<'ast> for Finalizer<'me, 'ast> {
program.body.extend(self.generate_namespace_variable_declaration());
}

old_body.into_iter().for_each(|mut top_stmt| {
if let Some(import_decl) = top_stmt.as_import_declaration() {
let rec_id = self.ctx.module.imports[&import_decl.span];
if self.should_remove_import_export_stmt(&mut top_stmt, rec_id) {
let mut stmt_infos = self.ctx.module.stmt_infos.iter();
stmt_infos.next();

old_body.into_iter().enumerate().zip(stmt_infos).for_each(
|((_top_stmt_idx, mut top_stmt), stmt_info)| {
debug_assert!(matches!(stmt_info.stmt_idx, Some(_top_stmt_idx)));
if !stmt_info.is_included {
return;
}
} else if let Some(export_all_decl) = top_stmt.as_export_all_declaration() {
let rec_id = self.ctx.module.imports[&export_all_decl.span];
// "export * as ns from 'path'"
if let Some(_alias) = &export_all_decl.exported {

if let Some(import_decl) = top_stmt.as_import_declaration() {
let rec_id = self.ctx.module.imports[&import_decl.span];
if self.should_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
}
} else {
// "export * from 'path'"
let rec = &self.ctx.module.import_records[rec_id];
let importee_id = rec.resolved_module;
let importee_linking_info = &self.ctx.linking_infos[importee_id];
let importee = &self.ctx.modules[importee_id];
if matches!(importee_linking_info.wrap_kind, WrapKind::Esm) {
let wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
program.body.push(self.snippet.call_expr_stmt(wrapper_ref_name.clone()));
}
} else if let Some(export_all_decl) = top_stmt.as_export_all_declaration() {
let rec_id = self.ctx.module.imports[&export_all_decl.span];
// "export * as ns from 'path'"
if let Some(_alias) = &export_all_decl.exported {
if self.should_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
}
} else {
// "export * from 'path'"
let rec = &self.ctx.module.import_records[rec_id];
let importee_id = rec.resolved_module;
let importee_linking_info = &self.ctx.linking_infos[importee_id];
let importee = &self.ctx.modules[importee_id];
if matches!(importee_linking_info.wrap_kind, WrapKind::Esm) {
let wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
program.body.push(self.snippet.call_expr_stmt(wrapper_ref_name.clone()));
}

match importee {
Module::Normal(importee) => {
match importee.exports_kind {
ExportsKind::Esm => {
if importee_linking_info.has_dynamic_exports {
match importee {
Module::Normal(importee) => {
match importee.exports_kind {
ExportsKind::Esm => {
if importee_linking_info.has_dynamic_exports {
let re_export_fn_name = self.canonical_name_for_runtime("__reExport");
let importer_namespace_name =
self.canonical_name_for(self.ctx.module.namespace_symbol);
// __reExport(exports, otherExports)
let importee_namespace_name =
self.canonical_name_for(importee.namespace_symbol);
program.body.push(
self
.snippet
.call_expr_with_2arg_expr(
re_export_fn_name.clone(),
importer_namespace_name.clone(),
importee_namespace_name.clone(),
)
.into_in(self.alloc),
);
}
}
ExportsKind::CommonJs => {
let re_export_fn_name = self.canonical_name_for_runtime("__reExport");
let importer_namespace_name =
self.canonical_name_for(self.ctx.module.namespace_symbol);
// __reExport(exports, otherExports)
let importee_namespace_name =
self.canonical_name_for(importee.namespace_symbol);
// __reExport(exports, __toESM(require_xxxx()))
let to_esm_fn_name = self.canonical_name_for_runtime("__toESM");
let importee_wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
program.body.push(
self
.snippet
.call_expr_with_2arg_expr(
.call_expr_with_2arg_expr_expr(
re_export_fn_name.clone(),
importer_namespace_name.clone(),
importee_namespace_name.clone(),
self.snippet.id_ref_expr(importer_namespace_name.clone()),
self.snippet.call_expr_with_arg_expr_expr(
to_esm_fn_name.clone(),
self.snippet.call_expr_expr(importee_wrapper_ref_name.clone()),
),
)
.into_in(self.alloc),
);
}
ExportsKind::None => {}
}
ExportsKind::CommonJs => {
let re_export_fn_name = self.canonical_name_for_runtime("__reExport");
let importer_namespace_name =
self.canonical_name_for(self.ctx.module.namespace_symbol);
// __reExport(exports, __toESM(require_xxxx()))
let to_esm_fn_name = self.canonical_name_for_runtime("__toESM");
let importee_wrapper_ref_name =
self.canonical_name_for(importee_linking_info.wrapper_ref.unwrap());
program.body.push(
self
.snippet
.call_expr_with_2arg_expr_expr(
re_export_fn_name.clone(),
self.snippet.id_ref_expr(importer_namespace_name.clone()),
self.snippet.call_expr_with_arg_expr_expr(
to_esm_fn_name.clone(),
self.snippet.call_expr_expr(importee_wrapper_ref_name.clone()),
),
)
.into_in(self.alloc),
);
}
ExportsKind::None => {}
}
Module::External(_) => {}
}
Module::External(_) => {}
}
// TODO handle this
return;
}
} else if let Some(default_decl) = top_stmt.as_export_default_declaration_mut() {
match &mut default_decl.declaration {
ast::ExportDefaultDeclarationKind::Expression(expr) => {
// "export default foo;" => "var default = foo;"
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
top_stmt = self.snippet.var_decl_stmt(
canonical_name_for_default_export_ref.clone(),
expr.take_in(self.alloc),
);
// TODO handle this
return;
}
ast::ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
// "export default function() {}" => "function default() {}"
// "export default function foo() {}" => "function foo() {}"
if func.id.is_none() {
} else if let Some(default_decl) = top_stmt.as_export_default_declaration_mut() {
match &mut default_decl.declaration {
ast::ExportDefaultDeclarationKind::Expression(expr) => {
// "export default foo;" => "var default = foo;"
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
func.id = Some(self.snippet.id(canonical_name_for_default_export_ref.clone()));
top_stmt = self.snippet.var_decl_stmt(
canonical_name_for_default_export_ref.clone(),
expr.take_in(self.alloc),
);
}
top_stmt = ast::Statement::Declaration(ast::Declaration::FunctionDeclaration(
func.take_in(self.alloc),
));
}
ast::ExportDefaultDeclarationKind::ClassDeclaration(class) => {
// "export default class {}" => "class default {}"
// "export default class Foo {}" => "class Foo {}"
if class.id.is_none() {
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
class.id = Some(self.snippet.id(canonical_name_for_default_export_ref.clone()));
ast::ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
// "export default function() {}" => "function default() {}"
// "export default function foo() {}" => "function foo() {}"
if func.id.is_none() {
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
func.id = Some(self.snippet.id(canonical_name_for_default_export_ref.clone()));
}
top_stmt = ast::Statement::Declaration(ast::Declaration::FunctionDeclaration(
func.take_in(self.alloc),
));
}
top_stmt = ast::Statement::Declaration(ast::Declaration::ClassDeclaration(
class.take_in(self.alloc),
));
ast::ExportDefaultDeclarationKind::ClassDeclaration(class) => {
// "export default class {}" => "class default {}"
// "export default class Foo {}" => "class Foo {}"
if class.id.is_none() {
let canonical_name_for_default_export_ref =
self.canonical_name_for(self.ctx.module.default_export_ref);
class.id = Some(self.snippet.id(canonical_name_for_default_export_ref.clone()));
}
top_stmt = ast::Statement::Declaration(ast::Declaration::ClassDeclaration(
class.take_in(self.alloc),
));
}
_ => {}
}
_ => {}
}
} else if let Some(named_decl) = top_stmt.as_export_named_declaration_mut() {
if named_decl.source.is_none() {
if let Some(decl) = &mut named_decl.declaration {
// `export var foo = 1` => `var foo = 1`
// `export function foo() {}` => `function foo() {}`
// `export class Foo {}` => `class Foo {}`
top_stmt = ast::Statement::Declaration(decl.take_in(self.alloc));
} else if let Some(named_decl) = top_stmt.as_export_named_declaration_mut() {
if named_decl.source.is_none() {
if let Some(decl) = &mut named_decl.declaration {
// `export var foo = 1` => `var foo = 1`
// `export function foo() {}` => `function foo() {}`
// `export class Foo {}` => `class Foo {}`
top_stmt = ast::Statement::Declaration(decl.take_in(self.alloc));
} else {
// `export { foo }`
// Remove this statement by ignoring it
return;
}
} else {
// `export { foo }`
// Remove this statement by ignoring it
return;
}
} else {
// `export { foo } from 'path'`
let rec_id = self.ctx.module.imports[&named_decl.span];
if self.should_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
// `export { foo } from 'path'`
let rec_id = self.ctx.module.imports[&named_decl.span];
if self.should_remove_import_export_stmt(&mut top_stmt, rec_id) {
return;
}
}
}
}

program.body.push(top_stmt);
});
program.body.push(top_stmt);
},
);

// visit children
for directive in program.directives.iter_mut() {
Expand Down

0 comments on commit 6e32999

Please sign in to comment.