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