rspack_core/compilation/create_hash/
mod.rs1use super::*;
2use crate::logger::Logger;
3
4pub struct ChunkHashResult {
5 pub hash: RspackHashDigest,
6 pub content_hash: ChunkContentHash,
7}
8
9pub async fn create_hash_pass(
10 compilation: &mut Compilation,
11 plugin_driver: SharedPluginDriver,
12) -> Result<()> {
13 let logger = compilation.get_logger("rspack.Compilation");
14 let start = logger.time("hashing");
15 compilation.create_hash(plugin_driver).await?;
16 compilation.runtime_modules_code_generation().await?;
17 logger.time_end(start);
18 Ok(())
19}
20
21impl Compilation {
22 #[instrument(name = "Compilation:create_hash",target=TRACING_BENCH_TARGET, skip_all)]
23 pub async fn create_hash(&mut self, plugin_driver: SharedPluginDriver) -> Result<()> {
24 let logger = self.get_logger("rspack.Compilation");
25
26 let mut full_hash_chunks = UkeySet::default();
31 for chunk_ukey in self.chunk_by_ukey.keys() {
32 let chunk_dependent_full_hash = plugin_driver
33 .compilation_hooks
34 .dependent_full_hash
35 .call(self, chunk_ukey)
36 .await?
37 .unwrap_or_default();
38 if chunk_dependent_full_hash {
39 full_hash_chunks.insert(*chunk_ukey);
40 }
41 }
42 if !full_hash_chunks.is_empty()
43 && let Some(diagnostic) = self.incremental.disable_passes(
44 IncrementalPasses::CHUNKS_HASHES,
45 "Chunk content that dependent on full hash",
46 "it requires calculating the hashes of all the chunks, which is a global effect",
47 )
48 && let Some(diagnostic) = diagnostic
49 {
50 self.push_diagnostic(diagnostic);
51 }
52 if !self
53 .incremental
54 .passes_enabled(IncrementalPasses::CHUNKS_HASHES)
55 {
56 self.chunk_hashes_artifact.clear();
57 }
58
59 let create_hash_chunks = if let Some(mutations) = self
60 .incremental
61 .mutations_read(IncrementalPasses::CHUNKS_HASHES)
62 && !self.chunk_hashes_artifact.is_empty()
63 {
64 let removed_chunks = mutations.iter().filter_map(|mutation| match mutation {
65 Mutation::ChunkRemove { chunk } => Some(*chunk),
66 _ => None,
67 });
68 for removed_chunk in removed_chunks {
69 self.chunk_hashes_artifact.remove(&removed_chunk);
70 }
71 self
72 .chunk_hashes_artifact
73 .retain(|chunk, _| self.chunk_by_ukey.contains(chunk));
74 let chunks = mutations.get_affected_chunks_with_chunk_graph(self);
75 tracing::debug!(target: incremental::TRACING_TARGET, passes = %IncrementalPasses::CHUNKS_HASHES, %mutations, ?chunks);
76 let logger = self.get_logger("rspack.incremental.chunksHashes");
77 logger.log(format!(
78 "{} chunks are affected, {} in total",
79 chunks.len(),
80 self.chunk_by_ukey.len(),
81 ));
82 chunks
83 } else {
84 self.chunk_by_ukey.keys().copied().collect()
85 };
86
87 let mut compilation_hasher = RspackHash::from(&self.options.output);
88
89 fn try_process_chunk_hash_results(
90 compilation: &mut Compilation,
91 chunk_hash_results: Vec<Result<(ChunkUkey, ChunkHashResult)>>,
92 ) -> Result<()> {
93 for hash_result in chunk_hash_results {
94 let (chunk_ukey, chunk_hash_result) = hash_result?;
95 let chunk = compilation.chunk_by_ukey.expect_get(&chunk_ukey);
96 let chunk_hashes_changed = chunk.set_hashes(
97 &mut compilation.chunk_hashes_artifact,
98 chunk_hash_result.hash,
99 chunk_hash_result.content_hash,
100 );
101 if chunk_hashes_changed
102 && let Some(mut mutations) = compilation.incremental.mutations_write()
103 {
104 mutations.add(Mutation::ChunkSetHashes { chunk: chunk_ukey });
105 }
106 }
107 Ok(())
108 }
109
110 let unordered_runtime_chunks: UkeySet<ChunkUkey> = self.get_chunk_graph_entries().collect();
111 let start = logger.time("hashing: hash chunks");
112 let other_chunks: Vec<_> = create_hash_chunks
113 .iter()
114 .filter(|key| !unordered_runtime_chunks.contains(key))
115 .collect();
116
117 let other_chunk_runtime_module_hashes = rspack_futures::scope::<_, Result<_>>(|token| {
119 other_chunks
120 .iter()
121 .flat_map(|chunk| self.chunk_graph.get_chunk_runtime_modules_iterable(chunk))
122 .for_each(|runtime_module_identifier| {
123 let s = unsafe { token.used((&self, runtime_module_identifier)) };
124 s.spawn(|(compilation, runtime_module_identifier)| async {
125 let runtime_module = &compilation.runtime_modules[runtime_module_identifier];
126 let digest = runtime_module.get_runtime_hash(compilation, None).await?;
127 Ok((*runtime_module_identifier, digest))
128 });
129 })
130 })
131 .await
132 .into_iter()
133 .map(|res| res.to_rspack_result())
134 .collect::<Result<Vec<_>>>()?;
135
136 for res in other_chunk_runtime_module_hashes {
137 let (runtime_module_identifier, digest) = res?;
138 self
139 .runtime_modules_hash
140 .insert(runtime_module_identifier, digest);
141 }
142
143 let other_chunks_hash_results = rspack_futures::scope::<_, Result<_>>(|token| {
145 for chunk in other_chunks {
146 let s = unsafe { token.used((&self, chunk, &plugin_driver)) };
147 s.spawn(|(compilation, chunk, plugin_driver)| async {
148 let hash_result = compilation
149 .process_chunk_hash(*chunk, plugin_driver)
150 .await?;
151 Ok((*chunk, hash_result))
152 });
153 }
154 })
155 .await
156 .into_iter()
157 .map(|res| res.to_rspack_result())
158 .collect::<Result<Vec<_>>>()?;
159
160 try_process_chunk_hash_results(self, other_chunks_hash_results)?;
161 logger.time_end(start);
162
163 let mut runtime_chunks_map: HashMap<ChunkUkey, (Vec<ChunkUkey>, u32)> =
165 unordered_runtime_chunks
166 .into_iter()
167 .map(|runtime_chunk| (runtime_chunk, (Vec::new(), 0)))
168 .collect();
169 let mut remaining: u32 = 0;
170 for runtime_chunk_ukey in runtime_chunks_map.keys().copied().collect::<Vec<_>>() {
171 let runtime_chunk = self.chunk_by_ukey.expect_get(&runtime_chunk_ukey);
172 let groups = runtime_chunk.get_all_referenced_async_entrypoints(&self.chunk_group_by_ukey);
173 for other in groups
174 .into_iter()
175 .map(|group| self.chunk_group_by_ukey.expect_get(&group))
176 .map(|group| group.get_runtime_chunk(&self.chunk_group_by_ukey))
177 {
178 let (other_referenced_by, _) = runtime_chunks_map
179 .get_mut(&other)
180 .expect("should in runtime_chunks_map");
181 other_referenced_by.push(runtime_chunk_ukey);
182 let info = runtime_chunks_map
183 .get_mut(&runtime_chunk_ukey)
184 .expect("should in runtime_chunks_map");
185 info.1 += 1;
186 remaining += 1;
187 }
188 }
189 let mut runtime_chunks = Vec::with_capacity(runtime_chunks_map.len());
191 for (runtime_chunk, (_, remaining)) in &runtime_chunks_map {
192 if *remaining == 0 {
193 runtime_chunks.push(*runtime_chunk);
194 }
195 }
196 let mut ready_chunks = Vec::new();
197
198 let mut i = 0;
199 while i < runtime_chunks.len() {
200 let chunk_ukey = runtime_chunks[i];
201 let has_full_hash_modules = full_hash_chunks.contains(&chunk_ukey)
202 || self
203 .chunk_graph
204 .has_chunk_full_hash_modules(&chunk_ukey, &self.runtime_modules);
205 if has_full_hash_modules {
206 full_hash_chunks.insert(chunk_ukey);
207 }
208 let referenced_by = runtime_chunks_map
209 .get(&chunk_ukey)
210 .expect("should in runtime_chunks_map")
211 .0
212 .clone();
213 for other in referenced_by {
214 if has_full_hash_modules {
215 for runtime_module in self.chunk_graph.get_chunk_runtime_modules_iterable(&other) {
216 let runtime_module = self
217 .runtime_modules
218 .get(runtime_module)
219 .expect("should have runtime_module");
220 if runtime_module.dependent_hash() {
221 full_hash_chunks.insert(other);
222 break;
223 }
224 }
225 }
226 remaining -= 1;
227 let (_, other_remaining) = runtime_chunks_map
228 .get_mut(&other)
229 .expect("should in runtime_chunks_map");
230 *other_remaining -= 1;
231 if *other_remaining == 0 {
232 ready_chunks.push(other);
233 }
234 }
235 if !ready_chunks.is_empty() {
236 runtime_chunks.append(&mut ready_chunks);
237 }
238 i += 1;
239 }
240 if remaining > 0 {
242 let mut circular: Vec<_> = runtime_chunks_map
243 .iter()
244 .filter(|(_, (_, remaining))| *remaining != 0)
245 .map(|(chunk_ukey, _)| self.chunk_by_ukey.expect_get(chunk_ukey))
246 .collect();
247 circular.sort_unstable_by(|a, b| a.id().cmp(&b.id()));
248 runtime_chunks.extend(circular.iter().map(|chunk| chunk.ukey()));
249 let circular_names = circular
250 .iter()
251 .map(|chunk| {
252 chunk
253 .name()
254 .or(chunk.id().map(|id| id.as_str()))
255 .unwrap_or("no id chunk")
256 })
257 .join(", ");
258 let error = rspack_error::Error::warning(format!(
259 "Circular dependency between chunks with runtime ({circular_names})\nThis prevents using hashes of each other and should be avoided."
260 ));
261 self.push_diagnostic(error.into());
262 }
263
264 let start = logger.time("hashing: hash runtime chunks");
269 for runtime_chunk_ukey in runtime_chunks {
270 let runtime_module_hashes = rspack_futures::scope::<_, Result<_>>(|token| {
271 self
272 .chunk_graph
273 .get_chunk_runtime_modules_iterable(&runtime_chunk_ukey)
274 .for_each(|runtime_module_identifier| {
275 let s = unsafe { token.used((&self, runtime_module_identifier)) };
276 s.spawn(|(compilation, runtime_module_identifier)| async {
277 let runtime_module = &compilation.runtime_modules[runtime_module_identifier];
278 let digest = runtime_module.get_runtime_hash(compilation, None).await?;
279 Ok((*runtime_module_identifier, digest))
280 });
281 })
282 })
283 .await
284 .into_iter()
285 .map(|res| res.to_rspack_result())
286 .collect::<Result<Vec<_>>>()?;
287
288 for res in runtime_module_hashes {
289 let (mid, digest) = res?;
290 self.runtime_modules_hash.insert(mid, digest);
291 }
292
293 let chunk_hash_result = self
294 .process_chunk_hash(runtime_chunk_ukey, &plugin_driver)
295 .await?;
296 let chunk = self.chunk_by_ukey.expect_get(&runtime_chunk_ukey);
297 let chunk_hashes_changed = chunk.set_hashes(
298 &mut self.chunk_hashes_artifact,
299 chunk_hash_result.hash,
300 chunk_hash_result.content_hash,
301 );
302 if chunk_hashes_changed && let Some(mut mutations) = self.incremental.mutations_write() {
303 mutations.add(Mutation::ChunkSetHashes {
304 chunk: runtime_chunk_ukey,
305 });
306 }
307 }
308 logger.time_end(start);
309
310 self
312 .chunk_by_ukey
313 .values()
314 .sorted_unstable_by_key(|chunk| chunk.ukey())
315 .filter_map(|chunk| chunk.hash(&self.chunk_hashes_artifact))
316 .for_each(|hash| {
317 hash.hash(&mut compilation_hasher);
318 });
319 self.hot_index.hash(&mut compilation_hasher);
320 self.hash = Some(compilation_hasher.digest(&self.options.output.hash_digest));
321
322 let start = logger.time("hashing: process full hash chunks");
324 for chunk_ukey in full_hash_chunks {
325 for runtime_module_identifier in self
326 .chunk_graph
327 .get_chunk_runtime_modules_iterable(&chunk_ukey)
328 {
329 let runtime_module = &self.runtime_modules[runtime_module_identifier];
330 if runtime_module.full_hash() || runtime_module.dependent_hash() {
331 let digest = runtime_module.get_runtime_hash(self, None).await?;
332 self
333 .runtime_modules_hash
334 .insert(*runtime_module_identifier, digest);
335 }
336 }
337 let chunk = self.chunk_by_ukey.expect_get(&chunk_ukey);
338 let new_chunk_hash = {
339 let chunk_hash = chunk
340 .hash(&self.chunk_hashes_artifact)
341 .expect("should have chunk hash");
342 let mut hasher = RspackHash::from(&self.options.output);
343 chunk_hash.hash(&mut hasher);
344 self.hash.hash(&mut hasher);
345 hasher.digest(&self.options.output.hash_digest)
346 };
347 let new_content_hash = {
348 let content_hash = chunk
349 .content_hash(&self.chunk_hashes_artifact)
350 .expect("should have content hash");
351 content_hash
352 .iter()
353 .map(|(source_type, content_hash)| {
354 let mut hasher = RspackHash::from(&self.options.output);
355 content_hash.hash(&mut hasher);
356 self.hash.hash(&mut hasher);
357 (
358 *source_type,
359 hasher.digest(&self.options.output.hash_digest),
360 )
361 })
362 .collect()
363 };
364 let chunk_hashes_changed = chunk.set_hashes(
365 &mut self.chunk_hashes_artifact,
366 new_chunk_hash,
367 new_content_hash,
368 );
369 if chunk_hashes_changed && let Some(mut mutations) = self.incremental.mutations_write() {
370 mutations.add(Mutation::ChunkSetHashes { chunk: chunk_ukey });
371 }
372 }
373 logger.time_end(start);
374 Ok(())
375 }
376
377 #[instrument(skip_all)]
378 pub async fn runtime_modules_code_generation(&mut self) -> Result<()> {
379 let results = rspack_futures::scope::<_, Result<_>>(|token| {
380 self
381 .runtime_modules
382 .iter()
383 .for_each(|(runtime_module_identifier, runtime_module)| {
384 let s = unsafe { token.used((&self, runtime_module_identifier, runtime_module)) };
385 s.spawn(
386 |(compilation, runtime_module_identifier, runtime_module)| async {
387 let result = runtime_module
388 .code_generation(compilation, None, None)
389 .await?;
390 let source = result
391 .get(&SourceType::Runtime)
392 .expect("should have source");
393 Ok((*runtime_module_identifier, source.clone()))
394 },
395 )
396 })
397 })
398 .await
399 .into_iter()
400 .map(|res| res.to_rspack_result())
401 .collect::<Result<Vec<_>>>()?;
402
403 let mut runtime_module_sources = IdentifierMap::<BoxSource>::default();
404 for result in results {
405 let (runtime_module_identifier, source) = result?;
406 runtime_module_sources.insert(runtime_module_identifier, source);
407 }
408
409 self.runtime_modules_code_generation_source = runtime_module_sources;
410 self
411 .code_generated_modules
412 .extend(self.runtime_modules.keys().copied());
413 Ok(())
414 }
415
416 async fn process_chunk_hash(
417 &self,
418 chunk_ukey: ChunkUkey,
419 plugin_driver: &SharedPluginDriver,
420 ) -> Result<ChunkHashResult> {
421 let mut hasher = RspackHash::from(&self.options.output);
422 if let Some(chunk) = self.chunk_by_ukey.get(&chunk_ukey) {
423 chunk.update_hash(&mut hasher, self);
424 }
425 plugin_driver
426 .compilation_hooks
427 .chunk_hash
428 .call(self, &chunk_ukey, &mut hasher)
429 .await?;
430 let chunk_hash = hasher.digest(&self.options.output.hash_digest);
431
432 let mut content_hashes: HashMap<SourceType, RspackHash> = HashMap::default();
433 plugin_driver
434 .compilation_hooks
435 .content_hash
436 .call(self, &chunk_ukey, &mut content_hashes)
437 .await?;
438
439 let content_hashes = content_hashes
440 .into_iter()
441 .map(|(t, mut hasher)| {
442 chunk_hash.hash(&mut hasher);
443 (t, hasher.digest(&self.options.output.hash_digest))
444 })
445 .collect();
446
447 Ok(ChunkHashResult {
448 hash: chunk_hash,
449 content_hash: content_hashes,
450 })
451 }
452}