1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use crate::stages::link_stage::{ModuleInclusionVec, ModuleNamespaceReasonVec, StmtInclusionVec};
use oxc_index::IndexVec;
use oxc_str::CompactStr;
use rolldown_common::{
ConcatenateWrappedModuleKind, EntryPointKind, ImportRecordIdx, ImportRecordMeta,
MemberExprRefResolutionMap, ModuleIdx, ModuleNamespaceIncludedReason, OutputFormat,
ResolvedExport, RuntimeHelper, StmtInfoIdx, SymbolRef, WrapKind,
dynamic_import_usage::DynamicImportExportsUsage,
};
use rolldown_utils::IndexBitSet;
use rolldown_utils::indexmap::{FxIndexMap, FxIndexSet};
use rustc_hash::{FxHashMap, FxHashSet};
/// Module metadata about linking
#[derive(Debug, Default)]
#[expect(clippy::struct_excessive_bools)]
pub struct LinkingMetadata {
/// A module could be wrapped for some reasons, eg. cjs module need to be wrapped with commonjs runtime function.
/// The `wrap_ref` is the binding identifier that store return value of executed the wrapper function.
///
/// ## Example
///
/// ```js
/// // cjs.js
/// module.exports = {}
/// ```
///
/// will be transformed to
///
/// ```js
/// // cjs.js
/// var require_cjs = __commonJS({
/// 'cjs.js'(exports, module) {
/// module.exports = {}
///
/// }
/// });
/// ```
///
/// `wrapper_ref` is the `require_cjs` identifier in above example.
pub wrapper_ref: Option<SymbolRef>,
pub wrapper_stmt_info: Option<StmtInfoIdx>,
/// The module representation decided during linking.
wrap_kind: WrapKind,
// Store the export info for each module, including export named declaration and export star declaration.
pub resolved_exports: FxHashMap<CompactStr, ResolvedExport>,
/// Store the names of exclude ambiguous resolved exports.
/// It will be used to generate chunk exports and module namespace binding.
/// The second element means if the export is came from commonjs module.
pub sorted_and_non_ambiguous_resolved_exports: FxIndexMap<CompactStr, bool>,
/// Direct `export *` record that first contributed each flattened resolved export.
pub star_export_record_by_name: FxHashMap<CompactStr, ImportRecordIdx>,
// If a esm module has export star from commonjs, it will be marked as ESMWithDynamicFallback at linker.
// The unknown export name will be resolved at runtime.
// esbuild add it to `ExportKind`, but the linker shouldn't mutate the module.
pub has_dynamic_exports: bool,
pub shimmed_missing_exports: FxHashMap<CompactStr, SymbolRef>,
pub required_by_other_module: bool,
// Entry chunks need to generate code that doesn't belong to any module. This is the list of symbols are referenced by the
// generated code. Tree-shaking will cares about these symbols to make sure they are not removed.
// The second element means if the symbol is a facade symbol.
pub referenced_symbols_by_entry_point_chunk: Vec<(SymbolRef, bool)>,
/// The dependencies of the module. It means if you want include this module, you need to include these dependencies too.
pub dependencies: FxIndexSet<ModuleIdx>,
/// The subset of module graph edges that force the target module to be loaded when this module
/// executes, used by code splitting to compute per-entry reachability
/// (`determine_reachable_modules_for_entry`):
///
/// - modules owning the canonical symbols referenced by this module's included statements
/// (these are what become cross-chunk symbol imports), and
/// - import-record targets whose evaluation has side effects, mirroring both
/// `include_side_effectful_dependencies` in tree-shaking and the bare-import emission in
/// `compute_cross_chunk_links`.
///
/// Unlike [`Self::dependencies`], a side-effect-free module imported only for bindings that
/// canonically resolve elsewhere (e.g. a pure barrel re-exporting them) is *not* a load
/// dependency, so an entry that never uses the barrel's own code doesn't pull the barrel (or
/// its subtree) into its chunk group (#8920). Populated by `patch_module_dependencies`; with
/// tree-shaking disabled it equals [`Self::dependencies`].
pub load_dependencies: FxIndexSet<ModuleIdx>,
/// Retained evaluation dependencies, excluding entry targets kept only for placement.
pub execution_dependencies: FxIndexSet<ModuleIdx>,
// `None` the member expression resolve to a ambiguous export.
pub resolved_member_expr_refs: MemberExprRefResolutionMap,
pub star_exports_from_external_modules: Vec<ImportRecordIdx>,
pub is_tla_or_contains_tla_dependency: bool,
pub concatenated_wrapped_module_kind: ConcatenateWrappedModuleKind,
/// Used to to track a facade binding referenced cjs module
/// included reexport symbol from commonjs module
pub named_import_to_cjs_module: FxHashMap<SymbolRef, ModuleIdx>,
pub import_record_ns_to_cjs_module: FxHashMap<SymbolRef, ModuleIdx>,
/// Currently our symbol link system could only link one symbol to another one, but for commonjs
/// tree shaking, when one symbol was linked it may not only link the namespace ref symbol, and
/// also need to link the exported facade symbol.
pub included_commonjs_export_symbol: FxHashSet<SymbolRef>,
pub depended_runtime_helper: RuntimeHelper,
/// Whether this module needs the runtime chunk loaded for its side effects.
/// Set when the runtime module has side effects (e.g. dev/HMR mode).
pub has_side_effectful_runtime_dep: bool,
pub module_namespace_included_reason: ModuleNamespaceIncludedReason,
/// Final decision on whether this module's namespace object is retained in the output.
/// Computed by [`crate::stages::generate_stage`]'s `finalized_module_namespace_ref_usage`
/// from `module_namespace_included_reason` together with the module's `exports_kind` and
/// `has_dynamic_exports`; only meaningful for passes that run after it.
pub namespace_included: bool,
/// Tracks which statements in this module are included after tree-shaking.
/// Each entry corresponds to a statement in the module's `stmt_infos`.
pub stmt_info_included: IndexBitSet<StmtInfoIdx>,
/// Tracks whether the module is included after tree-shaking.
pub is_included: bool,
}
impl LinkingMetadata {
pub fn canonical_exports(
&self,
needs_commonjs_export: bool,
) -> impl Iterator<Item = (&CompactStr, &ResolvedExport)> {
self.sorted_and_non_ambiguous_resolved_exports.iter().filter_map(
move |(name, came_from_cjs)| {
(needs_commonjs_export || !came_from_cjs).then_some((name, &self.resolved_exports[name]))
},
)
}
pub fn is_canonical_exports_empty(&self) -> bool {
self.sorted_and_non_ambiguous_resolved_exports.is_empty()
}
#[inline]
pub fn wrap_kind(&self) -> WrapKind {
self.wrap_kind
}
#[inline]
pub fn set_wrap_kind(&mut self, wrap_kind: WrapKind) {
self.wrap_kind = wrap_kind;
}
/// Whether the namespace-object declaration will emit a `__reExport(ns, <external>)` call for
/// this `export * from <external>` record when the namespace is rendered.
///
/// This is the single source of truth for the emission decision: the module finalizer emits
/// the call through it, and any pass that needs to predict the emission must call it instead
/// of re-deriving the condition. In ESM output an entry-level external star
/// re-export is flattened to a chunk-level `export * from '<external>'` statement instead, so
/// no runtime call is needed — unless the namespace object is genuinely observed
/// ([`ModuleNamespaceIncludedReason::Unknown`]), in which case the namespace must still merge
/// the external's exports at runtime.
pub fn ns_star_external_re_export_emitted(
&self,
rec_meta: ImportRecordMeta,
format: OutputFormat,
) -> bool {
match format {
OutputFormat::Esm => {
!rec_meta.contains(ImportRecordMeta::EntryLevelExternal)
|| self.module_namespace_included_reason.contains(ModuleNamespaceIncludedReason::Unknown)
}
OutputFormat::Cjs | OutputFormat::Iife | OutputFormat::Umd => true,
}
}
pub fn referenced_canonical_exports_symbols<'b, 'a: 'b>(
&'b self,
module_idx: ModuleIdx,
entry_point_kind: EntryPointKind,
dynamic_import_exports_usage_map: &'a FxHashMap<ModuleIdx, DynamicImportExportsUsage>,
needs_commonjs_export: bool,
) -> impl Iterator<Item = (&'b CompactStr, &'b ResolvedExport)> + 'b {
let partial_used_exports = match entry_point_kind {
rolldown_common::EntryPointKind::UserDefined
| rolldown_common::EntryPointKind::EmittedUserDefined => None,
rolldown_common::EntryPointKind::DynamicImport => {
dynamic_import_exports_usage_map.get(&module_idx).and_then(|usage| match usage {
DynamicImportExportsUsage::Complete => None,
DynamicImportExportsUsage::Partial(set) => Some(set),
DynamicImportExportsUsage::Single(_) => unreachable!(),
})
}
};
self.canonical_exports(needs_commonjs_export).filter(
move |(name, _)| match partial_used_exports {
Some(set) => set.contains(name.as_str()),
None => true,
},
)
}
}
pub type LinkingMetadataVec = IndexVec<ModuleIdx, LinkingMetadata>;
/// Extracts inclusion information from all module metas.
///
/// # Warning
/// This function uses `mem::take` to move `stmt_info_included` out of each meta,
/// leaving it with its default value. `is_included` and `module_namespace_included_reason`
/// are copied but not reset.
pub fn linking_metadata_vec_to_included_info(
metas: &mut LinkingMetadataVec,
) -> (StmtInclusionVec, ModuleInclusionVec, ModuleNamespaceReasonVec) {
let stmt_info_included_vec: StmtInclusionVec =
metas.iter_mut().map(|meta| std::mem::take(&mut meta.stmt_info_included)).collect();
let mut module_included_vec: ModuleInclusionVec = IndexBitSet::new(metas.len());
for (idx, meta) in metas.iter_enumerated() {
if meta.is_included {
module_included_vec.set_bit(idx);
}
}
let module_namespace_reason_vec: ModuleNamespaceReasonVec =
metas.iter().map(|meta| meta.module_namespace_included_reason).collect();
(stmt_info_included_vec, module_included_vec, module_namespace_reason_vec)
}
/// Restores inclusion information back to the module metas.
///
/// This is the reverse operation of `linking_metadata_vec_to_included_info`.
/// It should be called after modifications are done to restore the taken data.
pub fn included_info_to_linking_metadata_vec(
metas: &mut LinkingMetadataVec,
mut stmt_info_included_vec: StmtInclusionVec,
module_included_vec: &ModuleInclusionVec,
module_namespace_reason_vec: &ModuleNamespaceReasonVec,
) {
for (idx, meta) in metas.iter_mut_enumerated() {
meta.stmt_info_included = std::mem::take(&mut stmt_info_included_vec[idx]);
meta.is_included = module_included_vec.has_bit(idx);
meta.module_namespace_included_reason = module_namespace_reason_vec[idx];
}
}