Skip to content

Commit

Permalink
feat: runtime symbol deconflict (#54)
Browse files Browse the repository at this point in the history
* feat: create runtime symbol reference at linker

* chore: rename fn

* refactor: deconflict runtime symbol and module wrap symbol

* chore: fix lint

* chore: remove unused change

* fix: add test

* chore: remove refernce

* Improve naming (#59)

---------

Co-authored-by: Yunfei He <i.heyunfei@gmail.com>
  • Loading branch information
underfin and hyf0 committed Oct 16, 2023
1 parent 76e2c08 commit 0fc1d3a
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 47 deletions.
95 changes: 73 additions & 22 deletions crates/rolldown/src/bundler/graph/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ impl<'graph> Linker<'graph> {
}

pub fn link(&mut self) {
// Create symbols for wrapped module
self.mark_modules_wrap();
self.wrap_modules_if_needed();

// propagate star exports
for id in &self.graph.sorted_modules {
Expand Down Expand Up @@ -53,47 +52,99 @@ impl<'graph> Linker<'graph> {
})
}

fn mark_modules_wrap(&mut self) {
let mut modules_wrap = index_vec::index_vec![
fn wrap_modules_if_needed(&mut self) {
let mut module_to_wrapped = index_vec::index_vec![
false;
self.graph.modules.len()
];

for module_id in &self.graph.sorted_modules {
match &self.graph.modules[*module_id] {
Module::Normal(m) => {
if m.module_resolution == ModuleResolution::CommonJs {
mark_module_wrap(self.graph, *module_id, &mut modules_wrap);
for module in &self.graph.modules {
match module {
Module::Normal(module) => {
if module.module_resolution == ModuleResolution::CommonJs {
wrap_module(self.graph, module.id, &mut module_to_wrapped);
}
}
Module::External(_) => {}
}
}

for (module_id, wrap) in modules_wrap.into_iter_enumerated() {
if wrap {
for (module_id, wrapped) in module_to_wrapped.into_iter_enumerated() {
if wrapped {
match &mut self.graph.modules[module_id] {
Module::Normal(m) => {
m.add_wrap_symbol(&mut self.graph.symbols);
Module::Normal(module) => {
module.create_wrap_symbol(&mut self.graph.symbols);
let name = if module.module_resolution == ModuleResolution::CommonJs {
"__commonJS".into()
} else {
"__esm".into()
};
let runtime_symbol = self.graph.runtime.resolve_symbol(&name);
module.generate_symbol_import_and_use(&mut self.graph.symbols, runtime_symbol);
}
Module::External(_) => {}
};
}
}

let mut imported_symbols = vec![];

for module in &self.graph.modules {
match module {
Module::Normal(importer) => {
importer.import_records.iter().for_each(|r| {
let importee = &self.graph.modules[r.resolved_module];
let Module::Normal(importee) = importee else { return };

if let Some(importee_warp_symbol) = importee.wrap_symbol {
imported_symbols.push((importer.id, importee_warp_symbol));
}

match (importer.module_resolution, importee.module_resolution) {
(ModuleResolution::Esm, ModuleResolution::CommonJs) => {
imported_symbols.push((
importer.id,
self.graph.runtime.resolve_symbol(&"__toESM".into()),
));
}
(ModuleResolution::CommonJs, ModuleResolution::Esm) => {
imported_symbols.push((
importer.id,
self.graph.runtime.resolve_symbol(&"__toCommonJS".into()),
));
}
_ => {}
}
});
}
Module::External(_) => {}
}
}

fn mark_module_wrap(
for (module, symbol) in imported_symbols.into_iter() {
let importer = &mut self.graph.modules[module];
match importer {
Module::Normal(importer) => {
importer.generate_symbol_import_and_use(&mut self.graph.symbols, symbol);
}
Module::External(_) => {}
}
}

fn wrap_module(
graph: &Graph,
module_id: ModuleId,
modules_wrap: &mut IndexVec<ModuleId, bool>,
target: ModuleId,
module_to_wrapped: &mut IndexVec<ModuleId, bool>,
) {
match &graph.modules[module_id] {
if module_to_wrapped[target] {
return;
}

match &graph.modules[target] {
Module::Normal(module) => {
if modules_wrap[module_id] {
return;
}
modules_wrap[module_id] = true;
module_to_wrapped[target] = true;
module.import_records.iter().for_each(|record| {
mark_module_wrap(graph, record.resolved_module, modules_wrap);
wrap_module(graph, record.resolved_module, module_to_wrapped);
});
}
Module::External(_) => {}
Expand Down
30 changes: 26 additions & 4 deletions crates/rolldown/src/bundler/module/normal_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub struct NormalModule {
pub namespace_symbol: (SymbolRef, ReferenceId),
pub is_symbol_for_namespace_referenced: bool,
pub cjs_symbols: FxHashMap<Atom, SymbolRef>,
pub wrap_symbol: Option<SymbolId>,
pub wrap_symbol: Option<SymbolRef>,
}

pub enum Resolution {
Expand Down Expand Up @@ -298,7 +298,7 @@ impl NormalModule {
}
}

pub fn add_wrap_symbol(&mut self, symbols: &mut Symbols) {
pub fn create_wrap_symbol(&mut self, symbols: &mut Symbols) {
if self.wrap_symbol.is_none() {
let name = format!(
"{}_{}",
Expand All @@ -310,12 +310,34 @@ impl NormalModule {
self.resource_id.generate_unique_name()
)
.into();
self.wrap_symbol = Some(symbols.tables[self.id].create_symbol(name));
let symbol = symbols.tables[self.id].create_symbol(name);
self.wrap_symbol = Some((self.id, symbol).into());
self.stmt_infos.push(StmtInfo {
stmt_idx: self.ast.program().body.len(),
declared_symbols: vec![self.wrap_symbol.unwrap()],
declared_symbols: vec![symbol],
});
self.initialize_namespace();
}
}

pub fn generate_symbol_import_and_use(
&mut self,
symbols: &mut Symbols,
symbol_ref_from_importee: SymbolRef,
) {
debug_assert!(symbol_ref_from_importee.owner != self.id);
let name = symbols.get_original_name(symbol_ref_from_importee).clone();
let local_symbol = symbols.tables[self.id].create_symbol(name);
let local_symbol_ref = (self.id, local_symbol).into();
symbols.union(local_symbol_ref, symbol_ref_from_importee);
// TODO: we should add corresponding dependency info from the runtime module
// for the future tree shaking support
self.stmt_infos.push(StmtInfo {
stmt_idx: self.ast.program().body.len(),
// FIXME: should store the symbol in `used_symbols` instead of `declared_symbols`.
// The deconflict for runtime symbols would be handled in the deconflict on cross-chunk-imported
// symbols
declared_symbols: vec![local_symbol],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl<'ast> CommonJsSourceRender<'ast> {
self.visit_program(program);
let wrap_symbol_name = self.ctx.wrap_symbol_name.unwrap();
let module_path = self.ctx.module.resource_id.prettify();
let commonjs_runtime_symbol_name = self.ctx.get_runtime_symbol_final_name("__commonJS");
let commonjs_runtime_symbol_name = self.ctx.get_runtime_symbol_final_name("__commonJS".into());
self.ctx.source.prepend(format!(
"var {wrap_symbol_name} = {commonjs_runtime_symbol_name}({{\n'{module_path}'(exports, module) {{\n",
));
Expand All @@ -37,7 +37,7 @@ impl<'ast> Visit<'ast> for CommonJsSourceRender<'ast> {
if let Module::Normal(importee) = importee {
let wrap_symbol_name = self
.ctx
.get_symbol_final_name((importee.id, importee.wrap_symbol.unwrap()).into())
.get_symbol_final_name(importee.wrap_symbol.unwrap())
.unwrap();
if importee.module_resolution == ModuleResolution::CommonJs {
self.ctx.source.update(
Expand All @@ -50,8 +50,9 @@ impl<'ast> Visit<'ast> for CommonJsSourceRender<'ast> {
.ctx
.get_symbol_final_name((importee.id, importee.namespace_symbol.0.symbol).into())
.unwrap();
let to_commonjs_runtime_symbol_name =
self.ctx.get_runtime_symbol_final_name("__toCommonJS");
let to_commonjs_runtime_symbol_name = self
.ctx
.get_runtime_symbol_final_name("__toCommonJS".into());
self.ctx.source.update(
expr.span.start,
expr.span.end,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl<'ast> EsmWrapSourceRender<'ast> {
self.visit_program(program);
let namespace_name = self.ctx.namespace_symbol_name.unwrap();
let wrap_symbol_name = self.ctx.wrap_symbol_name.unwrap();
let esm_runtime_symbol_name = self.ctx.get_runtime_symbol_final_name("__esm");
let esm_runtime_symbol_name = self.ctx.get_runtime_symbol_final_name("__esm".into());
self.ctx.source.prepend(format!(
"var {wrap_symbol_name} = {esm_runtime_symbol_name}({{\n'{}'() {{\n",
self.ctx.module.resource_id.prettify(),
Expand Down
20 changes: 7 additions & 13 deletions crates/rolldown/src/bundler/visitors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<'ast> RendererContext<'ast> {
) -> Self {
let wrap_symbol_name = module
.wrap_symbol
.and_then(|s| get_symbol_final_name((module.id, s).into(), symbols, final_names));
.and_then(|s| get_symbol_final_name(s, symbols, final_names));
let namespace_symbol_name = get_symbol_final_name(
(module.id, module.namespace_symbol.0.symbol).into(),
symbols,
Expand Down Expand Up @@ -102,13 +102,9 @@ impl<'ast> RendererContext<'ast> {
get_reference_final_name(module_id, reference_id, self.symbols, self.final_names)
}

pub fn get_runtime_symbol_final_name(&self, name: &str) -> Atom {
let symbol = self.runtime.resolve_symbol(&name.into());
self
.get_symbol_final_name(symbol)
.cloned()
.unwrap_or_else(|| name.into())
// .expect(&format!("runtime symbol {name} not found"))
pub fn get_runtime_symbol_final_name(&self, name: Atom) -> &Atom {
let symbol = self.runtime.resolve_symbol(&name);
self.get_symbol_final_name(symbol).unwrap()
}

pub fn visit_binding_identifier(&mut self, ident: &'ast oxc::ast::ast::BindingIdentifier) {
Expand Down Expand Up @@ -167,9 +163,9 @@ impl<'ast> RendererContext<'ast> {
.get_symbol_final_name((importee.id, importee.namespace_symbol.0.symbol).into())
.unwrap();
let wrap_symbol_name = self
.get_symbol_final_name((importee.id, importee.wrap_symbol.unwrap()).into())
.get_symbol_final_name(importee.wrap_symbol.unwrap())
.unwrap();
let to_esm_runtime_symbol_name = self.get_runtime_symbol_final_name("__toESM");
let to_esm_runtime_symbol_name = self.get_runtime_symbol_final_name("__toESM".into());
self.source.prepend_left(
decl.span.start,
format!("var {namespace_name} = {to_esm_runtime_symbol_name}({wrap_symbol_name}());\n"),
Expand Down Expand Up @@ -214,10 +210,8 @@ impl<'ast> RendererContext<'ast> {
oxc::ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => {}
});
} else if let Some(wrap_symbol) = importee.wrap_symbol {
let wrap_symbol_name = self.get_symbol_final_name(wrap_symbol).unwrap();
// init wrapped esm module
let wrap_symbol_name = self
.get_symbol_final_name((importee.id, wrap_symbol).into())
.unwrap();
self
.source
.prepend_left(decl.span.start, format!("{wrap_symbol_name}();\n"));
Expand Down
5 changes: 3 additions & 2 deletions crates/rolldown/tests/fixtures/basic_commonjs/artifacts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ esm_named_class = class esm_named_class {}
}
});
// commonjs.js
var require_commonjs = __commonJS({
var require_commonjs$1 = __commonJS({
'commonjs.js'(exports, module) {
var value = (init_esm(), __toCommonJS(esm_ns));
module.exports = value;
}
});
// main.js
var _default = commonjs_ns.default;
var commonjs_ns = __toESM(require_commonjs());
var commonjs_ns = __toESM(require_commonjs$1());

init_esm();

console.log(_default, esm_default_fn, esm_named_var, esm_named_fn, esm_named_class)
const require_commonjs = () => {}
```
1 change: 1 addition & 0 deletions crates/rolldown/tests/fixtures/basic_commonjs/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import foo from './commonjs.js'
import esm, { esm_named_var, esm_named_fn, esm_named_class } from './esm.js'
console.log(foo, esm, esm_named_var, esm_named_fn, esm_named_class)
const require_commonjs = () => {}
2 changes: 1 addition & 1 deletion crates/rolldown_common/src/module_resolution.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ModuleResolution {
Esm,
CommonJs,
Expand Down

0 comments on commit 0fc1d3a

Please sign in to comment.