1use std::collections::HashSet;
2
3use anyhow::{Context, Result};
4use rustc_hash::FxHashMap;
5use turbo_tasks::{FxIndexSet, ResolvedVc, TryJoinIterExt, Value, Vc};
6
7use super::{
8 Chunk, ChunkGroupContent, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkingContext,
9 availability_info::AvailabilityInfo, chunking::make_chunks,
10};
11use crate::{
12 chunk::{
13 ChunkableModule, ChunkingType,
14 chunk_item_batch::{ChunkItemBatchGroup, ChunkItemOrBatchWithAsyncModuleInfo},
15 },
16 environment::ChunkLoading,
17 module::Module,
18 module_graph::{
19 GraphTraversalAction, ModuleGraph,
20 module_batch::{ChunkableModuleBatchGroup, ChunkableModuleOrBatch, ModuleOrBatch},
21 module_batches::{BatchingConfig, ModuleBatchesGraphEdge},
22 },
23 output::OutputAssets,
24 reference::ModuleReference,
25 traced_asset::TracedAsset,
26};
27
28pub struct MakeChunkGroupResult {
29 pub chunks: Vec<ResolvedVc<Box<dyn Chunk>>>,
30 pub availability_info: AvailabilityInfo,
31}
32
33pub async fn make_chunk_group(
35 chunk_group_entries: impl IntoIterator<
36 IntoIter = impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Send,
37 > + Send
38 + Clone,
39 module_graph: Vc<ModuleGraph>,
40 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
41 availability_info: AvailabilityInfo,
42) -> Result<MakeChunkGroupResult> {
43 let can_split_async = !matches!(
44 *chunking_context.environment().chunk_loading().await?,
45 ChunkLoading::Edge
46 );
47 let should_trace = *chunking_context.is_tracing_enabled().await?;
48 let batching_config = chunking_context.batching_config();
49
50 let ChunkGroupContent {
51 chunkable_items,
52 batch_groups,
53 async_modules,
54 traced_modules,
55 } = chunk_group_content(
56 module_graph,
57 chunk_group_entries.clone(),
58 availability_info,
59 can_split_async,
60 should_trace,
61 batching_config,
62 )
63 .await?;
64
65 let async_modules_info = module_graph.async_module_info().await?;
66
67 let mut chunk_items = chunkable_items
69 .iter()
70 .copied()
71 .map(|m| {
72 ChunkItemOrBatchWithAsyncModuleInfo::from_chunkable_module_or_batch(
73 m,
74 &async_modules_info,
75 module_graph,
76 *chunking_context,
77 )
78 })
79 .try_join()
80 .await?
81 .into_iter()
82 .flatten()
83 .collect::<Vec<_>>();
84
85 let chunk_item_batch_groups = batch_groups
86 .iter()
87 .map(|&batch_group| {
88 ChunkItemBatchGroup::from_module_batch_group(
89 ChunkableModuleBatchGroup::from_module_batch_group(*batch_group),
90 module_graph,
91 *chunking_context,
92 )
93 .to_resolved()
94 })
95 .try_join()
96 .await?;
97
98 let availability_info = availability_info
100 .with_modules(Vc::cell(chunkable_items))
101 .await?;
102
103 let async_loaders = async_modules
105 .into_iter()
106 .map(async |module| {
107 chunking_context
108 .async_loader_chunk_item(*module, module_graph, Value::new(availability_info))
109 .to_resolved()
110 .await
111 })
112 .try_join()
113 .await?;
114 let async_loader_chunk_items = async_loaders.iter().map(|&chunk_item| {
115 ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(ChunkItemWithAsyncModuleInfo {
116 chunk_item,
117 module: None,
118 async_info: None,
119 })
120 });
121
122 let async_loader_references = async_loaders
124 .iter()
125 .map(|&loader| loader.references())
126 .try_join()
127 .await?;
128
129 let mut referenced_output_assets = traced_modules
130 .into_iter()
131 .map(|module| async move {
132 Ok(ResolvedVc::upcast(
133 TracedAsset::new(*module).to_resolved().await?,
134 ))
135 })
136 .try_join()
137 .await?;
138
139 chunk_items.extend(async_loader_chunk_items);
140 referenced_output_assets.reserve(
141 async_loader_references
142 .iter()
143 .map(|r| r.len())
144 .sum::<usize>(),
145 );
146 referenced_output_assets.extend(async_loader_references.into_iter().flatten());
147
148 let chunks = make_chunks(
150 module_graph,
151 chunking_context,
152 chunk_items,
153 chunk_item_batch_groups,
154 "".into(),
155 ResolvedVc::cell(referenced_output_assets),
156 )
157 .await?;
158
159 Ok(MakeChunkGroupResult {
160 chunks,
161 availability_info,
162 })
163}
164
165pub async fn references_to_output_assets(
166 references: impl IntoIterator<Item = &ResolvedVc<Box<dyn ModuleReference>>>,
167) -> Result<Vc<OutputAssets>> {
168 let output_assets = references
169 .into_iter()
170 .map(|reference| reference.resolve_reference().primary_output_assets())
171 .try_join()
172 .await?;
173 let mut set = HashSet::new();
174 let output_assets = output_assets
175 .iter()
176 .flatten()
177 .copied()
178 .filter(|&asset| set.insert(asset))
179 .map(|asset| *asset)
180 .collect::<Vec<_>>();
181 Ok(OutputAssets::new(output_assets))
182}
183
184pub async fn chunk_group_content(
185 module_graph: Vc<ModuleGraph>,
186 chunk_group_entries: impl IntoIterator<
187 IntoIter = impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Send,
188 > + Send,
189 availability_info: AvailabilityInfo,
190 can_split_async: bool,
191 should_trace: bool,
192 batching_config: Vc<BatchingConfig>,
193) -> Result<ChunkGroupContent> {
194 let module_batches_graph = module_graph.module_batches(batching_config).await?;
195
196 type ModuleToChunkableMap = FxHashMap<ModuleOrBatch, ChunkableModuleOrBatch>;
197
198 struct TraverseState {
199 unsorted_items: ModuleToChunkableMap,
200 chunkable_items: FxIndexSet<ChunkableModuleOrBatch>,
201 async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
202 traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
203 }
204
205 let mut state = TraverseState {
206 unsorted_items: FxHashMap::default(),
207 chunkable_items: FxIndexSet::default(),
208 async_modules: FxIndexSet::default(),
209 traced_modules: FxIndexSet::default(),
210 };
211
212 let available_modules = match availability_info.available_modules() {
213 Some(available_modules) => Some(available_modules.snapshot().await?),
214 None => None,
215 };
216
217 let chunk_group_entries = chunk_group_entries.into_iter();
218 let mut entries = Vec::with_capacity(chunk_group_entries.size_hint().0);
219 for entry in chunk_group_entries {
220 entries.push(module_batches_graph.get_entry_index(entry).await?);
221 }
222
223 module_batches_graph.traverse_edges_from_entries_topological(
224 entries,
225 &mut state,
226 |parent_info, &node, state| {
227 if let Some((
229 _,
230 ModuleBatchesGraphEdge {
231 ty: ChunkingType::Traced,
232 ..
233 },
234 )) = parent_info
235 {
236 if should_trace {
237 let ModuleOrBatch::Module(module) = node else {
238 unreachable!();
239 };
240 state.traced_modules.insert(module);
241 }
242 return Ok(GraphTraversalAction::Exclude);
243 }
244
245 let Some(chunkable_node) = ChunkableModuleOrBatch::from_module_or_batch(node) else {
246 return Ok(GraphTraversalAction::Exclude);
247 };
248
249 let is_available = available_modules
250 .as_ref()
251 .is_some_and(|available_modules| available_modules.get(chunkable_node));
252
253 let Some((_, edge)) = parent_info else {
254 return Ok(if is_available {
256 GraphTraversalAction::Exclude
257 } else if state
258 .unsorted_items
259 .try_insert(node, chunkable_node)
260 .is_ok()
261 {
262 GraphTraversalAction::Continue
263 } else {
264 GraphTraversalAction::Exclude
265 });
266 };
267
268 Ok(match edge.ty {
269 ChunkingType::Parallel { .. } | ChunkingType::Shared { .. } => {
270 if is_available {
271 GraphTraversalAction::Exclude
272 } else if state
273 .unsorted_items
274 .try_insert(node, chunkable_node)
275 .is_ok()
276 {
277 GraphTraversalAction::Continue
278 } else {
279 GraphTraversalAction::Exclude
280 }
281 }
282 ChunkingType::Async => {
283 if can_split_async {
284 let chunkable_module = ResolvedVc::try_downcast(edge.module.unwrap())
285 .context("Module in async chunking edge is not chunkable")?;
286 state.async_modules.insert(chunkable_module);
287 GraphTraversalAction::Exclude
288 } else if is_available {
289 GraphTraversalAction::Exclude
290 } else if state
291 .unsorted_items
292 .try_insert(node, chunkable_node)
293 .is_ok()
294 {
295 GraphTraversalAction::Continue
296 } else {
297 GraphTraversalAction::Exclude
298 }
299 }
300 ChunkingType::Traced => {
301 unreachable!();
303 }
304 ChunkingType::Isolated { .. } => {
305 GraphTraversalAction::Exclude
307 }
308 })
309 },
310 |_, node, state| {
311 if let Some(chunkable_module) = state.unsorted_items.get(node).copied() {
313 state.chunkable_items.insert(chunkable_module);
314 }
315 },
316 )?;
317
318 let mut batch_groups = FxIndexSet::default();
319 for &module in &state.chunkable_items {
320 if let Some(batch_group) = module_batches_graph.get_batch_group(&module.into()) {
321 batch_groups.insert(batch_group);
322 }
323 }
324
325 Ok(ChunkGroupContent {
326 chunkable_items: state.chunkable_items,
327 batch_groups,
328 async_modules: state.async_modules,
329 traced_modules: state.traced_modules,
330 })
331}