rspack_ids/
deterministic_chunk_ids_plugin.rs1use 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}