turbopack_core/chunk/chunking/
mod.rs

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