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#[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 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 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 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 chunks: &'a mut Vec<Vc<Box<dyn Chunk>>>,
399}
400
401#[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}