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