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
use crate::stages::link_stage::{ModuleInclusionVec, ModuleNamespaceReasonVec, StmtInclusionVec};
use oxc_index::IndexVec;
use oxc_str::CompactStr;
use rolldown_common::{
ConcatenateWrappedModuleKind, EntryPointKind, ImportRecordIdx, MemberExprRefResolutionMap,
ModuleIdx, ModuleNamespaceIncludedReason, 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>,
/// Because when `strictExecutionOrder` is enabled, all modules will be wrapped
/// we need to store the original wrap kind that used for
/// [rolldown::stages::generate_stage::code_splitting::GenerateStage::ensure_lazy_module_initialization_order] analysis
original_wrap_kind: WrapKind,
/// The `wrap_kind` used for linking and code generation.
/// Intent to make those two fields private, so that we could ensure they are mutated in a more
/// safe way.
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>,
// 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>,
// `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,
/// 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,
/// Set for a standalone wrapped (`WrapKind::Esm`) module whose `__esm` closure body is empty
/// (every top-level statement is a hoisted function declaration or a source-less export clause,
/// so nothing lands inside the wrapper closure). Calling such an `init_*` is a no-op, so init
/// call sites are marked `@__PURE__` and the default `dce-only` minifier drops them (and the
/// now-unused wrapper). Computed by [`crate::stages::generate_stage`]'s
/// `compute_wrapped_esm_init_metadata`.
pub init_is_noop: bool,
/// For each non-included top-level re-export statement (`export * from`, `export {x} from`,
/// `export * as ns from`) of an included `WrapKind::Esm` module: the ordered wrapped-ESM
/// modules whose `init_*()` calls must be emitted in its place to preserve execution order.
/// Computed by [`crate::stages::generate_stage`]'s `compute_wrapped_esm_init_metadata`;
/// consumed by the module finalizer.
pub transitive_esm_init_targets: FxHashMap<StmtInfoIdx, Vec<ModuleIdx>>,
}
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 original_wrap_kind(&self) -> WrapKind {
self.original_wrap_kind
}
/// Synchronize the `wrap_kind` with the original wrap kind.
#[inline]
pub fn sync_wrap_kind(&mut self, wrap_kind: WrapKind) {
self.original_wrap_kind = wrap_kind;
self.wrap_kind = wrap_kind;
}
/// Use this api with caution, ideally it should be only used for https://github.com/rolldown/rolldown/blob/76350f2b77364dbba29ba93562589a6eba6211dd/crates/rolldown/src/stages/link_stage/wrapping.rs?plain=1#L165-L185
/// override the wrapping kind when `strictExecutionOrder` is enabled.
#[inline]
pub fn update_wrap_kind(&mut self, wrap_kind: WrapKind) {
self.wrap_kind = wrap_kind;
}
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];
}
}