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, eq = "manual")]
19#[derive(Debug)]
20pub struct ChunkListVersion {
21    pub id: RcStr,
22    /// A map from chunk path to its version.
23    pub by_path: FxIndexMap<String, VersionTraitRef>,
24    /// A map from chunk merger to the version of the merged contents of chunks.
25    //
26    // TODO: Remove this exemption after `Version` guarantees `NonLocalValue`.
27    // `turbopack_core::version::TotalUpdate` relies on that guarantee.
28    pub by_merger: FxIndexMap<ResolvedVc<Box<dyn VersionedContentMerger>>, VersionTraitRef>,
29}
30
31// `id` is a hash over every tracked chunk version, so comparing it is equivalent to comparing
32// the maps, which hold `VersionTraitRef`s that are not structurally comparable.
33impl PartialEq for ChunkListVersion {
34    fn eq(&self, other: &Self) -> bool {
35        self.id == other.id
36    }
37}
38
39impl Eq for ChunkListVersion {}
40
41#[turbo_tasks::value_impl]
42impl Version for ChunkListVersion {
43    #[turbo_tasks::function]
44    async fn id(&self) -> Result<Vc<RcStr>> {
45        Ok(Vc::cell(self.id.clone()))
46    }
47}
48
49async fn chunk_list_version_id(
50    by_path: &FxIndexMap<String, VersionTraitRef>,
51    by_merger: &FxIndexMap<ResolvedVc<Box<dyn VersionedContentMerger>>, VersionTraitRef>,
52) -> Result<RcStr> {
53    let by_path = {
54        let mut by_path = by_path
55            .iter()
56            .map(|(path, version)| (path, TraitRef::cell(version.clone())))
57            .map(async |(path, version)| {
58                let id = version.id().owned().await?;
59                Ok((path, id))
60            })
61            .try_join()
62            .await?;
63        by_path.sort();
64        by_path
65    };
66    let by_merger = {
67        let mut by_merger = by_merger
68            .iter()
69            .map(|(_merger, version)| TraitRef::cell(version.clone()).id().owned())
70            .try_join()
71            .await?;
72        by_merger.sort();
73        by_merger
74    };
75    let mut hasher = Xxh3Hash64Hasher::new();
76    hasher.write_value(by_path.len());
77    for (path, id) in by_path {
78        hasher.write_value(path);
79        hasher.write_value(id);
80    }
81    hasher.write_value(by_merger.len());
82    for id in by_merger {
83        hasher.write_value(id);
84    }
85    let hash = hasher.finish();
86    let hash = encode_base64(hash);
87    Ok(hash.into())
88}
89
90/// Computes a [`ChunkListVersion`] from a map of chunk paths to their
91/// [`VersionedContent`].
92///
93/// Chunks that implement [`MergeableVersionedContent`] are grouped by their
94/// merger and their versions are merged. Other chunks are tracked by path.
95///
96/// [`VersionedContent`]: turbopack_core::version::VersionedContent
97/// [`MergeableVersionedContent`]: turbopack_core::version::MergeableVersionedContent
98pub async fn compute_chunk_list_version(
99    chunks_contents: &FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
100) -> Result<Vc<ChunkListVersion>> {
101    let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
102    let mut by_path = FxIndexMap::<_, _>::default();
103
104    for (chunk_path, chunk_content) in chunks_contents {
105        if let Some(mergeable) =
106            ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
107        {
108            let merger = mergeable.get_merger().to_resolved().await?;
109            by_merger.entry(merger).or_default().push(*chunk_content);
110        } else {
111            by_path.insert(
112                chunk_path.clone(),
113                chunk_content.version().into_trait_ref().await?,
114            );
115        }
116    }
117
118    let by_merger = by_merger
119        .into_iter()
120        .map(|(merger, contents)| (merger, Vc::cell(contents)))
121        .map(async |(merger, contents)| {
122            Ok((
123                merger,
124                merger.merge(contents).version().into_trait_ref().await?,
125            ))
126        })
127        .try_join()
128        .await?
129        .into_iter()
130        .collect();
131
132    let id = chunk_list_version_id(&by_path, &by_merger).await?;
133    Ok(ChunkListVersion {
134        id,
135        by_path,
136        by_merger,
137    }
138    .cell())
139}