Skip to content

Commit e3adfda

Browse files
authored
fix: mf hoist should record mutation (#11560)
1 parent b44b75e commit e3adfda

File tree

14 files changed

+166
-147
lines changed

14 files changed

+166
-147
lines changed

crates/rspack_core/src/build_chunk_graph/code_splitter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,9 @@ Or do you want to use the entrypoints '{name}' and '{runtime}' independently on
14981498
compilation
14991499
.named_chunk_groups
15001500
.insert(name.to_owned(), entrypoint.ukey);
1501+
compilation
1502+
.named_chunks
1503+
.insert(name.to_owned(), chunk.ukey());
15011504
}
15021505

15031506
entrypoint.connect_chunk(chunk);

crates/rspack_core/src/compiler/compilation.rs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,41 +1713,10 @@ impl Compilation {
17131713
for revoked_module in revoked_modules {
17141714
self.cgm_hash_artifact.remove(&revoked_module);
17151715
}
1716-
let mg = self.get_module_graph();
1717-
let mut modules = mutations.get_affected_modules_with_module_graph(&mg);
1718-
for mutation in mutations.iter() {
1719-
match mutation {
1720-
Mutation::ModuleSetAsync { module } => {
1721-
modules.insert(*module);
1722-
}
1723-
Mutation::ModuleSetId { module } => {
1724-
modules.insert(*module);
1725-
modules.extend(
1726-
mg.get_incoming_connections(module)
1727-
.filter_map(|c| c.original_module_identifier),
1728-
);
1729-
}
1730-
Mutation::ChunkAdd { chunk } => {
1731-
modules.extend(self.chunk_graph.get_chunk_modules_identifier(chunk));
1732-
}
1733-
Mutation::ChunkSetId { chunk } => {
1734-
let chunk = self.chunk_by_ukey.expect_get(chunk);
1735-
modules.extend(
1736-
chunk
1737-
.groups()
1738-
.iter()
1739-
.flat_map(|group| {
1740-
let group = self.chunk_group_by_ukey.expect_get(group);
1741-
group.origins()
1742-
})
1743-
.filter_map(|origin| origin.module),
1744-
);
1745-
}
1746-
_ => {}
1747-
}
1748-
}
1716+
let mut modules = mutations.get_affected_modules_with_chunk_graph(self);
17491717

17501718
// check if module runtime changes
1719+
let mg = self.get_module_graph();
17511720
for mi in mg.modules().keys() {
17521721
let module_runtimes = self
17531722
.chunk_graph

crates/rspack_core/src/incremental/mutations.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Mutations {
1414
inner: Vec<Mutation>,
1515

1616
affected_modules_with_module_graph: OnceCell<IdentifierSet>,
17+
affected_modules_with_chunk_graph: OnceCell<IdentifierSet>,
1718
affected_chunks_with_chunk_graph: OnceCell<UkeySet<ChunkUkey>>,
1819
}
1920

@@ -136,6 +137,57 @@ impl Mutations {
136137
.clone()
137138
}
138139

140+
pub fn get_affected_modules_with_chunk_graph(&self, compilation: &Compilation) -> IdentifierSet {
141+
self
142+
.affected_modules_with_chunk_graph
143+
.get_or_init(|| {
144+
let mg = compilation.get_module_graph();
145+
let mut modules = self.get_affected_modules_with_module_graph(&mg);
146+
let mut chunks = UkeySet::default();
147+
for mutation in self.iter() {
148+
match mutation {
149+
Mutation::ModuleSetAsync { module } => {
150+
modules.insert(*module);
151+
}
152+
Mutation::ModuleSetId { module } => {
153+
modules.insert(*module);
154+
modules.extend(
155+
mg.get_incoming_connections(module)
156+
.filter_map(|c| c.original_module_identifier),
157+
);
158+
}
159+
Mutation::ChunkAdd { chunk } => {
160+
chunks.insert(chunk);
161+
}
162+
Mutation::ChunkRemove { chunk } => {
163+
chunks.remove(chunk);
164+
}
165+
Mutation::ChunkSetId { chunk } => {
166+
let chunk = compilation.chunk_by_ukey.expect_get(chunk);
167+
modules.extend(
168+
chunk
169+
.groups()
170+
.iter()
171+
.flat_map(|group| {
172+
let group = compilation.chunk_group_by_ukey.expect_get(group);
173+
group.origins()
174+
})
175+
.filter_map(|origin| origin.module),
176+
);
177+
}
178+
_ => {}
179+
}
180+
}
181+
modules.extend(
182+
chunks
183+
.into_iter()
184+
.flat_map(|chunk| compilation.chunk_graph.get_chunk_modules_identifier(chunk)),
185+
);
186+
modules
187+
})
188+
.clone()
189+
}
190+
139191
pub fn get_affected_chunks_with_chunk_graph(
140192
&self,
141193
compilation: &Compilation,

crates/rspack_plugin_mf/src/container/hoist_container_references_plugin.rs

Lines changed: 68 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ use std::{
1919

2020
use async_trait::async_trait;
2121
use rspack_core::{
22-
Compilation, CompilationOptimizeChunks, CompilerCompilation, Dependency, DependencyId, Module,
23-
ModuleIdentifier, Plugin,
22+
Compilation, CompilationOptimizeChunks, CompilerCompilation, Dependency, DependencyId,
23+
ModuleIdentifier, Plugin, incremental::Mutation,
2424
};
2525
use rspack_error::Result;
2626
use rspack_hook::{plugin, plugin_hook};
27-
use rustc_hash::FxHashSet;
27+
use rustc_hash::{FxHashMap, FxHashSet};
2828

2929
use super::{
3030
container_entry_dependency::ContainerEntryDependency, fallback_dependency::FallbackDependency,
@@ -140,14 +140,13 @@ async fn optimize_chunks(&self, compilation: &mut Compilation) -> Result<Option<
140140
// Helper: recursively collect all referenced modules
141141
fn get_all_referenced_modules(
142142
compilation: &Compilation,
143-
module: &dyn Module,
143+
module_id: ModuleIdentifier,
144144
ty: &str,
145145
) -> FxHashSet<ModuleIdentifier> {
146146
let mut collected = FxHashSet::default();
147147
let mut visited = FxHashSet::default();
148148
let mut stack = VecDeque::new();
149149

150-
let module_id = module.identifier();
151150
collected.insert(module_id);
152151
visited.insert(module_id);
153152
stack.push_back(module_id);
@@ -181,120 +180,82 @@ async fn optimize_chunks(&self, compilation: &mut Compilation) -> Result<Option<
181180
collected
182181
}
183182

184-
// Helper: get runtime chunks from entrypoints with enhanced detection
185-
fn get_runtime_chunks(compilation: &Compilation) -> FxHashSet<rspack_core::ChunkUkey> {
186-
let mut runtime_chunks = FxHashSet::default();
183+
let mg = compilation.get_module_graph();
187184

188-
// Get runtime chunks from entrypoints
189-
for entrypoint_ukey in compilation.entrypoints.values() {
190-
if let Some(entrypoint) = compilation.chunk_group_by_ukey.get(entrypoint_ukey) {
191-
let runtime_chunk = entrypoint.get_runtime_chunk(&compilation.chunk_group_by_ukey);
192-
runtime_chunks.insert(runtime_chunk);
193-
194-
// Enhanced: Also check all chunks for runtime (similar to external PR)
195-
for (chunk_ukey, chunk) in compilation.chunk_by_ukey.iter() {
196-
if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
197-
runtime_chunks.insert(*chunk_ukey);
198-
}
199-
}
200-
}
201-
}
202-
203-
runtime_chunks
204-
}
205-
206-
// Helper: clean up chunks by disconnecting unused modules
207-
fn clean_up_chunks(compilation: &mut Compilation, modules: &mut FxHashSet<ModuleIdentifier>) {
208-
for module_id in modules.iter() {
209-
let chunks_vec: Vec<_> = compilation
185+
// Collect all federation (container, runtime, and remote) referenced modules
186+
let all_modules_to_hoist = self
187+
.federation_deps
188+
.lock()
189+
.expect("Failed to lock federation deps")
190+
.iter()
191+
.filter_map(|dep| mg.module_identifier_by_dependency_id(dep))
192+
.flat_map(|module| get_all_referenced_modules(compilation, *module, "initial"))
193+
.collect::<FxHashSet<_>>();
194+
195+
// Hoist referenced modules to their runtime chunk
196+
let entries = compilation
197+
.get_chunk_graph_entries()
198+
.filter_map(|entry| {
199+
compilation
200+
.chunk_by_ukey
201+
.get(&entry)
202+
.map(|chunk| (chunk.runtime(), entry))
203+
})
204+
.collect::<FxHashMap<_, _>>();
205+
for module in &all_modules_to_hoist {
206+
let runtime_chunks = compilation
207+
.chunk_graph
208+
.get_module_runtimes_iter(*module, &compilation.chunk_by_ukey)
209+
.filter_map(|runtime_spec| entries.get(&runtime_spec).copied())
210+
.collect::<Vec<_>>();
211+
for runtime_chunk in runtime_chunks {
212+
if !compilation
210213
.chunk_graph
211-
.get_module_chunks(*module_id)
212-
.iter()
213-
.copied()
214-
.collect();
215-
216-
for chunk_ukey in chunks_vec {
217-
let chunk = compilation.chunk_by_ukey.get(&chunk_ukey);
218-
let has_runtime = chunk.is_some_and(|c| c.has_runtime(&compilation.chunk_group_by_ukey));
219-
220-
if !has_runtime {
221-
compilation
222-
.chunk_graph
223-
.disconnect_chunk_and_module(&chunk_ukey, *module_id);
224-
225-
if compilation
226-
.chunk_graph
227-
.get_number_of_chunk_modules(&chunk_ukey)
228-
== 0
229-
&& compilation
230-
.chunk_graph
231-
.get_number_of_entry_modules(&chunk_ukey)
232-
== 0
233-
&& let Some(mut removed_chunk) = compilation.chunk_by_ukey.remove(&chunk_ukey)
234-
{
235-
compilation
236-
.chunk_graph
237-
.disconnect_chunk(&mut removed_chunk, &mut compilation.chunk_group_by_ukey);
238-
compilation.chunk_graph.remove_chunk(&chunk_ukey);
239-
240-
// Remove from named chunks if it has a name
241-
if let Some(name) = removed_chunk.name() {
242-
compilation.named_chunks.remove(name);
243-
}
244-
}
245-
}
214+
.is_module_in_chunk(module, runtime_chunk)
215+
{
216+
compilation
217+
.chunk_graph
218+
.connect_chunk_and_module(runtime_chunk, *module);
246219
}
247220
}
248-
modules.clear();
249221
}
250222

251-
let _runtime_chunks = get_runtime_chunks(compilation);
252-
let mut all_modules_to_hoist = FxHashSet::default();
253-
254-
// Process all federation dependencies (container, runtime, and remote)
255-
for dep_id in self
256-
.federation_deps
257-
.lock()
258-
.expect("Failed to lock federation deps")
259-
.iter()
260-
{
261-
let module_graph = compilation.get_module_graph();
262-
if let Some(module_id) = module_graph.module_identifier_by_dependency_id(dep_id)
263-
&& let Some(module) = module_graph.module_by_identifier(module_id)
264-
{
265-
let referenced_modules = get_all_referenced_modules(compilation, module.as_ref(), "initial");
266-
all_modules_to_hoist.extend(&referenced_modules);
267-
268-
// Get module runtimes and hoist to runtime chunks
269-
let runtime_specs: Vec<_> = compilation
223+
// Disconnect hoisted modules from non-runtime chunks, this is safe since we already hoist them to runtime chunk
224+
let runtime_chunks = entries.values().copied().collect::<FxHashSet<_>>();
225+
for module in all_modules_to_hoist {
226+
let non_runtime_chunks = compilation
227+
.chunk_graph
228+
.get_module_chunks(module)
229+
.iter()
230+
.filter(|chunk| !runtime_chunks.contains(chunk))
231+
.copied()
232+
.collect::<Vec<_>>();
233+
for chunk in non_runtime_chunks {
234+
compilation
270235
.chunk_graph
271-
.get_module_runtimes_iter(*module_id, &compilation.chunk_by_ukey)
272-
.cloned()
273-
.collect();
274-
275-
for runtime_spec in runtime_specs {
276-
// Find runtime chunks by name
277-
for runtime_name in runtime_spec.iter() {
278-
if let Some(runtime_chunk) = compilation.named_chunks.get(runtime_name.as_str()) {
279-
for &ref_module_id in &referenced_modules {
280-
if !compilation
281-
.chunk_graph
282-
.is_module_in_chunk(&ref_module_id, *runtime_chunk)
283-
{
284-
compilation
285-
.chunk_graph
286-
.connect_chunk_and_module(*runtime_chunk, ref_module_id);
287-
}
288-
}
289-
}
236+
.disconnect_chunk_and_module(&chunk, module);
237+
238+
if compilation.chunk_graph.get_number_of_chunk_modules(&chunk) == 0
239+
&& compilation.chunk_graph.get_number_of_entry_modules(&chunk) == 0
240+
&& let Some(mut removed_chunk) = compilation.chunk_by_ukey.remove(&chunk)
241+
{
242+
compilation
243+
.chunk_graph
244+
.disconnect_chunk(&mut removed_chunk, &mut compilation.chunk_group_by_ukey);
245+
compilation.chunk_graph.remove_chunk(&chunk);
246+
247+
// Remove from named chunks if it has a name
248+
if let Some(name) = removed_chunk.name() {
249+
compilation.named_chunks.remove(name);
250+
}
251+
// Record mutation
252+
if let Some(mutations) = compilation.incremental.mutations_write() {
253+
mutations.add(Mutation::ChunkRemove { chunk });
290254
}
291255
}
292256
}
293257
}
294258

295-
// Cleanup: disconnect hoisted modules from non-runtime chunks
296-
clean_up_chunks(compilation, &mut all_modules_to_hoist);
297-
298259
Ok(None)
299260
}
300261

packages/rspack-test-tools/tests/Incremental-node.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describeByWalk(
3636
__dirname,
3737
`./js/incremental/webpack-test/hot-async-node`
3838
),
39-
exclude: [/require-disposed-module-warning/]
39+
exclude: []
4040
}
4141
);
4242

@@ -49,6 +49,6 @@ describeByWalk(
4949
{
5050
source: path.resolve(__dirname, "../../../tests/webpack-test/hotCases"),
5151
dist: path.resolve(__dirname, `./js/incremental/webpack-test/hot-node`),
52-
exclude: [/require-disposed-module-warning/]
52+
exclude: []
5353
}
5454
);

packages/rspack-test-tools/tests/Incremental-web.test.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ describeByWalk(
3232
{
3333
source: path.resolve(__dirname, "../../../tests/webpack-test/hotCases"),
3434
dist: path.resolve(__dirname, `./js/incremental/webpack-test/hot-web`),
35-
exclude: [
36-
// there is a self reference module in this case causing the make phase didn't found the module is removed
37-
/require-disposed-module-warning/
38-
]
35+
exclude: []
3936
}
4037
);

packages/rspack-test-tools/tests/Incremental-webworker.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ describeByWalk(
3636
__dirname,
3737
`./js/incremental/webpack-test/hot-webworker`
3838
),
39-
exclude: [/require-disposed-module-warning/]
39+
exclude: []
4040
}
4141
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default () => {
2+
const worker = new Worker(new URL("./worker", import.meta.url));
3+
worker.testName = `test worker ${WATCH_STEP}`;
4+
return worker;
5+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import("./main");
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import getWorker from "./getWorker";
2+
3+
it("should have correct value", () => {
4+
expect(getWorker().testName).toBe("test worker 0");
5+
})

0 commit comments

Comments
 (0)