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