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