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