Skip to content

Commit

Permalink
feat: compat node module commonjs interop (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed Oct 18, 2023
1 parent 7337341 commit 58c2786
Show file tree
Hide file tree
Showing 21 changed files with 196 additions and 54 deletions.
29 changes: 20 additions & 9 deletions crates/rolldown/src/bundler/graph/linker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use index_vec::IndexVec;
use oxc::{semantic::ReferenceId, span::Atom};
use rolldown_common::{LocalOrReExport, ModuleId, ModuleResolution, ResolvedExport, SymbolRef};
use rolldown_common::{ExportsKind, LocalOrReExport, ModuleId, ResolvedExport, SymbolRef};
use rustc_hash::FxHashMap;

use super::graph::Graph;
Expand Down Expand Up @@ -46,6 +46,9 @@ impl<'graph> Linker<'graph> {
}

fn wrap_modules_if_needed(&mut self) {
// Detect module need wrapped, here has two cases:
// - Commonjs module, because cjs symbols can't static binding, it need to be wrapped and lazy evaluated.
// - Import esm module at commonjs module.
let mut module_to_wrapped = index_vec::index_vec![
false;
self.graph.modules.len()
Expand All @@ -54,20 +57,23 @@ impl<'graph> Linker<'graph> {
for module in &self.graph.modules {
match module {
Module::Normal(module) => {
if module.module_resolution == ModuleResolution::CommonJs {
if module.exports_kind == ExportsKind::CommonJs {
wrap_module(self.graph, module.id, &mut module_to_wrapped);
}
}
Module::External(_) => {}
}
}

// Generate symbol for wrap module declaration
// Case commonjs, eg var require_a = __commonJS()
// Case esm, eg var init_a = __esm()
for (module_id, wrapped) in module_to_wrapped.into_iter_enumerated() {
if wrapped {
match &mut self.graph.modules[module_id] {
Module::Normal(module) => {
module.create_wrap_symbol(&mut self.graph.symbols);
let name = if module.module_resolution == ModuleResolution::CommonJs {
let name = if module.exports_kind == ExportsKind::CommonJs {
"__commonJS".into()
} else {
"__esm".into()
Expand All @@ -80,6 +86,9 @@ impl<'graph> Linker<'graph> {
}
}

// Generate symbol for import warp module
// Case esm import commonjs, eg var commonjs_ns = __toESM(require_a())
// Case commonjs require esm, eg (init_esm(), __toCommonJS(esm_ns))
let mut imported_symbols = vec![];

for module in &self.graph.modules {
Expand All @@ -93,14 +102,16 @@ impl<'graph> Linker<'graph> {

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

match (importer.module_resolution, importee.module_resolution) {
(ModuleResolution::Esm, ModuleResolution::CommonJs) => {
match (importer.exports_kind, importee.exports_kind) {
(ExportsKind::Esm, ExportsKind::CommonJs) => {
imported_symbols
.push((importer.id, self.graph.runtime.resolve_symbol(&"__toESM".into())));
imported_symbols.push((importer.id, importee.namespace_symbol.0));
}
(ModuleResolution::CommonJs, ModuleResolution::Esm) => {
(ExportsKind::CommonJs, ExportsKind::Esm) => {
imported_symbols
.push((importer.id, self.graph.runtime.resolve_symbol(&"__toCommonJS".into())));
}
Expand Down Expand Up @@ -166,7 +177,7 @@ impl<'graph> Linker<'graph> {
let importee = &graph.modules[import_record.resolved_module];
match importee {
Module::Normal(importee) => {
if importee.module_resolution == ModuleResolution::CommonJs {
if importee.exports_kind == ExportsKind::CommonJs {
extra_symbols.push((
import_record.resolved_module,
info.imported.clone(),
Expand Down Expand Up @@ -204,7 +215,7 @@ impl<'graph> Linker<'graph> {
let importee = &mut graph.modules[importee];
match importee {
Module::Normal(importee) => {
if importee.module_resolution == ModuleResolution::CommonJs {
if importee.exports_kind == ExportsKind::CommonJs {
importee.add_cjs_symbol(&mut graph.symbols, imported, is_imported_star);
}
}
Expand Down Expand Up @@ -312,7 +323,7 @@ impl<'graph> Linker<'graph> {
let importee = &self.graph.modules[import_record.resolved_module];
match importee {
Module::Normal(importee) => {
let resolved_ref = if importee.module_resolution == ModuleResolution::CommonJs {
let resolved_ref = if importee.exports_kind == ExportsKind::CommonJs {
importee.resolve_cjs_symbol(&info.imported, info.is_imported_star)
} else if info.is_imported_star {
importee.namespace_symbol.0
Expand Down
8 changes: 5 additions & 3 deletions crates/rolldown/src/bundler/module/module_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use oxc::{
span::{Atom, Span},
};
use rolldown_common::{
ImportRecord, ImportRecordId, LocalOrReExport, ModuleId, ModuleResolution, NamedImport,
ExportsKind, ImportRecord, ImportRecordId, LocalOrReExport, ModuleId, ModuleType, NamedImport,
ResourceId, StmtInfo, StmtInfoId, SymbolRef,
};
use rolldown_oxc::OxcProgram;
Expand All @@ -28,7 +28,8 @@ pub struct NormalModuleBuilder {
pub scope: Option<ScopeTree>,
pub default_export_symbol: Option<SymbolId>,
pub namespace_symbol: Option<(SymbolRef, ReferenceId)>,
pub module_resolution: Option<ModuleResolution>,
pub exports_kind: Option<ExportsKind>,
pub module_type: ModuleType,
}

impl NormalModuleBuilder {
Expand Down Expand Up @@ -58,9 +59,10 @@ impl NormalModuleBuilder {
namespace_symbol: self.namespace_symbol.unwrap(),
is_symbol_for_namespace_referenced: false,
source_mutations: Vec::default(),
module_resolution: self.module_resolution.unwrap_or(ModuleResolution::Esm),
exports_kind: self.exports_kind.unwrap_or(ExportsKind::Esm),
cjs_symbols: FxHashMap::default(),
wrap_symbol: None,
module_type: self.module_type,
}
}
}
15 changes: 10 additions & 5 deletions crates/rolldown/src/bundler/module/normal_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use oxc::{
span::{Atom, Span},
};
use rolldown_common::{
ImportRecord, ImportRecordId, LocalOrReExport, ModuleId, ModuleResolution, NamedImport,
ExportsKind, ImportRecord, ImportRecordId, LocalOrReExport, ModuleId, ModuleType, NamedImport,
ResolvedExport, ResourceId, StmtInfo, StmtInfoId, SymbolRef,
};
use rolldown_oxc::OxcProgram;
Expand All @@ -29,6 +29,7 @@ pub struct NormalModule {
pub exec_order: u32,
pub id: ModuleId,
pub resource_id: ResourceId,
pub module_type: ModuleType,
pub ast: OxcProgram,
pub source_mutations: Vec<BoxedSourceMutation>,
pub named_imports: FxHashMap<SymbolId, NamedImport>,
Expand All @@ -38,7 +39,7 @@ pub struct NormalModule {
pub imports: FxHashMap<Span, ImportRecordId>,
// [[StarExportEntries]] in https://tc39.es/ecma262/#sec-source-text-module-records
pub star_exports: Vec<ImportRecordId>,
pub module_resolution: ModuleResolution,
pub exports_kind: ExportsKind,
// resolved
pub resolved_exports: FxHashMap<Atom, ResolvedExport>,
pub resolved_star_exports: Vec<ModuleId>,
Expand All @@ -60,7 +61,11 @@ impl NormalModule {
#[allow(clippy::needless_pass_by_value)]
pub fn render(&self, ctx: ModuleRenderContext<'_>) -> Option<MagicString<'static>> {
// FIXME: should not clone
let mut source = MagicString::new(self.ast.source().to_string());
let source = self.ast.source();
if source.is_empty() {
return None;
}
let mut source = MagicString::new(source.to_string());

let ctx = RendererContext::new(
ctx.symbols,
Expand All @@ -73,7 +78,7 @@ impl NormalModule {
ctx.runtime,
);

if self.module_resolution == ModuleResolution::CommonJs {
if self.exports_kind == ExportsKind::CommonJs {
CommonJsSourceRender::new(ctx).apply();
} else if self.wrap_symbol.is_some() {
EsmWrapSourceRender::new(ctx).apply();
Expand Down Expand Up @@ -299,7 +304,7 @@ impl NormalModule {
if self.wrap_symbol.is_none() {
let name = format!(
"{}_{}",
if self.module_resolution == ModuleResolution::CommonJs { "require" } else { "init" },
if self.exports_kind == ExportsKind::CommonJs { "require" } else { "init" },
self.resource_id.generate_unique_name()
)
.into();
Expand Down
9 changes: 7 additions & 2 deletions crates/rolldown/src/bundler/module_loader/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,13 @@ impl<'a> ModuleLoader<'a> {

let module_path = ResourceId::new(info.path.clone(), &self.input_options.cwd);

let task =
NormalModuleTask::new(id, Arc::clone(&self.resolver), module_path, self.tx.clone());
let task = NormalModuleTask::new(
id,
Arc::<rolldown_resolver::Resolver>::clone(&self.resolver),
module_path,
info.module_type,
self.tx.clone(),
);
tokio::spawn(async move { task.run().await });
}
id
Expand Down
25 changes: 20 additions & 5 deletions crates/rolldown/src/bundler/module_loader/normal_module_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use oxc::{
semantic::{ScopeTree, SymbolTable},
span::SourceType,
};
use rolldown_common::{ImportRecord, ImportRecordId, ModuleId, ResourceId};
use rolldown_common::{ImportRecord, ImportRecordId, ModuleId, ModuleType, ResourceId};
use rolldown_oxc::{OxcCompiler, OxcProgram};
use rolldown_resolver::Resolver;

Expand All @@ -26,6 +26,7 @@ use crate::{
pub struct NormalModuleTask {
module_id: ModuleId,
path: ResourceId,
module_type: ModuleType,
tx: tokio::sync::mpsc::UnboundedSender<Msg>,
errors: Vec<BuildError>,
warnings: Vec<BuildError>,
Expand All @@ -37,9 +38,18 @@ impl NormalModuleTask {
id: ModuleId,
resolver: SharedResolver,
path: ResourceId,
module_type: ModuleType,
tx: tokio::sync::mpsc::UnboundedSender<Msg>,
) -> Self {
Self { module_id: id, resolver, path, tx, errors: Vec::default(), warnings: Vec::default() }
Self {
module_id: id,
resolver,
path,
module_type,
tx,
errors: Vec::default(),
warnings: Vec::default(),
}
}

pub async fn run(mut self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -67,7 +77,7 @@ impl NormalModuleTask {
star_exports,
export_default_symbol_id,
imports,
module_resolution,
exports_kind,
} = scan_result;

builder.id = Some(self.module_id);
Expand All @@ -81,8 +91,9 @@ impl NormalModuleTask {
builder.star_exports = Some(star_exports);
builder.default_export_symbol = export_default_symbol_id;
builder.scope = Some(scope);
builder.module_resolution = module_resolution;
builder.exports_kind = exports_kind;
builder.initialize_namespace_binding(&mut symbol_map);
builder.module_type = self.module_type;

self
.tx
Expand Down Expand Up @@ -130,7 +141,11 @@ impl NormalModuleTask {
match resolved_id {
Some(info) => Ok(info),
None => {
Ok(ResolvedRequestInfo { path: specifier.to_string().into(), is_external: true })
Ok(ResolvedRequestInfo {
path: specifier.to_string().into(),
module_type: ModuleType::Unknown,
is_external: true,
})
// // TODO: should emit warnings like https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency
// return Err(rolldown_error::Error::unresolved_import(
// specifier.to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl RuntimeNormalModuleTask {
star_exports,
export_default_symbol_id,
imports,
module_resolution,
exports_kind,
} = scan_result;

builder.id = Some(self.module_id);
Expand All @@ -65,7 +65,7 @@ impl RuntimeNormalModuleTask {
builder.star_exports = Some(star_exports);
builder.default_export_symbol = export_default_symbol_id;
builder.scope = Some(scope);
builder.module_resolution = module_resolution;
builder.exports_kind = exports_kind;
builder.initialize_namespace_binding(&mut symbol_map);

self
Expand Down
9 changes: 7 additions & 2 deletions crates/rolldown/src/bundler/resolve_id.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use rolldown_common::{RawPath, ResourceId};
use rolldown_common::{ModuleType, RawPath, ResourceId};
use rolldown_resolver::Resolver;

use crate::BuildResult;

pub struct ResolvedRequestInfo {
pub path: RawPath,
pub module_type: ModuleType,
pub is_external: bool,
}

Expand All @@ -23,6 +24,10 @@ pub async fn resolve_id(
Ok(None)
} else {
let resolved = resolver.resolve(importer, request)?;
Ok(Some(ResolvedRequestInfo { path: resolved.resolved, is_external: false }))
Ok(Some(ResolvedRequestInfo {
path: resolved.resolved,
module_type: resolved.module_type,
is_external: false,
}))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use oxc::ast::Visit;
use rolldown_common::ModuleResolution;
use rolldown_common::ExportsKind;

use crate::bundler::module::module::Module;

Expand Down Expand Up @@ -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.wrap_symbol.unwrap()).unwrap();
if importee.module_resolution == ModuleResolution::CommonJs {
if importee.exports_kind == ExportsKind::CommonJs {
self.ctx.source.update(expr.span.start, expr.span.end, format!("{wrap_symbol_name}()"));
} else {
let namespace_name = self
Expand Down
18 changes: 11 additions & 7 deletions crates/rolldown/src/bundler/visitors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use oxc::{
semantic::ReferenceId,
span::{Atom, Span},
};
use rolldown_common::{ModuleId, ModuleResolution, SymbolRef};
use rolldown_common::{ExportsKind, ModuleId, SymbolRef};
use rustc_hash::FxHashMap;
use string_wizard::{MagicString, UpdateOptions};

Expand Down Expand Up @@ -154,7 +154,7 @@ impl<'ast> RendererContext<'ast> {
let rec = &self.module.import_records[self.module.imports.get(&decl.span).copied().unwrap()];
let importee = &self.modules[rec.resolved_module];
if let Module::Normal(importee) = importee {
if importee.module_resolution == ModuleResolution::CommonJs {
if importee.exports_kind == ExportsKind::CommonJs {
// add import cjs symbol binding
let namespace_name = self
.get_symbol_final_name((importee.id, importee.namespace_symbol.0.symbol).into())
Expand All @@ -163,7 +163,10 @@ impl<'ast> RendererContext<'ast> {
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"),
format!(
"var {namespace_name} = {to_esm_runtime_symbol_name}({wrap_symbol_name}(){});\n",
if self.module.module_type.is_esm() { ", 1" } else { "" }
),
);
decl.specifiers.iter().for_each(|s| match s {
oxc::ast::ast::ImportDeclarationSpecifier::ImportSpecifier(spec) => {
Expand All @@ -172,16 +175,17 @@ impl<'ast> RendererContext<'ast> {
) {
self
.source
.prepend_left(decl.span.start, format!("var {name} = {namespace_name}.{name};\n"));
.prepend_right(decl.span.start, format!("var {name} = {namespace_name}.{name};\n"));
}
}
oxc::ast::ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(_) => {
if let Some(name) = self.get_symbol_final_name(
(importee.id, importee.cjs_symbols[&Atom::new_inline("default")].symbol).into(),
) {
self
.source
.prepend_left(decl.span.start, format!("var {name} = {namespace_name}.default;\n"));
self.source.prepend_right(
decl.span.start,
format!("var {name} = {namespace_name}.default;\n"),
);
}
}
oxc::ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => {}
Expand Down

0 comments on commit 58c2786

Please sign in to comment.