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