Skip to main content

rspack_plugin_remove_duplicate_modules/
lib.rs

1use 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    // split chunks from original chunks and create new chunk
47    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}