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