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