Skip to content

Commit

Permalink
feat: support mix cjs and esm (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed Oct 19, 2023
1 parent 465e6e7 commit 22794c3
Show file tree
Hide file tree
Showing 20 changed files with 173 additions and 54 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ serde_json = "1.0.87"
insta = "1.21.0"
testing_macros = "0.2.7"
scoped-tls = "1.0"
string_wizard = { version = "0.0.8" }
string_wizard = { version = "0.0.9" }
async-trait = "0.1.62"
futures = "0.3.25"
itertools = "0.10.5"
3 changes: 0 additions & 3 deletions crates/rolldown/src/bundler/module/normal_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ impl NormalModule {
pub fn render(&self, ctx: ModuleRenderContext<'_>) -> Option<MagicString<'static>> {
// FIXME: should not clone
let source = self.ast.source();
if source.is_empty() {
return None;
}
let mut source = MagicString::new(source.to_string());

let ctx = RendererContext::new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ impl NormalModuleTask {
let semantic = program.make_semantic(source_type);
let (mut symbol_table, mut scope) = semantic.into_symbol_table_and_scope_tree();
let unique_name = self.path.generate_unique_name();
let mut scanner =
scanner::Scanner::new(self.module_id, &mut scope, &mut symbol_table, &unique_name);
let mut scanner = scanner::Scanner::new(
self.module_id,
&mut scope,
&mut symbol_table,
&unique_name,
self.module_type,
);
scanner.visit_program(program.program_mut());
let scan_result = scanner.result;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use oxc::{
semantic::{ScopeTree, SymbolTable},
span::SourceType,
};
use rolldown_common::{ModuleId, ResourceId};
use rolldown_common::{ModuleId, ModuleType, ResourceId};
use rolldown_oxc::{OxcCompiler, OxcProgram};

use super::Msg;
Expand All @@ -20,6 +20,7 @@ use crate::{
pub struct RuntimeNormalModuleTask {
module_id: ModuleId,
tx: tokio::sync::mpsc::UnboundedSender<Msg>,
module_type: ModuleType,
errors: Vec<BuildError>,
warnings: Vec<BuildError>,
resolver: SharedResolver,
Expand All @@ -31,7 +32,14 @@ impl RuntimeNormalModuleTask {
resolver: SharedResolver,
tx: tokio::sync::mpsc::UnboundedSender<Msg>,
) -> Self {
Self { module_id: id, resolver, tx, errors: Vec::default(), warnings: Vec::default() }
Self {
module_id: id,
module_type: ModuleType::EsmMjs,
resolver,
tx,
errors: Vec::default(),
warnings: Vec::default(),
}
}

pub fn run(self) {
Expand Down Expand Up @@ -92,6 +100,7 @@ impl RuntimeNormalModuleTask {
&mut scope,
&mut symbol_table,
"should be unreachable for runtime module",
self.module_type,
);
scanner.visit_program(program.program_mut());
let scan_result = scanner.result;
Expand Down
36 changes: 5 additions & 31 deletions crates/rolldown/src/bundler/visitors/commonjs_source_render.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use oxc::ast::Visit;
use rolldown_common::ExportsKind;

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

use super::RendererContext;

Expand Down Expand Up @@ -29,37 +26,14 @@ impl<'ast> CommonJsSourceRender<'ast> {

impl<'ast> Visit<'ast> for CommonJsSourceRender<'ast> {
fn visit_call_expression(&mut self, expr: &'ast oxc::ast::ast::CallExpression<'ast>) {
if let oxc::ast::ast::Expression::Identifier(ident) = &expr.callee {
if ident.name == "require" {
let rec = &self.ctx.module.import_records
[self.ctx.module.imports.get(&expr.span).copied().unwrap()];
let importee = &self.ctx.modules[rec.resolved_module];
if let Module::Normal(importee) = importee {
let wrap_symbol_name =
self.ctx.get_symbol_final_name(importee.wrap_symbol.unwrap()).unwrap();
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
.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".into());
self.ctx.source.update(
expr.span.start,
expr.span.end,
format!(
"({wrap_symbol_name}(), {to_commonjs_runtime_symbol_name}({namespace_name}))"
),
);
}
}
}
}
self.ctx.visit_call_expression(expr);
for arg in &expr.arguments {
self.visit_argument(arg);
}
self.visit_expression(&expr.callee);
}

fn visit_import_declaration(&mut self, decl: &'ast oxc::ast::ast::ImportDeclaration<'ast>) {
self.ctx.visit_import_declaration(decl);
}
}
8 changes: 8 additions & 0 deletions crates/rolldown/src/bundler/visitors/esm_source_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,12 @@ impl<'ast> Visit<'ast> for EsmSourceRender<'ast> {
fn visit_import_expression(&mut self, expr: &oxc::ast::ast::ImportExpression<'ast>) {
self.ctx.visit_import_expression(expr);
}

fn visit_call_expression(&mut self, expr: &'ast oxc::ast::ast::CallExpression<'ast>) {
self.ctx.visit_call_expression(expr);
for arg in &expr.arguments {
self.visit_argument(arg);
}
self.visit_expression(&expr.callee);
}
}
12 changes: 11 additions & 1 deletion crates/rolldown/src/bundler/visitors/esm_wrap_source_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ impl<'ast> EsmWrapSourceRender<'ast> {
// here move end of function to the keep "\n"
self.ctx.source.relocate(f.start, f.end + 1, 0);
});
self.ctx.source.append_right(0, format!("var {};\n", self.hoisted_vars.join(",")));
if !self.hoisted_vars.is_empty() {
self.ctx.source.append_right(0, format!("var {};\n", self.hoisted_vars.join(",")));
}

let namespace_name = self.ctx.namespace_symbol_name.unwrap();
let exports: String = self
Expand Down Expand Up @@ -154,4 +156,12 @@ impl<'ast> Visit<'ast> for EsmWrapSourceRender<'ast> {
fn visit_import_expression(&mut self, expr: &oxc::ast::ast::ImportExpression<'ast>) {
self.ctx.visit_import_expression(expr);
}

fn visit_call_expression(&mut self, expr: &'ast oxc::ast::ast::CallExpression<'ast>) {
self.ctx.visit_call_expression(expr);
for arg in &expr.arguments {
self.visit_argument(arg);
}
self.visit_expression(&expr.callee);
}
}
29 changes: 29 additions & 0 deletions crates/rolldown/src/bundler/visitors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,33 @@ impl<'ast> RendererContext<'ast> {
}
}
}

pub fn visit_call_expression(&mut self, expr: &'ast oxc::ast::ast::CallExpression<'ast>) {
if let oxc::ast::ast::Expression::Identifier(ident) = &expr.callee {
if ident.name == "require" {
let rec =
&self.module.import_records[self.module.imports.get(&expr.span).copied().unwrap()];
let importee = &self.modules[rec.resolved_module];
if let Module::Normal(importee) = importee {
let wrap_symbol_name = self.get_symbol_final_name(importee.wrap_symbol.unwrap()).unwrap();
if importee.exports_kind == ExportsKind::CommonJs {
self.source.update(expr.span.start, expr.span.end, format!("{wrap_symbol_name}()"));
} else {
let namespace_name = self
.get_symbol_final_name((importee.id, importee.namespace_symbol.0.symbol).into())
.unwrap();
let to_commonjs_runtime_symbol_name =
self.get_runtime_symbol_final_name(&"__toCommonJS".into());
self.source.update(
expr.span.start,
expr.span.end,
format!(
"({wrap_symbol_name}(), {to_commonjs_runtime_symbol_name}({namespace_name}))"
),
);
}
}
}
}
}
}
45 changes: 35 additions & 10 deletions crates/rolldown/src/bundler/visitors/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use oxc::{
};
use rolldown_common::{
ExportsKind, ImportKind, ImportRecord, ImportRecordId, LocalExport, LocalOrReExport, ModuleId,
NamedImport, ReExport, StmtInfo, StmtInfoId,
ModuleType, NamedImport, ReExport, StmtInfo, StmtInfoId,
};
use rolldown_oxc::BindingIdentifierExt;
use rustc_hash::FxHashMap;
Expand All @@ -31,11 +31,14 @@ pub struct ScanResult {

pub struct Scanner<'a> {
pub idx: ModuleId,
pub module_type: ModuleType,
pub scope: &'a mut ScopeTree,
pub symbol_table: &'a mut SymbolTable,
pub current_stmt_info: StmtInfo,
pub result: ScanResult,
pub unique_name: &'a str,
pub esm_export_keyword: Option<Span>,
pub cjs_export_keyword: Option<Span>,
}

impl<'a> Scanner<'a> {
Expand All @@ -44,6 +47,7 @@ impl<'a> Scanner<'a> {
scope: &'a mut ScopeTree,
symbol_table: &'a mut SymbolTable,
unique_name: &'a str,
module_type: ModuleType,
) -> Self {
Self {
idx,
Expand All @@ -52,16 +56,35 @@ impl<'a> Scanner<'a> {
current_stmt_info: StmtInfo::default(),
result: ScanResult::default(),
unique_name,
esm_export_keyword: None,
cjs_export_keyword: None,
module_type,
}
}

fn set_exports_kind(&mut self, exports_kind: ExportsKind) {
if let Some(resolution) = &self.result.exports_kind {
if resolution != &exports_kind {
// TODO shouldn't mix esm syntax and cjs syntax
}
fn set_esm_export_keyword(&mut self, span: Span) {
if self.esm_export_keyword.is_none() {
self.esm_export_keyword = Some(span);
}
}

fn set_cjs_export_keyword(&mut self, span: Span) {
if self.cjs_export_keyword.is_none() {
self.cjs_export_keyword = Some(span);
}
}

fn set_exports_kind(&mut self) {
if self.esm_export_keyword.is_some() {
self.result.exports_kind = Some(ExportsKind::Esm);
} else if self.cjs_export_keyword.is_some() {
self.result.exports_kind = Some(ExportsKind::CommonJs);
} else if self.module_type.is_esm() {
self.result.exports_kind = Some(ExportsKind::Esm);
} else if self.module_type.is_commonjs() {
self.result.exports_kind = Some(ExportsKind::CommonJs);
} else {
self.result.exports_kind = Some(exports_kind);
self.result.exports_kind = Some(ExportsKind::Esm);
}
}

Expand Down Expand Up @@ -251,12 +274,15 @@ impl<'a> Scanner<'a> {
self.scan_import_decl(decl);
}
oxc::ast::ast::ModuleDeclaration::ExportAllDeclaration(decl) => {
self.set_esm_export_keyword(decl.span);
self.scan_export_all_decl(decl);
}
oxc::ast::ast::ModuleDeclaration::ExportNamedDeclaration(decl) => {
self.set_esm_export_keyword(decl.span);
self.scan_export_named_decl(decl);
}
oxc::ast::ast::ModuleDeclaration::ExportDefaultDeclaration(decl) => {
self.set_esm_export_keyword(decl.span);
self.scan_export_default_decl(decl);
}
_ => {}
Expand All @@ -276,6 +302,7 @@ impl<'ast, 'p> VisitMut<'ast, 'p> for Scanner<'ast> {
self.visit_statement(stmt);
self.result.stmt_infos.push(std::mem::take(&mut self.current_stmt_info));
}
self.set_exports_kind();
}

fn visit_binding_identifier(&mut self, ident: &'p mut oxc::ast::ast::BindingIdentifier) {
Expand All @@ -298,7 +325,7 @@ impl<'ast, 'p> VisitMut<'ast, 'p> for Scanner<'ast> {
if ident.name == "module" || ident.name == "exports" {
if let Some(refs) = self.scope.root_unresolved_references().get(&ident.name) {
if refs.iter().any(|r| (*r).eq(&ident.reference_id.get().unwrap())) {
self.set_exports_kind(ExportsKind::CommonJs);
self.set_cjs_export_keyword(ident.span);
}
}
}
Expand All @@ -307,7 +334,6 @@ impl<'ast, 'p> VisitMut<'ast, 'p> for Scanner<'ast> {
fn visit_statement(&mut self, stmt: &'p mut oxc::ast::ast::Statement<'ast>) {
if let oxc::ast::ast::Statement::ModuleDeclaration(decl) = stmt {
self.scan_module_decl(decl.0);
self.set_exports_kind(ExportsKind::Esm);
}
self.visit_statement_match(stmt);
}
Expand All @@ -324,7 +350,6 @@ impl<'ast, 'p> VisitMut<'ast, 'p> for Scanner<'ast> {
if ident.name == "require" {
if let Some(refs) = self.scope.root_unresolved_references().get(&ident.name) {
if refs.iter().any(|r| (*r).eq(&ident.reference_id.get().unwrap())) {
self.set_exports_kind(ExportsKind::CommonJs);
if let Some(oxc::ast::ast::Argument::Expression(
oxc::ast::ast::Expression::StringLiteral(request),
)) = &expr.arguments.get(0)
Expand Down
40 changes: 40 additions & 0 deletions crates/rolldown/tests/fixtures/mix-cjs-esm/artifacts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
source: crates/rolldown/tests/common/case.rs
expression: content
input_file: crates/rolldown/tests/fixtures/mix-cjs-esm
---
# main.js

```js
// foo.js
var foo_ns = {

};
var init_foo = __esm({
'foo.js'() {

}
});
// esm_import_cjs_require.js
init_foo();

(init_foo(), __toCommonJS(foo_ns))
// esm_import_cjs_export.js
var require_esm_import_cjs_export = __commonJS({
'esm_import_cjs_export.js'(exports, module) {
init_foo();

module.exports = 1
}
});
// esm_export_cjs_require.js
(init_foo(), __toCommonJS(foo_ns))
const value$1 = 1;
// esm_export_cjs_export.js
module.exports = 1;
const value = 1;
// main.js


var esm_import_cjs_export_ns = __toESM(require_esm_import_cjs_export());
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module.exports = 1;
export const value = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require('./foo')
export const value = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './foo'
module.exports = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './foo'
require('./foo')
Empty file.
4 changes: 4 additions & 0 deletions crates/rolldown/tests/fixtures/mix-cjs-esm/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "./esm_export_cjs_export"
import "./esm_export_cjs_require"
import "./esm_import_cjs_export"
import "./esm_import_cjs_require"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit 22794c3

Please sign in to comment.