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
286pub 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 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 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 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 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#[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}