Skip to content

Commit 3d76b2b

Browse files
authored
fix(esm-library): deconflict module external helper names (#13695)
* fix(esm-library): deconflict module external helper names * test(esm-output): cover helper-shaped node-commonjs source * chore(esm-library): address clippy warnings * fix(esm-library): restore init fragment helper build * fix(esm-library): track init fragment top-level decl symbols
1 parent a769141 commit 3d76b2b

8 files changed

Lines changed: 341 additions & 60 deletions

File tree

crates/rspack_core/src/external_module.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ impl ExternalModule {
387387
InitFragmentKey::ModuleExternal("node-commonjs".to_string()),
388388
None,
389389
)
390+
.with_top_level_decl_symbols(vec![
391+
"__rspack_createRequire".into(),
392+
"__rspack_createRequire_require".into(),
393+
])
390394
.boxed(),
391395
);
392396
let (request, specifiers) = if let Some(request) = request {

crates/rspack_core/src/init_fragment.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ pub trait InitFragment<C>: IntoAny + DynHash + DynClone + Debug + Sync + Send {
171171
fn position(&self) -> i32;
172172

173173
fn key(&self) -> &InitFragmentKey;
174+
175+
fn top_level_decl_symbols(&self) -> &[Atom] {
176+
&[]
177+
}
174178
}
175179

176180
clone_trait_object!(InitFragment<GenerateContext<'_>>);
@@ -292,6 +296,7 @@ pub struct NormalInitFragment {
292296
position: i32,
293297
key: InitFragmentKey,
294298
end_content: Option<String>,
299+
top_level_decl_symbols: Vec<Atom>,
295300
}
296301

297302
impl NormalInitFragment {
@@ -308,8 +313,14 @@ impl NormalInitFragment {
308313
position,
309314
key,
310315
end_content,
316+
top_level_decl_symbols: Vec::new(),
311317
}
312318
}
319+
320+
pub fn with_top_level_decl_symbols(mut self, top_level_decl_symbols: Vec<Atom>) -> Self {
321+
self.top_level_decl_symbols = top_level_decl_symbols;
322+
self
323+
}
313324
}
314325

315326
impl<C> InitFragment<C> for NormalInitFragment {
@@ -331,6 +342,10 @@ impl<C> InitFragment<C> for NormalInitFragment {
331342
fn key(&self) -> &InitFragmentKey {
332343
&self.key
333344
}
345+
346+
fn top_level_decl_symbols(&self) -> &[Atom] {
347+
&self.top_level_decl_symbols
348+
}
334349
}
335350

336351
#[derive(Debug, Clone, Hash)]

crates/rspack_plugin_esm_library/src/link.rs

Lines changed: 182 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,72 @@ enum ExternalImportBinding {
7474
}
7575

7676
impl EsmLibraryPlugin {
77+
fn module_external_fragment_content(
78+
init_fragment: Box<dyn rspack_core::InitFragment<ChunkRenderContext>>,
79+
) -> Option<String> {
80+
if !matches!(init_fragment.key(), InitFragmentKey::ModuleExternal(_)) {
81+
return None;
82+
}
83+
84+
if let Ok(fragment) = init_fragment
85+
.clone()
86+
.into_any()
87+
.downcast::<ConditionalInitFragment>()
88+
{
89+
Some(fragment.content().to_owned())
90+
} else {
91+
init_fragment
92+
.clone()
93+
.contents(&mut ChunkRenderContext {})
94+
.ok()
95+
.map(|contents| contents.start)
96+
}
97+
}
98+
99+
fn collect_module_external_fragments_in_render_order<'a>(
100+
init_fragment_groups: impl IntoIterator<Item = &'a ChunkInitFragments>,
101+
) -> Vec<Box<dyn rspack_core::InitFragment<ChunkRenderContext>>> {
102+
let mut ordered_fragments = Vec::new();
103+
104+
for init_fragments in init_fragment_groups {
105+
for init_fragment in init_fragments {
106+
if !matches!(init_fragment.key(), InitFragmentKey::ModuleExternal(_)) {
107+
continue;
108+
}
109+
110+
ordered_fragments.push((
111+
init_fragment.stage(),
112+
init_fragment.position(),
113+
ordered_fragments.len(),
114+
init_fragment.key().clone(),
115+
init_fragment.clone(),
116+
));
117+
}
118+
}
119+
120+
ordered_fragments.sort_by(|a, b| {
121+
let stage = a.0.cmp(&b.0);
122+
if !stage.is_eq() {
123+
return stage;
124+
}
125+
let position = a.1.cmp(&b.1);
126+
if !position.is_eq() {
127+
return position;
128+
}
129+
a.2.cmp(&b.2)
130+
});
131+
132+
let mut rendered_keys = FxHashSet::default();
133+
let mut rendered_fragments = Vec::with_capacity(ordered_fragments.len());
134+
for (_, _, _, key, init_fragment) in ordered_fragments {
135+
if rendered_keys.insert(key) {
136+
rendered_fragments.push(init_fragment);
137+
}
138+
}
139+
140+
rendered_fragments
141+
}
142+
77143
fn parse_module_external_namespace_import(content: &str) -> Option<(RawImportSource, Atom)> {
78144
let content = content.trim_start();
79145
let content = content.strip_prefix("import * as ")?;
@@ -119,54 +185,10 @@ impl EsmLibraryPlugin {
119185
fn collect_module_external_namespace_imports_in_render_order<'a>(
120186
init_fragment_groups: impl IntoIterator<Item = &'a ChunkInitFragments>,
121187
) -> Vec<(RawImportSource, Atom)> {
122-
let mut ordered_imports = Vec::new();
123-
124-
for init_fragments in init_fragment_groups {
125-
for init_fragment in init_fragments {
126-
if !matches!(init_fragment.key(), InitFragmentKey::ModuleExternal(_)) {
127-
continue;
128-
}
129-
130-
let content = if let Ok(fragment) = init_fragment
131-
.clone()
132-
.into_any()
133-
.downcast::<ConditionalInitFragment>()
134-
{
135-
fragment.content().to_owned()
136-
} else {
137-
let Ok(contents) = init_fragment.clone().contents(&mut ChunkRenderContext {}) else {
138-
continue;
139-
};
140-
contents.start
141-
};
142-
143-
if let Some((source, local_name)) = Self::parse_module_external_namespace_import(&content) {
144-
ordered_imports.push((
145-
init_fragment.stage(),
146-
init_fragment.position(),
147-
ordered_imports.len(),
148-
source,
149-
local_name,
150-
));
151-
}
152-
}
153-
}
154-
155-
ordered_imports.sort_by(|a, b| {
156-
let stage = a.0.cmp(&b.0);
157-
if !stage.is_eq() {
158-
return stage;
159-
}
160-
let position = a.1.cmp(&b.1);
161-
if !position.is_eq() {
162-
return position;
163-
}
164-
a.2.cmp(&b.2)
165-
});
166-
167-
ordered_imports
188+
Self::collect_module_external_fragments_in_render_order(init_fragment_groups)
168189
.into_iter()
169-
.map(|(_, _, _, source, local_name)| (source, local_name))
190+
.filter_map(Self::module_external_fragment_content)
191+
.filter_map(|content| Self::parse_module_external_namespace_import(&content))
170192
.collect()
171193
}
172194

@@ -191,6 +213,25 @@ impl EsmLibraryPlugin {
191213
}
192214
}
193215

216+
#[cfg(test)]
217+
fn reserve_module_external_top_level_decls(
218+
init_fragments: &ChunkInitFragments,
219+
used_names: &mut FxHashSet<Atom>,
220+
) {
221+
Self::reserve_module_external_top_level_decls_in_render_order([init_fragments], used_names);
222+
}
223+
224+
fn reserve_module_external_top_level_decls_in_render_order<'a>(
225+
init_fragment_groups: impl IntoIterator<Item = &'a ChunkInitFragments>,
226+
used_names: &mut FxHashSet<Atom>,
227+
) {
228+
for init_fragment in
229+
Self::collect_module_external_fragments_in_render_order(init_fragment_groups)
230+
{
231+
used_names.extend(init_fragment.top_level_decl_symbols().iter().cloned());
232+
}
233+
}
234+
194235
fn assign_external_candidate_name(
195236
readable_identifier: &str,
196237
candidate_used_names: &mut FxHashSet<Atom>,
@@ -971,10 +1012,14 @@ var {} = {{}};
9711012
}
9721013
}
9731014
Self::reserve_module_external_namespace_import_locals_in_render_order(
974-
module_external_init_fragment_groups,
1015+
module_external_init_fragment_groups.iter().copied(),
9751016
&mut all_used_names,
9761017
Some(&mut chunk_link.module_external_namespace_imports),
9771018
);
1019+
Self::reserve_module_external_top_level_decls_in_render_order(
1020+
module_external_init_fragment_groups.iter().copied(),
1021+
&mut all_used_names,
1022+
);
9781023

9791024
// deconflict top level symbols
9801025
for id in chunk_link
@@ -3572,6 +3617,95 @@ mod tests {
35723617
assert!(chunk_used_names.is_empty());
35733618
}
35743619

3620+
#[test]
3621+
fn module_external_non_namespace_init_fragment_claims_top_level_decls() {
3622+
let init_fragments: ChunkInitFragments = vec![Box::new(rspack_core::NormalInitFragment::new(
3623+
"import { createRequire as __rspack_createRequire } from \"node:module\";\nconst __rspack_createRequire_require = __rspack_createRequire(import.meta.url);\n"
3624+
.into(),
3625+
rspack_core::InitFragmentStage::StageESMImports,
3626+
0,
3627+
InitFragmentKey::ModuleExternal("node-commonjs".into()),
3628+
None,
3629+
)
3630+
.with_top_level_decl_symbols(vec![
3631+
"__rspack_createRequire".into(),
3632+
"__rspack_createRequire_require".into(),
3633+
]))];
3634+
let mut chunk_used_names = FxHashSet::default();
3635+
3636+
EsmLibraryPlugin::reserve_module_external_top_level_decls(
3637+
&init_fragments,
3638+
&mut chunk_used_names,
3639+
);
3640+
3641+
assert!(chunk_used_names.contains(&Atom::from("__rspack_createRequire")));
3642+
assert!(chunk_used_names.contains(&Atom::from("__rspack_createRequire_require")));
3643+
}
3644+
3645+
#[test]
3646+
fn module_external_top_level_decls_keep_first_rendered_fragment() {
3647+
let init_fragments: ChunkInitFragments = vec![
3648+
Box::new(rspack_core::NormalInitFragment::new(
3649+
"import { createRequire as __rspack_createRequire_1 } from \"node:module\";\nconst __rspack_createRequire_require_1 = __rspack_createRequire_1(import.meta.url);\n"
3650+
.into(),
3651+
rspack_core::InitFragmentStage::StageESMImports,
3652+
1,
3653+
InitFragmentKey::ModuleExternal("node-commonjs".into()),
3654+
None,
3655+
)
3656+
.with_top_level_decl_symbols(vec![
3657+
"__rspack_createRequire_1".into(),
3658+
"__rspack_createRequire_require_1".into(),
3659+
])),
3660+
Box::new(rspack_core::NormalInitFragment::new(
3661+
"import { createRequire as __rspack_createRequire_0 } from \"node:module\";\nconst __rspack_createRequire_require_0 = __rspack_createRequire_0(import.meta.url);\n"
3662+
.into(),
3663+
rspack_core::InitFragmentStage::StageESMImports,
3664+
0,
3665+
InitFragmentKey::ModuleExternal("node-commonjs".into()),
3666+
None,
3667+
)
3668+
.with_top_level_decl_symbols(vec![
3669+
"__rspack_createRequire_0".into(),
3670+
"__rspack_createRequire_require_0".into(),
3671+
])),
3672+
];
3673+
let mut chunk_used_names = FxHashSet::default();
3674+
3675+
EsmLibraryPlugin::reserve_module_external_top_level_decls(
3676+
&init_fragments,
3677+
&mut chunk_used_names,
3678+
);
3679+
3680+
assert!(chunk_used_names.contains(&Atom::from("__rspack_createRequire_0")));
3681+
assert!(chunk_used_names.contains(&Atom::from("__rspack_createRequire_require_0")));
3682+
assert!(!chunk_used_names.contains(&Atom::from("__rspack_createRequire_1")));
3683+
assert!(!chunk_used_names.contains(&Atom::from("__rspack_createRequire_require_1")));
3684+
}
3685+
3686+
#[test]
3687+
fn module_external_var_init_fragment_claims_top_level_decl() {
3688+
let init_fragments: ChunkInitFragments = vec![Box::new(
3689+
rspack_core::NormalInitFragment::new(
3690+
"/* provided dependency */ var provided_identifier = __webpack_require__(\"./dep\");\n"
3691+
.into(),
3692+
rspack_core::InitFragmentStage::StageProvides,
3693+
1,
3694+
InitFragmentKey::ModuleExternal("provided provided_identifier".into()),
3695+
None,
3696+
)
3697+
.with_top_level_decl_symbols(vec!["provided_identifier".into()]),
3698+
)];
3699+
let mut chunk_used_names = FxHashSet::default();
3700+
3701+
EsmLibraryPlugin::reserve_module_external_top_level_decls(
3702+
&init_fragments,
3703+
&mut chunk_used_names,
3704+
);
3705+
3706+
assert!(chunk_used_names.contains(&Atom::from("provided_identifier")));
3707+
}
3708+
35753709
#[test]
35763710
fn external_candidate_name_does_not_claim_chunk_top_level_name() {
35773711
let module = ModuleIdentifier::from("test_module");

crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,21 @@ impl DependencyTemplate for ProvideDependencyTemplate {
194194
)
195195
};
196196

197-
init_fragments.push(Box::new(NormalInitFragment::new(
198-
format!(
199-
"/* provided dependency */ var {} = {}{};\n",
200-
dep.identifier,
201-
runtime_template.module_raw(compilation, dep.id(), dep.request(), dep.weak()),
202-
path_to_string(used_name.as_ref())
203-
),
204-
InitFragmentStage::StageProvides,
205-
1,
206-
InitFragmentKey::ModuleExternal(format!("provided {}", dep.identifier)),
207-
None,
208-
)));
197+
init_fragments.push(Box::new(
198+
NormalInitFragment::new(
199+
format!(
200+
"/* provided dependency */ var {} = {}{};\n",
201+
dep.identifier,
202+
runtime_template.module_raw(compilation, dep.id(), dep.request(), dep.weak()),
203+
path_to_string(used_name.as_ref())
204+
),
205+
InitFragmentStage::StageProvides,
206+
1,
207+
InitFragmentKey::ModuleExternal(format!("provided {}", dep.identifier)),
208+
None,
209+
)
210+
.with_top_level_decl_symbols(vec![dep.identifier.clone().into()]),
211+
));
209212
source.replace(dep.range.start, dep.range.end, dep.identifier.clone(), None);
210213
}
211214
}

crates/rspack_plugin_javascript/src/plugin/api_plugin.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ async fn render_module_content(
6161
InitFragmentKey::ModuleExternal("node-commonjs".to_string()),
6262
None,
6363
)
64+
.with_top_level_decl_symbols(vec![
65+
"__rspack_createRequire".into(),
66+
"__rspack_createRequire_require".into(),
67+
])
6468
.boxed(),
6569
);
6670
}

0 commit comments

Comments
 (0)