1use std::{cell::RefCell, 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 Chunk, ChunkGroupContent, ChunkItemWithAsyncModuleInfo, ChunkingContext,
10 availability_info::AvailabilityInfo, chunking::make_chunks,
11};
12use crate::{
13 chunk::{
14 ChunkableModule, ChunkingType,
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: Vec<ResolvedVc<Box<dyn Chunk>>>,
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 chunk_items,
159 chunk_item_batch_groups,
160 rcstr!(""),
161 )
162 .await?;
163
164 Ok(MakeChunkGroupResult {
165 chunks,
166 referenced_output_assets,
167 references: ResolvedVc::upcast_vec(async_loaders),
168 availability_info: new_availability_info,
169 })
170}
171
172pub async fn references_to_output_assets(
173 references: impl IntoIterator<Item = &ResolvedVc<Box<dyn ModuleReference>>>,
174) -> Result<Vc<OutputAssetsWithReferenced>> {
175 let output_assets = references
176 .into_iter()
177 .map(|reference| reference.resolve_reference().primary_output_assets())
178 .try_join()
179 .await?;
180 let mut set = HashSet::new();
181 let output_assets = output_assets
182 .iter()
183 .flatten()
184 .copied()
185 .filter(|&asset| set.insert(asset))
186 .collect::<Vec<_>>();
187 Ok(OutputAssetsWithReferenced {
188 assets: ResolvedVc::cell(output_assets),
189 referenced_assets: OutputAssets::empty_resolved(),
190 references: OutputAssetsReferences::empty_resolved(),
191 }
192 .cell())
193}
194
195pub struct ChunkGroupContentOptions {
196 pub availability_info: AvailabilityInfo,
198 pub can_split_async: bool,
200 pub should_trace: bool,
202 pub should_merge_modules: bool,
204 pub batching_config: Vc<BatchingConfig>,
206}
207
208pub async fn chunk_group_content(
210 module_graph: Vc<ModuleGraph>,
211 chunk_group_entries: impl IntoIterator<
212 IntoIter = impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Send,
213 > + Send,
214 ChunkGroupContentOptions {
215 availability_info,
216 can_split_async,
217 should_trace,
218 should_merge_modules,
219 batching_config,
220 }: ChunkGroupContentOptions,
221) -> Result<ChunkGroupContent> {
222 let module_batches_graph = module_graph.module_batches(batching_config).await?;
223
224 type ModuleToChunkableMap = FxHashMap<ModuleOrBatch, ChunkableModuleOrBatch>;
225
226 struct TraverseState {
227 unsorted_items: ModuleToChunkableMap,
228 chunkable_items: FxIndexSet<ChunkableModuleOrBatch>,
229 async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
230 traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
231 }
232
233 let mut state = TraverseState {
234 unsorted_items: FxHashMap::default(),
235 chunkable_items: FxIndexSet::default(),
236 async_modules: FxIndexSet::default(),
237 traced_modules: FxIndexSet::default(),
238 };
239
240 let available_modules = match availability_info.available_modules() {
241 Some(available_modules) => Some(available_modules.snapshot().await?),
242 None => None,
243 };
244
245 let chunk_group_entries = chunk_group_entries.into_iter();
246 let mut entries = Vec::with_capacity(chunk_group_entries.size_hint().0);
247 for entry in chunk_group_entries {
248 entries.push(module_batches_graph.get_entry_index(entry).await?);
249 }
250
251 module_batches_graph.traverse_edges_from_entries_dfs(
252 entries,
253 &mut state,
254 |parent_info, &node, state| {
255 if matches!(node, ModuleOrBatch::None(_)) {
256 return Ok(GraphTraversalAction::Continue);
257 }
258 if let Some((
260 _,
261 ModuleBatchesGraphEdge {
262 ty: ChunkingType::Traced,
263 ..
264 },
265 )) = parent_info
266 {
267 if should_trace {
268 let ModuleOrBatch::Module(module) = node else {
269 unreachable!();
270 };
271 state.traced_modules.insert(module);
272 }
273 return Ok(GraphTraversalAction::Exclude);
274 }
275
276 let Some(chunkable_node) = ChunkableModuleOrBatch::from_module_or_batch(node) else {
277 return Ok(GraphTraversalAction::Exclude);
278 };
279
280 let is_available = available_modules
281 .as_ref()
282 .is_some_and(|available_modules| available_modules.get(chunkable_node.into()));
283
284 let Some((_, edge)) = parent_info else {
285 return Ok(if is_available {
287 GraphTraversalAction::Exclude
288 } else if state
289 .unsorted_items
290 .try_insert(node, chunkable_node)
291 .is_ok()
292 {
293 GraphTraversalAction::Continue
294 } else {
295 GraphTraversalAction::Exclude
296 });
297 };
298
299 Ok(match edge.ty {
300 ChunkingType::Parallel { .. } | ChunkingType::Shared { .. } => {
301 if is_available {
302 GraphTraversalAction::Exclude
303 } else if state
304 .unsorted_items
305 .try_insert(node, chunkable_node)
306 .is_ok()
307 {
308 GraphTraversalAction::Continue
309 } else {
310 GraphTraversalAction::Exclude
311 }
312 }
313 ChunkingType::Async => {
314 if can_split_async {
315 let chunkable_module = ResolvedVc::try_downcast(edge.module.unwrap())
316 .context("Module in async chunking edge is not chunkable")?;
317 let is_async_loader_available =
318 available_modules.as_ref().is_some_and(|available_modules| {
319 available_modules
320 .get(AvailableModuleItem::AsyncLoader(chunkable_module))
321 });
322 if !is_async_loader_available {
323 state.async_modules.insert(chunkable_module);
324 }
325 GraphTraversalAction::Exclude
326 } else if is_available {
327 GraphTraversalAction::Exclude
328 } else if state
329 .unsorted_items
330 .try_insert(node, chunkable_node)
331 .is_ok()
332 {
333 GraphTraversalAction::Continue
334 } else {
335 GraphTraversalAction::Exclude
336 }
337 }
338 ChunkingType::Traced => {
339 unreachable!();
341 }
342 ChunkingType::Isolated { .. } => {
343 GraphTraversalAction::Exclude
345 }
346 })
347 },
348 |_, node, state| {
349 if let Some(chunkable_module) = state.unsorted_items.get(node).copied() {
351 state.chunkable_items.insert(chunkable_module);
352 }
353 },
354 )?;
355
356 let available_modules = state
358 .chunkable_items
359 .iter()
360 .copied()
361 .map(Into::into)
362 .chain(
363 state
364 .async_modules
365 .iter()
366 .copied()
367 .map(AvailableModuleItem::AsyncLoader),
368 )
369 .collect();
370 let availability_info = availability_info
371 .with_modules(Vc::cell(available_modules))
372 .await?;
373
374 let should_merge_modules = if should_merge_modules {
375 let merged_modules = module_graph.merged_modules();
376 let merged_modules_ref = merged_modules.await?;
377 Some((merged_modules, merged_modules_ref))
378 } else {
379 None
380 };
381
382 let chunkable_items = if let Some((merged_modules, merged_modules_ref)) = &should_merge_modules
383 {
384 state
385 .chunkable_items
386 .into_iter()
387 .map(async |chunkable_module| match chunkable_module {
388 ChunkableModuleOrBatch::Module(module) => {
389 if !merged_modules_ref.should_create_chunk_item_for(ResolvedVc::upcast(module))
390 {
391 return Ok(None);
392 }
393
394 let module = if let Some(replacement) =
395 merged_modules_ref.should_replace_module(ResolvedVc::upcast(module))
396 {
397 replacement
398 } else {
399 module
400 };
401
402 Ok(Some(ChunkableModuleOrBatch::Module(module)))
403 }
404 ChunkableModuleOrBatch::Batch(batch) => Ok(Some(ChunkableModuleOrBatch::Batch(
405 map_module_batch(*merged_modules, *batch)
406 .to_resolved()
407 .await?,
408 ))),
409 ChunkableModuleOrBatch::None(i) => Ok(Some(ChunkableModuleOrBatch::None(i))),
410 })
411 .try_flat_join()
412 .await?
413 } else {
414 state.chunkable_items.into_iter().collect()
415 };
416
417 let mut batch_groups = FxIndexSet::default();
418 for &module in &chunkable_items {
419 if let Some(batch_group) = module_batches_graph.get_batch_group(&module.into()) {
420 batch_groups.insert(batch_group);
421 }
422 }
423
424 let batch_groups = if let Some((merged_modules, _)) = &should_merge_modules {
425 batch_groups
426 .into_iter()
427 .map(|group| map_module_batch_group(*merged_modules, *group).to_resolved())
428 .try_join()
429 .await?
430 } else {
431 batch_groups.into_iter().collect()
432 };
433
434 Ok(ChunkGroupContent {
435 chunkable_items,
436 batch_groups,
437 async_modules: state.async_modules,
438 traced_modules: state.traced_modules,
439 availability_info,
440 })
441}
442
443#[turbo_tasks::function]
444async fn map_module_batch(
445 merged_modules: Vc<MergedModuleInfo>,
446 batch: Vc<ModuleBatch>,
447) -> Result<Vc<ModuleBatch>> {
448 let merged_modules = merged_modules.await?;
449 let batch_ref = batch.await?;
450
451 let modified = RefCell::new(false);
452 let modules = batch_ref
453 .modules
454 .iter()
455 .flat_map(|&module| {
456 if !merged_modules.should_create_chunk_item_for(ResolvedVc::upcast(module)) {
457 *modified.borrow_mut() = true;
458 return None;
459 }
460
461 let module = if let Some(replacement) =
462 merged_modules.should_replace_module(ResolvedVc::upcast(module))
463 {
464 *modified.borrow_mut() = true;
465 replacement
466 } else {
467 module
468 };
469
470 Some(module)
471 })
472 .collect::<Vec<_>>();
473
474 if modified.into_inner() {
475 Ok(ModuleBatch::new(
476 ResolvedVc::deref_vec(modules),
477 batch_ref.chunk_groups.clone(),
478 ))
479 } else {
480 Ok(batch)
481 }
482}
483
484#[turbo_tasks::function]
485async fn map_module_batch_group(
486 merged_modules: Vc<MergedModuleInfo>,
487 group: Vc<ModuleBatchGroup>,
488) -> Result<Vc<ModuleBatchGroup>> {
489 let merged_modules_ref = merged_modules.await?;
490 let group_ref = group.await?;
491
492 let modified = AtomicBool::new(false);
493 let items = group_ref
494 .items
495 .iter()
496 .copied()
497 .map(async |chunkable_module| match chunkable_module {
498 ModuleOrBatch::Module(module) => {
499 if !merged_modules_ref.should_create_chunk_item_for(module) {
500 modified.store(true, std::sync::atomic::Ordering::Relaxed);
501 return Ok(None);
502 }
503
504 let module =
505 if let Some(replacement) = merged_modules_ref.should_replace_module(module) {
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}