turbopack_core/module_graph/
mod.rs

1use std::{
2    collections::{BinaryHeap, HashSet, VecDeque, hash_map::Entry},
3    future::Future,
4};
5
6use anyhow::{Context, Result, bail};
7use auto_hash_map::AutoSet;
8use petgraph::{
9    graph::{DiGraph, EdgeIndex, NodeIndex},
10    visit::{
11        Dfs, EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed, VisitMap,
12        Visitable,
13    },
14};
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::{Deserialize, Serialize};
17use tracing::{Instrument, Span};
18use turbo_rcstr::RcStr;
19use turbo_tasks::{
20    CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt,
21    ValueToString, Vc,
22    debug::ValueDebugFormat,
23    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
24    trace::TraceRawVcs,
25};
26use turbo_tasks_fs::FileSystemPath;
27
28use crate::{
29    chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
30    issue::{ImportTrace, ImportTracer, ImportTraces, Issue},
31    module::Module,
32    module_graph::{
33        async_module_info::{AsyncModulesInfo, compute_async_module_info},
34        chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
35        merged_modules::{MergedModuleInfo, compute_merged_modules},
36        module_batches::{ModuleBatchesGraph, compute_module_batches},
37        style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
38        traced_di_graph::{TracedDiGraph, iter_neighbors_rev},
39    },
40    reference::primary_chunkable_referenced_modules,
41    resolve::ExportUsage,
42};
43
44pub mod async_module_info;
45pub mod chunk_group_info;
46pub mod merged_modules;
47pub mod module_batch;
48pub(crate) mod module_batches;
49pub(crate) mod style_groups;
50mod traced_di_graph;
51
52pub use self::module_batches::BatchingConfig;
53
54#[derive(
55    Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, TraceRawVcs,
56)]
57pub struct GraphNodeIndex {
58    #[turbo_tasks(trace_ignore)]
59    graph_idx: u32,
60    #[turbo_tasks(trace_ignore)]
61    node_idx: NodeIndex,
62}
63impl GraphNodeIndex {
64    #[inline(always)]
65    fn graph_idx(&self) -> usize {
66        self.graph_idx as usize
67    }
68}
69
70unsafe impl NonLocalValue for GraphNodeIndex {}
71
72#[turbo_tasks::value]
73#[derive(Clone, Debug)]
74pub struct VisitedModules {
75    pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
76    next_graph_idx: u32,
77}
78
79#[turbo_tasks::value_impl]
80impl VisitedModules {
81    #[turbo_tasks::function]
82    pub fn empty() -> Vc<Self> {
83        Self {
84            modules: Default::default(),
85            next_graph_idx: 0,
86        }
87        .cell()
88    }
89
90    #[turbo_tasks::function]
91    pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
92        Ok(Self {
93            modules: graph
94                .await?
95                .enumerate_nodes()
96                .flat_map(|(node_idx, module)| match module {
97                    SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
98                        module, ..
99                    }) => Some((
100                        *module,
101                        GraphNodeIndex {
102                            graph_idx: 0,
103                            node_idx,
104                        },
105                    )),
106                    SingleModuleGraphNode::VisitedModule { .. } => None,
107                })
108                .collect(),
109            next_graph_idx: 1,
110        }
111        .cell())
112    }
113
114    #[turbo_tasks::function]
115    pub fn with_incremented_index(&self) -> Result<Vc<Self>> {
116        Ok(Self {
117            modules: self.modules.clone(),
118            next_graph_idx: self.next_graph_idx + 1,
119        }
120        .cell())
121    }
122
123    #[turbo_tasks::function]
124    pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
125        let graph = graph.await?;
126        let iter = self
127            .modules
128            .iter()
129            .map(|(module, idx)| (*module, *idx))
130            .chain(
131                graph
132                    .enumerate_nodes()
133                    .flat_map(|(node_idx, module)| match module {
134                        SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
135                            module,
136                            ..
137                        }) => Some((
138                            *module,
139                            GraphNodeIndex {
140                                graph_idx: self.next_graph_idx,
141                                node_idx,
142                            },
143                        )),
144                        SingleModuleGraphNode::VisitedModule { .. } => None,
145                    }),
146            );
147
148        let mut map = FxIndexMap::with_capacity_and_hasher(
149            self.modules.len() + graph.number_of_modules,
150            Default::default(),
151        );
152        for (k, v) in iter {
153            map.entry(k).or_insert(v);
154        }
155        map.shrink_to_fit();
156
157        Ok(Self {
158            modules: map,
159            next_graph_idx: self.next_graph_idx + 1,
160        }
161        .cell())
162    }
163}
164
165pub type GraphEntriesT = Vec<ChunkGroupEntry>;
166
167#[turbo_tasks::value(transparent)]
168pub struct GraphEntries(GraphEntriesT);
169
170#[turbo_tasks::value_impl]
171impl GraphEntries {
172    #[turbo_tasks::function]
173    pub fn empty() -> Vc<Self> {
174        Vc::cell(Vec::new())
175    }
176}
177
178#[turbo_tasks::value(cell = "new", eq = "manual", into = "new")]
179#[derive(Clone, Default)]
180pub struct SingleModuleGraph {
181    pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
182
183    /// The number of modules in the graph (excluding VisitedModule nodes)
184    pub number_of_modules: usize,
185
186    // NodeIndex isn't necessarily stable (because of swap_remove), but we never remove nodes.
187    //
188    // HashMaps have nondeterministic order, but this map is only used for lookups (in
189    // `get_module`) and not iteration.
190    //
191    // This contains Vcs, but they are already contained in the graph, so no need to trace this.
192    #[turbo_tasks(trace_ignore)]
193    modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
194
195    #[turbo_tasks(trace_ignore)]
196    pub entries: GraphEntriesT,
197}
198
199#[derive(
200    Debug,
201    Clone,
202    Hash,
203    TraceRawVcs,
204    Serialize,
205    Deserialize,
206    Eq,
207    PartialEq,
208    ValueDebugFormat,
209    NonLocalValue,
210)]
211pub struct RefData {
212    pub chunking_type: ChunkingType,
213    pub export: ExportUsage,
214}
215
216impl SingleModuleGraph {
217    /// Walks the graph starting from the given entries and collects all reachable nodes, skipping
218    /// nodes listed in `visited_modules`
219    /// The resulting graph's outgoing edges are in reverse order.
220    async fn new_inner(
221        entries: &GraphEntriesT,
222        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
223        include_traced: bool,
224    ) -> Result<Vc<Self>> {
225        let root_edges = entries
226            .iter()
227            .flat_map(|e| e.entries())
228            .map(|e| async move {
229                Ok(SingleModuleGraphBuilderEdge {
230                    to: SingleModuleGraphBuilderNode::new_module(e).await?,
231                    export: ExportUsage::All,
232                })
233            })
234            .try_join()
235            .await?;
236
237        let (children_nodes_iter, visited_nodes) = AdjacencyMap::new()
238            .skip_duplicates_with_key(|node: &(SingleModuleGraphBuilderNode, ExportUsage)| &node.0)
239            .visit(
240                root_edges,
241                SingleModuleGraphBuilder {
242                    visited_modules,
243                    include_traced,
244                },
245            )
246            .await
247            .completed()?
248            .into_inner_with_visited();
249        let node_count = visited_nodes.0.len();
250        drop(visited_nodes);
251
252        let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
253            node_count,
254            // From real world measurements each module has about 3-4 children
255            // If it has more this would cause an additional allocation, but that's fine
256            node_count * 4,
257        );
258
259        let mut number_of_modules = 0;
260        let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
261            FxHashMap::with_capacity_and_hasher(node_count, Default::default());
262        {
263            let _span = tracing::info_span!("build module graph").entered();
264            for (parent, (current, export)) in children_nodes_iter.into_breadth_first_edges() {
265                let parent_edge = match parent.map(|v| v.0) {
266                    Some(SingleModuleGraphBuilderNode::Module { module, .. }) => Some((
267                        *modules.get(&module).unwrap(),
268                        RefData {
269                            chunking_type: COMMON_CHUNKING_TYPE,
270                            export,
271                        },
272                    )),
273                    Some(SingleModuleGraphBuilderNode::ChunkableReference { .. }) => {
274                        // Handled when visiting ChunkableReference below
275                        continue;
276                    }
277                    Some(SingleModuleGraphBuilderNode::VisitedModule { .. }) => unreachable!(),
278                    None => None,
279                };
280
281                match current {
282                    SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
283                        // Find the current node, if it was already added
284                        let current_idx = if let Some(current_idx) = modules.get(&module) {
285                            *current_idx
286                        } else {
287                            let idx = graph.add_node(SingleModuleGraphNode::Module(
288                                SingleModuleGraphModuleNode { module },
289                            ));
290                            number_of_modules += 1;
291                            modules.insert(module, idx);
292                            idx
293                        };
294                        // Add the edge
295                        if let Some((parent_idx, ref_data)) = parent_edge {
296                            graph.add_edge(parent_idx, current_idx, ref_data);
297                        }
298                    }
299                    SingleModuleGraphBuilderNode::VisitedModule { module, idx } => {
300                        // Find the current node, if it was already added
301                        let current_idx = if let Some(current_idx) = modules.get(&module) {
302                            *current_idx
303                        } else {
304                            let idx = graph
305                                .add_node(SingleModuleGraphNode::VisitedModule { idx, module });
306                            modules.insert(module, idx);
307                            idx
308                        };
309                        // Add the edge
310                        if let Some((parent_idx, data)) = parent_edge {
311                            graph.add_edge(parent_idx, current_idx, data);
312                        }
313                    }
314                    SingleModuleGraphBuilderNode::ChunkableReference {
315                        source,
316                        target,
317                        ref_data,
318                        ..
319                    } => {
320                        // Find the current node, if it was already added
321                        let target_idx = if let Some(target_idx) = modules.get(&target) {
322                            *target_idx
323                        } else {
324                            let target_idx = visited_modules.get(&target);
325                            let idx = graph.add_node(match target_idx {
326                                Some(idx) => SingleModuleGraphNode::VisitedModule {
327                                    idx: *idx,
328                                    module: target,
329                                },
330                                None => {
331                                    SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
332                                        module: target,
333                                    })
334                                }
335                            });
336                            modules.insert(target, idx);
337                            idx
338                        };
339                        graph.add_edge(*modules.get(&source).unwrap(), target_idx, ref_data);
340                    }
341                }
342            }
343        }
344
345        graph.shrink_to_fit();
346
347        #[cfg(debug_assertions)]
348        {
349            use once_cell::sync::Lazy;
350
351            // TODO(PACK-4578): This is temporary while the last issues are being addressed.
352            static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
353                match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
354                    Some(v) => v != "1" && v != "true",
355                    None => true,
356                }
357            });
358            if *CHECK_FOR_DUPLICATE_MODULES {
359                let mut duplicates = Vec::new();
360                let mut set = FxHashSet::default();
361                for &module in modules.keys() {
362                    let ident = module.ident().to_string().await?;
363                    if !set.insert(ident.clone()) {
364                        duplicates.push(ident)
365                    }
366                }
367                if !duplicates.is_empty() {
368                    panic!("Duplicate module idents in graph: {duplicates:#?}");
369                }
370            }
371        }
372
373        let graph = SingleModuleGraph {
374            graph: TracedDiGraph::new(graph),
375            number_of_modules,
376            modules,
377            entries: entries.clone(),
378        }
379        .cell();
380
381        turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
382            ModuleGraphImportTracer::new(graph).to_resolved().await?,
383        ));
384        Ok(graph)
385    }
386
387    fn get_module(&self, module: ResolvedVc<Box<dyn Module>>) -> Result<NodeIndex> {
388        self.modules
389            .get(&module)
390            .copied()
391            .context("Couldn't find module in graph")
392    }
393
394    /// Iterate over all nodes in the graph
395    pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ SingleModuleGraphModuleNode> + '_ {
396        self.graph.node_weights().filter_map(|n| match n {
397            SingleModuleGraphNode::Module(node) => Some(node),
398            SingleModuleGraphNode::VisitedModule { .. } => None,
399        })
400    }
401
402    /// Iterate over all nodes in the graph
403    pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
404        self.entries.iter().flat_map(|e| e.entries())
405    }
406
407    /// Enumerate all nodes in the graph
408    pub fn enumerate_nodes(
409        &self,
410    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
411        self.graph.node_references()
412    }
413
414    /// Traverses all reachable nodes (once)
415    pub fn traverse_from_entry<'a>(
416        &'a self,
417        entry: ResolvedVc<Box<dyn Module>>,
418        mut visitor: impl FnMut(&'a SingleModuleGraphModuleNode),
419    ) -> Result<()> {
420        let entry_node = self.get_module(entry)?;
421
422        let mut dfs = Dfs::new(&*self.graph, entry_node);
423        while let Some(nx) = dfs.next(&*self.graph) {
424            let SingleModuleGraphNode::Module(weight) = self.graph.node_weight(nx).unwrap() else {
425                return Ok(());
426            };
427            // weight.emit_issues();
428            visitor(weight);
429        }
430        Ok(())
431    }
432
433    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
434    /// target.
435    ///
436    /// This means that target nodes can be revisited (once per incoming edge).
437    ///
438    /// * `entry` - The entry module to start the traversal from
439    /// * `visitor` - Called before visiting the children of a node.
440    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
441    ///      &SingleModuleGraphNode, state &S
442    ///    - Can return [GraphTraversalAction]s to control the traversal
443    pub fn traverse_edges_from_entries<'a>(
444        &'a self,
445        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
446        mut visitor: impl FnMut(
447            Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
448            &'a SingleModuleGraphModuleNode,
449        ) -> GraphTraversalAction,
450    ) -> Result<()> {
451        let graph = &self.graph;
452        let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
453
454        let mut stack = entries.collect::<Vec<_>>();
455        let mut discovered = graph.visit_map();
456        // entry_weight.emit_issues();
457        for entry_node in &stack {
458            let SingleModuleGraphNode::Module(entry_weight) =
459                graph.node_weight(*entry_node).unwrap()
460            else {
461                continue;
462            };
463            visitor(None, entry_weight);
464        }
465
466        while let Some(node) = stack.pop() {
467            let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
468            else {
469                continue;
470            };
471            if discovered.visit(node) {
472                let neighbors = {
473                    let mut neighbors = vec![];
474                    let mut walker = graph.neighbors(node).detach();
475                    while let Some((edge, succ)) = walker.next(graph) {
476                        neighbors.push((edge, succ));
477                    }
478                    neighbors
479                };
480
481                for (edge, succ) in neighbors {
482                    let SingleModuleGraphNode::Module(succ_weight) =
483                        graph.node_weight(succ).unwrap()
484                    else {
485                        continue;
486                    };
487                    let edge_weight = graph.edge_weight(edge).unwrap();
488                    let action = visitor(Some((node_weight, edge_weight)), succ_weight);
489                    if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
490                        stack.push(succ);
491                    }
492                }
493            }
494        }
495
496        Ok(())
497    }
498
499    /// Traverses all edges exactly once and calls the visitor with the edge source and
500    /// target.
501    ///
502    /// This means that target nodes can be revisited (once per incoming edge).
503    pub fn traverse_edges<'a>(
504        &'a self,
505        mut visitor: impl FnMut(
506            (
507                Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
508                &'a SingleModuleGraphModuleNode,
509            ),
510        ) -> GraphTraversalAction,
511    ) -> Result<()> {
512        let graph = &self.graph;
513        let mut stack: Vec<NodeIndex> = self
514            .entries
515            .iter()
516            .flat_map(|e| e.entries())
517            .map(|e| *self.modules.get(&e).unwrap())
518            .collect();
519        let mut discovered = graph.visit_map();
520        for entry_node in &stack {
521            let SingleModuleGraphNode::Module(entry_node) = graph.node_weight(*entry_node).unwrap()
522            else {
523                continue;
524            };
525            visitor((None, entry_node));
526        }
527
528        while let Some(node) = stack.pop() {
529            if discovered.visit(node) {
530                let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
531                else {
532                    continue;
533                };
534                for edge in graph.edges(node).collect::<Vec<_>>() {
535                    let edge_weight = edge.weight();
536                    let succ = edge.target();
537                    let SingleModuleGraphNode::Module(succ_weight) =
538                        graph.node_weight(succ).unwrap()
539                    else {
540                        continue;
541                    };
542                    let action = visitor((Some((node_weight, edge_weight)), succ_weight));
543                    if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
544                        stack.push(succ);
545                    }
546                }
547            }
548        }
549
550        Ok(())
551    }
552
553    /// Traverses all reachable edges in topological order. The preorder visitor can be used to
554    /// forward state down the graph, and to skip subgraphs
555    ///
556    /// Use this to collect modules in evaluation order.
557    ///
558    /// Target nodes can be revisited (once per incoming edge).
559    /// Edges are traversed in normal order, so should correspond to reference order.
560    ///
561    /// * `entry` - The entry module to start the traversal from
562    /// * `state` - The state to be passed to the visitors
563    /// * `visit_preorder` - Called before visiting the children of a node.
564    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
565    ///      &SingleModuleGraphNode, state &S
566    ///    - Can return [GraphTraversalAction]s to control the traversal
567    /// * `visit_postorder` - Called after visiting the children of a node. Return
568    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
569    ///      &SingleModuleGraphNode, state &S
570    pub fn traverse_edges_from_entries_topological<'a, S>(
571        &'a self,
572        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
573        state: &mut S,
574        mut visit_preorder: impl FnMut(
575            Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
576            &'a SingleModuleGraphNode,
577            &mut S,
578        ) -> Result<GraphTraversalAction>,
579        mut visit_postorder: impl FnMut(
580            Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
581            &'a SingleModuleGraphNode,
582            &mut S,
583        ) -> Result<()>,
584    ) -> Result<()> {
585        let graph = &self.graph;
586        let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
587
588        enum TopologicalPass {
589            Visit,
590            ExpandAndVisit,
591        }
592
593        #[allow(clippy::type_complexity)] // This is a temporary internal structure
594        let mut stack: Vec<(TopologicalPass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> = entries
595            .map(|e| (TopologicalPass::ExpandAndVisit, None, e))
596            .collect();
597        let mut expanded = FxHashSet::default();
598        while let Some((pass, parent, current)) = stack.pop() {
599            let parent_arg = parent.map(|parent| {
600                (
601                    match graph.node_weight(parent.0).unwrap() {
602                        SingleModuleGraphNode::Module(node) => node,
603                        SingleModuleGraphNode::VisitedModule { .. } => {
604                            unreachable!()
605                        }
606                    },
607                    graph.edge_weight(parent.1).unwrap(),
608                )
609            });
610            match pass {
611                TopologicalPass::Visit => {
612                    visit_postorder(parent_arg, graph.node_weight(current).unwrap(), state)?;
613                }
614                TopologicalPass::ExpandAndVisit => match graph.node_weight(current).unwrap() {
615                    current_node @ SingleModuleGraphNode::Module(_) => {
616                        let action = visit_preorder(parent_arg, current_node, state)?;
617                        if action == GraphTraversalAction::Exclude {
618                            continue;
619                        }
620                        stack.push((TopologicalPass::Visit, parent, current));
621                        if action == GraphTraversalAction::Continue && expanded.insert(current) {
622                            stack.extend(iter_neighbors_rev(graph, current).map(
623                                |(edge, child)| {
624                                    (
625                                        TopologicalPass::ExpandAndVisit,
626                                        Some((current, edge)),
627                                        child,
628                                    )
629                                },
630                            ));
631                        }
632                    }
633                    current_node @ SingleModuleGraphNode::VisitedModule { .. } => {
634                        visit_preorder(parent_arg, current_node, state)?;
635                        visit_postorder(parent_arg, current_node, state)?;
636                    }
637                },
638            }
639        }
640
641        Ok(())
642    }
643
644    pub fn traverse_cycles<'l>(
645        &'l self,
646        edge_filter: impl Fn(&'l RefData) -> bool,
647        mut visit_cycle: impl FnMut(&[&'l SingleModuleGraphModuleNode]),
648    ) {
649        // see https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
650        // but iteratively instead of recursively
651
652        #[derive(Clone)]
653        struct NodeState {
654            index: u32,
655            lowlink: u32,
656            on_stack: bool,
657        }
658        enum VisitStep {
659            UnvisitedNode(NodeIndex),
660            EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
661            AfterVisit(NodeIndex),
662        }
663        let mut node_states = vec![None; self.graph.node_bound()];
664        let mut stack = Vec::new();
665        let mut visit_stack = Vec::new();
666        let mut index = 0;
667        let mut scc = Vec::new();
668        for initial_index in self.graph.node_indices() {
669            // Skip over already visited nodes
670            if node_states[initial_index.index()].is_some() {
671                continue;
672            }
673            visit_stack.push(VisitStep::UnvisitedNode(initial_index));
674            while let Some(step) = visit_stack.pop() {
675                match step {
676                    VisitStep::UnvisitedNode(node) => {
677                        node_states[node.index()] = Some(NodeState {
678                            index,
679                            lowlink: index,
680                            on_stack: true,
681                        });
682                        index += 1;
683                        stack.push(node);
684                        visit_stack.push(VisitStep::AfterVisit(node));
685                        let mut neighbors = self.graph.neighbors(node).detach();
686                        while let Some((edge, succ)) = neighbors.next(&self.graph) {
687                            let edge_weight = self.graph.edge_weight(edge).unwrap();
688                            if !edge_filter(edge_weight) {
689                                continue;
690                            }
691                            let node_state = &node_states[succ.index()];
692                            if let Some(node_state) = node_state {
693                                if node_state.on_stack {
694                                    let index = node_state.index;
695                                    let parent_state = node_states[node.index()].as_mut().unwrap();
696                                    parent_state.lowlink = parent_state.lowlink.min(index);
697                                }
698                            } else {
699                                visit_stack.push(VisitStep::EdgeAfterVisit {
700                                    parent: node,
701                                    child: succ,
702                                });
703                                visit_stack.push(VisitStep::UnvisitedNode(succ));
704                            }
705                        }
706                    }
707                    VisitStep::EdgeAfterVisit { parent, child } => {
708                        let child_state = node_states[child.index()].as_ref().unwrap();
709                        let lowlink = child_state.lowlink;
710
711                        let parent_state = node_states[parent.index()].as_mut().unwrap();
712                        parent_state.lowlink = parent_state.lowlink.min(lowlink);
713                    }
714                    VisitStep::AfterVisit(node) => {
715                        let node_state = node_states[node.index()].as_ref().unwrap();
716                        if node_state.lowlink == node_state.index {
717                            loop {
718                                let poppped = stack.pop().unwrap();
719                                let popped_state = node_states[poppped.index()].as_mut().unwrap();
720                                popped_state.on_stack = false;
721                                if let SingleModuleGraphNode::Module(module) =
722                                    self.graph.node_weight(poppped).unwrap()
723                                {
724                                    scc.push(module);
725                                }
726                                if poppped == node {
727                                    break;
728                                }
729                            }
730                            if scc.len() > 1 {
731                                visit_cycle(&scc);
732                            }
733                            scc.clear();
734                        }
735                    }
736                }
737            }
738        }
739    }
740
741    /// For each issue computes a (possibly empty) list of traces from the file that produced the
742    /// issue to roots in this module graph.
743    /// There are potentially multiple traces because a given file may get assigned to multiple
744    /// modules depend on how it is used in the application.  Consider a simple utility that is used
745    /// by SSR pages, client side code, and the edge runtime.  This may lead to there being 3
746    /// traces.
747    /// The returned map is guaranteed to have an entry for every issue.
748    pub async fn compute_import_traces_for_issues(
749        &self,
750        issues: &AutoSet<ResolvedVc<Box<dyn Issue>>>,
751    ) -> Result<FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>>> {
752        let issue_paths = issues
753            .iter()
754            .map(|issue| async move { Ok((*issue.file_path().await?).clone()) })
755            .try_join()
756            .await?;
757        let mut file_path_to_traces: FxHashMap<FileSystemPath, Vec<ImportTrace>> =
758            FxHashMap::with_capacity_and_hasher(issue_paths.len(), Default::default());
759        // initialize an empty vec for each path we care about
760        for issue in &issue_paths {
761            file_path_to_traces.entry(issue.clone()).or_default();
762        }
763
764        {
765            let modules = self
766                .modules
767                .iter()
768                .map(|(module, &index)| async move {
769                    Ok(((*module.ident().path().await?).clone(), index))
770                })
771                .try_join()
772                .await?;
773            // Reverse the graph so we can find paths to roots
774            let reversed_graph = Reversed(&self.graph.0);
775            for (path, module_idx) in modules {
776                if let Entry::Occupied(mut entry) = file_path_to_traces.entry(path) {
777                    // compute the path from this index to a root of the graph.
778                    let Some((_, path)) = petgraph::algo::astar(
779                        &reversed_graph,
780                        module_idx,
781                        |n| reversed_graph.neighbors(n).next().is_none(),
782                        // Edge weights
783                        |e| match e.weight().chunking_type {
784                            // Prefer following normal imports/requires when we can
785                            ChunkingType::Parallel { .. } => 0,
786                            _ => 1,
787                        },
788                        // `astar` can be accelerated with a distance estimation heuristic, as long
789                        // as our estimate is never > the actual distance.
790                        // However we don't have a mechanism, so just
791                        // estimate 0 which essentially makes this behave like
792                        // dijktra's shortest path algorithm.  `petgraph` has an implementation of
793                        // dijkstra's but it doesn't report  paths, just distances.
794                        // NOTE: dijkstra's with integer weights can be accelerated with incredibly
795                        // efficient priority queue structures (basically with only 0 and 1 as
796                        // weights you can use a `VecDeque`!).  However,
797                        // this is unlikely to be a performance concern.
798                        // Furthermore, if computing paths _does_ become a performance concern, the
799                        // solution would be a hand written implementation of dijkstras so we can
800                        // hoist redundant work out of this loop.
801                        |_| 0,
802                    ) else {
803                        unreachable!("there must be a path to a root");
804                    };
805                    // Represent the path as a sequence of AssetIdents
806                    // TODO: consider hinting at various transitions (e.g. was this an
807                    // import/require/dynamic-import?)
808                    let path = path
809                        .into_iter()
810                        .map(async |n| {
811                            Ok(self
812                                .graph
813                                .node_weight(n)
814                                .unwrap()
815                                .module()
816                                .ident()
817                                .await?
818                                .clone())
819                        })
820                        .try_join()
821                        .await?;
822                    entry.get_mut().push(path);
823                }
824            }
825        }
826        let mut issue_to_traces: FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>> =
827            FxHashMap::with_capacity_and_hasher(issues.len(), Default::default());
828        // Map filepaths back to issues
829        // We can do this by zipping the issue_paths with the issues since they are in the same
830        // order.
831        for (path, issue) in issue_paths.iter().zip(issues) {
832            if let Some(traces) = file_path_to_traces.get(path) {
833                issue_to_traces.insert(*issue, traces.clone());
834            }
835        }
836        Ok(issue_to_traces)
837    }
838}
839
840#[turbo_tasks::value]
841struct ModuleGraphImportTracer {
842    graph: ResolvedVc<SingleModuleGraph>,
843}
844
845#[turbo_tasks::value(shared)]
846struct PathToModulesMap {
847    map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
848}
849
850#[turbo_tasks::value_impl]
851impl ModuleGraphImportTracer {
852    #[turbo_tasks::function]
853    fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
854        Self::cell(Self { graph })
855    }
856
857    // Compute this mapping on demand since it might not always be needed.
858    #[turbo_tasks::function]
859    async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
860        let path_and_modules = self
861            .graph
862            .await?
863            .modules
864            .iter()
865            .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
866            .try_join()
867            .await?;
868        let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
869            FxHashMap::default();
870        for (path, module) in path_and_modules {
871            map.entry(path).or_default().push(module)
872        }
873        Ok(PathToModulesMap::cell(PathToModulesMap { map }))
874    }
875}
876
877#[turbo_tasks::value_impl]
878impl ImportTracer for ModuleGraphImportTracer {
879    #[turbo_tasks::function]
880    async fn get_traces(
881        self: Vc<Self>,
882        path: ResolvedVc<FileSystemPath>,
883    ) -> Result<Vc<ImportTraces>> {
884        let path_to_modules = self.path_to_modules().await?;
885        let Some(modules) = path_to_modules.map.get(&*path.await?) else {
886            return Ok(Vc::default()); // This isn't unusual, the file just might not be in this
887            // graph.
888        };
889        debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
890        let graph = &*self.await?.graph.await?;
891
892        let reversed_graph = Reversed(&graph.graph.0);
893        return Ok(ImportTraces::cell(ImportTraces(
894            modules
895                .iter()
896                .map(|m| async move {
897                    let Some(&module_idx) = graph.modules.get(m) else {
898                        // The only way this could really happen is if `path_to_modules` is computed
899                        // from a different graph than graph`.  Just error out.
900                        bail!("inconsistent read?")
901                    };
902                    // compute the path from this index to a root of the graph.
903                    let Some((_, path)) = petgraph::algo::astar(
904                        &reversed_graph,
905                        module_idx,
906                        |n| reversed_graph.neighbors(n).next().is_none(),
907                        // Edge weights
908                        |e| match e.weight().chunking_type {
909                            // Prefer following normal imports/requires when we can
910                            ChunkingType::Parallel { .. } => 0,
911                            _ => 1,
912                        },
913                        // `astar` can be accelerated with a distance estimation heuristic, as long
914                        // as our estimate is never > the actual distance.
915                        // However we don't have a mechanism, so just
916                        // estimate 0 which essentially makes this behave like
917                        // dijktra's shortest path algorithm.  `petgraph` has an implementation of
918                        // dijkstra's but it doesn't report  paths, just distances.
919                        // NOTE: dijkstra's with integer weights can be accelerated with incredibly
920                        // efficient priority queue structures (basically with only 0 and 1 as
921                        // weights you can use a `VecDeque`!).  However,
922                        // this is unlikely to be a performance concern.
923                        // Furthermore, if computing paths _does_ become a performance concern, the
924                        // solution would be a hand written implementation of dijkstras so we can
925                        // hoist redundant work out of this loop.
926                        |_| 0,
927                    ) else {
928                        unreachable!("there must be a path to a root");
929                    };
930
931                    // Represent the path as a sequence of AssetIdents
932                    // TODO: consider hinting at various transitions (e.g. was this an
933                    // import/require/dynamic-import?)
934                    let path = path
935                        .into_iter()
936                        .map(async |n| {
937                            graph
938                                .graph
939                                .node_weight(n)
940                                .unwrap() // This is safe since `astar`` only returns indices from the graph
941                                .module()
942                                .ident()
943                                .await
944                        })
945                        .try_join()
946                        .await?;
947                    Ok(path)
948                })
949                .try_join()
950                .await?,
951        )));
952    }
953}
954
955#[turbo_tasks::value(shared)]
956#[derive(Clone, Default)]
957pub struct ModuleGraph {
958    pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
959}
960
961#[turbo_tasks::value_impl]
962impl ModuleGraph {
963    #[turbo_tasks::function]
964    pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
965        Self { graphs }.cell()
966    }
967
968    #[turbo_tasks::function]
969    pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
970        Self {
971            graphs: vec![graph],
972        }
973        .cell()
974    }
975
976    #[turbo_tasks::function]
977    pub fn from_entry_module(
978        module: ResolvedVc<Box<dyn Module>>,
979        include_traced: bool,
980    ) -> Vc<Self> {
981        Self::from_single_graph(SingleModuleGraph::new_with_entries(
982            Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
983            include_traced,
984        ))
985    }
986
987    #[turbo_tasks::function]
988    pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
989        Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
990    }
991
992    #[turbo_tasks::function]
993    pub async fn chunk_group_info(&self) -> Result<Vc<ChunkGroupInfo>> {
994        compute_chunk_group_info(self).await
995    }
996
997    #[turbo_tasks::function]
998    pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
999        compute_merged_modules(self).await
1000    }
1001
1002    #[turbo_tasks::function]
1003    pub async fn module_batches(
1004        self: Vc<Self>,
1005        config: Vc<BatchingConfig>,
1006    ) -> Result<Vc<ModuleBatchesGraph>> {
1007        compute_module_batches(self, &*config.await?).await
1008    }
1009
1010    #[turbo_tasks::function]
1011    pub async fn style_groups(
1012        self: Vc<Self>,
1013        chunking_context: Vc<Box<dyn ChunkingContext>>,
1014        config: StyleGroupsConfig,
1015    ) -> Result<Vc<StyleGroups>> {
1016        compute_style_groups(self, chunking_context, &config).await
1017    }
1018
1019    #[turbo_tasks::function]
1020    pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
1021        // `compute_async_module_info` calls `module.is_self_async()`, so we need to again ignore
1022        // all issues such that they aren't emitted multiple times.
1023        async move {
1024            let result_op = compute_async_module_info(self.to_resolved().await?);
1025            let result_vc = result_op.resolve_strongly_consistent().await?;
1026            let _issues = result_op.take_collectibles::<Box<dyn Issue>>();
1027            anyhow::Ok(*result_vc)
1028        }
1029        .instrument(tracing::info_span!("compute async module info"))
1030        .await
1031    }
1032
1033    #[turbo_tasks::function]
1034    pub async fn referenced_async_modules(
1035        self: Vc<Self>,
1036        module: ResolvedVc<Box<dyn Module>>,
1037    ) -> Result<Vc<AsyncModuleInfo>> {
1038        let this = self.await?;
1039        let graphs = this.get_graphs().await?;
1040        let async_modules_info = self.async_module_info().await?;
1041
1042        let entry = ModuleGraph::get_entry(&graphs, module).await?;
1043        let referenced_modules =
1044            iter_neighbors_rev(&graphs[entry.graph_idx()].graph, entry.node_idx)
1045                .filter(|(edge_idx, _)| {
1046                    let ty = graphs[entry.graph_idx()]
1047                        .graph
1048                        .edge_weight(*edge_idx)
1049                        .unwrap();
1050                    ty.chunking_type.is_inherit_async()
1051                })
1052                .map(|(_, child_idx)| {
1053                    anyhow::Ok(
1054                        get_node!(
1055                            graphs,
1056                            GraphNodeIndex {
1057                                graph_idx: entry.graph_idx,
1058                                node_idx: child_idx
1059                            }
1060                        )?
1061                        .module,
1062                    )
1063                })
1064                .collect::<Result<Vec<_>>>()?
1065                .into_iter()
1066                .rev()
1067                .filter(|m| async_modules_info.contains(m))
1068                .map(|m| *m)
1069                .collect();
1070
1071        Ok(AsyncModuleInfo::new(referenced_modules))
1072    }
1073}
1074
1075// fn get_node<T>(
1076//     graphs: Vec<ReadRef<SingleModuleGraph>>,
1077//     node: GraphNodeIndex,
1078// ) -> Result<&'static SingleModuleGraphModuleNode> {
1079macro_rules! get_node {
1080    ($graphs:expr, $node:expr) => {{
1081        let node_idx = $node;
1082        match $graphs[node_idx.graph_idx()]
1083            .graph
1084            .node_weight(node_idx.node_idx)
1085        {
1086            Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1087            Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1088                match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1089                    Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1090                    Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1091                        "Expected visited target node to be module"
1092                    )),
1093                    None => Err(::anyhow::anyhow!("Expected visited target node")),
1094                }
1095            }
1096            None => Err(::anyhow::anyhow!("Expected graph node")),
1097        }
1098    }};
1099}
1100pub(crate) use get_node;
1101macro_rules! get_node_idx {
1102    ($graphs:expr, $node:expr) => {{
1103        let node_idx = $node;
1104        match $graphs[node_idx.graph_idx()]
1105            .graph
1106            .node_weight(node_idx.node_idx)
1107        {
1108            Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, node_idx)),
1109            Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1110                match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1111                    Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, *idx)),
1112                    Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1113                        "Expected visited target node to be module"
1114                    )),
1115                    None => Err(::anyhow::anyhow!("Expected visited target node")),
1116                }
1117            }
1118            None => Err(::anyhow::anyhow!("Expected graph node")),
1119        }
1120    }};
1121}
1122pub(crate) use get_node_idx;
1123
1124impl ModuleGraph {
1125    pub async fn get_graphs(&self) -> Result<Vec<ReadRef<SingleModuleGraph>>> {
1126        self.graphs.iter().try_join().await
1127    }
1128
1129    async fn get_entry(
1130        graphs: &[ReadRef<SingleModuleGraph>],
1131        entry: ResolvedVc<Box<dyn Module>>,
1132    ) -> Result<GraphNodeIndex> {
1133        let Some(idx) = graphs.iter().enumerate().find_map(|(graph_idx, graph)| {
1134            graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1135                graph_idx: u32::try_from(graph_idx).unwrap(),
1136                node_idx: *node_idx,
1137            })
1138        }) else {
1139            bail!(
1140                "Couldn't find entry module {} in module graph (potential entries: {:?})",
1141                entry.ident().to_string().await?,
1142                graphs
1143                    .iter()
1144                    .flat_map(|g| g.entries.iter())
1145                    .flat_map(|e| e.entries())
1146                    .map(|e| e.ident().to_string())
1147                    .try_join()
1148                    .await?
1149            );
1150        };
1151        Ok(idx)
1152    }
1153
1154    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
1155    /// target.
1156    ///
1157    /// This means that target nodes can be revisited (once per incoming edge).
1158    ///
1159    /// * `entry` - The entry module to start the traversal from
1160    /// * `visitor` - Called before visiting the children of a node.
1161    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1162    ///      &SingleModuleGraphNode, state &S
1163    ///    - Can return [GraphTraversalAction]s to control the traversal
1164    pub async fn traverse_edges_from_entries_bfs(
1165        &self,
1166        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1167        mut visitor: impl FnMut(
1168            Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1169            &'_ SingleModuleGraphModuleNode,
1170        ) -> Result<GraphTraversalAction>,
1171    ) -> Result<()> {
1172        let graphs = self.get_graphs().await?;
1173
1174        let mut queue = VecDeque::from(
1175            entries
1176                .into_iter()
1177                .map(|e| ModuleGraph::get_entry(&graphs, e))
1178                .try_join()
1179                .await?,
1180        );
1181        let mut visited = HashSet::new();
1182        for entry_node in &queue {
1183            visitor(None, get_node!(graphs, entry_node)?)?;
1184        }
1185        while let Some(node) = queue.pop_front() {
1186            let graph = &graphs[node.graph_idx()].graph;
1187            let node_weight = get_node!(graphs, node)?;
1188            if visited.insert(node) {
1189                let neighbors = iter_neighbors_rev(graph, node.node_idx);
1190
1191                for (edge, succ) in neighbors {
1192                    let succ = GraphNodeIndex {
1193                        graph_idx: node.graph_idx,
1194                        node_idx: succ,
1195                    };
1196                    let succ_weight = get_node!(graphs, succ)?;
1197                    let edge_weight = graph.edge_weight(edge).unwrap();
1198                    let action = visitor(Some((node_weight, edge_weight)), succ_weight)?;
1199                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1200                        queue.push_back(succ);
1201                    }
1202                }
1203            }
1204        }
1205
1206        Ok(())
1207    }
1208
1209    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
1210    /// target.
1211    ///
1212    /// This means that target nodes can be revisited (once per incoming edge).
1213    ///
1214    /// * `entry` - The entry module to start the traversal from
1215    /// * `visitor` - Called before visiting the children of a node.
1216    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1217    ///      &SingleModuleGraphNode, state &S
1218    ///    - Can return [GraphTraversalAction]s to control the traversal
1219    pub async fn traverse_edges_from_entry(
1220        &self,
1221        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1222        mut visitor: impl FnMut(
1223            Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1224            &'_ SingleModuleGraphModuleNode,
1225        ) -> GraphTraversalAction,
1226    ) -> Result<()> {
1227        let graphs = self.get_graphs().await?;
1228
1229        let entries = entries.into_iter();
1230        let mut stack = Vec::with_capacity(entries.size_hint().0);
1231        for entry in entries {
1232            stack.push(ModuleGraph::get_entry(&graphs, entry).await?);
1233        }
1234        let mut visited = HashSet::new();
1235        for entry_node in &stack {
1236            visitor(None, get_node!(graphs, entry_node)?);
1237        }
1238        while let Some(node) = stack.pop() {
1239            let graph = &graphs[node.graph_idx()].graph;
1240            let node_weight = get_node!(graphs, node)?;
1241            if visited.insert(node) {
1242                let neighbors = iter_neighbors_rev(graph, node.node_idx);
1243
1244                for (edge, succ) in neighbors {
1245                    let succ = GraphNodeIndex {
1246                        graph_idx: node.graph_idx,
1247                        node_idx: succ,
1248                    };
1249                    let succ_weight = get_node!(graphs, succ)?;
1250                    let edge_weight = graph.edge_weight(edge).unwrap();
1251                    let action = visitor(Some((node_weight, edge_weight)), succ_weight);
1252                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1253                        stack.push(succ);
1254                    }
1255                }
1256            }
1257        }
1258
1259        Ok(())
1260    }
1261
1262    /// Traverses all edges exactly once (in an unspecified order) and calls the visitor with the
1263    /// edge source and target.
1264    ///
1265    /// This means that target nodes can be revisited (once per incoming edge).
1266    ///
1267    /// * `visitor` - Called before visiting the children of a node.
1268    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1269    ///      &SingleModuleGraphNode
1270    pub async fn traverse_all_edges_unordered(
1271        &self,
1272        mut visitor: impl FnMut(
1273            (&'_ SingleModuleGraphModuleNode, &'_ RefData),
1274            &'_ SingleModuleGraphModuleNode,
1275        ) -> Result<()>,
1276    ) -> Result<()> {
1277        let graphs = self.get_graphs().await?;
1278
1279        for graph in &graphs {
1280            let graph = &graph.graph;
1281            for edge in graph.edge_references() {
1282                let source = match graph.node_weight(edge.source()).unwrap() {
1283                    SingleModuleGraphNode::Module(node) => node,
1284                    SingleModuleGraphNode::VisitedModule { .. } => unreachable!(),
1285                };
1286                let target = match graph.node_weight(edge.target()).unwrap() {
1287                    SingleModuleGraphNode::Module(node) => node,
1288                    SingleModuleGraphNode::VisitedModule { idx, .. } => get_node!(graphs, idx)?,
1289                };
1290                visitor((source, edge.weight()), target)?;
1291            }
1292        }
1293
1294        Ok(())
1295    }
1296
1297    /// Traverses all reachable edges in topological order. The preorder visitor can be used to
1298    /// forward state down the graph, and to skip subgraphs
1299    ///
1300    /// Use this to collect modules in evaluation order.
1301    ///
1302    /// Target nodes can be revisited (once per incoming edge).
1303    /// Edges are traversed in normal order, so should correspond to reference order.
1304    ///
1305    /// * `entry` - The entry module to start the traversal from
1306    /// * `state` - The state to be passed to the visitors
1307    /// * `visit_preorder` - Called before visiting the children of a node.
1308    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1309    ///      &SingleModuleGraphNode, state &S
1310    ///    - Can return [GraphTraversalAction]s to control the traversal
1311    /// * `visit_postorder` - Called after visiting the children of a node. Return
1312    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1313    ///      &SingleModuleGraphNode, state &S
1314    ///    - Can return [GraphTraversalAction]s to control the traversal
1315    pub async fn traverse_edges_from_entries_topological<S>(
1316        &self,
1317        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1318        state: &mut S,
1319        mut visit_preorder: impl FnMut(
1320            Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1321            &'_ SingleModuleGraphModuleNode,
1322            &mut S,
1323        ) -> Result<GraphTraversalAction>,
1324        mut visit_postorder: impl FnMut(
1325            Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1326            &'_ SingleModuleGraphModuleNode,
1327            &mut S,
1328        ) -> Result<()>,
1329    ) -> Result<()> {
1330        let graphs = self.get_graphs().await?;
1331
1332        let entries = entries.into_iter().collect::<Vec<_>>();
1333
1334        enum TopologicalPass {
1335            Visit,
1336            ExpandAndVisit,
1337        }
1338        #[allow(clippy::type_complexity)] // This is a temporary internal structure
1339        let mut stack: Vec<(
1340            TopologicalPass,
1341            Option<(GraphNodeIndex, EdgeIndex)>,
1342            GraphNodeIndex,
1343        )> = Vec::with_capacity(entries.len());
1344        for entry in entries.into_iter().rev() {
1345            stack.push((
1346                TopologicalPass::ExpandAndVisit,
1347                None,
1348                ModuleGraph::get_entry(&graphs, entry).await?,
1349            ));
1350        }
1351        let mut expanded = HashSet::new();
1352        while let Some((pass, parent, current)) = stack.pop() {
1353            let parent_arg = match parent {
1354                Some((parent_node, parent_edge)) => Some((
1355                    get_node!(graphs, parent_node)?,
1356                    graphs[parent_node.graph_idx()]
1357                        .graph
1358                        .edge_weight(parent_edge)
1359                        .unwrap(),
1360                )),
1361                None => None,
1362            };
1363            let current_node = get_node!(graphs, current)?;
1364            match pass {
1365                TopologicalPass::Visit => {
1366                    visit_postorder(parent_arg, current_node, state)?;
1367                }
1368                TopologicalPass::ExpandAndVisit => {
1369                    let action = visit_preorder(parent_arg, current_node, state)?;
1370                    if action == GraphTraversalAction::Exclude {
1371                        continue;
1372                    }
1373                    stack.push((TopologicalPass::Visit, parent, current));
1374                    if action == GraphTraversalAction::Continue && expanded.insert(current) {
1375                        let graph = &graphs[current.graph_idx()].graph;
1376                        let (neighbors_rev, current) = match graph
1377                            .node_weight(current.node_idx)
1378                            .unwrap()
1379                        {
1380                            SingleModuleGraphNode::Module(_) => {
1381                                (iter_neighbors_rev(graph, current.node_idx), current)
1382                            }
1383                            SingleModuleGraphNode::VisitedModule { idx, .. } => (
1384                                // We switch graphs
1385                                iter_neighbors_rev(&graphs[idx.graph_idx()].graph, idx.node_idx),
1386                                *idx,
1387                            ),
1388                        };
1389                        stack.extend(neighbors_rev.map(|(edge, child)| {
1390                            (
1391                                TopologicalPass::ExpandAndVisit,
1392                                Some((current, edge)),
1393                                GraphNodeIndex {
1394                                    graph_idx: current.graph_idx,
1395                                    node_idx: child,
1396                                },
1397                            )
1398                        }));
1399                    }
1400                }
1401            }
1402        }
1403
1404        Ok(())
1405    }
1406
1407    /// Traverse all cycles in the graph (where the edge filter returns true for the whole cycle)
1408    /// and call the visitor with the nodes in the cycle.
1409    pub async fn traverse_cycles(
1410        &self,
1411        edge_filter: impl Fn(&RefData) -> bool,
1412        mut visit_cycle: impl FnMut(&[&SingleModuleGraphModuleNode]),
1413    ) -> Result<()> {
1414        for graph in &self.graphs {
1415            graph.await?.traverse_cycles(&edge_filter, &mut visit_cycle);
1416        }
1417        Ok(())
1418    }
1419
1420    /// Traverses all reachable nodes and also continue revisiting them as long the visitor returns
1421    /// GraphTraversalAction::Continue. The visitor is responsible for the runtime complexity and
1422    /// eventual termination of the traversal. This corresponds to computing a fixed point state for
1423    /// the graph.
1424    ///
1425    /// Nodes are (re)visited according to the returned priority of the node, prioritizing high
1426    /// values. This priority is intended to be used a heuristic to reduce the number of
1427    /// retraversals.
1428    ///
1429    /// * `entries` - The entry modules to start the traversal from
1430    /// * `state` - The state to be passed to the callbacks
1431    /// * `visit` - Called for a specific edge
1432    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1433    ///      &SingleModuleGraphNode, state &S
1434    ///    - Return [GraphTraversalAction]s to control the traversal
1435    /// * `priority` - Called for before visiting the children of a node to determine its priority.
1436    ///    - Receives: target &SingleModuleGraphNode, state &S
1437    ///    - Return a priority value for the node
1438    ///
1439    /// Returns the number of node visits (i.e. higher than the node count if there are
1440    /// retraversals).
1441    pub async fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1442        &self,
1443        entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1444        state: &mut S,
1445        mut visit: impl FnMut(
1446            Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1447            &'_ SingleModuleGraphModuleNode,
1448            &mut S,
1449        ) -> Result<GraphTraversalAction>,
1450        priority: impl Fn(&'_ SingleModuleGraphModuleNode, &mut S) -> Result<P>,
1451    ) -> Result<usize> {
1452        let graphs = self.get_graphs().await?;
1453
1454        #[derive(PartialEq, Eq)]
1455        struct NodeWithPriority<T: Ord> {
1456            node: GraphNodeIndex,
1457            priority: T,
1458        }
1459        impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1460            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1461                Some(self.cmp(other))
1462            }
1463        }
1464        impl<T: Ord> Ord for NodeWithPriority<T> {
1465            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1466                // BinaryHeap prioritizes high values
1467
1468                self.priority
1469                    .cmp(&other.priority)
1470                    // include GraphNodeIndex for total and deterministic ordering
1471                    .then(other.node.cmp(&self.node))
1472            }
1473        }
1474
1475        let mut queue_set = FxHashSet::default();
1476        let mut queue = BinaryHeap::from_iter(
1477            entries
1478                .into_iter()
1479                .map(async |(m, priority)| {
1480                    Ok(NodeWithPriority {
1481                        node: ModuleGraph::get_entry(&graphs, m).await?,
1482                        priority,
1483                    })
1484                })
1485                .try_join()
1486                .await?,
1487        );
1488        for entry_node in &queue {
1489            visit(None, get_node!(graphs, entry_node.node)?, state)?;
1490        }
1491
1492        let mut visit_count = 0usize;
1493        while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1494            queue_set.remove(&node);
1495            let (node_weight, node) = get_node_idx!(graphs, node)?;
1496            let graph = &graphs[node.graph_idx()].graph;
1497            let neighbors = iter_neighbors_rev(graph, node.node_idx);
1498
1499            visit_count += 1;
1500
1501            for (edge, succ) in neighbors {
1502                let succ = GraphNodeIndex {
1503                    graph_idx: node.graph_idx,
1504                    node_idx: succ,
1505                };
1506                let (succ_weight, succ) = get_node_idx!(graphs, succ)?;
1507                let edge_weight = graph.edge_weight(edge).unwrap();
1508                let action = visit(Some((node_weight, edge_weight)), succ_weight, state)?;
1509
1510                if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1511                    queue.push(NodeWithPriority {
1512                        node: succ,
1513                        priority: priority(succ_weight, state)?,
1514                    });
1515                }
1516            }
1517        }
1518
1519        Ok(visit_count)
1520    }
1521}
1522
1523#[turbo_tasks::value_impl]
1524impl SingleModuleGraph {
1525    #[turbo_tasks::function]
1526    pub async fn new_with_entries(
1527        entries: Vc<GraphEntries>,
1528        include_traced: bool,
1529    ) -> Result<Vc<Self>> {
1530        SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1531    }
1532
1533    #[turbo_tasks::function]
1534    pub async fn new_with_entries_visited(
1535        entries: Vc<GraphEntries>,
1536        visited_modules: Vc<VisitedModules>,
1537        include_traced: bool,
1538    ) -> Result<Vc<Self>> {
1539        SingleModuleGraph::new_inner(
1540            &*entries.await?,
1541            &visited_modules.await?.modules,
1542            include_traced,
1543        )
1544        .await
1545    }
1546
1547    #[turbo_tasks::function]
1548    pub async fn new_with_entries_visited_intern(
1549        // This must not be a Vc<Vec<_>> to ensure layout segment optimization hits the cache
1550        entries: GraphEntriesT,
1551        visited_modules: Vc<VisitedModules>,
1552        include_traced: bool,
1553    ) -> Result<Vc<Self>> {
1554        SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1555            .await
1556    }
1557}
1558
1559#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1560pub struct SingleModuleGraphModuleNode {
1561    pub module: ResolvedVc<Box<dyn Module>>,
1562}
1563
1564#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1565pub enum SingleModuleGraphNode {
1566    Module(SingleModuleGraphModuleNode),
1567    // Models a module that is referenced but has already been visited by an earlier graph.
1568    VisitedModule {
1569        idx: GraphNodeIndex,
1570        module: ResolvedVc<Box<dyn Module>>,
1571    },
1572}
1573
1574impl SingleModuleGraphNode {
1575    pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1576        match self {
1577            SingleModuleGraphNode::Module(SingleModuleGraphModuleNode { module }) => *module,
1578            SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1579        }
1580    }
1581}
1582
1583#[derive(PartialEq, Eq, Debug)]
1584pub enum GraphTraversalAction {
1585    /// Continue visiting children
1586    Continue,
1587    /// Skip the immediate children, but visit the node in postorder
1588    Skip,
1589    /// Skip the immediate children and the node in postorder
1590    Exclude,
1591}
1592
1593// These nodes are created while walking the Turbopack modules references, and are used to then
1594// afterwards build the SingleModuleGraph.
1595#[derive(Clone, Hash, PartialEq, Eq)]
1596enum SingleModuleGraphBuilderNode {
1597    /// This edge is represented as a node: source Module -> ChunkableReference ->  target Module
1598    ChunkableReference {
1599        ref_data: RefData,
1600        source: ResolvedVc<Box<dyn Module>>,
1601        target: ResolvedVc<Box<dyn Module>>,
1602        // These two fields are only used for tracing. Derived from `source.ident()` and
1603        // `target.ident()`
1604        source_ident: ReadRef<RcStr>,
1605        target_ident: ReadRef<RcStr>,
1606    },
1607    /// A regular module
1608    Module {
1609        module: ResolvedVc<Box<dyn Module>>,
1610        // module.ident().to_string(), eagerly computed for tracing
1611        ident: ReadRef<RcStr>,
1612    },
1613    /// A reference to a module that is already listed in visited_modules
1614    VisitedModule {
1615        module: ResolvedVc<Box<dyn Module>>,
1616        idx: GraphNodeIndex,
1617    },
1618}
1619
1620impl SingleModuleGraphBuilderNode {
1621    async fn new_module(module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1622        let ident = module.ident();
1623        Ok(Self::Module {
1624            module,
1625            ident: ident.to_string().await?,
1626        })
1627    }
1628    async fn new_chunkable_ref(
1629        source: ResolvedVc<Box<dyn Module>>,
1630        target: ResolvedVc<Box<dyn Module>>,
1631        ref_data: RefData,
1632    ) -> Result<Self> {
1633        Ok(Self::ChunkableReference {
1634            ref_data,
1635            source,
1636            source_ident: source.ident().to_string().await?,
1637            target,
1638            target_ident: target.ident().to_string().await?,
1639        })
1640    }
1641    fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1642        Self::VisitedModule { module, idx }
1643    }
1644}
1645struct SingleModuleGraphBuilderEdge {
1646    to: SingleModuleGraphBuilderNode,
1647    export: ExportUsage,
1648}
1649
1650/// The chunking type that occurs most often, is handled more efficiently by not creating
1651/// intermediate SingleModuleGraphBuilderNode::ChunkableReference nodes.
1652const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1653    inherit_async: true,
1654    hoisted: true,
1655};
1656
1657struct SingleModuleGraphBuilder<'a> {
1658    visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1659    /// Whether to walk ChunkingType::Traced references
1660    include_traced: bool,
1661}
1662impl Visit<(SingleModuleGraphBuilderNode, ExportUsage)> for SingleModuleGraphBuilder<'_> {
1663    type Edge = SingleModuleGraphBuilderEdge;
1664    type EdgesIntoIter = Vec<Self::Edge>;
1665    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1666
1667    fn visit(
1668        &mut self,
1669        edge: Self::Edge,
1670    ) -> VisitControlFlow<(SingleModuleGraphBuilderNode, ExportUsage)> {
1671        match edge.to {
1672            SingleModuleGraphBuilderNode::Module { .. } => {
1673                VisitControlFlow::Continue((edge.to, edge.export))
1674            }
1675            SingleModuleGraphBuilderNode::ChunkableReference { ref ref_data, .. } => {
1676                match &ref_data.chunking_type {
1677                    ChunkingType::Traced => VisitControlFlow::Skip((edge.to, edge.export)),
1678                    _ => VisitControlFlow::Continue((edge.to, edge.export)),
1679                }
1680            }
1681            // Module was already visited previously
1682            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1683                VisitControlFlow::Skip((edge.to, edge.export))
1684            }
1685        }
1686    }
1687
1688    fn edges(
1689        &mut self,
1690        // The `skip_duplicates_with_key()` above ensures only a single `edges()` call per module
1691        // (and not per `(module, export)` pair), so the export must not be read here!
1692        (node, _): &(SingleModuleGraphBuilderNode, ExportUsage),
1693    ) -> Self::EdgesFuture {
1694        // Destructure beforehand to not have to clone the whole node when entering the async block
1695        let (module, chunkable_ref_target) = match node {
1696            SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1697            SingleModuleGraphBuilderNode::ChunkableReference {
1698                target, ref_data, ..
1699            } => (None, Some((*target, ref_data.export.clone()))),
1700            // These are always skipped in `visit()`
1701            SingleModuleGraphBuilderNode::VisitedModule { .. } => unreachable!(),
1702        };
1703        let visited_modules = self.visited_modules;
1704        let include_traced = self.include_traced;
1705        async move {
1706            Ok(match (module, chunkable_ref_target) {
1707                (Some(module), None) => {
1708                    let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1709                    let refs = match refs_cell.await {
1710                        Ok(refs) => refs,
1711                        Err(e) => {
1712                            return Err(e.context(module.ident().to_string().await?));
1713                        }
1714                    };
1715
1716                    refs.iter()
1717                        .flat_map(|(ty, export, modules)| {
1718                            modules.iter().map(|m| (ty.clone(), export.clone(), *m))
1719                        })
1720                        .map(async |(ty, export, target)| {
1721                            let to = if ty == COMMON_CHUNKING_TYPE {
1722                                if let Some(idx) = visited_modules.get(&target) {
1723                                    SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1724                                } else {
1725                                    SingleModuleGraphBuilderNode::new_module(target).await?
1726                                }
1727                            } else {
1728                                SingleModuleGraphBuilderNode::new_chunkable_ref(
1729                                    module,
1730                                    target,
1731                                    RefData {
1732                                        chunking_type: ty,
1733                                        export: export.clone(),
1734                                    },
1735                                )
1736                                .await?
1737                            };
1738                            Ok(SingleModuleGraphBuilderEdge { to, export })
1739                        })
1740                        .try_join()
1741                        .await?
1742                }
1743                (None, Some((chunkable_ref_target, export))) => {
1744                    vec![SingleModuleGraphBuilderEdge {
1745                        to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1746                            SingleModuleGraphBuilderNode::new_visited_module(
1747                                chunkable_ref_target,
1748                                *idx,
1749                            )
1750                        } else {
1751                            SingleModuleGraphBuilderNode::new_module(chunkable_ref_target).await?
1752                        },
1753                        export,
1754                    }]
1755                }
1756                _ => unreachable!(),
1757            })
1758        }
1759    }
1760
1761    fn span(&mut self, (node, _): &(SingleModuleGraphBuilderNode, ExportUsage)) -> tracing::Span {
1762        match node {
1763            SingleModuleGraphBuilderNode::Module { ident, .. } => {
1764                tracing::info_span!("module", name = display(ident))
1765            }
1766
1767            SingleModuleGraphBuilderNode::ChunkableReference {
1768                ref_data,
1769                source_ident,
1770                target_ident,
1771                ..
1772            } => match &ref_data.chunking_type {
1773                ChunkingType::Parallel {
1774                    inherit_async: false,
1775                    ..
1776                } => Span::current(),
1777                _ => {
1778                    tracing::info_span!(
1779                        "chunkable reference",
1780                        ty = debug(&ref_data.chunking_type),
1781                        source = display(source_ident),
1782                        target = display(target_ident)
1783                    )
1784                }
1785            },
1786            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1787                tracing::info_span!("visited module")
1788            }
1789        }
1790    }
1791}