turbopack_core/chunk/chunking/
mod.rs

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