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