Skip to content

Commit

Permalink
feat: generate the namespace variable on demand
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf0 committed Feb 18, 2024
1 parent f09f5aa commit 44d5aa8
Show file tree
Hide file tree
Showing 150 changed files with 221 additions and 715 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ impl<'a> SideEffectDetector<'a> {
},
Statement::ExpressionStatement(expr) => self.detect_side_effect_of_expr(&expr.expression),
Statement::BlockStatement(_)
| Statement::ModuleDeclaration(_)
| Statement::BreakStatement(_)
| Statement::DebuggerStatement(_)
| Statement::DoWhileStatement(_)
Expand All @@ -159,7 +160,6 @@ impl<'a> SideEffectDetector<'a> {
| Statement::TryStatement(_)
| Statement::WhileStatement(_)
| Statement::WithStatement(_)
| Statement::ModuleDeclaration(_)
| Statement::ContinueStatement(_) => true,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ impl<'ast, 'me: 'ast> VisitMut<'ast> for Finalizer<'me, 'ast> {
fn visit_program(&mut self, program: &mut ast::Program<'ast>) {
let old_body = program.body.take_in(self.alloc);
let is_namespace_referenced = matches!(self.ctx.module.exports_kind, ExportsKind::Esm)
&& self.ctx.module.id != self.ctx.runtime.id();
&& self.ctx.module.stmt_infos[0].is_included;

if is_namespace_referenced {
program.body.extend(self.generate_namespace_variable_declaration());
}
Expand Down
1 change: 1 addition & 0 deletions crates/rolldown/src/bundler/module/normal_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct NormalModule {
pub scope: AstScope,
pub default_export_ref: SymbolRef,
pub sourcemap_chain: Vec<rolldown_sourcemap::SourceMap>,
pub is_included: bool,
}

impl NormalModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ impl NormalModuleBuilder {
is_user_defined_entry: self.is_user_defined_entry.unwrap(),
pretty_path: self.pretty_path.unwrap(),
sourcemap_chain: self.sourcemap_chain,
is_included: false,
}
}
}
12 changes: 10 additions & 2 deletions crates/rolldown/src/bundler/stages/bundle_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,16 @@ impl<'a> BundleStage<'a> {
let chunk_meta_imports = &chunk_meta_imports_vec[chunk_id];
for import_ref in chunk_meta_imports.iter().copied() {
let import_symbol = self.link_output.symbols.get(import_ref);
let importee_chunk_id =
import_symbol.chunk_id.expect("Symbol should be declared in a chunk");

let importee_chunk_id = import_symbol.chunk_id.unwrap_or_else(|| {
let symbol_owner = &self.link_output.modules[import_ref.owner];
let symbol_name = self.link_output.symbols.get_original_name(import_ref);
panic!(
"Symbol {:?} in {:?} should belong to a chunk",
symbol_name,
symbol_owner.resource_id()
)
});
// Find out the import_ref whether comes from the chunk or external module.
if chunk_id != importee_chunk_id {
chunk
Expand Down
36 changes: 22 additions & 14 deletions crates/rolldown/src/bundler/stages/link_stage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use crate::bundler::{

use super::scan_stage::ScanStageOutput;

mod tree_shaking;

#[derive(Debug)]
pub struct LinkStageOutput {
pub modules: ModuleVec,
Expand Down Expand Up @@ -66,17 +68,25 @@ impl LinkStage {
return;
};

// Create a StmtInfo for the namespace statement
let namespace_stmt_info = StmtInfo {
stmt_idx: None,
declared_symbols: vec![module.namespace_symbol],
referenced_symbols: vec![self.runtime.resolve_symbol("__export")],
side_effect: false,
is_included: true,
import_records: Vec::new(),
};
if matches!(module.exports_kind, ExportsKind::Esm) {
let linking_info = &self.linking_infos[module.id];

// Create a StmtInfo for the namespace statement
let namespace_stmt_info = StmtInfo {
stmt_idx: None,
declared_symbols: vec![module.namespace_symbol],
referenced_symbols: linking_info
.sorted_exports()
.map(|(_, export)| export.symbol_ref)
.chain([self.runtime.resolve_symbol("__export")])
.collect(),
side_effect: false,
is_included: false,
import_records: Vec::new(),
};

let _namespace_stmt_id = module.stmt_infos.add_stmt_info(namespace_stmt_info);
module.stmt_infos.replace_namespace_stmt_info(namespace_stmt_info);
}

// We don't create actual ast nodes for the namespace statement here. It will be deferred
// to the finalize stage.
Expand All @@ -103,6 +113,7 @@ impl LinkStage {
}
});
}
self.include_statements();
LinkStageOutput {
modules: self.modules,
entries: self.entries,
Expand Down Expand Up @@ -331,10 +342,7 @@ impl LinkStage {
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"));
let Module::Normal(importee) = &self.modules[importee_id] else {
unreachable!("importee should be a normal module")
};
stmt_info.referenced_symbols.push(importee.namespace_symbol);
stmt_info.referenced_symbols.push(importer.namespace_symbol);
} else {
// something like `var import_foo = __toESM(require_foo())`
// Reference to `require_foo`
Expand Down
152 changes: 152 additions & 0 deletions crates/rolldown/src/bundler/stages/link_stage/tree_shaking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use index_vec::IndexVec;
use rolldown_common::{ModuleId, StmtInfoId, SymbolRef};

use crate::bundler::{
module::{Module, ModuleVec, NormalModule},
utils::symbols::Symbols,
};

use super::LinkStage;

struct Context<'a> {
modules: &'a ModuleVec,
symbols: &'a Symbols,
is_included_vec: &'a mut IndexVec<ModuleId, IndexVec<StmtInfoId, bool>>,
is_module_included_vec: &'a mut IndexVec<ModuleId, bool>,
}

fn include_module(ctx: &mut Context, module: &NormalModule) {
let is_included = ctx.is_module_included_vec[module.id];
if is_included {
return;
}

ctx.is_module_included_vec[module.id] = true;

module.stmt_infos.iter_enumerated().for_each(|(stmt_info_id, stmt_info)| {
if stmt_info.side_effect {
include_statement(ctx, module, stmt_info_id);
}
});

module.import_records.iter().for_each(|import_record| {
let importee = &ctx.modules[import_record.resolved_module];
if let Module::Normal(importee) = importee {
include_module(ctx, importee);
}
});
}

fn include_symbol(ctx: &mut Context, symbol_ref: SymbolRef) {
let mut canonical_ref = ctx.symbols.par_canonical_ref_for(symbol_ref);
let canonical_ref_module = &ctx.modules[canonical_ref.owner];
let canonical_ref_symbol = ctx.symbols.get(canonical_ref);
if let Some(namespace_alias) = &canonical_ref_symbol.namespace_alias {
canonical_ref = namespace_alias.namespace_ref;
}
let Module::Normal(canonical_ref_module) = canonical_ref_module else {
return;
};
include_module(ctx, canonical_ref_module);
canonical_ref_module
.stmt_infos
.declared_stmts_by_symbol(&canonical_ref)
.iter()
.copied()
.for_each(|stmt_info_id| {
include_statement(ctx, canonical_ref_module, stmt_info_id);
});
}

fn include_statement(ctx: &mut Context, module: &NormalModule, stmt_info_id: StmtInfoId) {
let is_included = &mut ctx.is_included_vec[module.id][stmt_info_id];
if *is_included {
return;
}

// include the statement itself
*is_included = true;

let stmt_info = module.stmt_infos.get(stmt_info_id);

// include statements that are referenced by this statement
stmt_info.declared_symbols.iter().chain(stmt_info.referenced_symbols.iter()).for_each(
|symbol_ref| {
include_symbol(ctx, *symbol_ref);
},
);
}

impl LinkStage {
pub fn include_statements(&mut self) {
use rayon::prelude::*;

let mut is_included_vec: IndexVec<ModuleId, IndexVec<StmtInfoId, bool>> = self
.modules
.iter()
.map(|m| match m {
Module::Normal(m) => {
m.stmt_infos.iter().map(|_| false).collect::<IndexVec<StmtInfoId, _>>()
}
Module::External(_) => IndexVec::default(),
})
.collect::<IndexVec<ModuleId, _>>();

let mut is_module_included_vec: IndexVec<ModuleId, bool> =
index_vec::index_vec![false; self.modules.len()];

let context = &mut Context {
modules: &self.modules,
symbols: &self.symbols,
is_included_vec: &mut is_included_vec,
is_module_included_vec: &mut is_module_included_vec,
};

for module in &self.modules {
match module {
Module::Normal(module) => {
let mut stmt_infos = module.stmt_infos.iter_enumerated();
// Skip the first one, because it's the namespace variable declaration.
// We want to include it on demand.
stmt_infos.next();
stmt_infos.for_each(|(stmt_info_id, stmt_info)| {
if stmt_info.side_effect {
include_statement(context, module, stmt_info_id);
}
});
if module.is_user_defined_entry {
let linking_info = &self.linking_infos[module.id];
linking_info.resolved_exports.values().for_each(|resolved_export| {
include_symbol(context, resolved_export.symbol_ref);
});
}
}
Module::External(_) => {}
}
}

self.entries.iter().for_each(|entry| {
let module = &self.modules[entry.id];
let Module::Normal(module) = module else {
return;
};

include_module(context, module);

let linking_info = &self.linking_infos[module.id];
linking_info.resolved_exports.values().for_each(|resolved_export| {
include_symbol(context, resolved_export.symbol_ref);
});
});

self.modules.iter_mut().par_bridge().for_each(|module| {
let Module::Normal(module) = module else {
return;
};
module.is_included = is_module_included_vec[module.id];
is_included_vec[module.id].iter_enumerated().for_each(|(stmt_info_id, is_included)| {
module.stmt_infos.get_mut(stmt_info_id).is_included = *is_included;
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,15 @@ input_file: crates/rolldown/tests/esbuild/default/ambiguous_reexport_msg
import { __export } from "./_rolldown_runtime.mjs";
// a.js
var a_ns = {};
__export(a_ns, {
a:() => a,
x:() => x$1
});
let a = 1, x$1 = 2;
// b.js
var b_ns = {};
__export(b_ns, {
b:() => b,
x:() => b
});
let b = 3;
// c.js
var c_ns = {};
__export(c_ns, {
c:() => c,
x:() => x
});
let c = 4, x = 5;
// entry.js
var entry_ns = {};
__export(entry_ns, {
a:() => a,
b:() => b,
c:() => c
});
export { a, b, c };
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ input_file: crates/rolldown/tests/esbuild/default/arrow_fn_scope
## entry_js.mjs

```js
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
tests = {
0:(x$1=y$2 => x$1 + y$2, y$1) => x$1 + y$1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ import "data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgMTIz";
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
var entry_ns = {};
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ input_file: crates/rolldown/tests/esbuild/default/avoid_tdz
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
var entry_ns = {};
__export(entry_ns, {
Bar:() => Bar,
bar:() => bar
});
class Foo {
static foo=new Foo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ input_file: crates/rolldown/tests/esbuild/default/await_import_inside_try
## entry_js.mjs

```js
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
async function main(name) {
try{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ input_file: crates/rolldown/tests/esbuild/default/const_with_let
## entry_js.mjs

```js
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
const a = 1;
console.log(a);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ input_file: crates/rolldown/tests/esbuild/default/duplicate_entry_point
## entry_js-3.mjs

```js
import { __export } from "./_rolldown_runtime.mjs";
// entry.js
console.log(123);
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ var require_bar = __commonJSMin((exports, module) => {
});
// entry.js
var entry_ns = {};
var import_foo = __toESM(require_foo());
var import_bar = __toESM(require_bar());
console.log((0,import_foo.foo)(), (0,import_bar.bar)());
Expand Down

0 comments on commit 44d5aa8

Please sign in to comment.