Skip to main content

turbopack_core/chunk/
chunk_item_batch.rs

1use std::{future::Future, hash::Hash, ops::Deref};
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use either::Either;
6use rustc_hash::FxHashMap;
7use smallvec::{SmallVec, smallvec};
8use turbo_tasks::{
9    FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryFlatJoinIterExt, TryJoinIterExt,
10    Vc, trace::TraceRawVcs,
11};
12
13use crate::{
14    chunk::{ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkType, ChunkableModule, ChunkingContext},
15    module_graph::{
16        ModuleGraph,
17        async_module_info::AsyncModulesInfo,
18        chunk_group_info::RoaringBitmapWrapper,
19        module_batch::{ChunkableModuleBatchGroup, ChunkableModuleOrBatch, ModuleBatch},
20    },
21};
22
23/// Converts a [`ChunkableModule`] into a [`ChunkItemWithAsyncModuleInfo`] by resolving its chunk
24/// item and, if the module is async, looking up its referenced async modules from the graph.
25///
26/// Uses keyed access on `async_module_info` so only the queried module's entry is read,
27/// enabling per-key invalidation via `cell = "keyed"` on [`AsyncModulesInfo`].
28pub async fn attach_async_info_to_chunkable_module(
29    module: ResolvedVc<Box<dyn ChunkableModule>>,
30    async_module_info: Vc<AsyncModulesInfo>,
31    module_graph: Vc<ModuleGraph>,
32    chunking_context: Vc<Box<dyn ChunkingContext>>,
33) -> Result<ChunkItemWithAsyncModuleInfo> {
34    let general_module = ResolvedVc::upcast(module);
35    let async_info = if async_module_info.is_async(general_module).await? {
36        Some(
37            module_graph
38                .referenced_async_modules(*general_module)
39                .to_resolved()
40                .await?,
41        )
42    } else {
43        None
44    };
45    let chunk_item = module
46        .as_chunk_item(module_graph, chunking_context)
47        .to_resolved()
48        .await?;
49    Ok(ChunkItemWithAsyncModuleInfo {
50        chunk_item,
51        module: Some(module),
52        async_info,
53    })
54}
55
56#[derive(
57    Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, NonLocalValue, TaskInput, Encode, Decode,
58)]
59pub enum ChunkItemOrBatchWithAsyncModuleInfo {
60    ChunkItem(ChunkItemWithAsyncModuleInfo),
61    Batch(ResolvedVc<ChunkItemBatchWithAsyncModuleInfo>),
62}
63
64type ChunkItemOrBatchWithAsyncModuleInfoByChunkType = Either<
65    ChunkItemBatchWithAsyncModuleInfoByChunkTypeData,
66    ReadRef<ChunkItemBatchWithAsyncModuleInfoByChunkType>,
67>;
68
69#[turbo_tasks::value(transparent)]
70pub struct ChunkItemOrBatchWithAsyncModuleInfos(Vec<ChunkItemOrBatchWithAsyncModuleInfo>);
71
72impl ChunkItemOrBatchWithAsyncModuleInfo {
73    pub async fn from_chunkable_module_or_batch(
74        chunkable_module_or_batch: ChunkableModuleOrBatch,
75        async_module_info: Vc<AsyncModulesInfo>,
76        module_graph: Vc<ModuleGraph>,
77        chunking_context: Vc<Box<dyn ChunkingContext>>,
78    ) -> Result<Option<Self>> {
79        Ok(match chunkable_module_or_batch {
80            ChunkableModuleOrBatch::Module(module) => Some(Self::ChunkItem(
81                attach_async_info_to_chunkable_module(
82                    module,
83                    async_module_info,
84                    module_graph,
85                    chunking_context,
86                )
87                .await?,
88            )),
89            ChunkableModuleOrBatch::Batch(batch) => Some(Self::Batch(
90                ChunkItemBatchWithAsyncModuleInfo::from_module_batch(
91                    *batch,
92                    module_graph,
93                    chunking_context,
94                )
95                .to_resolved()
96                .await?,
97            )),
98            ChunkableModuleOrBatch::None(_) => None,
99        })
100    }
101
102    pub async fn split_by_chunk_type(
103        &self,
104    ) -> Result<ChunkItemOrBatchWithAsyncModuleInfoByChunkType> {
105        Ok(match self {
106            Self::ChunkItem(item) => Either::Left(smallvec![(
107                item.chunk_item.ty().to_resolved().await?,
108                Self::ChunkItem(item.clone())
109            )]),
110            Self::Batch(batch) => Either::Right(batch.split_by_chunk_type().await?),
111        })
112    }
113}
114
115#[turbo_tasks::value]
116#[derive(Debug, Clone, Hash, TaskInput)]
117pub struct ChunkItemBatchWithAsyncModuleInfo {
118    pub chunk_items: Vec<ChunkItemWithAsyncModuleInfo>,
119    pub chunk_groups: Option<RoaringBitmapWrapper>,
120}
121
122#[turbo_tasks::value_impl]
123impl ChunkItemBatchWithAsyncModuleInfo {
124    #[turbo_tasks::function]
125    pub fn new(chunk_items: Vec<ChunkItemWithAsyncModuleInfo>) -> Vc<Self> {
126        Self {
127            chunk_items,
128            chunk_groups: None,
129        }
130        .cell()
131    }
132
133    #[turbo_tasks::function]
134    pub async fn from_module_batch(
135        batch: Vc<ModuleBatch>,
136        module_graph: Vc<ModuleGraph>,
137        chunking_context: Vc<Box<dyn ChunkingContext>>,
138    ) -> Result<Vc<Self>> {
139        let async_module_info = module_graph.async_module_info();
140        let batch = batch.await?;
141        let chunk_items = batch
142            .modules
143            .iter()
144            .map(|module| {
145                attach_async_info_to_chunkable_module(
146                    *module,
147                    async_module_info,
148                    module_graph,
149                    chunking_context,
150                )
151            })
152            .try_join()
153            .await?;
154        Ok(Self {
155            chunk_items,
156            chunk_groups: batch.chunk_groups.clone(),
157        }
158        .cell())
159    }
160
161    #[turbo_tasks::function]
162    pub async fn split_by_chunk_type(
163        self: Vc<Self>,
164    ) -> Result<Vc<ChunkItemBatchWithAsyncModuleInfoByChunkType>> {
165        let this = self.await?;
166        let mut iter = this.chunk_items.iter().enumerate();
167        let Some((_, first)) = iter.next() else {
168            return Ok(Vc::cell(SmallVec::new()));
169        };
170        let chunk_type = first.chunk_item.ty().to_resolved().await?;
171        while let Some((i, item)) = iter.next() {
172            let ty = item.chunk_item.ty().to_resolved().await?;
173            if ty != chunk_type {
174                let mut map = FxIndexMap::default();
175                map.insert(chunk_type, this.chunk_items[..i].to_vec());
176                map.insert(ty, vec![item.clone()]);
177                for (_, item) in iter {
178                    map.entry(item.chunk_item.ty().to_resolved().await?)
179                        .or_default()
180                        .push(item.clone());
181                }
182                return Ok(Vc::cell(
183                    map.into_iter()
184                        .map(|(ty, chunk_items)| {
185                            let item = if chunk_items.len() == 1 {
186                                ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(
187                                    chunk_items.into_iter().next().unwrap(),
188                                )
189                            } else {
190                                ChunkItemOrBatchWithAsyncModuleInfo::Batch(
191                                    Self {
192                                        chunk_items,
193                                        chunk_groups: this.chunk_groups.clone(),
194                                    }
195                                    .resolved_cell(),
196                                )
197                            };
198                            (ty, item)
199                        })
200                        .collect(),
201                ));
202            }
203        }
204        Ok(Vc::cell(smallvec![(
205            chunk_type,
206            ChunkItemOrBatchWithAsyncModuleInfo::Batch(self.to_resolved().await?)
207        )]))
208    }
209}
210
211type ChunkItemBatchWithAsyncModuleInfoByChunkTypeData = SmallVec<
212    [(
213        ResolvedVc<Box<dyn ChunkType>>,
214        ChunkItemOrBatchWithAsyncModuleInfo,
215    ); 1],
216>;
217
218#[turbo_tasks::value(transparent)]
219pub struct ChunkItemBatchWithAsyncModuleInfoByChunkType(
220    ChunkItemBatchWithAsyncModuleInfoByChunkTypeData,
221);
222
223type ChunkItemBatchGroupByChunkTypeT = SmallVec<
224    [(
225        ResolvedVc<Box<dyn ChunkType>>,
226        ResolvedVc<ChunkItemBatchGroup>,
227    ); 1],
228>;
229
230#[turbo_tasks::value(transparent)]
231pub struct ChunkItemBatchGroupByChunkType(ChunkItemBatchGroupByChunkTypeT);
232
233#[turbo_tasks::value(transparent)]
234pub struct ChunkItemBatchGroups(Vec<ResolvedVc<ChunkItemBatchGroup>>);
235
236#[turbo_tasks::value]
237pub struct ChunkItemBatchGroup {
238    pub items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
239    pub chunk_groups: RoaringBitmapWrapper,
240}
241
242#[turbo_tasks::value_impl]
243impl ChunkItemBatchGroup {
244    #[turbo_tasks::function]
245    pub async fn from_module_batch_group(
246        batch_group: Vc<ChunkableModuleBatchGroup>,
247        module_graph: Vc<ModuleGraph>,
248        chunking_context: Vc<Box<dyn ChunkingContext>>,
249    ) -> Result<Vc<Self>> {
250        let async_module_info = module_graph.async_module_info();
251        let batch_group = batch_group.await?;
252        let items = batch_group
253            .items
254            .iter()
255            .map(|&batch| {
256                ChunkItemOrBatchWithAsyncModuleInfo::from_chunkable_module_or_batch(
257                    batch,
258                    async_module_info,
259                    module_graph,
260                    chunking_context,
261                )
262            })
263            .try_flat_join()
264            .await?;
265        Ok(Self {
266            items,
267            chunk_groups: batch_group.chunk_groups.clone(),
268        }
269        .cell())
270    }
271
272    #[turbo_tasks::function]
273    pub async fn split_by_chunk_type(self: Vc<Self>) -> Result<Vc<ChunkItemBatchGroupByChunkType>> {
274        let this = self.await?;
275        // TODO it could avoid the FxIndexMap with some iterator magic...
276        let mut map: FxIndexMap<_, Vec<_>> = FxIndexMap::default();
277        for item in &this.items {
278            let split = item.split_by_chunk_type().await?;
279            for (ty, value) in split.iter() {
280                map.entry(*ty).or_default().push(value.clone());
281            }
282        }
283        let result = if map.len() == 1 {
284            let (ty, _) = map.into_iter().next().unwrap();
285            smallvec![(ty, self.to_resolved().await?)]
286        } else {
287            map.into_iter()
288                .map(|(ty, items)| {
289                    (
290                        ty,
291                        ChunkItemBatchGroup {
292                            items,
293                            chunk_groups: this.chunk_groups.clone(),
294                        }
295                        .resolved_cell(),
296                    )
297                })
298                .collect()
299        };
300        Ok(Vc::cell(result))
301    }
302}
303
304pub async fn batch_info<'a, BatchGroup, Item, Info, BatchGroupInfo, A, B>(
305    batch_groups: &[ResolvedVc<BatchGroup>],
306    items: &[Item],
307    get_batch_group_info: impl Fn(Vc<BatchGroup>) -> A + Send + 'a,
308    get_item_info: impl Fn(&Item) -> B + Send + 'a,
309) -> Result<Vec<Info>>
310where
311    A: Future<Output = Result<BatchGroupInfo>> + Send + 'a,
312    B: Future<Output = Result<Info>> + Send + 'a,
313    BatchGroup: Send,
314    Item: Send + Eq + Hash,
315    BatchGroupInfo: Deref<Target = FxHashMap<Item, Info>> + Send,
316    Info: Clone + Send,
317{
318    let batch_group_info: Vec<BatchGroupInfo> = batch_groups
319        .iter()
320        .map(|&batch_group| get_batch_group_info(*batch_group))
321        .try_join()
322        .await?;
323    let batch_group_info = batch_group_info
324        .iter()
325        .flat_map(|info| info.iter())
326        .collect::<FxHashMap<_, _>>();
327    items
328        .iter()
329        .map(async |item| {
330            Ok(if let Some(&info) = batch_group_info.get(item) {
331                info.clone()
332            } else {
333                get_item_info(item).await?
334            })
335        })
336        .try_join()
337        .await
338}