rspack_ids/
deterministic_chunk_ids_plugin.rs

1use rayon::prelude::*;
2use rspack_collections::{DatabaseItem, UkeyMap};
3use rspack_core::{CompilationChunkIds, Plugin, incremental::IncrementalPasses};
4use rspack_error::Result;
5use rspack_hook::{plugin, plugin_hook};
6use rustc_hash::{FxBuildHasher, FxHashMap};
7
8use crate::id_helpers::{
9  assign_deterministic_ids, compare_chunks_natural, get_full_chunk_name, get_used_chunk_ids,
10};
11
12#[plugin]
13#[derive(Debug, Default)]
14pub struct DeterministicChunkIdsPlugin {
15  pub delimiter: String,
16  pub context: Option<String>,
17}
18
19impl DeterministicChunkIdsPlugin {
20  pub fn new(delimiter: Option<String>, context: Option<String>) -> Self {
21    Self::new_inner(delimiter.unwrap_or_else(|| "~".to_string()), context)
22  }
23}
24
25#[plugin_hook(CompilationChunkIds for DeterministicChunkIdsPlugin)]
26async fn chunk_ids(&self, compilation: &mut rspack_core::Compilation) -> rspack_error::Result<()> {
27  if let Some(diagnostic) = compilation.incremental.disable_passes(
28    IncrementalPasses::CHUNK_IDS,
29    "DeterministicChunkIdsPlugin (optimization.chunkIds = \"deterministic\")",
30    "it requires calculating the id of all the chunks, which is a global effect",
31  ) {
32    if let Some(diagnostic) = diagnostic {
33      compilation.push_diagnostic(diagnostic);
34    }
35    compilation.chunk_ids_artifact.clear();
36  }
37
38  let mut used_ids = get_used_chunk_ids(compilation);
39  let used_ids_len = used_ids.len();
40
41  let chunk_graph = &compilation.chunk_graph;
42  let module_graph = compilation.get_module_graph();
43  let module_graph_cache = &compilation.module_graph_cache_artifact;
44  let context = self
45    .context
46    .clone()
47    .unwrap_or_else(|| compilation.options.context.as_str().to_string());
48
49  let max_length = 3;
50  let expand_factor = 10;
51  let salt = 10;
52
53  let chunks = compilation
54    .chunk_by_ukey
55    .values()
56    .filter(|chunk| chunk.id(&compilation.chunk_ids_artifact).is_none())
57    .collect::<Vec<_>>();
58  let mut chunk_key_to_id =
59    FxHashMap::with_capacity_and_hasher(chunks.len(), FxBuildHasher::default());
60
61  let chunk_names = chunks
62    .par_iter()
63    .map(|chunk| {
64      (
65        chunk.ukey(),
66        get_full_chunk_name(
67          chunk,
68          chunk_graph,
69          &module_graph,
70          module_graph_cache,
71          &context,
72        ),
73      )
74    })
75    .collect::<UkeyMap<_, _>>();
76
77  let mut ordered_chunk_modules_cache = Default::default();
78
79  assign_deterministic_ids(
80    chunks,
81    |chunk| {
82      chunk_names
83        .get(&chunk.ukey())
84        .expect("should have generated full chunk name")
85        .to_string()
86    },
87    |a, b| {
88      compare_chunks_natural(
89        chunk_graph,
90        &module_graph,
91        &compilation.chunk_group_by_ukey,
92        &compilation.module_ids_artifact,
93        a,
94        b,
95        &mut ordered_chunk_modules_cache,
96      )
97    },
98    |chunk, id| {
99      let size = used_ids.len();
100      used_ids.insert(id.to_string());
101      if used_ids.len() == size {
102        return false;
103      }
104
105      chunk_key_to_id.insert(chunk.ukey(), id);
106      true
107    },
108    &[usize::pow(10, max_length)],
109    expand_factor,
110    used_ids_len,
111    salt,
112  );
113
114  chunk_key_to_id.into_iter().for_each(|(chunk_ukey, id)| {
115    let chunk = compilation.chunk_by_ukey.expect_get_mut(&chunk_ukey);
116    chunk.set_id(&mut compilation.chunk_ids_artifact, id.to_string());
117  });
118
119  Ok(())
120}
121
122impl Plugin for DeterministicChunkIdsPlugin {
123  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
124    ctx.compilation_hooks.chunk_ids.tap(chunk_ids::new(self));
125    Ok(())
126  }
127}