turbopack_core/chunk/chunking/
mod.rs

1use std::future::IntoFuture;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use rustc_hash::FxHashMap;
6use smallvec::{SmallVec, smallvec};
7use tracing::{Instrument, Level};
8use turbo_rcstr::RcStr;
9use turbo_tasks::{
10    FxIndexMap, FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc,
11    debug::ValueDebugFormat, trace::TraceRawVcs,
12};
13
14use crate::{
15    chunk::{
16        Chunk, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkType, ChunkingContext, batch_info,
17        chunk_item_batch::{
18            ChunkItemBatchGroup, ChunkItemBatchWithAsyncModuleInfo,
19            ChunkItemOrBatchWithAsyncModuleInfo,
20        },
21        chunking::{
22            dev::{app_vendors_split, expand_batches},
23            production::make_production_chunks,
24            style_production::make_style_production_chunks,
25        },
26    },
27    module_graph::ModuleGraph,
28};
29
30mod dev;
31mod production;
32mod style_production;
33
34#[turbo_tasks::value]
35struct ChunkItemsWithInfo {
36    #[allow(clippy::type_complexity)]
37    by_type: SmallVec<
38        [(
39            ResolvedVc<Box<dyn ChunkType>>,
40            SmallVec<[ChunkItemOrBatchWithInfo; 1]>,
41            SmallVec<[ResolvedVc<ChunkItemBatchGroup>; 1]>,
42        ); 1],
43    >,
44}
45
46#[turbo_tasks::value(transparent)]
47struct BatchChunkItemsWithInfo(
48    FxHashMap<ChunkItemOrBatchWithAsyncModuleInfo, ResolvedVc<ChunkItemsWithInfo>>,
49);
50
51#[derive(Clone, PartialEq, Eq, TraceRawVcs, NonLocalValue, ValueDebugFormat, Encode, Decode)]
52enum ChunkItemOrBatchWithInfo {
53    ChunkItem {
54        chunk_item: ChunkItemWithAsyncModuleInfo,
55        size: usize,
56        asset_ident: RcStr,
57    },
58    Batch {
59        batch: ResolvedVc<ChunkItemBatchWithAsyncModuleInfo>,
60        size: usize,
61    },
62}
63
64impl ChunkItemOrBatchWithInfo {
65    fn size(&self) -> usize {
66        match self {
67            ChunkItemOrBatchWithInfo::ChunkItem { size, .. } => *size,
68            ChunkItemOrBatchWithInfo::Batch { size, .. } => *size,
69        }
70    }
71}
72
73#[turbo_tasks::function]
74async fn batch_size(
75    chunking_context: Vc<Box<dyn ChunkingContext>>,
76    ty: ResolvedVc<Box<dyn ChunkType>>,
77    batch: Vc<ChunkItemBatchWithAsyncModuleInfo>,
78) -> Result<Vc<usize>> {
79    let size = batch
80        .await?
81        .chunk_items
82        .iter()
83        .map(
84            |&ChunkItemWithAsyncModuleInfo {
85                 chunk_item,
86                 async_info,
87                 module: _,
88             }| {
89                ty.chunk_item_size(chunking_context, *chunk_item, async_info.map(|info| *info))
90            },
91        )
92        .try_join()
93        .await?
94        .into_iter()
95        .map(|size| *size)
96        .sum();
97    Ok(Vc::cell(size))
98}
99
100async fn plain_chunk_items_with_info(
101    chunk_item_or_batch: ChunkItemOrBatchWithAsyncModuleInfo,
102    chunking_context: Vc<Box<dyn ChunkingContext>>,
103) -> Result<ChunkItemsWithInfo> {
104    Ok(match chunk_item_or_batch {
105        ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(chunk_item_with_info) => {
106            let ChunkItemWithAsyncModuleInfo {
107                chunk_item,
108                async_info,
109                module: _,
110            } = chunk_item_with_info;
111
112            let asset_ident = chunk_item.asset_ident().to_string();
113            let ty = chunk_item.ty();
114            let chunk_item_size =
115                ty.chunk_item_size(chunking_context, *chunk_item, async_info.map(|info| *info));
116
117            ChunkItemsWithInfo {
118                by_type: smallvec![(
119                    ty.to_resolved().await?,
120                    smallvec![ChunkItemOrBatchWithInfo::ChunkItem {
121                        chunk_item: chunk_item_with_info,
122                        size: *chunk_item_size.await?,
123                        asset_ident: asset_ident.owned().await?,
124                    }],
125                    SmallVec::new(),
126                )],
127            }
128        }
129        ChunkItemOrBatchWithAsyncModuleInfo::Batch(batch) => {
130            let batch_by_type = batch.split_by_chunk_type().await?;
131            let by_type = batch_by_type
132                .iter()
133                .map(|&(ty, ref chunk_item_or_batch)| {
134                    plain_chunk_items_with_info_with_type(
135                        chunk_item_or_batch,
136                        ty,
137                        None,
138                        chunking_context,
139                    )
140                })
141                .try_join()
142                .await?;
143            ChunkItemsWithInfo {
144                by_type: by_type.into_iter().collect(),
145            }
146        }
147    })
148}
149
150async fn plain_chunk_items_with_info_with_type(
151    chunk_item_or_batch: &ChunkItemOrBatchWithAsyncModuleInfo,
152    ty: ResolvedVc<Box<dyn ChunkType>>,
153    batch_group: Option<ResolvedVc<ChunkItemBatchGroup>>,
154    chunking_context: Vc<Box<dyn ChunkingContext>>,
155) -> Result<(
156    ResolvedVc<Box<dyn ChunkType>>,
157    SmallVec<[ChunkItemOrBatchWithInfo; 1]>,
158    SmallVec<[ResolvedVc<ChunkItemBatchGroup>; 1]>,
159)> {
160    match chunk_item_or_batch {
161        ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(chunk_item_with_info) => {
162            let &ChunkItemWithAsyncModuleInfo {
163                chunk_item,
164                async_info,
165                module: _,
166            } = chunk_item_with_info;
167
168            let asset_ident = chunk_item.asset_ident().to_string();
169            let chunk_item_size =
170                ty.chunk_item_size(chunking_context, *chunk_item, async_info.map(|info| *info));
171            Ok((
172                ty,
173                smallvec![ChunkItemOrBatchWithInfo::ChunkItem {
174                    chunk_item: chunk_item_with_info.clone(),
175                    size: *chunk_item_size.await?,
176                    asset_ident: asset_ident.owned().await?,
177                }],
178                batch_group.into_iter().collect(),
179            ))
180        }
181        &ChunkItemOrBatchWithAsyncModuleInfo::Batch(batch) => {
182            let size = *batch_size(chunking_context, *ty, *batch).await?;
183            Ok((
184                ty,
185                smallvec![ChunkItemOrBatchWithInfo::Batch { batch, size }],
186                batch_group.into_iter().collect(),
187            ))
188        }
189    }
190}
191
192#[turbo_tasks::function]
193async fn chunk_items_with_info(
194    chunk_item_or_batch: ChunkItemOrBatchWithAsyncModuleInfo,
195    chunking_context: Vc<Box<dyn ChunkingContext>>,
196) -> Result<Vc<ChunkItemsWithInfo>> {
197    let chunk_items_with_info =
198        plain_chunk_items_with_info(chunk_item_or_batch, chunking_context).await?;
199    Ok(chunk_items_with_info.cell())
200}
201
202#[turbo_tasks::function]
203async fn chunk_items_with_info_with_type(
204    chunk_item_or_batch: ChunkItemOrBatchWithAsyncModuleInfo,
205    ty: ResolvedVc<Box<dyn ChunkType>>,
206    batch_group: Option<ResolvedVc<ChunkItemBatchGroup>>,
207    chunking_context: Vc<Box<dyn ChunkingContext>>,
208) -> Result<Vc<ChunkItemsWithInfo>> {
209    let result = plain_chunk_items_with_info_with_type(
210        &chunk_item_or_batch,
211        ty,
212        batch_group,
213        chunking_context,
214    )
215    .await?;
216    Ok(ChunkItemsWithInfo {
217        by_type: smallvec![result],
218    }
219    .cell())
220}
221
222#[turbo_tasks::function]
223async fn batch_chunk_items_with_info(
224    batch_group: Vc<ChunkItemBatchGroup>,
225    chunking_context: Vc<Box<dyn ChunkingContext>>,
226) -> Result<Vc<BatchChunkItemsWithInfo>> {
227    let split_batch_group = batch_group.split_by_chunk_type().await?;
228    if split_batch_group.len() == 1 {
229        let &(ty, batch) = split_batch_group.into_iter().next().unwrap();
230        Ok(batch_chunk_items_with_info_with_type(
231            *batch,
232            *ty,
233            chunking_context,
234        ))
235    } else {
236        let maps = split_batch_group
237            .into_iter()
238            .map(|&(ty, batch)| {
239                batch_chunk_items_with_info_with_type(*batch, *ty, chunking_context)
240            })
241            .try_join()
242            .await?;
243        Ok(Vc::cell(
244            maps.iter()
245                .flatten()
246                .map(|(key, &value)| (key.clone(), value))
247                .collect(),
248        ))
249    }
250}
251
252#[turbo_tasks::function]
253async fn batch_chunk_items_with_info_with_type(
254    batch_group: Vc<ChunkItemBatchGroup>,
255    ty: Vc<Box<dyn ChunkType>>,
256    chunking_context: Vc<Box<dyn ChunkingContext>>,
257) -> Result<Vc<BatchChunkItemsWithInfo>> {
258    let map = batch_group
259        .await?
260        .items
261        .iter()
262        .map(async |item| {
263            Ok((
264                item.clone(),
265                chunk_items_with_info_with_type(
266                    item.clone(),
267                    ty,
268                    Some(batch_group),
269                    chunking_context,
270                )
271                .to_resolved()
272                .await?,
273            ))
274        })
275        .try_join()
276        .await?
277        .into_iter()
278        .collect();
279    Ok(Vc::cell(map))
280}
281
282/// Creates chunks based on heuristics for the passed `chunk_items`.
283pub async fn make_chunks(
284    module_graph: Vc<ModuleGraph>,
285    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
286    chunk_items_or_batches: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
287    batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
288    key_prefix: RcStr,
289) -> Result<Vec<ResolvedVc<Box<dyn Chunk>>>> {
290    let chunking_configs = &*chunking_context.chunking_configs().await?;
291
292    let span = tracing::trace_span!(
293        "get chunk item info",
294        chunk_items_or_batches = chunk_items_or_batches.len(),
295        batch_groups = batch_groups.len()
296    );
297    let chunk_items: Vec<ReadRef<ChunkItemsWithInfo>> = batch_info(
298        &batch_groups,
299        &chunk_items_or_batches,
300        |batch_group| batch_chunk_items_with_info(batch_group, *chunking_context).into_future(),
301        |c| chunk_items_with_info(c.clone(), *chunking_context).to_resolved(),
302    )
303    .instrument(span)
304    .await?
305    .into_iter()
306    .try_join()
307    .await?;
308
309    let mut map = FxIndexMap::<_, (Vec<_>, FxIndexSet<_>)>::default();
310    for result in chunk_items.iter() {
311        for (ty, chunk_items, batch_groups) in result.by_type.iter() {
312            let entry = map.entry(*ty).or_default();
313            entry.0.extend(chunk_items);
314            entry.1.extend(batch_groups);
315        }
316    }
317
318    let mut chunks = Vec::new();
319    for (ty, (chunk_items, batch_groups)) in map {
320        let ty_name = ty.to_string().await?;
321        let span = tracing::trace_span!("make chunks for type", name = display(&ty_name));
322        async {
323            let mut split_context = SplitContext {
324                ty,
325                chunking_context,
326                chunks: &mut chunks,
327            };
328
329            if let Some(chunking_config) = chunking_configs.get(&ty) {
330                // Production chunking
331                if *ty.is_style().await? {
332                    make_style_production_chunks(
333                        chunk_items,
334                        batch_groups.into_iter().collect(),
335                        module_graph,
336                        chunking_context,
337                        chunking_config,
338                        split_context,
339                    )
340                    .await?;
341                } else {
342                    make_production_chunks(
343                        chunk_items,
344                        batch_groups.into_iter().collect(),
345                        module_graph,
346                        chunking_config,
347                        split_context,
348                    )
349                    .await?;
350                }
351            } else {
352                // Development chunking
353                if *ty.is_style().await? {
354                    make_chunk(
355                        chunk_items,
356                        Vec::new(),
357                        &mut format!("{key_prefix}{ty_name}"),
358                        &mut split_context,
359                    )
360                    .await?;
361                } else {
362                    let chunk_items = expand_batches(chunk_items, ty, chunking_context).await?;
363                    let chunk_items = chunk_items.iter().collect();
364                    app_vendors_split(
365                        chunk_items,
366                        format!("{key_prefix}{ty_name}"),
367                        &mut split_context,
368                    )
369                    .await?;
370                }
371            }
372
373            anyhow::Ok(())
374        }
375        .instrument(span)
376        .await?
377    }
378
379    // Resolve all chunks before returning
380    let resolved_chunks = chunks
381        .into_iter()
382        .map(|chunk| chunk.to_resolved())
383        .try_join()
384        .await?;
385
386    Ok(resolved_chunks)
387}
388
389struct SplitContext<'a> {
390    ty: ResolvedVc<Box<dyn ChunkType>>,
391    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
392    // resolution of `chunks` is deferred so it can be done with `try_join` at the end, letting as
393    // much work happen in parallel as possible.
394    chunks: &'a mut Vec<Vc<Box<dyn Chunk>>>,
395}
396
397/// Creates a chunk with the given `chunk_items. `key` should be unique.
398#[tracing::instrument(level = Level::TRACE, skip_all, fields(key = display(key)))]
399async fn make_chunk(
400    chunk_items: Vec<&'_ ChunkItemOrBatchWithInfo>,
401    batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
402    key: &mut String,
403    split_context: &mut SplitContext<'_>,
404) -> Result<()> {
405    split_context.chunks.push(
406        split_context.ty.chunk(
407            *split_context.chunking_context,
408            chunk_items
409                .into_iter()
410                .map(|item| match item {
411                    ChunkItemOrBatchWithInfo::ChunkItem { chunk_item, .. } => {
412                        ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(chunk_item.clone())
413                    }
414                    &ChunkItemOrBatchWithInfo::Batch { batch, .. } => {
415                        ChunkItemOrBatchWithAsyncModuleInfo::Batch(batch)
416                    }
417                })
418                .collect(),
419            ResolvedVc::deref_vec(batch_groups),
420        ),
421    );
422    Ok(())
423}