Skip to content

Commit 2ff307f

Browse files
Merge branch 'main' into fix/browser-binding-dts
2 parents b631bdd + 6ab459a commit 2ff307f

File tree

32 files changed

+436
-99
lines changed

32 files changed

+436
-99
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ jobs:
198198
cache: true
199199

200200
- name: Download Native Rolldown Build
201+
if: ${{ needs.changes.outputs.node-changes == 'true' }}
201202
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
202203
with:
203204
name: rolldown-native-ubuntu-latest

crates/rolldown/src/ast_scanner/cjs_export_analyzer.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use oxc::ast::{
33
AstKind, MemberExpressionKind,
44
ast::{self, AssignmentExpression, Expression, IdentifierReference, PropertyKey},
55
};
6-
use oxc::span::CompactStr;
6+
use oxc::span::{CompactStr, Span};
77
use rolldown_common::{AstScopes, EcmaModuleAstUsage};
88
use rolldown_ecmascript_utils::ExpressionExt;
99

@@ -41,7 +41,7 @@ pub enum CommonJsAstType {
4141
/// `console.log(exports)`
4242
ExportsRead,
4343
EsModuleFlag,
44-
Reexport,
44+
Reexport(Span),
4545
}
4646

4747
impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
@@ -118,8 +118,9 @@ impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
118118
Some(CommonJsAstType::ExportsPropWrite(prop)) if prop == "*" => {
119119
self.result.ast_usage.remove(EcmaModuleAstUsage::AllStaticExportPropertyAccess);
120120
}
121-
Some(CommonJsAstType::Reexport) => {
121+
Some(CommonJsAstType::Reexport(span)) => {
122122
self.result.ast_usage.insert(EcmaModuleAstUsage::IsCjsReexport);
123+
self.result.cjs_reexport_require_spans.push(*span);
123124
}
124125
_ => {}
125126
}
@@ -179,7 +180,7 @@ impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
179180
.as_expression()?
180181
.as_string_literal()
181182
.is_some()
182-
.then_some(CommonJsAstType::Reexport)
183+
.then_some(CommonJsAstType::Reexport(call_expr.span))
183184
}
184185
}
185186

crates/rolldown/src/ast_scanner/impl_visit.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ impl<'me, 'ast: 'me> Visit<'ast> for AstScanner<'me, 'ast> {
285285
}
286286
if id.name == "exports" && self.is_global_identifier_reference(id) {
287287
self.cjs_exports_ident.get_or_insert(Span::new(id.span.start, id.span.start + 7));
288-
289288
if let Some((span, export_name)) = member_expr.static_property_info() {
290289
// `exports.test = ...`
291290
let exported_symbol =
@@ -593,7 +592,7 @@ impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
593592
self.cjs_named_exports_usage.entry(prop).or_default().write += 1;
594593
}
595594
Some(CommonJsAstType::EsModuleFlag) => {}
596-
Some(CommonJsAstType::Reexport) => {
595+
Some(CommonJsAstType::Reexport(_)) => {
597596
// This is only usd for `module.exports = require('mod')`
598597
// should only reached when `ident_ref` is `module`
599598
unreachable!()

crates/rolldown/src/ast_scanner/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ pub struct ScanResult {
116116
pub directive_range: Vec<Span>,
117117
pub constant_export_map: FxHashMap<SymbolId, ConstExportMeta>,
118118
pub import_attribute_map: FxHashMap<ImportRecordIdx, ImportAttribute>,
119+
/// Temporary storage for spans of `require()` calls in `module.exports = require(...)` patterns.
120+
/// Resolved to `cjs_reexport_import_record_ids` after scanning completes.
121+
pub cjs_reexport_require_spans: Vec<Span>,
122+
/// Import record indices for `module.exports = require(...)` patterns.
123+
pub cjs_reexport_import_record_ids: Vec<ImportRecordIdx>,
119124
}
120125

121126
bitflags::bitflags! {
@@ -223,6 +228,8 @@ impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
223228
constant_export_map: FxHashMap::default(),
224229
ecma_view_meta: EcmaViewMeta::default(),
225230
import_attribute_map: FxHashMap::default(),
231+
cjs_reexport_require_spans: Vec::new(),
232+
cjs_reexport_import_record_ids: Vec::new(),
226233
};
227234

228235
Self {
@@ -354,6 +361,14 @@ impl<'me, 'ast: 'me> AstScanner<'me, 'ast> {
354361

355362
self.result.exports_kind = exports_kind;
356363

364+
// Resolve CJS re-export require spans to import record indices
365+
self.result.cjs_reexport_import_record_ids = self
366+
.result
367+
.cjs_reexport_require_spans
368+
.iter()
369+
.filter_map(|span| self.result.imports.get(span).copied())
370+
.collect();
371+
357372
// If some commonjs module facade exports was used locally, we need to explicitly mark them as
358373
// has side effects, so that they should not be removed in linking stage.
359374
let mut bailout_inlined_cjs_exports_symbol_ids = FxHashSet::default();

crates/rolldown/src/ecmascript/ecma_module_view_factory.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ pub async fn create_ecma_view(
7575
dummy_record_set,
7676
constant_export_map,
7777
import_attribute_map,
78+
cjs_reexport_require_spans: _,
79+
cjs_reexport_import_record_ids,
7880
} = scanner.scan(ast.program())?;
7981
// If a export symbol in commonjs defined in multiple time, we just bailout treeshake it.
8082
for (k, v) in commonjs_exports {
@@ -136,6 +138,7 @@ pub async fn create_ecma_view(
136138
depended_runtime_helper: Box::default(),
137139
import_attribute_map,
138140
json_module_none_self_reference_included_symbol: None,
141+
cjs_reexport_import_record_ids,
139142
};
140143

141144
let ecma_related = EcmaRelated { ast, symbols, dynamic_import_rec_exports_usage, preserve_jsx };

crates/rolldown/src/module_finalizers/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,13 @@ impl<'me, 'ast> ScopeHoistingFinalizer<'me, 'ast> {
446446

447447
let resolved_export =
448448
self.ctx.linking_infos[importee.idx].resolved_exports.get(&namespace_alias.property_name)?;
449+
450+
// Don't inline when there are conflicting CJS sources — the value could differ per branch
451+
// TODO(hana): Optimize this with conditional inlining
452+
if resolved_export.cjs_conflicting_symbol_refs.is_some() {
453+
return None;
454+
}
455+
449456
let export_symbol = resolved_export.symbol_ref;
450457
let canonical_export_ref = self.ctx.symbol_db.canonical_ref_for(export_symbol);
451458

crates/rolldown/src/module_loader/module_task.rs

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,23 @@ impl<Fs: FileSystem + Clone + 'static> ModuleTask<Fs> {
127127

128128
let mut warnings = vec![];
129129

130-
let ret = create_ecma_view(
131-
&mut CreateModuleContext {
132-
stable_id: &stable_id,
133-
module_idx: self.module_idx,
134-
plugin_driver: &self.ctx.plugin_driver,
135-
resolved_id: &self.resolved_id,
136-
options: &self.ctx.options,
137-
warnings: &mut warnings,
138-
module_type: module_type.clone(),
139-
replace_global_define_config: self.ctx.meta.replace_global_define_config.clone(),
140-
is_user_defined_entry: self.is_user_defined_entry,
141-
flat_options: self.flat_options,
142-
},
143-
CreateModuleViewArgs { source, sourcemap_chain, hook_side_effects },
144-
)
145-
.await?;
146-
147-
let CreateEcmaViewReturn {
148-
mut ecma_view,
149-
ecma_related,
150-
raw_import_records: ecma_raw_import_records,
151-
} = ret;
152-
153-
let raw_import_records = ecma_raw_import_records;
130+
let CreateEcmaViewReturn { mut ecma_view, ecma_related, raw_import_records } =
131+
create_ecma_view(
132+
&mut CreateModuleContext {
133+
stable_id: &stable_id,
134+
module_idx: self.module_idx,
135+
plugin_driver: &self.ctx.plugin_driver,
136+
resolved_id: &self.resolved_id,
137+
options: &self.ctx.options,
138+
warnings: &mut warnings,
139+
module_type: module_type.clone(),
140+
replace_global_define_config: self.ctx.meta.replace_global_define_config.clone(),
141+
is_user_defined_entry: self.is_user_defined_entry,
142+
flat_options: self.flat_options,
143+
},
144+
CreateModuleViewArgs { source, sourcemap_chain, hook_side_effects },
145+
)
146+
.await?;
154147

155148
let resolved_deps = resolve_dependencies(
156149
&self.resolved_id,

crates/rolldown/src/module_loader/runtime_module_task.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ impl<Fs: FileSystem + Clone + 'static> RuntimeModuleTask<Fs> {
199199
depended_runtime_helper: Box::default(),
200200
import_attribute_map: FxHashMap::default(),
201201
json_module_none_self_reference_included_symbol: None,
202+
cjs_reexport_import_record_ids: Vec::new(),
202203
},
203204
// TODO(hyf0/hmr): We might need to find a better way to handle this.
204205
originative_resolved_id: resolved_id,

crates/rolldown/src/stages/generate_stage/compute_cross_chunk_links.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ impl GenerateStage<'_> {
266266
.map(|(_, export)| export)
267267
// A chunk should always consume a cjs export symbol by property access, so filter
268268
// out a exported symbol that came from a cjs module.
269-
.filter(|resolved_export| !resolved_export.came_from_cjs)
269+
.filter(|resolved_export| !resolved_export.came_from_commonjs)
270270
{
271271
depended_symbols
272272
.insert(symbols.canonical_ref_resolving_namespace(export_ref.symbol_ref));

crates/rolldown/src/stages/link_stage/bind_imports_and_exports.rs

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl PartialEq for MatchImportKindNormal {
6262
}
6363

6464
#[derive(Debug, PartialEq, Eq)]
65+
#[expect(clippy::box_collection)]
6566
pub enum MatchImportKind {
6667
/// The import is either external or not defined.
6768
_Ignore,
@@ -81,7 +82,7 @@ pub enum MatchImportKind {
8182
// The import resolved to multiple symbols via "export * from"
8283
Ambiguous {
8384
symbol_ref: SymbolRef,
84-
potentially_ambiguous_symbol_refs: Vec<SymbolRef>,
85+
potentially_ambiguous_symbol_refs: Box<Vec<SymbolRef>>,
8586
},
8687
NoMatch,
8788
}
@@ -145,12 +146,7 @@ impl LinkStage<'_> {
145146
.named_exports
146147
.iter()
147148
.map(|(name, local)| {
148-
let resolved_export = ResolvedExport {
149-
symbol_ref: local.referenced,
150-
potentially_ambiguous_symbol_refs: None,
151-
came_from_cjs: local.came_from_commonjs,
152-
};
153-
(name.clone(), resolved_export)
149+
(name.clone(), ResolvedExport::new(local.referenced, local.came_from_commonjs))
154150
})
155151
.collect::<FxHashMap<_, _>>();
156152

@@ -222,15 +218,15 @@ impl LinkStage<'_> {
222218
{
223219
let main_ref = self.symbols.canonical_ref_for(resolved_export.symbol_ref);
224220

225-
for ambiguous_ref in potentially_ambiguous_symbol_refs {
221+
for ambiguous_ref in potentially_ambiguous_symbol_refs.iter() {
226222
let ambiguous_ref = self.symbols.canonical_ref_for(*ambiguous_ref);
227223
if main_ref != ambiguous_ref {
228224
continue 'next_export;
229225
}
230226
}
231227
}
232228
sorted_and_non_ambiguous_resolved_exports
233-
.push((exported_name.clone(), resolved_export.came_from_cjs));
229+
.push((exported_name.clone(), resolved_export.came_from_commonjs));
234230
}
235231
sorted_and_non_ambiguous_resolved_exports.sort_unstable();
236232
meta.sorted_and_non_ambiguous_resolved_exports =
@@ -319,12 +315,19 @@ impl LinkStage<'_> {
319315
return;
320316
};
321317

322-
let is_cjsreexports = module.ast_usage.contains(EcmaModuleAstUsage::IsCjsReexport);
323-
324-
let cjs_reexport_module =
325-
is_cjsreexports.then(|| module.import_records.first().unwrap().into_resolved_module());
318+
let cjs_reexport_modules: Vec<ModuleIdx> =
319+
if module.ast_usage.contains(EcmaModuleAstUsage::IsCjsReexport) {
320+
module
321+
.ecma_view
322+
.cjs_reexport_import_record_ids
323+
.iter()
324+
.filter_map(|&rec_idx| module.import_records[rec_idx].resolved_module)
325+
.collect()
326+
} else {
327+
vec![]
328+
};
326329

327-
for dep_id in module.star_export_module_ids().chain(cjs_reexport_module) {
330+
for dep_id in module.star_export_module_ids().chain(cjs_reexport_modules) {
328331
let Module::Normal(dep_module) = &normal_modules[dep_id] else {
329332
continue;
330333
};
@@ -348,20 +351,27 @@ impl LinkStage<'_> {
348351
// We have filled `resolve_exports` with `named_exports`. If the export is already exists, it means that the importer
349352
// has a named export with the same name. So the export from dep module is shadowed.
350353
if let Some(resolved_export) = resolve_exports.get_mut(exported_name) {
351-
if named_export.referenced != resolved_export.symbol_ref && !resolved_export.came_from_cjs
352-
{
353-
resolved_export
354-
.potentially_ambiguous_symbol_refs
355-
.get_or_insert(Vec::default())
356-
.push(named_export.referenced);
354+
if named_export.referenced != resolved_export.symbol_ref {
355+
if resolved_export.came_from_commonjs || named_export.came_from_commonjs {
356+
// CJS conflict: at least one side came from CJS (e.g., conditional re-exports
357+
// mixing ESM and CJS targets). Track these separately — they're expected runtime
358+
// branches, not static ambiguity errors.
359+
resolved_export
360+
.cjs_conflicting_symbol_refs
361+
.get_or_insert(Box::default())
362+
.push(named_export.referenced);
363+
} else {
364+
resolved_export
365+
.potentially_ambiguous_symbol_refs
366+
.get_or_insert(Box::default())
367+
.push(named_export.referenced);
368+
}
357369
}
358370
} else {
359-
let resolved_export = ResolvedExport {
360-
symbol_ref: named_export.referenced,
361-
potentially_ambiguous_symbol_refs: None,
362-
came_from_cjs: named_export.came_from_commonjs,
363-
};
364-
resolve_exports.insert(exported_name.clone(), resolved_export);
371+
resolve_exports.insert(
372+
exported_name.clone(),
373+
ResolvedExport::new(named_export.referenced, named_export.came_from_commonjs),
374+
);
365375
}
366376
}
367377

@@ -422,7 +432,7 @@ impl LinkStage<'_> {
422432
let name = &prop.name;
423433
let meta = &self.metas[canonical_ref_owner.idx];
424434
let export_symbol = meta.resolved_exports.get(name).and_then(|resolved_export| {
425-
(!resolved_export.came_from_cjs).then_some(resolved_export)
435+
(!resolved_export.came_from_commonjs).then_some(resolved_export)
426436
});
427437
let Some(export_symbol) = export_symbol else {
428438
// when we try to resolve `a.b.c`, and found that `b` is not exported by module
@@ -539,7 +549,7 @@ impl LinkStage<'_> {
539549
.resolved_exports
540550
.get(&member_expr_ref.prop_and_span_list[cursor].name)
541551
.and_then(|resolved_export| {
542-
resolved_export.came_from_cjs.then_some(resolved_export)
552+
resolved_export.came_from_commonjs.then_some(resolved_export)
543553
})
544554
})
545555
{
@@ -611,7 +621,7 @@ impl LinkStage<'_> {
611621
self.metas[*cjs_module_idx]
612622
.resolved_exports
613623
.values()
614-
.filter(|e| e.came_from_cjs)
624+
.filter(|e| e.came_from_commonjs)
615625
.map(|e| e.symbol_ref),
616626
);
617627
}
@@ -842,7 +852,7 @@ impl BindImportsAndExportsContext<'_> {
842852
Specifier::Literal(literal_imported) => {
843853
match self.metas[importee_id].resolved_exports.get(literal_imported) {
844854
Some(export) => {
845-
if export.came_from_cjs {
855+
if export.came_from_commonjs {
846856
ImportStatus::DynamicFallbackWithCommonjsReference {
847857
namespace_ref: importee.namespace_object_ref,
848858
commonjs_symbol: export.symbol_ref,
@@ -853,7 +863,8 @@ impl BindImportsAndExportsContext<'_> {
853863
symbol: export.symbol_ref,
854864
potentially_ambiguous_export_star_refs: export
855865
.potentially_ambiguous_symbol_refs
856-
.clone()
866+
.as_deref()
867+
.cloned()
857868
.unwrap_or_default(),
858869
}
859870
}
@@ -1023,15 +1034,19 @@ impl BindImportsAndExportsContext<'_> {
10231034
if let MatchImportKind::Normal(MatchImportKindNormal { symbol, .. }) = ret {
10241035
return MatchImportKind::Ambiguous {
10251036
symbol_ref: symbol,
1026-
potentially_ambiguous_symbol_refs: ambiguous_results
1027-
.iter()
1028-
.filter_map(|kind| match *kind {
1029-
MatchImportKind::Normal(MatchImportKindNormal { symbol, .. }) => Some(symbol),
1030-
MatchImportKind::Namespace { namespace_ref }
1031-
| MatchImportKind::NormalAndNamespace { namespace_ref, .. } => Some(namespace_ref),
1032-
_ => None,
1033-
})
1034-
.collect(),
1037+
potentially_ambiguous_symbol_refs: Box::new(
1038+
ambiguous_results
1039+
.iter()
1040+
.filter_map(|kind| match *kind {
1041+
MatchImportKind::Normal(MatchImportKindNormal { symbol, .. }) => Some(symbol),
1042+
MatchImportKind::Namespace { namespace_ref }
1043+
| MatchImportKind::NormalAndNamespace { namespace_ref, .. } => {
1044+
Some(namespace_ref)
1045+
}
1046+
_ => None,
1047+
})
1048+
.collect(),
1049+
),
10351050
};
10361051
}
10371052

0 commit comments

Comments
 (0)