Skip to main content

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