Skip to main content

rspack_core/compilation/create_hash/
mod.rs

1use 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    // Check if there are any chunks that depend on full hash, usually only runtime chunks are
27    // possible to depend on full hash, but for library type commonjs/module, it's possible to
28    // have non-runtime chunks depend on full hash, the library format plugin is using
29    // dependent_full_hash hook to declare it.
30    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    // create hash for runtime modules in other chunks
118    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    // create hash for other chunks
144    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    // collect references for runtime chunks
164    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    // sort runtime chunks by its references
190    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    // create warning for remaining circular references
241    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    // create hash for runtime chunks and the runtime modules within them
265    // The subsequent runtime chunks and runtime modules will depend on
266    // the hash results of the previous runtime chunks and runtime modules.
267    // Therefore, create hashes one by one in sequence.
268    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    // create full hash
311    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    // re-create runtime chunk hash that depend on full hash
323    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}