Skip to content

Commit 7dd60cb

Browse files
fix: 8812
1 parent e0dd665 commit 7dd60cb

File tree

10 files changed

+261
-117
lines changed

10 files changed

+261
-117
lines changed

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

Lines changed: 139 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -269,145 +269,167 @@ impl GenerateStage<'_> {
269269
// guaranteed.
270270
return;
271271
}
272-
chunk_graph
273-
.chunk_table
274-
.iter_mut()
275-
.filter(|chunk| matches!(chunk.kind, ChunkKind::EntryPoint { .. }))
276-
.for_each(|chunk| {
277-
let ChunkKind::EntryPoint { module: entry_module, .. } = &chunk.kind else {
278-
return;
279-
};
280-
// After modules in chunk is sorted, it is always sorted by execution order whatever the
281-
// `chunk_modules_order` is `exec_order` or `module_id`. Because for `module_id` we only sort
282-
// by `module_id` for side effects free leaf modules, those should always execute first and
283-
// has no wrapping.
284-
let mut wrapped_modules = vec![];
285-
// If a none wrapped module has higher execution order than a wrapped module
286-
// we called the none wrapped module depended on the wrapped module(e.g. the none wrapped
287-
// module may depended on a global variable initialization in the wrapped module, however
288-
// the wrapped module are usually lazy evaluate). So we need to adjust the initialization
289-
// order
290-
// manually.
291-
let imported_symbol_owner_from_other_chunk = chunk
292-
.imports_from_other_chunks
293-
.iter()
294-
.flat_map(|(_, import_items)| {
295-
import_items
296-
.iter()
297-
.map(|item| self.link_output.symbol_db.canonical_ref_for(item.import_ref).owner)
298-
})
299-
.filter_map(|idx| {
300-
let module = self.link_output.module_table[idx].as_normal()?;
301-
(!self.link_output.metas[module.idx].original_wrap_kind().is_none()).then_some(idx)
302-
})
303-
.collect::<FxHashSet<_>>();
304-
let chunk_module_to_exec_order = chunk
305-
.modules
306-
.iter()
307-
.chain(imported_symbol_owner_from_other_chunk.iter())
308-
.map(|idx| (*idx, self.link_output.module_table[*idx].exec_order()))
309-
.collect::<FxHashMap<_, _>>();
310-
311-
// the key is the module_idx of none wrapped module
312-
// the value is the how many wrapped modules did the none wrapped module depends on.
313-
// when getting all depended wrapped modules, just use wrapped_modules[0..none_wrapped_module_to_wrapped_dependency_length[none_wrap_module_idx]].
314-
let mut none_wrapped_module_to_wrapped_dependency_length = FxHashMap::default();
315-
let js_import_order = self.js_import_order(*entry_module, &chunk_module_to_exec_order);
316-
for idx in js_import_order {
317-
match self.link_output.metas[idx].original_wrap_kind() {
318-
WrapKind::None => {
319-
if !wrapped_modules.is_empty() {
320-
none_wrapped_module_to_wrapped_dependency_length.insert(idx, wrapped_modules.len());
321-
}
322-
}
323-
WrapKind::Cjs | WrapKind::Esm => {
324-
wrapped_modules.push(idx);
272+
chunk_graph.chunk_table.iter_mut().for_each(|chunk| {
273+
// Determine DFS roots based on chunk kind.
274+
// For entry chunks, the root is the entry module.
275+
// For common chunks, roots are modules not imported by any other module in the chunk.
276+
let roots: Vec<ModuleIdx> = match &chunk.kind {
277+
ChunkKind::EntryPoint { module, .. } => vec![*module],
278+
ChunkKind::Common => {
279+
let chunk_modules_set: FxHashSet<ModuleIdx> = chunk.modules.iter().copied().collect();
280+
let imported_in_chunk: FxHashSet<ModuleIdx> = chunk
281+
.modules
282+
.iter()
283+
.filter_map(|&idx| self.link_output.module_table[idx].as_normal())
284+
.flat_map(|normal| &normal.import_records)
285+
.filter_map(|rec| {
286+
let resolved_module = rec.resolved_module?;
287+
(rec.kind == ImportKind::Import && chunk_modules_set.contains(&resolved_module))
288+
.then_some(resolved_module)
289+
})
290+
.collect();
291+
let mut roots: Vec<ModuleIdx> =
292+
chunk.modules.iter().filter(|idx| !imported_in_chunk.contains(idx)).copied().collect();
293+
roots.sort_unstable_by_key(|idx| self.link_output.module_table[*idx].exec_order());
294+
roots
295+
}
296+
};
297+
298+
if roots.is_empty() {
299+
return;
300+
}
301+
302+
// After modules in chunk is sorted, it is always sorted by execution order whatever the
303+
// `chunk_modules_order` is `exec_order` or `module_id`. Because for `module_id` we only sort
304+
// by `module_id` for side effects free leaf modules, those should always execute first and
305+
// has no wrapping.
306+
let mut wrapped_modules = vec![];
307+
// If a none wrapped module has higher execution order than a wrapped module
308+
// we called the none wrapped module depended on the wrapped module(e.g. the none wrapped
309+
// module may depended on a global variable initialization in the wrapped module, however
310+
// the wrapped module are usually lazy evaluate). So we need to adjust the initialization
311+
// order
312+
// manually.
313+
let imported_symbol_owner_from_other_chunk = chunk
314+
.imports_from_other_chunks
315+
.iter()
316+
.flat_map(|(_, import_items)| {
317+
import_items
318+
.iter()
319+
.map(|item| self.link_output.symbol_db.canonical_ref_for(item.import_ref).owner)
320+
})
321+
.filter_map(|idx| {
322+
let module = self.link_output.module_table[idx].as_normal()?;
323+
(!self.link_output.metas[module.idx].original_wrap_kind().is_none()).then_some(idx)
324+
})
325+
.collect::<FxHashSet<_>>();
326+
let chunk_module_to_exec_order = chunk
327+
.modules
328+
.iter()
329+
.chain(imported_symbol_owner_from_other_chunk.iter())
330+
.map(|idx| (*idx, self.link_output.module_table[*idx].exec_order()))
331+
.collect::<FxHashMap<_, _>>();
332+
333+
// the key is the module_idx of none wrapped module
334+
// the value is the how many wrapped modules did the none wrapped module depends on.
335+
// when getting all depended wrapped modules, just use wrapped_modules[0..none_wrapped_module_to_wrapped_dependency_length[none_wrap_module_idx]].
336+
let mut none_wrapped_module_to_wrapped_dependency_length = FxHashMap::default();
337+
let js_import_order = self.js_import_order(&roots, &chunk_module_to_exec_order);
338+
for idx in js_import_order {
339+
match self.link_output.metas[idx].original_wrap_kind() {
340+
WrapKind::None => {
341+
if !wrapped_modules.is_empty() {
342+
none_wrapped_module_to_wrapped_dependency_length.insert(idx, wrapped_modules.len());
325343
}
326344
}
345+
WrapKind::Cjs | WrapKind::Esm => {
346+
wrapped_modules.push(idx);
347+
}
327348
}
328-
// All modules that we need to ensure the initialization order.
329-
let mut modules_need_to_check: FxHashSet<ModuleIdx> = FxHashSet::default();
330-
let mut max_length = 0;
331-
for (none_wrapped, dep_length) in &none_wrapped_module_to_wrapped_dependency_length {
332-
modules_need_to_check.insert(*none_wrapped);
333-
max_length = max_length.max(*dep_length);
334-
}
335-
modules_need_to_check.extend(&wrapped_modules[0..max_length]);
349+
}
350+
// All modules that we need to ensure the initialization order.
351+
let mut modules_need_to_check: FxHashSet<ModuleIdx> = FxHashSet::default();
352+
let mut max_length = 0;
353+
for (none_wrapped, dep_length) in &none_wrapped_module_to_wrapped_dependency_length {
354+
modules_need_to_check.insert(*none_wrapped);
355+
max_length = max_length.max(*dep_length);
356+
}
357+
modules_need_to_check.extend(&wrapped_modules[0..max_length]);
336358

337-
if modules_need_to_check.is_empty() {
338-
// No wrapped modules or none wrapped modules that depends on wrapped modules, so we can
339-
// skip the initialization order check.
340-
return;
341-
}
359+
if modules_need_to_check.is_empty() {
360+
// No wrapped modules or none wrapped modules that depends on wrapped modules, so we can
361+
// skip the initialization order check.
362+
return;
363+
}
342364

343-
// Record each module in `modules_need_to_check` first init position.
344-
let mut module_init_position = FxIndexMap::default();
365+
// Record each module in `modules_need_to_check` first init position.
366+
let mut module_init_position = FxIndexMap::default();
345367

346-
for idx in &chunk.modules {
347-
let Some(module) = self.link_output.module_table[*idx].as_normal() else {
348-
continue;
349-
};
350-
module
351-
.import_records
352-
.iter_enumerated()
353-
.filter_map(|(rec_idx, rec)| {
354-
rec.resolved_module.map(|module_idx| (rec_idx, rec, module_idx))
355-
})
356-
.for_each(|(rec_idx, rec, module_idx)| {
357-
if rec.kind == ImportKind::Import && modules_need_to_check.contains(&module_idx) {
358-
module_init_position.entry(module_idx).or_insert((*idx, rec_idx));
359-
}
360-
});
361-
if module_init_position.len() == modules_need_to_check.len() {
362-
break;
363-
}
368+
for idx in &chunk.modules {
369+
let Some(module) = self.link_output.module_table[*idx].as_normal() else {
370+
continue;
371+
};
372+
module
373+
.import_records
374+
.iter_enumerated()
375+
.filter_map(|(rec_idx, rec)| {
376+
rec.resolved_module.map(|module_idx| (rec_idx, rec, module_idx))
377+
})
378+
.for_each(|(rec_idx, rec, module_idx)| {
379+
if rec.kind == ImportKind::Import && modules_need_to_check.contains(&module_idx) {
380+
module_init_position.entry(module_idx).or_insert((*idx, rec_idx));
381+
}
382+
});
383+
if module_init_position.len() == modules_need_to_check.len() {
384+
break;
364385
}
386+
}
365387

366-
let mut module_init_position = module_init_position.into_iter().collect_vec();
367-
module_init_position.sort_by_cached_key(|(idx, _)| chunk_module_to_exec_order[idx]);
368-
369-
let mut pending_transfer = vec![];
370-
let mut insert_map: FxHashMap<ModuleIdx, Vec<(ModuleIdx, ImportRecordIdx)>> =
371-
FxHashMap::default();
372-
let mut remove_map: FxHashMap<ModuleIdx, Vec<ImportRecordIdx>> = FxHashMap::default();
373-
for (module_idx, (importer_idx, rec_idx)) in module_init_position {
374-
match self.link_output.metas[module_idx].original_wrap_kind() {
375-
WrapKind::None => {
376-
if let Some(deps_length) =
377-
none_wrapped_module_to_wrapped_dependency_length.get(&module_idx)
378-
{
379-
let transfer_item = pending_transfer
380-
.extract_if(0.., |(midx, _, _)| wrapped_modules[0..*deps_length].contains(midx));
381-
for (_midx, iidx, ridx) in transfer_item {
382-
// Should always avoid transfer any initialization from a low execution order module to a high execution order module.
383-
if chunk_module_to_exec_order[&iidx] <= chunk_module_to_exec_order[&module_idx] {
384-
// If the module is the same, we can skip the transfer.
385-
continue;
386-
}
387-
insert_map.entry(module_idx).or_default().push((iidx, ridx));
388-
remove_map.entry(iidx).or_default().push(ridx);
388+
let mut module_init_position = module_init_position.into_iter().collect_vec();
389+
module_init_position.sort_by_cached_key(|(idx, _)| chunk_module_to_exec_order[idx]);
390+
391+
let mut pending_transfer = vec![];
392+
let mut insert_map: FxHashMap<ModuleIdx, Vec<(ModuleIdx, ImportRecordIdx)>> =
393+
FxHashMap::default();
394+
let mut remove_map: FxHashMap<ModuleIdx, Vec<ImportRecordIdx>> = FxHashMap::default();
395+
for (module_idx, (importer_idx, rec_idx)) in module_init_position {
396+
match self.link_output.metas[module_idx].original_wrap_kind() {
397+
WrapKind::None => {
398+
if let Some(deps_length) =
399+
none_wrapped_module_to_wrapped_dependency_length.get(&module_idx)
400+
{
401+
let transfer_item = pending_transfer
402+
.extract_if(0.., |(midx, _, _)| wrapped_modules[0..*deps_length].contains(midx));
403+
for (_midx, iidx, ridx) in transfer_item {
404+
// Should always avoid transfer any initialization from a low execution order module to a high execution order module.
405+
if chunk_module_to_exec_order[&iidx] <= chunk_module_to_exec_order[&module_idx] {
406+
// If the module is the same, we can skip the transfer.
407+
continue;
389408
}
409+
insert_map.entry(module_idx).or_default().push((iidx, ridx));
410+
remove_map.entry(iidx).or_default().push(ridx);
390411
}
391412
}
392-
WrapKind::Cjs | WrapKind::Esm => {
393-
pending_transfer.push((module_idx, importer_idx, rec_idx));
394-
}
413+
}
414+
WrapKind::Cjs | WrapKind::Esm => {
415+
pending_transfer.push((module_idx, importer_idx, rec_idx));
395416
}
396417
}
397-
chunk.insert_map = insert_map;
398-
chunk.remove_map = remove_map;
399-
});
418+
}
419+
chunk.insert_map = insert_map;
420+
chunk.remove_map = remove_map;
421+
});
400422
}
401423

402424
/// Only considering module eager initialization order, both `require()` and `import()` are lazy
403425
/// initialization.
404426
fn js_import_order(
405427
&self,
406-
entry: ModuleIdx,
428+
roots: &[ModuleIdx],
407429
chunk_modules_map: &FxHashMap<ModuleIdx, u32>,
408430
) -> Vec<ModuleIdx> {
409431
// traverse module graph with depth-first search to determine the order of JS imports
410-
let mut stack = vec![entry];
432+
let mut stack: Vec<ModuleIdx> = roots.iter().copied().rev().collect();
411433
let mut visited = FxHashSet::default();
412434
let mut js_import_order = vec![];
413435
while let Some(module_idx) = stack.pop() {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"config": {
3+
"input": [
4+
{
5+
"name": "entry-a",
6+
"import": "entry-a.js"
7+
},
8+
{
9+
"name": "entry-b",
10+
"import": "entry-b.js"
11+
}
12+
]
13+
}
14+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
source: crates/rolldown_testing/src/integration_test.rs
3+
---
4+
# Assets
5+
6+
## entry-a.js
7+
8+
```js
9+
import { n as import_fake_core, t as useCore } from "./utils.js";
10+
//#region entry-a.js
11+
console.log("Entry A:", import_fake_core.default, useCore());
12+
//#endregion
13+
14+
```
15+
16+
## entry-b.js
17+
18+
```js
19+
import { n as import_fake_core, t as useCore } from "./utils.js";
20+
//#region entry-b.js
21+
console.log("Entry B:", import_fake_core.default, useCore());
22+
//#endregion
23+
24+
```
25+
26+
## utils.js
27+
28+
```js
29+
// HIDDEN [\0rolldown/runtime.js]
30+
//#region fake-core.cjs
31+
var require_fake_core = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32+
(function() {
33+
"use strict";
34+
var fakeCore = {
35+
registry: [],
36+
register: function(name) {
37+
this.registry.push(name);
38+
}
39+
};
40+
globalThis.fakeCore = fakeCore;
41+
if (typeof module === "object") try {
42+
module.exports = fakeCore;
43+
} catch (_e) {}
44+
})();
45+
}));
46+
//#endregion
47+
//#region fake-plugin-model.cjs
48+
var require_fake_plugin_model = /* @__PURE__ */ __commonJSMin((() => {
49+
(function() {
50+
"use strict";
51+
globalThis.fakeCore.register("dom-model");
52+
})();
53+
}));
54+
//#endregion
55+
//#region fake-plugin.cjs
56+
var require_fake_plugin = /* @__PURE__ */ __commonJSMin((() => {
57+
require_fake_plugin_model();
58+
}));
59+
//#endregion
60+
//#region wrapper.js
61+
var import_fake_core = /* @__PURE__ */ __toESM(require_fake_core());
62+
require_fake_plugin();
63+
//#endregion
64+
//#region utils.js
65+
function useCore() {
66+
return import_fake_core.default.registry;
67+
}
68+
//#endregion
69+
export { import_fake_core as n, useCore as t };
70+
71+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { fakeCore } from './wrapper.js';
2+
import { useCore } from './utils.js';
3+
console.log('Entry A:', fakeCore, useCore());
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { fakeCore } from './wrapper.js';
2+
import { useCore } from './utils.js';
3+
console.log('Entry B:', fakeCore, useCore());
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// CJS IIFE: sets globalThis.fakeCore, exports via module.exports.
2+
(function () {
3+
'use strict';
4+
var fakeCore = {
5+
registry: [],
6+
register: function (name) {
7+
this.registry.push(name);
8+
},
9+
};
10+
globalThis.fakeCore = fakeCore;
11+
if (typeof module === 'object') {
12+
try {
13+
module.exports = fakeCore;
14+
} catch (_e) {}
15+
}
16+
})();

0 commit comments

Comments
 (0)