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