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#[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 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 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 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 chunks: &'a mut Vec<Vc<Box<dyn Chunk>>>,
402}
403
404#[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}