turbopack_core/chunk/
chunk_group.rs

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
33/// Creates a chunk group from a set of entries.
34pub 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    // Attach async info to chunkable modules
68    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    // Compute new [AvailabilityInfo]
99    let availability_info = availability_info
100        .with_modules(Vc::cell(chunkable_items))
101        .await?;
102
103    // Insert async chunk loaders for every referenced async module
104    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    // And also add output assets referenced by async chunk loaders
123    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    // Pass chunk items to chunking algorithm
149    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            // Traced modules need to have a special handling
228            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                // An entry from the entries list
255                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                    // handled above before the sidecast
302                    unreachable!();
303                }
304                ChunkingType::Isolated { .. } => {
305                    // TODO currently not implemented
306                    GraphTraversalAction::Exclude
307                }
308            })
309        },
310        |_, node, state| {
311            // Insert modules in topological order
312            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}