Skip to content

Commit e1a267b

Browse files
authored
fix: build chunk graph when dynamic entry with depend-on chain (#11486)
1 parent e4dfc8d commit e1a267b

File tree

6 files changed

+109
-8
lines changed

6 files changed

+109
-8
lines changed

crates/rspack_core/src/build_chunk_graph/incremental.rs

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use crate::{
1717

1818
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1919
pub enum ChunkReCreation {
20-
Entry(String),
20+
Entry {
21+
name: String,
22+
depend_on: Option<Vec<String>>,
23+
},
2124
Normal(NormalChunkRecreation),
2225
}
2326

@@ -32,7 +35,10 @@ pub struct NormalChunkRecreation {
3235
impl ChunkReCreation {
3336
pub fn rebuild(self, splitter: &mut CodeSplitter, compilation: &mut Compilation) -> Result<()> {
3437
match self {
35-
ChunkReCreation::Entry(entry) => {
38+
ChunkReCreation::Entry {
39+
name: entry,
40+
depend_on: _,
41+
} => {
3642
let input = splitter.prepare_entry_input(&entry, compilation)?;
3743
splitter.set_entry_runtime_and_depend_on(&entry, compilation)?;
3844
splitter.prepare_entries(std::iter::once(input).collect(), compilation)?;
@@ -249,7 +255,10 @@ impl CodeSplitter {
249255
&& chunk_group.is_initial()
250256
&& chunk_group.parents.is_empty()
251257
{
252-
edges = vec![ChunkReCreation::Entry(name)];
258+
edges = vec![ChunkReCreation::Entry {
259+
name,
260+
depend_on: None,
261+
}];
253262
}
254263

255264
// If the invalidated chunk group is an entrypoint, we must also invalidate any
@@ -567,14 +576,20 @@ impl CodeSplitter {
567576

568577
edges = compilation
569578
.entries
570-
.keys()
571-
.map(|name| ChunkReCreation::Entry(name.to_owned()))
579+
.iter()
580+
.map(|(name, entry_data)| ChunkReCreation::Entry {
581+
name: name.to_owned(),
582+
depend_on: entry_data.options.depend_on.clone(),
583+
})
572584
.collect();
573585
} else {
574586
'outer: for (entry, data) in &compilation.entries {
575587
if !compilation.entrypoints.contains_key(entry) {
576588
// new entry
577-
edges.push(ChunkReCreation::Entry(entry.to_owned()));
589+
edges.push(ChunkReCreation::Entry {
590+
name: entry.to_owned(),
591+
depend_on: data.options.depend_on.clone(),
592+
});
578593
continue 'outer;
579594
}
580595

@@ -594,21 +609,60 @@ impl CodeSplitter {
594609
for m in &curr_modules {
595610
// there is new entry
596611
if !prev_entry_modules.contains(m) {
597-
edges.push(ChunkReCreation::Entry(entry.to_owned()));
612+
edges.push(ChunkReCreation::Entry {
613+
name: entry.to_owned(),
614+
depend_on: data.options.depend_on.clone(),
615+
});
598616
continue 'outer;
599617
}
600618
}
601619

602620
for m in prev_entry_modules {
603621
if !curr_modules.contains(&m) {
604622
// there is removed entry modules
605-
edges.push(ChunkReCreation::Entry(entry.to_owned()));
623+
edges.push(ChunkReCreation::Entry {
624+
name: entry.to_owned(),
625+
depend_on: data.options.depend_on.clone(),
626+
});
606627
continue 'outer;
607628
}
608629
}
609630
}
610631
}
611632

633+
// For entries with dependencies (`dependOn`), we must ensure that the dependency
634+
// is processed before the entry that depends on it. This is a topological sort.
635+
edges.sort_unstable_by(|a, b| {
636+
let (a_name, a_depend_on) = if let ChunkReCreation::Entry { name, depend_on } = a {
637+
(name, depend_on)
638+
} else {
639+
return std::cmp::Ordering::Equal;
640+
};
641+
642+
let (b_name, b_depend_on) = if let ChunkReCreation::Entry { name, depend_on } = b {
643+
(name, depend_on)
644+
} else {
645+
return std::cmp::Ordering::Equal;
646+
};
647+
648+
let a_depends_on_b = a_depend_on
649+
.as_deref()
650+
.is_some_and(|deps| deps.contains(b_name));
651+
let b_depends_on_a = b_depend_on
652+
.as_deref()
653+
.is_some_and(|deps| deps.contains(a_name));
654+
655+
// If a depends on b, b must come first (a > b).
656+
// If b depends on a, a must come first (a < b).
657+
// If there's no direct dependency, their relative order doesn't matter.
658+
if a_depends_on_b {
659+
std::cmp::Ordering::Greater
660+
} else if b_depends_on_a {
661+
std::cmp::Ordering::Less
662+
} else {
663+
std::cmp::Ordering::Equal
664+
}
665+
});
612666
for edge in edges {
613667
edge.rebuild(self, compilation)?;
614668
}

packages/rspack-test-tools/tests/watchCases/chunks/dynamic-entry-with-depend-on-chain/0/main-app.js

Whitespace-only changes.

packages/rspack-test-tools/tests/watchCases/chunks/dynamic-entry-with-depend-on-chain/1/_app.js

Whitespace-only changes.

packages/rspack-test-tools/tests/watchCases/chunks/dynamic-entry-with-depend-on-chain/1/_error.js

Whitespace-only changes.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
let ERROR;
5+
6+
/** @type {import("@rspack/core").Configuration} */
7+
module.exports = {
8+
entry() {
9+
if (fs.existsSync(ERROR)) {
10+
return {
11+
"main-app": "./main-app.js",
12+
"pages/_error": {
13+
import: "./_error.js",
14+
dependOn: "pages/_app"
15+
},
16+
"pages/_app": {
17+
import: "./_app.js",
18+
dependOn: "main-app"
19+
},
20+
}
21+
}
22+
return {
23+
"main-app": "./main-app.js"
24+
}
25+
},
26+
output: {
27+
filename: "[name].js"
28+
},
29+
plugins: [
30+
{
31+
apply(compiler) {
32+
ERROR = path.join(compiler.context, "_error.js");
33+
34+
compiler.hooks.finishMake.tap("PLUGIN", compilation => {
35+
if (!fs.existsSync(ERROR)) {
36+
compilation.missingDependencies.add(ERROR);
37+
}
38+
});
39+
}
40+
}
41+
]
42+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
findBundle() {
3+
return [];
4+
}
5+
}

0 commit comments

Comments
 (0)