1use std::sync::atomic::AtomicBool;
2
3use anyhow::{Context, Result};
4use rustc_hash::FxHashMap;
5use turbo_rcstr::rcstr;
6use turbo_tasks::{FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc};
7
8use super::{
9 ChunkGroupContent, ChunkItemWithAsyncModuleInfo, ChunkingContext,
10 availability_info::AvailabilityInfo, chunking::make_chunks,
11};
12use crate::{
13 chunk::{
14 ChunkableModule, ChunkingType, Chunks,
15 available_modules::AvailableModuleItem,
16 chunk_item_batch::{ChunkItemBatchGroup, ChunkItemOrBatchWithAsyncModuleInfo},
17 },
18 environment::ChunkLoading,
19 module::Module,
20 module_graph::{
21 GraphTraversalAction, ModuleGraph,
22 merged_modules::MergedModuleInfo,
23 module_batch::{
24 ChunkableModuleBatchGroup, ChunkableModuleOrBatch, ModuleBatch, ModuleBatchGroup,
25 ModuleOrBatch,
26 },
27 module_batches::{BatchingConfig, ModuleBatchesGraphEdge},
28 },
29 output::{OutputAsset, OutputAssetsReference},
30 traced_asset::TracedAsset,
31};
32
33pub struct MakeChunkGroupResult {
34 pub chunks: ResolvedVc<Chunks>,
35 pub referenced_output_assets: Vec<ResolvedVc<Box<dyn OutputAsset>>>,
36 pub references: Vec<ResolvedVc<Box<dyn OutputAssetsReference>>>,
37 pub availability_info: AvailabilityInfo,
38}
39
40pub async fn make_chunk_group(
42 chunk_group_entries: impl IntoIterator<
43 IntoIter = impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Send,
44 > + Send
45 + Clone,
46 module_graph: Vc<ModuleGraph>,
47 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
48 availability_info: AvailabilityInfo,
49) -> Result<MakeChunkGroupResult> {
50 let can_split_async = !matches!(
51 *chunking_context.environment().chunk_loading().await?,
52 ChunkLoading::Edge
53 );
54 let is_nested_async_availability_enabled = *chunking_context
55 .is_nested_async_availability_enabled()
56 .await?;
57 let should_trace = *chunking_context.is_tracing_enabled().await?;
58 let should_merge_modules = *chunking_context.is_module_merging_enabled().await?;
59 let batching_config = chunking_context.batching_config();
60
61 let ChunkGroupContent {
62 chunkable_items,
63 batch_groups,
64 async_modules,
65 traced_modules,
66 availability_info: new_availability_info,
67 } = chunk_group_content(
68 module_graph,
69 chunk_group_entries.clone(),
70 ChunkGroupContentOptions {
71 availability_info,
72 can_split_async,
73 should_trace,
74 should_merge_modules,
75 batching_config,
76 },
77 )
78 .await?;
79
80 let async_module_info = module_graph.async_module_info();
81
82 let mut chunk_items = chunkable_items
84 .iter()
85 .copied()
86 .map(|m| {
87 ChunkItemOrBatchWithAsyncModuleInfo::from_chunkable_module_or_batch(
88 m,
89 async_module_info,
90 module_graph,
91 *chunking_context,
92 )
93 })
94 .try_join()
95 .await?
96 .into_iter()
97 .flatten()
98 .collect::<Vec<_>>();
99
100 let chunk_item_batch_groups = batch_groups
101 .iter()
102 .map(|&batch_group| {
103 ChunkItemBatchGroup::from_module_batch_group(
104 ChunkableModuleBatchGroup::from_module_batch_group(*batch_group),
105 module_graph,
106 *chunking_context,
107 )
108 .to_resolved()
109 })
110 .try_join()
111 .await?;
112
113 let async_availability_info =
115 if is_nested_async_availability_enabled || !availability_info.is_in_async_module() {
116 new_availability_info.in_async_module()
117 } else {
118 availability_info
119 };
120 let async_loaders = async_modules
121 .into_iter()
122 .map(async |module| {
123 chunking_context
124 .async_loader_chunk_item(*module, module_graph, async_availability_info)
125 .to_resolved()
126 .await
127 })
128 .try_join()
129 .await?;
130 let async_loader_chunk_items = async_loaders
131 .iter()
132 .map(async |&chunk_item| {
133 let chunk_type = chunk_item
134 .into_trait_ref()
135 .await?
136 .ty()
137 .to_resolved()
138 .await?;
139 Ok(ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(
140 ChunkItemWithAsyncModuleInfo {
141 chunk_item,
142 chunk_type,
143 module: None,
144 async_info: None,
145 },
146 ))
147 })
148 .try_join()
149 .await?;
150
151 let referenced_output_assets = traced_modules
152 .into_iter()
153 .map(|module| async move {
154 Ok(ResolvedVc::upcast(
155 TracedAsset::new(*module).to_resolved().await?,
156 ))
157 })
158 .try_join()
159 .await?;
160
161 chunk_items.extend(async_loader_chunk_items);
162
163 let chunks = make_chunks(
165 module_graph,
166 *chunking_context,
167 Vc::cell(chunk_items),
168 Vc::cell(chunk_item_batch_groups),
169 rcstr!(""),
170 )
171 .to_resolved()
172 .await?;
173
174 Ok(MakeChunkGroupResult {
175 chunks,
176 referenced_output_assets,
177 references: ResolvedVc::upcast_vec(async_loaders),
178 availability_info: new_availability_info,
179 })
180}
181
182pub struct ChunkGroupContentOptions {
183 pub availability_info: AvailabilityInfo,
185 pub can_split_async: bool,
187 pub should_trace: bool,
189 pub should_merge_modules: bool,
191 pub batching_config: Vc<BatchingConfig>,
193}
194
195pub async fn chunk_group_content(
197 module_graph: Vc<ModuleGraph>,
198 chunk_group_entries: impl IntoIterator<
199 IntoIter = impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Send,
200 > + Send,
201 ChunkGroupContentOptions {
202 availability_info,
203 can_split_async,
204 should_trace,
205 should_merge_modules,
206 batching_config,
207 }: ChunkGroupContentOptions,
208) -> Result<ChunkGroupContent> {
209 let module_batches_graph = module_graph.module_batches(batching_config).await?;
210
211 type ModuleToChunkableMap = FxHashMap<ModuleOrBatch, ChunkableModuleOrBatch>;
212
213 struct TraverseState {
214 unsorted_items: ModuleToChunkableMap,
215 chunkable_items: FxIndexSet<ChunkableModuleOrBatch>,
216 async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
217 traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
218 }
219
220 let mut state = TraverseState {
221 unsorted_items: FxHashMap::default(),
222 chunkable_items: FxIndexSet::default(),
223 async_modules: FxIndexSet::default(),
224 traced_modules: FxIndexSet::default(),
225 };
226
227 let available_modules = match availability_info.available_modules() {
228 Some(available_modules) => Some(available_modules.snapshot().await?),
229 None => None,
230 };
231
232 let chunk_group_entries = chunk_group_entries.into_iter();
233 let mut entries = Vec::with_capacity(chunk_group_entries.size_hint().0);
234 for entry in chunk_group_entries {
235 entries.push(module_batches_graph.get_entry_index(entry).await?);
236 }
237
238 module_batches_graph.traverse_edges_from_entries_dfs(
239 entries,
240 &mut state,
241 |parent_info, &node, state| {
242 if matches!(node, ModuleOrBatch::None(_)) {
243 return Ok(GraphTraversalAction::Continue);
244 }
245 if let Some((
247 _,
248 ModuleBatchesGraphEdge {
249 ty: ChunkingType::Traced,
250 ..
251 },
252 )) = parent_info
253 {
254 if should_trace {
255 let ModuleOrBatch::Module(module) = node else {
256 unreachable!();
257 };
258 state.traced_modules.insert(module);
259 }
260 return Ok(GraphTraversalAction::Exclude);
261 }
262
263 let Some(chunkable_node) = ChunkableModuleOrBatch::from_module_or_batch(node) else {
264 return Ok(GraphTraversalAction::Exclude);
265 };
266
267 let is_available = available_modules
268 .as_ref()
269 .is_some_and(|available_modules| available_modules.get(chunkable_node.into()));
270
271 let Some((_, edge)) = parent_info else {
272 return Ok(if is_available {
274 GraphTraversalAction::Exclude
275 } else if state
276 .unsorted_items
277 .try_insert(node, chunkable_node)
278 .is_ok()
279 {
280 GraphTraversalAction::Continue
281 } else {
282 GraphTraversalAction::Exclude
283 });
284 };
285
286 Ok(match edge.ty {
287 ChunkingType::Parallel { .. } | ChunkingType::Shared { .. } => {
288 if is_available {
289 GraphTraversalAction::Exclude
290 } else if state
291 .unsorted_items
292 .try_insert(node, chunkable_node)
293 .is_ok()
294 {
295 GraphTraversalAction::Continue
296 } else {
297 GraphTraversalAction::Exclude
298 }
299 }
300 ChunkingType::Async => {
301 if can_split_async {
302 let chunkable_module = ResolvedVc::try_downcast(edge.module.unwrap())
303 .context("Module in async chunking edge is not chunkable")?;
304 let is_async_loader_available =
305 available_modules.as_ref().is_some_and(|available_modules| {
306 available_modules
307 .get(AvailableModuleItem::AsyncLoader(chunkable_module))
308 });
309 if !is_async_loader_available {
310 state.async_modules.insert(chunkable_module);
311 }
312 GraphTraversalAction::Exclude
313 } else if is_available {
314 GraphTraversalAction::Exclude
315 } else if state
316 .unsorted_items
317 .try_insert(node, chunkable_node)
318 .is_ok()
319 {
320 GraphTraversalAction::Continue
321 } else {
322 GraphTraversalAction::Exclude
323 }
324 }
325 ChunkingType::Traced => {
326 unreachable!();
328 }
329 ChunkingType::Isolated { .. } => {
330 GraphTraversalAction::Exclude
332 }
333 })
334 },
335 |_, node, state| {
336 if let Some(chunkable_module) = state.unsorted_items.get(node).copied() {
338 state.chunkable_items.insert(chunkable_module);
339 }
340 },
341 )?;
342
343 let available_modules = state
345 .chunkable_items
346 .iter()
347 .copied()
348 .map(Into::into)
349 .chain(
350 state
351 .async_modules
352 .iter()
353 .copied()
354 .map(AvailableModuleItem::AsyncLoader),
355 )
356 .collect();
357 let availability_info = availability_info
358 .with_modules(Vc::cell(available_modules))
359 .await?;
360
361 let should_merge_modules = if should_merge_modules {
362 let merged_modules = module_graph.merged_modules();
363 let merged_modules_ref = merged_modules.await?;
364 Some((merged_modules, merged_modules_ref))
365 } else {
366 None
367 };
368
369 let chunkable_items = if let Some((merged_modules, merged_modules_ref)) = &should_merge_modules
370 {
371 state
372 .chunkable_items
373 .into_iter()
374 .map(async |chunkable_module| match chunkable_module {
375 ChunkableModuleOrBatch::Module(module) => {
376 if !merged_modules_ref
377 .should_create_chunk_item_for(ResolvedVc::upcast(module))
378 .await?
379 {
380 return Ok(None);
381 }
382
383 let module = if let Some(replacement) = merged_modules_ref
384 .should_replace_module(ResolvedVc::upcast(module))
385 .await?
386 {
387 replacement
388 } else {
389 module
390 };
391
392 Ok(Some(ChunkableModuleOrBatch::Module(module)))
393 }
394 ChunkableModuleOrBatch::Batch(batch) => Ok(Some(ChunkableModuleOrBatch::Batch(
395 map_module_batch(*merged_modules, *batch)
396 .to_resolved()
397 .await?,
398 ))),
399 ChunkableModuleOrBatch::None(i) => Ok(Some(ChunkableModuleOrBatch::None(i))),
400 })
401 .try_flat_join()
402 .await?
403 } else {
404 state.chunkable_items.into_iter().collect()
405 };
406
407 let mut batch_groups = FxIndexSet::default();
408 for &module in &chunkable_items {
409 if let Some(batch_group) = module_batches_graph.get_batch_group(&module.into()) {
410 batch_groups.insert(batch_group);
411 }
412 }
413
414 let batch_groups = if let Some((merged_modules, _)) = &should_merge_modules {
415 batch_groups
416 .into_iter()
417 .map(|group| map_module_batch_group(*merged_modules, *group).to_resolved())
418 .try_join()
419 .await?
420 } else {
421 batch_groups.into_iter().collect()
422 };
423
424 Ok(ChunkGroupContent {
425 chunkable_items,
426 batch_groups,
427 async_modules: state.async_modules,
428 traced_modules: state.traced_modules,
429 availability_info,
430 })
431}
432
433#[turbo_tasks::function]
434async fn map_module_batch(
435 merged_modules: Vc<MergedModuleInfo>,
436 batch: Vc<ModuleBatch>,
437) -> Result<Vc<ModuleBatch>> {
438 let merged_modules = merged_modules.await?;
439 let batch_ref = batch.await?;
440
441 let modified = AtomicBool::new(false);
442 let modules = batch_ref
443 .modules
444 .iter()
445 .copied()
446 .map(async |module| {
447 if !merged_modules
448 .should_create_chunk_item_for(ResolvedVc::upcast(module))
449 .await?
450 {
451 modified.store(true, std::sync::atomic::Ordering::Relaxed);
452 return Ok(None);
453 }
454
455 let module = if let Some(replacement) = merged_modules
456 .should_replace_module(ResolvedVc::upcast(module))
457 .await?
458 {
459 modified.store(true, std::sync::atomic::Ordering::Relaxed);
460 replacement
461 } else {
462 module
463 };
464
465 Ok(Some(module))
466 })
467 .try_flat_join()
468 .await?;
469
470 if modified.into_inner() {
471 Ok(ModuleBatch::new(
472 ResolvedVc::deref_vec(modules),
473 batch_ref.chunk_groups.clone(),
474 ))
475 } else {
476 Ok(batch)
477 }
478}
479
480#[turbo_tasks::function]
481async fn map_module_batch_group(
482 merged_modules: Vc<MergedModuleInfo>,
483 group: Vc<ModuleBatchGroup>,
484) -> Result<Vc<ModuleBatchGroup>> {
485 let merged_modules_ref = merged_modules.await?;
486 let group_ref = group.await?;
487
488 let modified = AtomicBool::new(false);
489 let items = group_ref
490 .items
491 .iter()
492 .copied()
493 .map(async |chunkable_module| match chunkable_module {
494 ModuleOrBatch::Module(module) => {
495 if !merged_modules_ref
496 .should_create_chunk_item_for(module)
497 .await?
498 {
499 modified.store(true, std::sync::atomic::Ordering::Relaxed);
500 return Ok(None);
501 }
502
503 let module = if let Some(replacement) =
504 merged_modules_ref.should_replace_module(module).await?
505 {
506 modified.store(true, std::sync::atomic::Ordering::Relaxed);
507 ResolvedVc::upcast(replacement)
508 } else {
509 module
510 };
511
512 Ok(Some(ModuleOrBatch::Module(module)))
513 }
514 ModuleOrBatch::Batch(batch) => {
515 let replacement = map_module_batch(merged_modules, *batch)
516 .to_resolved()
517 .await?;
518 if replacement != batch {
519 modified.store(true, std::sync::atomic::Ordering::Relaxed);
520 }
521 Ok(Some(ModuleOrBatch::Batch(replacement)))
522 }
523 ModuleOrBatch::None(i) => Ok(Some(ModuleOrBatch::None(i))),
524 })
525 .try_flat_join()
526 .await?;
527
528 if modified.into_inner() {
529 Ok(ModuleBatchGroup::new(items, group_ref.chunk_groups.clone()))
530 } else {
531 Ok(group)
532 }
533}