Skip to main content

turbopack_ecmascript/chunk_list/
version.rs

1use anyhow::Result;
2use turbo_rcstr::RcStr;
3use turbo_tasks::{FxIndexMap, ResolvedVc, TraitRef, TryJoinIterExt, Vc};
4use turbo_tasks_hash::{Xxh3Hash64Hasher, encode_base64};
5use turbopack_core::version::{
6    MergeableVersionedContent, Version, VersionedContent, VersionedContentMerger,
7};
8
9type VersionTraitRef = TraitRef<Box<dyn Version>>;
10
11/// The version of a chunk list content.
12///
13/// Tracks versions of individual chunks by path and by merger. Chunks that
14/// implement [`MergeableVersionedContent`] are grouped by their merger and
15/// their versions are merged. Other chunks are tracked by path.
16///
17/// [`MergeableVersionedContent`]: turbopack_core::version::MergeableVersionedContent
18#[turbo_tasks::value(serialization = "skip", shared)]
19pub struct ChunkListVersion {
20    /// A map from chunk path to its version.
21    #[turbo_tasks(trace_ignore)]
22    pub by_path: FxIndexMap<String, VersionTraitRef>,
23    /// A map from chunk merger to the version of the merged contents of chunks.
24    //
25    // TODO: This trace_ignore is *very* wrong, and could cause problems if/when we add a GC!
26    // Version is also expected not to contain `Vc`/`ResolvedVc`/`OperationVc`, and
27    // `turbopack_core::version::TotalUpdate` assumes it doesn't.
28    #[turbo_tasks(trace_ignore)]
29    pub by_merger: FxIndexMap<ResolvedVc<Box<dyn VersionedContentMerger>>, VersionTraitRef>,
30}
31
32#[turbo_tasks::value_impl]
33impl Version for ChunkListVersion {
34    #[turbo_tasks::function]
35    async fn id(&self) -> Result<Vc<RcStr>> {
36        let by_path = {
37            let mut by_path = self
38                .by_path
39                .iter()
40                .map(|(path, version)| (path, TraitRef::cell(version.clone())))
41                .map(|(path, version)| async move {
42                    let id = version.id().owned().await?;
43                    Ok((path, id))
44                })
45                .try_join()
46                .await?;
47            by_path.sort();
48            by_path
49        };
50        let by_merger = {
51            let mut by_merger = self
52                .by_merger
53                .iter()
54                .map(|(_merger, version)| TraitRef::cell(version.clone()).id().owned())
55                .try_join()
56                .await?;
57            by_merger.sort();
58            by_merger
59        };
60        let mut hasher = Xxh3Hash64Hasher::new();
61        hasher.write_value(by_path.len());
62        for (path, id) in by_path {
63            hasher.write_value(path);
64            hasher.write_value(id);
65        }
66        hasher.write_value(by_merger.len());
67        for id in by_merger {
68            hasher.write_value(id);
69        }
70        let hash = hasher.finish();
71        let hash = encode_base64(hash);
72        Ok(Vc::cell(hash.into()))
73    }
74}
75
76/// Computes a [`ChunkListVersion`] from a map of chunk paths to their
77/// [`VersionedContent`].
78///
79/// Chunks that implement [`MergeableVersionedContent`] are grouped by their
80/// merger and their versions are merged. Other chunks are tracked by path.
81///
82/// [`VersionedContent`]: turbopack_core::version::VersionedContent
83/// [`MergeableVersionedContent`]: turbopack_core::version::MergeableVersionedContent
84pub async fn compute_chunk_list_version(
85    chunks_contents: &FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
86) -> Result<Vc<ChunkListVersion>> {
87    let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
88    let mut by_path = FxIndexMap::<_, _>::default();
89
90    for (chunk_path, chunk_content) in chunks_contents {
91        if let Some(mergeable) =
92            ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
93        {
94            let merger = mergeable.get_merger().to_resolved().await?;
95            by_merger.entry(merger).or_default().push(*chunk_content);
96        } else {
97            by_path.insert(
98                chunk_path.clone(),
99                chunk_content.version().into_trait_ref().await?,
100            );
101        }
102    }
103
104    let by_merger = by_merger
105        .into_iter()
106        .map(|(merger, contents)| (merger, Vc::cell(contents)))
107        .map(async |(merger, contents)| {
108            Ok((
109                merger,
110                merger.merge(contents).version().into_trait_ref().await?,
111            ))
112        })
113        .try_join()
114        .await?
115        .into_iter()
116        .collect();
117
118    Ok(ChunkListVersion { by_path, by_merger }.cell())
119}