turbopack_ecmascript/chunk_list/
update.rs1use std::sync::Arc;
2
3use anyhow::Result;
4use serde::Serialize;
5use turbo_tasks::{FxIndexMap, ResolvedVc, TraitRef, Vc};
6use turbopack_core::version::{
7 MergeableVersionedContent, PartialUpdate, TotalUpdate, Update, Version, VersionedContent,
8 VersionedContentMerger,
9};
10
11use super::version::ChunkListVersion;
12
13#[derive(Serialize)]
15#[serde(tag = "type")]
16#[serde(rename_all = "camelCase")]
17struct ChunkListUpdate<'a> {
18 #[serde(skip_serializing_if = "FxIndexMap::is_empty")]
20 chunks: FxIndexMap<&'a str, ChunkUpdate>,
21 #[serde(skip_serializing_if = "Vec::is_empty")]
23 merged: Vec<Arc<serde_json::Value>>,
24}
25
26#[derive(Serialize)]
28#[serde(tag = "type")]
29#[serde(rename_all = "camelCase")]
30enum ChunkUpdate {
31 Total,
33 Partial { instruction: Arc<serde_json::Value> },
35 Added,
37 Deleted,
39}
40
41impl ChunkListUpdate<'_> {
42 fn is_empty(&self) -> bool {
44 let ChunkListUpdate { chunks, merged } = self;
45 chunks.is_empty() && merged.is_empty()
46 }
47}
48
49pub async fn update_chunk_list(
55 chunks_contents: &FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
56 to_version: Vc<ChunkListVersion>,
57 from_version: ResolvedVc<Box<dyn Version>>,
58) -> Result<Vc<Update>> {
59 let from_version =
60 if let Some(from) = ResolvedVc::try_downcast_type::<ChunkListVersion>(from_version) {
61 from
62 } else {
63 return Ok(Update::Total(TotalUpdate {
65 to: Vc::upcast::<Box<dyn Version>>(to_version)
66 .into_trait_ref()
67 .await?,
68 })
69 .cell());
70 };
71
72 let to = to_version.await?;
73 let from = from_version.await?;
74
75 if from.ptr_eq(&to) {
79 return Ok(Update::None.cell());
80 }
81
82 let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
85 let mut by_path = FxIndexMap::<_, _>::default();
86
87 for (chunk_path, chunk_content) in chunks_contents {
88 if let Some(mergeable) =
89 ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
90 {
91 let merger = mergeable.get_merger().to_resolved().await?;
92 by_merger.entry(merger).or_default().push(*chunk_content);
93 } else {
94 by_path.insert(chunk_path, chunk_content);
95 }
96 }
97
98 let mut chunks = FxIndexMap::<_, _>::default();
99
100 for (chunk_path, from_chunk_version) in &from.by_path {
101 if let Some(chunk_content) = by_path.swap_remove(chunk_path) {
102 let chunk_update = chunk_content
103 .update(TraitRef::cell(from_chunk_version.clone()))
104 .await?;
105
106 match &*chunk_update {
107 Update::Total(_) => {
108 chunks.insert(chunk_path.as_ref(), ChunkUpdate::Total);
109 }
110 Update::Partial(partial) => {
111 chunks.insert(
112 chunk_path.as_ref(),
113 ChunkUpdate::Partial {
114 instruction: partial.instruction.clone(),
115 },
116 );
117 }
118 Update::Missing | Update::None => {}
119 }
120 } else {
121 chunks.insert(chunk_path.as_ref(), ChunkUpdate::Deleted);
122 }
123 }
124
125 for chunk_path in by_path.keys() {
126 chunks.insert(chunk_path.as_ref(), ChunkUpdate::Added);
127 }
128
129 let mut merged = vec![];
130
131 for (merger, chunks_contents) in by_merger {
132 if let Some(from_version) = from.by_merger.get(&merger) {
133 let content = merger.merge(Vc::cell(chunks_contents));
134
135 let chunk_update = content.update(TraitRef::cell(from_version.clone())).await?;
136
137 match &*chunk_update {
138 Update::Total(_) => {
142 return Ok(Update::Total(TotalUpdate {
143 to: Vc::upcast::<Box<dyn Version>>(to_version)
144 .into_trait_ref()
145 .await?,
146 })
147 .cell());
148 }
149 Update::Partial(partial) => {
150 merged.push(partial.instruction.clone());
151 }
152 Update::Missing | Update::None => {}
153 }
154 }
155 }
156 let update = ChunkListUpdate { chunks, merged };
157
158 let update = if update.is_empty() {
159 Update::None
160 } else {
161 Update::Partial(PartialUpdate {
162 to: Vc::upcast::<Box<dyn Version>>(to_version)
163 .into_trait_ref()
164 .await?,
165 instruction: Arc::new(serde_json::to_value(&update)?),
166 })
167 };
168
169 Ok(update.cell())
170}