rspack_plugin_remove_duplicate_modules/
lib.rs1use std::sync::Arc;
2
3use rspack_collections::IdentifierSet;
4use rspack_core::{
5 ChunkUkey, Compilation, CompilationOptimizeChunks, ModuleIdentifier, Plugin,
6 incremental::Mutation,
7};
8use rspack_error::Result;
9use rspack_hook::{plugin, plugin_hook};
10use rustc_hash::FxHashMap;
11
12#[derive(Debug)]
13#[plugin]
14pub struct RemoveDuplicateModulesPlugin {}
15
16impl std::default::Default for RemoveDuplicateModulesPlugin {
17 fn default() -> Self {
18 Self {
19 inner: Arc::new(RemoveDuplicateModulesPluginInner {}),
20 }
21 }
22}
23
24#[plugin_hook(CompilationOptimizeChunks for RemoveDuplicateModulesPlugin)]
25async fn optimize_chunks(&self, compilation: &mut Compilation) -> Result<Option<bool>> {
26 let module_graph = compilation.get_module_graph();
27 let chunk_graph = &compilation.chunk_graph;
28
29 let mut chunk_map: FxHashMap<Vec<ChunkUkey>, Vec<ModuleIdentifier>> = FxHashMap::default();
30
31 for identifier in module_graph.modules().keys() {
32 let chunks = chunk_graph.get_module_chunks(*identifier);
33 let mut sorted_chunks = chunks.iter().copied().collect::<Vec<_>>();
34 sorted_chunks.sort();
35 chunk_map
36 .entry(sorted_chunks)
37 .or_default()
38 .push(*identifier);
39 }
40
41 for (chunks, modules) in chunk_map {
42 if chunks.len() <= 1 {
43 continue;
44 }
45
46 let new_chunk_ukey = Compilation::add_chunk(&mut compilation.chunk_by_ukey);
48 if let Some(mut mutations) = compilation.incremental.mutations_write() {
49 mutations.add(Mutation::ChunkAdd {
50 chunk: new_chunk_ukey,
51 });
52 }
53 let new_chunk = compilation.chunk_by_ukey.expect_get_mut(&new_chunk_ukey);
54 *new_chunk.chunk_reason_mut() = Some("modules are shared across multiple chunks".into());
55 compilation.chunk_graph.add_chunk(new_chunk_ukey);
56
57 let mut entry_modules = IdentifierSet::default();
58
59 for chunk_ukey in &chunks {
60 let [Some(new_chunk), Some(origin)] = compilation
61 .chunk_by_ukey
62 .get_many_mut([&new_chunk_ukey, chunk_ukey])
63 else {
64 panic!("should have both chunks")
65 };
66 entry_modules.extend(compilation.chunk_graph.get_chunk_entry_modules(chunk_ukey));
67 origin.split(new_chunk, &mut compilation.chunk_group_by_ukey);
68 if let Some(mut mutations) = compilation.incremental.mutations_write() {
69 mutations.add(Mutation::ChunkSplit {
70 from: *chunk_ukey,
71 to: new_chunk_ukey,
72 });
73 }
74 }
75
76 for m in modules {
77 let is_entry = entry_modules.contains(&m);
78 for chunk_ukey in &chunks {
79 compilation
80 .chunk_graph
81 .disconnect_chunk_and_module(chunk_ukey, m);
82
83 if is_entry {
84 compilation
85 .chunk_graph
86 .disconnect_chunk_and_entry_module(chunk_ukey, m);
87 }
88 }
89
90 compilation
91 .chunk_graph
92 .connect_chunk_and_module(new_chunk_ukey, m);
93
94 if is_entry {
95 let chunk = compilation.chunk_by_ukey.expect_get(&new_chunk_ukey);
96 for group in chunk.groups().iter().filter(|group| {
97 let group = compilation.chunk_group_by_ukey.expect_get(group);
98
99 group.is_initial() && group.kind.is_entrypoint()
100 }) {
101 compilation
102 .chunk_graph
103 .connect_chunk_and_entry_module(new_chunk_ukey, m, *group);
104 }
105 }
106 }
107 }
108
109 Ok(None)
110}
111
112impl Plugin for RemoveDuplicateModulesPlugin {
113 fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
114 ctx
115 .compilation_hooks
116 .optimize_chunks
117 .tap(optimize_chunks::new(self));
118 Ok(())
119 }
120}