Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support mix cjs and esm #76

Merged
merged 6 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_key_word: Option<Span>,
underfin marked this conversation as resolved.
Show resolved Hide resolved
pub cjs_export_key_word: 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_key_word: None,
cjs_export_key_word: 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_key_word(&mut self, span: Span) {
underfin marked this conversation as resolved.
Show resolved Hide resolved
if self.esm_export_key_word.is_none() {
self.esm_export_key_word = Some(span);
}
}

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

fn set_exports_kind(&mut self) {
if self.esm_export_key_word.is_some() {
self.result.exports_kind = Some(ExportsKind::Esm);
} else if self.cjs_export_key_word.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_key_word(decl.span);
self.scan_export_all_decl(decl);
}
oxc::ast::ast::ModuleDeclaration::ExportNamedDeclaration(decl) => {
self.set_esm_export_key_word(decl.span);
self.scan_export_named_decl(decl);
}
oxc::ast::ast::ModuleDeclaration::ExportDefaultDeclaration(decl) => {
self.set_esm_export_key_word(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_key_word(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