1use std::{
2 collections::{BinaryHeap, VecDeque},
3 future::Future,
4 iter::FusedIterator,
5 ops::Deref,
6};
7
8use anyhow::{Context, Result, bail};
9use bincode::{Decode, Encode};
10use petgraph::{
11 Direction,
12 graph::{DiGraph, EdgeIndex, NodeIndex},
13 visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
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, OperationVc, ReadRef, ResolvedVc,
21 TryFlatJoinIterExt, TryJoinIterExt, 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, TracedMode},
30 issue::{ImportTracer, ImportTraces, Issue},
31 module::Module,
32 module_graph::{
33 async_module_info::{AsyncModulesInfo, compute_async_module_info},
34 binding_usage_info::BindingUsageInfo,
35 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
36 collect::{CollectedModules, collect_graph},
37 merged_modules::{MergedModuleInfo, compute_merged_modules},
38 module_batches::{ModuleBatchesGraph, compute_module_batches},
39 style_groups::{StyleGroups, StyleGroupsAlgorithm, StyleGroupsConfig},
40 style_groups_graph::compute_style_groups_graph,
41 style_groups_loose::compute_style_groups,
42 traced_di_graph::TracedDiGraph,
43 },
44 reference::{
45 ModuleReference, primary_chunkable_referenced_modules,
46 referenced_modules_and_affecting_sources,
47 },
48 resolve::BindingUsage,
49};
50
51pub mod async_module_info;
52pub mod binding_usage_info;
53pub mod chunk_group_info;
54pub mod collect;
55pub mod merged_modules;
56pub mod module_batch;
57pub(crate) mod module_batches;
58mod side_effect_module_info;
59pub mod style_groups;
60pub mod style_groups_graph;
61pub mod style_groups_loose;
62mod traced_di_graph;
63
64pub use self::module_batches::BatchingConfig;
65
66#[derive(
67 Debug,
68 Copy,
69 Clone,
70 Eq,
71 PartialOrd,
72 Ord,
73 Hash,
74 PartialEq,
75 Serialize,
76 Deserialize,
77 TraceRawVcs,
78 Encode,
79 Decode,
80)]
81pub struct GraphNodeIndex {
82 #[turbo_tasks(trace_ignore)]
83 graph_idx: u32,
84 #[turbo_tasks(trace_ignore)]
85 #[bincode(with_serde)]
86 node_idx: NodeIndex,
87}
88impl GraphNodeIndex {
89 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
90 Self {
91 graph_idx,
92 node_idx,
93 }
94 }
95}
96
97unsafe impl NonLocalValue for GraphNodeIndex {}
98
99#[derive(
100 Debug,
101 Copy,
102 Clone,
103 Eq,
104 PartialOrd,
105 Ord,
106 Hash,
107 PartialEq,
108 TraceRawVcs,
109 NonLocalValue,
110 Encode,
111 Decode,
112)]
113pub struct GraphEdgeIndex {
114 graph_idx: u32,
115 #[turbo_tasks(trace_ignore)]
116 #[bincode(with_serde)]
117 edge_idx: EdgeIndex,
118}
119
120impl GraphEdgeIndex {
121 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
122 Self {
123 graph_idx,
124 edge_idx,
125 }
126 }
127}
128
129#[turbo_tasks::value]
130#[derive(Clone, Debug)]
131pub struct VisitedModules {
132 #[bincode(with = "turbo_bincode::indexmap")]
133 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
134 next_graph_idx: u32,
135}
136
137#[turbo_tasks::value_impl]
138impl VisitedModules {
139 #[turbo_tasks::function(operation)]
140 pub fn empty() -> Vc<Self> {
141 Self {
142 modules: Default::default(),
143 next_graph_idx: 0,
144 }
145 .cell()
146 }
147
148 #[turbo_tasks::function(operation)]
149 pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
150 Ok(Self {
151 modules: graph
152 .connect()
153 .await?
154 .enumerate_nodes()
155 .flat_map(|(node_idx, module)| match module {
156 SingleModuleGraphNode::Module(module) => Some((
157 *module,
158 GraphNodeIndex {
159 graph_idx: 0,
160 node_idx,
161 },
162 )),
163 SingleModuleGraphNode::VisitedModule { .. } => None,
164 })
165 .collect(),
166 next_graph_idx: 1,
167 }
168 .cell())
169 }
170
171 #[turbo_tasks::function(operation)]
172 pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
173 let this = this.connect().await?;
174 Ok(Self {
175 modules: this.modules.clone(),
176 next_graph_idx: this.next_graph_idx + 1,
177 }
178 .cell())
179 }
180
181 #[turbo_tasks::function(operation)]
182 pub async fn concatenate(
183 this: OperationVc<Self>,
184 graph: OperationVc<SingleModuleGraph>,
185 ) -> Result<Vc<Self>> {
186 let graph = graph.connect().await?;
187 let this = this.connect().await?;
188 let iter = this
189 .modules
190 .iter()
191 .map(|(module, idx)| (*module, *idx))
192 .chain(
193 graph
194 .enumerate_nodes()
195 .flat_map(|(node_idx, module)| match module {
196 SingleModuleGraphNode::Module(module) => Some((
197 *module,
198 GraphNodeIndex {
199 graph_idx: this.next_graph_idx,
200 node_idx,
201 },
202 )),
203 SingleModuleGraphNode::VisitedModule { .. } => None,
204 }),
205 );
206
207 let mut map = FxIndexMap::with_capacity_and_hasher(
208 this.modules.len() + graph.number_of_modules,
209 Default::default(),
210 );
211 for (k, v) in iter {
212 map.entry(k).or_insert(v);
213 }
214 map.shrink_to_fit();
215
216 Ok(Self {
217 modules: map,
218 next_graph_idx: this.next_graph_idx + 1,
219 }
220 .cell())
221 }
222}
223
224#[turbo_tasks::value(shared, task_input)]
225#[derive(Debug, Clone, Hash, Default)]
226pub struct GraphEntries {
227 chunk_groups: Vec<ChunkGroupEntry>,
229 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
232}
233
234#[turbo_tasks::value_impl]
235impl GraphEntries {
236 #[turbo_tasks::function]
237 pub fn empty() -> Vc<Self> {
238 Self::default().cell()
239 }
240}
241
242impl GraphEntries {
243 pub fn new(
244 chunk_groups: Vec<ChunkGroupEntry>,
245 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
246 ) -> Self {
247 Self {
248 chunk_groups,
249 traced_modules,
250 }
251 }
252 pub fn from_chunk_groups(chunk_groups: Vec<ChunkGroupEntry>) -> Self {
253 Self {
254 chunk_groups,
255 traced_modules: vec![],
256 }
257 }
258
259 pub fn concatenate(entries: impl IntoIterator<Item = GraphEntries>) -> Self {
260 let (chunk_groups, traced_modules): (Vec<_>, Vec<_>) = entries
261 .into_iter()
262 .map(|e| (e.chunk_groups, e.traced_modules))
263 .unzip();
264 Self {
265 chunk_groups: chunk_groups.into_iter().flatten().collect(),
266 traced_modules: traced_modules.into_iter().flatten().collect(),
267 }
268 }
269
270 pub fn all_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
272 self.chunk_groups
273 .iter()
274 .flat_map(|e| e.entries())
275 .chain(self.traced_modules.iter().cloned())
276 }
277
278 pub fn all_modules_with_is_traced(
280 &self,
281 ) -> impl Iterator<Item = (ResolvedVc<Box<dyn Module>>, bool)> + '_ {
282 self.chunk_groups
283 .iter()
284 .flat_map(|e| e.entries().map(|m| (m, false)))
285 .chain(self.traced_modules.iter().cloned().map(|m| (m, true)))
286 }
287
288 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
290 self.chunk_groups.iter().flat_map(|e| e.entries())
291 }
292}
293
294#[turbo_tasks::value(cell = "new", eq = "manual")]
295#[derive(Clone, Default)]
296pub struct SingleModuleGraph {
297 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
298
299 pub number_of_modules: usize,
301
302 #[turbo_tasks(trace_ignore)]
309 #[bincode(with_serde)]
310 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
311
312 #[turbo_tasks(trace_ignore)]
313 pub entries: GraphEntries,
314}
315
316#[derive(
317 Debug,
318 Clone,
319 Hash,
320 TraceRawVcs,
321 Serialize,
322 Deserialize,
323 Eq,
324 PartialEq,
325 ValueDebugFormat,
326 NonLocalValue,
327 Encode,
328 Decode,
329)]
330pub struct RefData {
331 pub chunking_type: ChunkingType,
332 pub binding_usage: BindingUsage,
333 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
334}
335
336impl SingleModuleGraph {
337 async fn new_inner(
341 entries: &GraphEntries,
342 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
343 include_traced: bool,
344 include_binding_usage: bool,
345 ) -> Result<Vc<Self>> {
346 let emit_spans = tracing::enabled!(Level::INFO);
347 let root_nodes = entries
348 .all_modules_with_is_traced()
349 .map(|(e, is_traced)| {
350 SingleModuleGraphBuilderNode::new_module(emit_spans, e, is_traced)
351 })
352 .try_join()
353 .await?;
354
355 let children_nodes_iter = AdjacencyMap::new()
356 .visit(
357 root_nodes,
358 SingleModuleGraphBuilder {
359 visited_modules,
360 emit_spans,
361 include_traced,
362 include_binding_usage,
363 },
364 )
365 .await
366 .completed()?;
367 let node_count = children_nodes_iter.len();
368
369 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
370 node_count,
371 node_count * 4,
374 );
375
376 let mut number_of_modules = 0;
377 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
378 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
379 {
380 let _span = tracing::info_span!("build module graph").entered();
381 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
382 let (module, graph_node, count) = match current {
383 SingleModuleGraphBuilderNode::Module {
384 module,
385 is_traced: _,
386 ident: _,
387 } => (module, SingleModuleGraphNode::Module(module), 1),
388 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
389 module,
390 SingleModuleGraphNode::VisitedModule { idx, module },
391 0,
392 ),
393 };
394
395 let current_idx = if let Some(current_idx) = modules.get(&module) {
397 *current_idx
398 } else {
399 let idx = graph.add_node(graph_node);
400 number_of_modules += count;
401 modules.insert(module, idx);
402 idx
403 };
404 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
406 parent
407 {
408 let parent_idx = *modules.get(&module).unwrap();
409 graph.add_edge(parent_idx, current_idx, ref_data);
410 }
411 }
412 }
413
414 graph.shrink_to_fit();
415
416 #[cfg(debug_assertions)]
417 {
418 use std::sync::LazyLock;
419 static CHECK_FOR_DUPLICATE_MODULES: LazyLock<bool> = LazyLock::new(|| {
420 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
421 Some(v) => v != "1" && v != "true",
422 None => true,
423 }
424 });
425 if *CHECK_FOR_DUPLICATE_MODULES {
426 let mut duplicates = FxHashSet::default();
427 let mut set = FxHashSet::default();
428 for &module in modules.keys() {
429 let ident = module.ident().to_string().await?;
430 if !set.insert(ident.clone()) {
431 duplicates.insert(ident);
432 }
433 }
434 if !duplicates.is_empty() {
435 use turbo_tasks::TryFlatJoinIterExt;
436
437 let duplicates_clone = duplicates.clone();
438 let duplicate_modules = modules
439 .iter()
440 .map(async |(&m, &idx)| {
441 let id = m.ident().to_string().await?;
442 if duplicates_clone.contains(&id) {
443 let debug = m.value_debug_format(3).try_to_string().await?;
446
447 let parent_modules: Vec<_> = graph
450 .edges_directed(idx, petgraph::Direction::Incoming)
451 .filter_map(|edge| match graph.node_weight(edge.source()) {
452 Some(SingleModuleGraphNode::Module(m)) => Some(*m),
453 Some(SingleModuleGraphNode::VisitedModule {
454 module,
455 ..
456 }) => Some(*module),
457 None => None,
458 })
459 .collect();
460 let parents: Vec<String> = parent_modules
461 .iter()
462 .map(async |p| {
463 let ident = p.ident().to_string().await?;
464 Ok((*ident).to_string())
465 })
466 .try_join()
467 .await?;
468
469 Ok(Some((id, debug, parents)))
470 } else {
471 Ok(None)
472 }
473 })
474 .try_flat_join()
475 .await?;
476 let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
478 for (key, debug, parents) in duplicate_modules {
479 map.entry(key).or_default().push((debug, parents));
480 }
481 let result = map
482 .into_iter()
483 .map(|(ident, modules)| {
484 let modules = modules
485 .into_iter()
486 .map(|(debug, parents)| {
487 format!("Module: {debug}, Parents: {parents:?}")
488 })
489 .collect::<Vec<_>>()
490 .join("\n");
491 format!("Ident: {ident}\n{modules}")
492 })
493 .collect::<Vec<_>>()
494 .join("\n\n");
495 bail!("Duplicate module idents in graph: {result}");
496 }
497 }
498 }
499
500 let graph = SingleModuleGraph {
501 graph: TracedDiGraph::new(graph),
502 number_of_modules,
503 modules,
504 entries: entries.clone(),
505 }
506 .cell();
507
508 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
509 ModuleGraphImportTracer::new(graph).to_resolved().await?,
510 ));
511 Ok(graph)
512 }
513
514 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
517 self.graph.node_weights().filter_map(|n| match n {
518 SingleModuleGraphNode::Module(node) => Some(*node),
519 SingleModuleGraphNode::VisitedModule { .. } => None,
520 })
521 }
522
523 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
525 if let Some(index) = self.modules.get(&module) {
526 self.graph
527 .edges_directed(*index, Direction::Incoming)
528 .next()
529 .is_none()
530 } else {
531 false
532 }
533 }
534
535 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
537 self.entries.chunk_group_modules()
538 }
539
540 pub fn enumerate_nodes(
543 &self,
544 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
545 self.graph.node_references()
546 }
547
548 fn traverse_cycles<'l>(
549 &'l self,
550 edge_filter: impl Fn(&'l RefData) -> bool,
551 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
552 graph_idx: u32,
553 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
554 ) -> Result<()> {
555 #[derive(Clone)]
562 struct NodeState {
563 index: u32,
564 lowlink: u32,
565 on_stack: bool,
566 has_self_loop: bool,
567 }
568 enum VisitStep {
569 UnvisitedNode(NodeIndex),
570 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
571 AfterVisit(NodeIndex),
572 }
573 let mut node_states = vec![None; self.graph.node_bound()];
574 let mut stack = Vec::new();
575 let mut visit_stack = Vec::new();
576 let mut index = 0;
577 let mut scc = Vec::new();
578 for initial_index in self.graph.node_indices() {
579 if node_states[initial_index.index()].is_some() {
581 continue;
582 }
583 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
584 while let Some(step) = visit_stack.pop() {
585 match step {
586 VisitStep::UnvisitedNode(node) => {
587 node_states[node.index()] = Some(NodeState {
588 index,
589 lowlink: index,
590 on_stack: true,
591 has_self_loop: false,
592 });
593 index += 1;
594 stack.push(node);
595 visit_stack.push(VisitStep::AfterVisit(node));
596 let mut neighbors = self.graph.neighbors(node).detach();
597 while let Some((edge, succ)) = neighbors.next(&self.graph) {
598 if binding_usage.as_ref().is_some_and(|binding_usage| {
599 binding_usage
600 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
601 }) {
602 continue;
603 }
604
605 let edge_weight = self.graph.edge_weight(edge).unwrap();
606 if !edge_filter(edge_weight) {
607 continue;
608 }
609 let node_state = &node_states[succ.index()];
610 if let Some(node_state) = node_state {
611 if node_state.on_stack {
612 let index = node_state.index;
613 let parent_state = node_states[node.index()].as_mut().unwrap();
614 parent_state.lowlink = parent_state.lowlink.min(index);
615 if succ == node {
616 parent_state.has_self_loop = true;
617 }
618 }
619 } else {
620 visit_stack.push(VisitStep::EdgeAfterVisit {
621 parent: node,
622 child: succ,
623 });
624 visit_stack.push(VisitStep::UnvisitedNode(succ));
625 }
626 }
627 }
628 VisitStep::EdgeAfterVisit { parent, child } => {
629 let child_state = node_states[child.index()].as_ref().unwrap();
630 let lowlink = child_state.lowlink;
631
632 let parent_state = node_states[parent.index()].as_mut().unwrap();
633 parent_state.lowlink = parent_state.lowlink.min(lowlink);
634 }
635 VisitStep::AfterVisit(node) => {
636 let node_state = node_states[node.index()].as_ref().unwrap();
637 let node_has_self_loop = node_state.has_self_loop;
638 if node_state.lowlink == node_state.index {
639 loop {
640 let poppped = stack.pop().unwrap();
641 let popped_state = node_states[poppped.index()].as_mut().unwrap();
642 popped_state.on_stack = false;
643 if let SingleModuleGraphNode::Module(module) =
644 self.graph.node_weight(poppped).unwrap()
645 {
646 scc.push(module);
647 }
648 if poppped == node {
649 break;
650 }
651 }
652 if scc.len() > 1 || node_has_self_loop {
653 visit_cycle(&scc)?;
654 }
655 scc.clear();
656 }
657 }
658 }
659 }
660 }
661 Ok(())
662 }
663}
664
665#[turbo_tasks::value]
666struct ModuleGraphImportTracer {
667 graph: ResolvedVc<SingleModuleGraph>,
668}
669
670#[turbo_tasks::value(shared)]
671struct PathToModulesMap {
672 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
673}
674
675#[turbo_tasks::value_impl]
676impl ModuleGraphImportTracer {
677 #[turbo_tasks::function]
678 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
679 Self::cell(Self { graph })
680 }
681
682 #[turbo_tasks::function]
684 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
685 let path_and_modules = self
686 .graph
687 .await?
688 .modules
689 .iter()
690 .map(async |(&module, _)| Ok((module.ident().await?.path.clone(), module)))
691 .try_join()
692 .await?;
693 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
694 FxHashMap::default();
695 for (path, module) in path_and_modules {
696 map.entry(path).or_default().push(module)
697 }
698 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
699 }
700}
701
702#[turbo_tasks::value_impl]
703impl ImportTracer for ModuleGraphImportTracer {
704 #[turbo_tasks::function]
705 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
706 let path_to_modules = self.path_to_modules().await?;
707 let Some(modules) = path_to_modules.map.get(&path) else {
708 return Ok(Vc::default()); };
711 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
712 let graph = &*self.await?.graph.await?;
713
714 let reversed_graph = Reversed(&graph.graph.0);
715 return Ok(ImportTraces::cell(ImportTraces(
716 modules
717 .iter()
718 .map(async |m| {
719 let Some(&module_idx) = graph.modules.get(m) else {
720 bail!("inconsistent read?")
723 };
724 let Some((_, path)) = petgraph::algo::astar(
726 &reversed_graph,
727 module_idx,
728 |n| reversed_graph.neighbors(n).next().is_none(),
729 |e| match e.weight().chunking_type {
731 ChunkingType::Parallel { .. } => 0,
733 _ => 1,
734 },
735 |_| 0,
749 ) else {
750 unreachable!("there must be a path to a root");
751 };
752
753 let path = path
757 .into_iter()
758 .map(|n| {
759 graph
760 .graph
761 .node_weight(n)
762 .unwrap() .module()
764 .ident()
765 })
766 .try_join()
767 .await?;
768 Ok(path)
769 })
770 .try_join()
771 .await?,
772 )));
773 }
774}
775
776#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
779pub struct ModuleGraph {
780 input_graphs: Vec<OperationVc<SingleModuleGraph>>,
781 input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
782
783 snapshot: ModuleGraphSnapshot,
784}
785
786#[turbo_tasks::value_impl]
787impl ModuleGraph {
788 #[turbo_tasks::function(operation)]
791 pub async fn from_graphs(
792 graphs: Vec<OperationVc<SingleModuleGraph>>,
793 binding_usage: Option<OperationVc<BindingUsageInfo>>,
794 ) -> Result<Vc<Self>> {
795 let graph = Self::from_graphs_inner(graphs, binding_usage)
796 .read_strongly_consistent()
797 .await?;
798
799 Ok(ReadRef::cell(graph))
800 }
801
802 #[turbo_tasks::function(operation, root)]
803 async fn from_graphs_inner(
804 graphs: Vec<OperationVc<SingleModuleGraph>>,
805 binding_usage: Option<OperationVc<BindingUsageInfo>>,
806 ) -> Result<Vc<ModuleGraph>> {
807 Ok(ModuleGraph {
808 input_graphs: graphs.clone(),
809 input_binding_usage: binding_usage,
810 snapshot: ModuleGraphSnapshot {
811 graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
812 skip_visited_module_children: false,
813 graph_idx_override: None,
814 binding_usage: if let Some(binding_usage) = binding_usage {
815 Some(binding_usage.connect().await?)
816 } else {
817 None
818 },
819 },
820 }
821 .cell())
822 }
823
824 #[turbo_tasks::function]
825 pub async fn collected_modules(self: Vc<Self>) -> Result<Vc<CollectedModules>> {
826 collect_graph(self).await
827 }
828
829 #[turbo_tasks::function]
830 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
831 compute_chunk_group_info(&*self.await?).await
832 }
833
834 #[turbo_tasks::function]
835 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
836 compute_merged_modules(self).await
837 }
838
839 #[turbo_tasks::function]
840 pub async fn module_batches(
841 self: Vc<Self>,
842 config: Vc<BatchingConfig>,
843 ) -> Result<Vc<ModuleBatchesGraph>> {
844 compute_module_batches(self, &*config.await?).await
845 }
846
847 #[turbo_tasks::function]
848 pub async fn style_groups(
849 self: Vc<Self>,
850 chunking_context: Vc<Box<dyn ChunkingContext>>,
851 config: StyleGroupsConfig,
852 ) -> Result<Vc<StyleGroups>> {
853 match &config.algorithm {
854 StyleGroupsAlgorithm::Default => {
855 compute_style_groups(self, chunking_context, &config).await
856 }
857 StyleGroupsAlgorithm::Graph {
858 weight_distribution,
859 request_cost,
860 } => {
861 compute_style_groups_graph(
862 self,
863 chunking_context,
864 request_cost.get(),
865 weight_distribution.get(),
866 config.max_chunk_size as u64,
867 )
868 .await
869 }
870 }
871 }
872
873 #[turbo_tasks::function(root)]
874 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
875 async move {
878 let result_op = compute_async_module_info(self.to_resolved().await?);
879 let result_vc = result_op.resolve().strongly_consistent().await?;
880 result_op.drop_collectibles::<Box<dyn Issue>>();
881 anyhow::Ok(*result_vc)
882 }
883 .instrument(tracing::info_span!("compute async module info"))
884 .await
885 }
886
887 #[turbo_tasks::function]
888 pub async fn referenced_async_modules(
889 self: Vc<Self>,
890 module: ResolvedVc<Box<dyn Module>>,
891 ) -> Result<Vc<AsyncModuleInfo>> {
892 let graph_ref = self.await?;
893 let async_module_info = self.async_module_info();
894
895 let entry = graph_ref.get_entry(module)?;
896 let referenced_modules = graph_ref
897 .iter_graphs_neighbors_rev(entry, Direction::Outgoing, false)
898 .filter(|(edge_idx, _)| {
899 let ty = graph_ref.get_edge(*edge_idx).unwrap();
900 ty.chunking_type.is_inherit_async()
901 })
902 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
903 .collect::<Result<Vec<_>>>()?
904 .into_iter()
905 .rev()
906 .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
907 .try_flat_join()
908 .await?;
909
910 Ok(AsyncModuleInfo::new(referenced_modules))
911 }
912
913 #[turbo_tasks::function]
915 pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
916 Vc::cell(
917 self.input_graphs
918 .iter()
919 .enumerate()
920 .map(|(graph_idx, graph)| {
921 ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
922 })
923 .collect(),
924 )
925 }
926}
927
928impl Deref for ModuleGraph {
929 type Target = ModuleGraphSnapshot;
930
931 fn deref(&self) -> &Self::Target {
932 &self.snapshot
933 }
934}
935
936#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
937pub struct ModuleGraphLayer {
938 snapshot: ModuleGraphSnapshot,
939}
940
941#[turbo_tasks::value_impl]
942impl ModuleGraphLayer {
943 #[turbo_tasks::function(operation, root)]
944 async fn new(
945 graph: OperationVc<SingleModuleGraph>,
946 graph_idx: u32,
947 binding_usage: Option<OperationVc<BindingUsageInfo>>,
948 ) -> Result<Vc<Self>> {
949 Ok(Self {
950 snapshot: ModuleGraphSnapshot {
951 graphs: vec![graph.connect().await?],
952 skip_visited_module_children: true,
953 graph_idx_override: Some(graph_idx),
954 binding_usage: if let Some(binding_usage) = binding_usage {
955 Some(binding_usage.connect().await?)
956 } else {
957 None
958 },
959 },
960 }
961 .cell())
962 }
963}
964
965impl Deref for ModuleGraphLayer {
966 type Target = ModuleGraphSnapshot;
967
968 fn deref(&self) -> &Self::Target {
969 &self.snapshot
970 }
971}
972
973#[turbo_tasks::value(transparent)]
974pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
975
976#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
986pub struct ModuleGraphSnapshot {
987 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
989 skip_visited_module_children: bool,
993
994 graph_idx_override: Option<u32>,
995
996 binding_usage: Option<ReadRef<BindingUsageInfo>>,
997}
998
999impl ModuleGraphSnapshot {
1000 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
1001 if self.graph_idx_override.is_some() {
1002 debug_assert_eq!(self.graphs.len(), 1,);
1003 }
1004
1005 let Some(idx) = self
1006 .graphs
1007 .iter()
1008 .enumerate()
1009 .find_map(|(graph_idx, graph)| {
1010 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1011 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
1012 node_idx: *node_idx,
1013 })
1014 })
1015 else {
1016 bail!("Couldn't find entry module {entry:?} in module graph");
1017 };
1018 Ok(idx)
1019 }
1020
1021 pub fn all_chunk_group_entries(&self) -> impl Iterator<Item = &ChunkGroupEntry> + '_ {
1023 self.graphs
1024 .iter()
1025 .flat_map(|g| g.entries.chunk_groups.iter())
1026 }
1027
1028 pub fn all_chunk_group_entry_modules(
1030 &self,
1031 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1032 self.graphs
1033 .iter()
1034 .flat_map(|g| g.entries.chunk_group_modules())
1035 }
1036
1037 pub fn all_entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1039 self.graphs.iter().flat_map(|g| g.entries.all_modules())
1040 }
1041
1042 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
1043 if self.graph_idx_override.is_some() {
1044 self.graphs.first().unwrap()
1045 } else {
1046 &self.graphs[graph_idx as usize]
1047 }
1048 }
1049
1050 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
1051 let graph = self.get_graph(node.graph_idx);
1052 graph
1053 .graph
1054 .node_weight(node.node_idx)
1055 .context("Expected graph node")
1056 }
1057
1058 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
1059 let graph = self.get_graph(edge.graph_idx);
1060 graph
1061 .graph
1062 .edge_weight(edge.edge_idx)
1063 .context("Expected graph node")
1064 }
1065
1066 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
1067 if self.skip_visited_module_children && direction == Direction::Outgoing {
1068 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
1069 } else {
1070 true
1071 }
1072 }
1073
1074 pub fn enumerate_nodes(
1077 &self,
1078 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
1079 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
1080 }
1081
1082 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1085 self.graphs.iter().flat_map(|g| g.iter_nodes())
1086 }
1087
1088 fn iter_graphs_neighbors_rev<'a>(
1090 &'a self,
1091 node: GraphNodeIndex,
1092 direction: Direction,
1093 include_traced: bool,
1094 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1095 let graph = &*self.get_graph(node.graph_idx).graph;
1096
1097 if cfg!(debug_assertions) && direction == Direction::Outgoing {
1098 let node_weight = graph.node_weight(node.node_idx).unwrap();
1099 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1100 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1101 }
1102 }
1103
1104 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1105 std::iter::from_fn(move || {
1106 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1107 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1108 if self
1109 .binding_usage
1110 .as_ref()
1111 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
1112 {
1113 continue;
1115 }
1116
1117 if !include_traced && self.get_edge(edge_idx).unwrap().chunking_type.is_traced() {
1118 continue;
1119 }
1120
1121 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1122 }
1123 None
1124 })
1125 }
1126
1127 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1130 Ok(self
1131 .iter_nodes()
1132 .map(async |n| Ok((n, n.ident().to_string().await?)))
1133 .try_join()
1134 .await?
1135 .into_iter()
1136 .collect::<FxHashMap<_, _>>())
1137 }
1138
1139 pub fn traverse_nodes_dfs<S>(
1148 &self,
1149 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1150 state: &mut S,
1151 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1152 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1153 ) -> Result<()> {
1154 let entries = entries.into_iter().collect::<Vec<_>>();
1155
1156 enum Pass {
1157 Visit,
1158 ExpandAndVisit,
1159 }
1160 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1161 for entry in entries.into_iter().rev() {
1162 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1163 }
1164 let mut expanded = FxHashSet::default();
1165 while let Some((pass, current)) = stack.pop() {
1166 let current_node = self.get_node(current)?;
1167 match pass {
1168 Pass::Visit => {
1169 visit_postorder(current_node.module(), state)?;
1170 }
1171 Pass::ExpandAndVisit => {
1172 if !expanded.insert(current) {
1173 continue;
1174 }
1175 let action = visit_preorder(current_node.module(), state)?;
1176 if action == GraphTraversalAction::Exclude {
1177 continue;
1178 }
1179 stack.push((Pass::Visit, current));
1180 if action == GraphTraversalAction::Continue
1181 && self.should_visit_node(current_node, Direction::Outgoing)
1182 {
1183 let current = current_node
1184 .target_idx(Direction::Outgoing)
1185 .unwrap_or(current);
1186 stack.extend(
1187 self.iter_graphs_neighbors_rev(current, Direction::Outgoing, false)
1188 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1189 );
1190 }
1191 }
1192 }
1193 }
1194
1195 Ok(())
1196 }
1197
1198 pub fn traverse_edges_bfs(
1209 &self,
1210 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1211 mut visitor: impl FnMut(
1212 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1213 ResolvedVc<Box<dyn Module>>,
1214 ) -> Result<GraphTraversalAction>,
1215 ) -> Result<()> {
1216 let mut queue = VecDeque::from(
1217 entries
1218 .into_iter()
1219 .map(|e| self.get_entry(e))
1220 .collect::<Result<Vec<_>>>()?,
1221 );
1222 let mut visited = FxHashSet::default();
1223 for entry_node in &queue {
1224 visitor(None, self.get_node(*entry_node)?.module())?;
1225 }
1226 while let Some(node) = queue.pop_front() {
1227 if visited.insert(node) {
1228 let node_weight = self.get_node(node)?;
1229 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1230 {
1231 let succ_weight = self.get_node(succ)?;
1232 let action = visitor(
1233 Some((node_weight.module(), self.get_edge(edge)?)),
1234 succ_weight.module(),
1235 )?;
1236 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1237 continue;
1238 }
1239 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1240 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1241 queue.push_back(succ);
1242 }
1243 }
1244 }
1245 }
1246
1247 Ok(())
1248 }
1249
1250 pub fn traverse_edges_unordered(
1259 &self,
1260 mut visitor: impl FnMut(
1261 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1262 ResolvedVc<Box<dyn Module>>,
1263 ) -> Result<()>,
1264 ) -> Result<()> {
1265 self.traverse_edges_dfs(
1268 self.all_chunk_group_entry_modules(),
1269 &mut (),
1270 |parent, target, _| {
1271 visitor(parent, target)?;
1272 Ok(GraphTraversalAction::Continue)
1273 },
1274 |_, _, _| Ok(()),
1275 false,
1276 )
1277 }
1278
1279 pub fn traverse_edges_dfs<S>(
1298 &self,
1299 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1300 state: &mut S,
1301 visit_preorder: impl FnMut(
1302 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1303 ResolvedVc<Box<dyn Module>>,
1304 &mut S,
1305 ) -> Result<GraphTraversalAction>,
1306 visit_postorder: impl FnMut(
1307 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1308 ResolvedVc<Box<dyn Module>>,
1309 &mut S,
1310 ) -> Result<()>,
1311 include_traced: bool,
1312 ) -> Result<()> {
1313 self.traverse_edges_dfs_impl::<S>(
1314 entries,
1315 state,
1316 visit_preorder,
1317 visit_postorder,
1318 Direction::Outgoing,
1319 include_traced,
1320 )
1321 }
1322
1323 pub fn traverse_edges_reverse_dfs<S>(
1340 &self,
1341 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1342 state: &mut S,
1343 visit_preorder: impl FnMut(
1344 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1345 ResolvedVc<Box<dyn Module>>,
1346 &mut S,
1347 ) -> Result<GraphTraversalAction>,
1348 visit_postorder: impl FnMut(
1349 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1350 ResolvedVc<Box<dyn Module>>,
1351 &mut S,
1352 ) -> Result<()>,
1353 ) -> Result<()> {
1354 self.traverse_edges_dfs_impl::<S>(
1355 entries,
1356 state,
1357 visit_preorder,
1358 visit_postorder,
1359 Direction::Incoming,
1360 false,
1361 )
1362 }
1363
1364 fn traverse_edges_dfs_impl<S>(
1365 &self,
1366 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1367 state: &mut S,
1368 mut visit_preorder: impl FnMut(
1369 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1370 ResolvedVc<Box<dyn Module>>,
1371 &mut S,
1372 ) -> Result<GraphTraversalAction>,
1373 mut visit_postorder: impl FnMut(
1374 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1375 ResolvedVc<Box<dyn Module>>,
1376 &mut S,
1377 ) -> Result<()>,
1378 direction: Direction,
1379 include_traced: bool,
1380 ) -> Result<()> {
1381 if direction == Direction::Incoming {
1382 debug_assert!(
1383 self.skip_visited_module_children,
1384 "Can only trace reverse edges in a single layer graph. We do not model cross \
1385 graph reverse edges"
1386 );
1387 }
1388 let entries = entries.into_iter().collect::<Vec<_>>();
1389
1390 enum Pass {
1391 Visit,
1392 ExpandAndVisit,
1393 }
1394 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1396 Pass,
1397 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1398 GraphNodeIndex,
1399 )> = Vec::with_capacity(entries.len());
1400 for entry in entries.into_iter().rev() {
1401 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1402 }
1403 let mut expanded = FxHashSet::default();
1404 while let Some((pass, parent, current)) = stack.pop() {
1405 let parent_arg = match parent {
1406 Some((parent_node, parent_edge)) => Some((
1407 self.get_node(parent_node)?.module(),
1408 self.get_edge(parent_edge)?,
1409 )),
1410 None => None,
1411 };
1412 let current_node = self.get_node(current)?;
1413 match pass {
1414 Pass::Visit => {
1415 visit_postorder(parent_arg, current_node.module(), state)?;
1416 }
1417 Pass::ExpandAndVisit => {
1418 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1419 if action == GraphTraversalAction::Exclude {
1420 continue;
1421 }
1422 stack.push((Pass::Visit, parent, current));
1423 if action == GraphTraversalAction::Continue
1424 && expanded.insert(current)
1425 && self.should_visit_node(current_node, direction)
1426 {
1427 let current = current_node.target_idx(direction).unwrap_or(current);
1428 stack.extend(
1429 self.iter_graphs_neighbors_rev(current, direction, include_traced)
1430 .map(|(edge, child)| {
1431 (Pass::ExpandAndVisit, Some((current, edge)), child)
1432 }),
1433 );
1434 }
1435 }
1436 }
1437 }
1438
1439 Ok(())
1440 }
1441
1442 pub fn traverse_cycles(
1446 &self,
1447 edge_filter: impl Fn(&RefData) -> bool,
1448 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1449 ) -> Result<()> {
1450 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1451 graph.traverse_cycles(
1452 &edge_filter,
1453 &mut visit_cycle,
1454 graph_idx as u32,
1455 &self.binding_usage,
1456 )?;
1457 }
1458 Ok(())
1459 }
1460
1461 pub fn traverse_edges_fixed_point_with_priority<'graph, S, P: Ord>(
1483 &'graph self,
1484 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1485 state: &mut S,
1486 mut visit: impl FnMut(
1487 Option<(ResolvedVc<Box<dyn Module>>, &'graph RefData, GraphEdgeIndex)>,
1488 ResolvedVc<Box<dyn Module>>,
1489 GraphNodeIndex,
1490 &mut S,
1491 ) -> Result<GraphTraversalAction>,
1492 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1493 ) -> Result<usize> {
1494 if self.skip_visited_module_children {
1495 panic!(
1496 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1497 );
1498 }
1499
1500 let mut visit_order = 0usize;
1501 let mut order = || {
1502 let order = visit_order;
1503 visit_order += 1;
1504 order
1505 };
1506 #[derive(PartialEq, Eq)]
1507 struct NodeWithPriority<T: Ord> {
1508 node: GraphNodeIndex,
1509 priority: T,
1510 visit_order: usize,
1511 }
1512 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1513 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1514 Some(self.cmp(other))
1515 }
1516 }
1517 impl<T: Ord> Ord for NodeWithPriority<T> {
1518 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1519 self.priority
1522 .cmp(&other.priority)
1523 .then(self.visit_order.cmp(&other.visit_order))
1526 }
1527 }
1528
1529 let mut queue_set = FxHashSet::default();
1530 let mut queue = BinaryHeap::from_iter(
1531 entries
1532 .into_iter()
1533 .map(|(m, priority)| {
1534 Ok(NodeWithPriority {
1535 node: self.get_entry(m)?,
1536 priority,
1537 visit_order: order(),
1538 })
1539 })
1540 .collect::<Result<Vec<_>>>()?,
1541 );
1542
1543 for entry_node in &queue {
1544 visit(
1545 None,
1546 self.get_node(entry_node.node)?.module(),
1547 entry_node.node,
1548 state,
1549 )?;
1550 }
1551
1552 let mut visit_count = 0usize;
1553 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1554 queue_set.remove(&node);
1555 let node_weight = self.get_node(node)?;
1556 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1557
1558 visit_count += 1;
1559
1560 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false) {
1561 let succ_weight = self.get_node(succ)?;
1562
1563 let action = visit(
1564 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1565 succ_weight.module(),
1566 succ,
1567 state,
1568 )?;
1569
1570 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1571 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1572 queue.push(NodeWithPriority {
1573 node: succ,
1574 priority: priority(succ_weight.module(), state)?,
1575 visit_order: order(),
1576 });
1577 }
1578 }
1579 }
1580
1581 Ok(visit_count)
1582 }
1583
1584 pub fn iter_reachable_modules(
1586 &self,
1587 ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1588 Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1589 SingleModuleGraphNode::Module(m) => Some(*m),
1590 SingleModuleGraphNode::VisitedModule { .. } => None,
1591 }))
1592 }
1593
1594 pub fn iter_reachable_nodes<'a>(
1597 &'a self,
1598 ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1599 ModuleGraphSnapshotNodeIterator::new(self)
1600 }
1601}
1602
1603struct ModuleGraphSnapshotNodeIterator<'a> {
1604 graph: &'a ModuleGraphSnapshot,
1605 visited: FxHashSet<GraphNodeIndex>,
1606 visit_queue: VecDeque<GraphNodeIndex>,
1607}
1608
1609impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1610 fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1611 let entries = graph
1612 .graphs
1613 .iter()
1614 .flat_map(|g| g.chunk_group_modules())
1615 .map(|e| graph.get_entry(e))
1616 .collect::<Result<VecDeque<_>>>()?;
1617
1618 Ok(Self {
1619 graph,
1620 visited: FxHashSet::default(),
1621 visit_queue: entries,
1622 })
1623 }
1624}
1625impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1626 type Item = &'a SingleModuleGraphNode;
1627
1628 fn next(&mut self) -> Option<Self::Item> {
1629 while let Some(node_idx) = self.visit_queue.pop_front() {
1630 if self.visited.insert(node_idx) {
1631 let node_weight = self.graph.get_node(node_idx).unwrap();
1632 if self
1633 .graph
1634 .should_visit_node(node_weight, Direction::Outgoing)
1635 {
1636 let node = node_weight
1637 .target_idx(Direction::Outgoing)
1638 .unwrap_or(node_idx);
1639 self.visit_queue.extend(
1640 self.graph
1641 .iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1642 .map(|(_, succ)| succ)
1643 .filter(|succ| !self.visited.contains(succ)),
1644 );
1645 }
1646 return Some(node_weight);
1647 }
1648 }
1649 None
1650 }
1651}
1652impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1653
1654#[turbo_tasks::value_impl]
1655impl SingleModuleGraph {
1656 #[turbo_tasks::function(operation)]
1657 pub async fn new_with_entry(
1658 entry: ChunkGroupEntry,
1659 include_traced: bool,
1660 include_binding_usage: bool,
1661 ) -> Result<Vc<Self>> {
1662 SingleModuleGraph::new_inner(
1663 &GraphEntries::from_chunk_groups(vec![entry]),
1664 &Default::default(),
1665 include_traced,
1666 include_binding_usage,
1667 )
1668 .await
1669 }
1670
1671 #[turbo_tasks::function(operation)]
1672 pub async fn new_with_entries(
1673 entries: ResolvedVc<GraphEntries>,
1674 include_traced: bool,
1675 include_binding_usage: bool,
1676 ) -> Result<Vc<Self>> {
1677 SingleModuleGraph::new_inner(
1678 &*entries.await?,
1679 &Default::default(),
1680 include_traced,
1681 include_binding_usage,
1682 )
1683 .await
1684 }
1685
1686 #[turbo_tasks::function(operation)]
1687 pub async fn new_with_entries_visited(
1688 entries: ResolvedVc<GraphEntries>,
1689 visited_modules: OperationVc<VisitedModules>,
1690 include_traced: bool,
1691 include_binding_usage: bool,
1692 ) -> Result<Vc<Self>> {
1693 SingleModuleGraph::new_inner(
1694 &*entries.await?,
1695 &visited_modules.connect().await?.modules,
1696 include_traced,
1697 include_binding_usage,
1698 )
1699 .await
1700 }
1701
1702 #[turbo_tasks::function(operation)]
1703 pub async fn new_with_entries_visited_intern(
1704 entries: GraphEntries,
1706 visited_modules: OperationVc<VisitedModules>,
1707 include_traced: bool,
1708 include_binding_usage: bool,
1709 ) -> Result<Vc<Self>> {
1710 SingleModuleGraph::new_inner(
1711 &entries,
1712 &visited_modules.connect().await?.modules,
1713 include_traced,
1714 include_binding_usage,
1715 )
1716 .await
1717 }
1718
1719 #[turbo_tasks::function]
1720 pub async fn module_count(&self) -> Vc<u64> {
1721 Vc::cell(self.number_of_modules as u64)
1722 }
1723
1724 #[turbo_tasks::function]
1725 pub async fn edge_count(&self) -> Vc<u64> {
1726 Vc::cell(self.graph.edge_count() as u64)
1727 }
1728}
1729
1730#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1731pub enum SingleModuleGraphNode {
1732 Module(ResolvedVc<Box<dyn Module>>),
1733 VisitedModule {
1735 idx: GraphNodeIndex,
1736 module: ResolvedVc<Box<dyn Module>>,
1737 },
1738}
1739
1740impl SingleModuleGraphNode {
1741 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1742 match self {
1743 SingleModuleGraphNode::Module(module) => *module,
1744 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1745 }
1746 }
1747 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1748 match self {
1749 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1750 Direction::Outgoing => Some(*idx),
1751 Direction::Incoming => None,
1752 },
1753 SingleModuleGraphNode::Module(_) => None,
1754 }
1755 }
1756}
1757
1758#[derive(PartialEq, Eq, Debug)]
1759pub enum GraphTraversalAction {
1760 Continue,
1762 Skip,
1764 Exclude,
1766}
1767
1768#[derive(Clone, Hash, PartialEq, Eq)]
1771enum SingleModuleGraphBuilderNode {
1772 Module {
1774 module: ResolvedVc<Box<dyn Module>>,
1775 ident: Option<ReadRef<RcStr>>,
1777 is_traced: bool,
1779 },
1780 VisitedModule {
1782 module: ResolvedVc<Box<dyn Module>>,
1783 idx: GraphNodeIndex,
1784 },
1785}
1786
1787impl SingleModuleGraphBuilderNode {
1788 async fn new_module(
1789 emit_spans: bool,
1790 module: ResolvedVc<Box<dyn Module>>,
1791 is_traced: bool,
1792 ) -> Result<Self> {
1793 Ok(Self::Module {
1794 module,
1795 ident: if emit_spans {
1796 Some(module.ident_string().untracked().await?)
1798 } else {
1799 None
1800 },
1801 is_traced,
1802 })
1803 }
1804 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1805 Self::VisitedModule { module, idx }
1806 }
1807}
1808
1809struct SingleModuleGraphBuilder<'a> {
1810 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1811
1812 emit_spans: bool,
1813
1814 include_traced: bool,
1816
1817 include_binding_usage: bool,
1819}
1820impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1821 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1822 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1823
1824 fn visit(
1825 &mut self,
1826 node: &SingleModuleGraphBuilderNode,
1827 _edge: Option<&RefData>,
1828 ) -> VisitControlFlow {
1829 match node {
1830 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1831 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1833 }
1834 }
1835
1836 fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1837 let &SingleModuleGraphBuilderNode::Module {
1839 module, is_traced, ..
1840 } = node
1841 else {
1842 unreachable!()
1844 };
1845 let visited_modules = self.visited_modules;
1846 let emit_spans = self.emit_spans;
1847 let include_traced = self.include_traced;
1848 let include_binding_usage = self.include_binding_usage;
1849 async move {
1850 let refs_cell = if !is_traced {
1851 primary_chunkable_referenced_modules(*module, include_traced, include_binding_usage)
1852 } else {
1853 referenced_modules_and_affecting_sources(*module, false)
1855 };
1856 let refs = match refs_cell.await {
1857 Ok(refs) => refs,
1858 Err(e) => {
1859 return Err(e.context(module.ident().to_string().await?));
1860 }
1861 };
1862
1863 refs.iter()
1864 .flat_map(|(reference, resolved)| {
1865 resolved.modules.iter().map(|m| {
1866 (
1867 *reference,
1868 resolved.chunking_type.clone(),
1869 resolved.binding_usage.clone(),
1870 *m,
1871 )
1872 })
1873 })
1874 .filter(|(_, ty, _, _)| {
1875 !matches!(
1884 ty,
1885 ChunkingType::Traced {
1886 mode: TracedMode::Transitive
1887 }
1888 ) || is_traced
1889 })
1890 .map(async |(reference, ty, binding_usage, target)| {
1891 let to = if let Some(idx) = visited_modules.get(&target) {
1892 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1893 } else {
1894 SingleModuleGraphBuilderNode::new_module(
1895 emit_spans,
1896 target,
1897 is_traced || ty.is_traced(),
1898 )
1899 .await?
1900 };
1901 Ok((
1902 to,
1903 RefData {
1904 chunking_type: ty,
1905 binding_usage,
1906 reference,
1907 },
1908 ))
1909 })
1910 .try_join()
1911 .await
1912 }
1913 }
1914
1915 fn span(
1916 &mut self,
1917 node: &SingleModuleGraphBuilderNode,
1918 edge: Option<&RefData>,
1919 ) -> tracing::Span {
1920 if !self.emit_spans {
1921 return Span::none();
1922 }
1923
1924 let mut span = match node {
1925 SingleModuleGraphBuilderNode::Module {
1926 ident: Some(ident), ..
1927 } => {
1928 tracing::info_span!("module", name = display(ident))
1929 }
1930 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1931 tracing::info_span!("visited module")
1932 }
1933 _ => unreachable!(),
1934 };
1935
1936 if let Some(edge) = edge {
1937 match &edge.chunking_type {
1938 ChunkingType::Parallel {
1939 inherit_async: _,
1940 hoisted: _,
1941 } => {}
1942 ChunkingType::Traced { .. } => {
1943 let _span = span.entered();
1944 span = tracing::info_span!("traced reference");
1945 }
1946 ChunkingType::Async => {
1947 let _span = span.entered();
1948 span = tracing::info_span!("async reference");
1949 }
1950 ChunkingType::PerEntry => {
1951 let _span = span.entered();
1952 span = tracing::info_span!("per-entry reference");
1953 }
1954 ChunkingType::Emitted { namespace, .. } => {
1955 let _span = span.entered();
1956 span = tracing::info_span!("emitted reference", namespace = debug(&namespace));
1957 }
1958 ChunkingType::Collected { namespace, .. } => {
1959 let _span = span.entered();
1960 span =
1961 tracing::info_span!("collected reference", namespace = debug(&namespace));
1962 }
1963 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1964 let _span = span.entered();
1965 span = tracing::info_span!(
1966 "isolated reference",
1967 ty = debug(&ty),
1968 merge_tag = debug(&merge_tag)
1969 );
1970 }
1971 ChunkingType::Shared {
1972 inherit_async: _,
1973 merge_tag,
1974 } => {
1975 let _span = span.entered();
1976 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1977 }
1978 };
1979 }
1980
1981 span
1982 }
1983}
1984
1985#[cfg(test)]
1986pub mod tests {
1987 use anyhow::Result;
1988 use rustc_hash::FxHashMap;
1989 use turbo_rcstr::{RcStr, rcstr};
1990 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1991 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1992 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1993
1994 use super::*;
1995 use crate::{
1996 asset::{Asset, AssetContent},
1997 ident::AssetIdent,
1998 module::{Module, ModuleSideEffects},
1999 module_graph::chunk_group_info::EntryHeuristics,
2000 reference::{ModuleReference, ModuleReferences},
2001 resolve::ModuleResolveResult,
2002 };
2003
2004 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2005 async fn test_traverse_dfs_from_entries_diamond() {
2006 run_graph_test(
2007 vec![rcstr!("a.js")],
2008 {
2009 let mut deps = FxHashMap::default();
2010 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2012 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
2013 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
2014 deps
2015 },
2016 |graph, entry_modules, module_to_name| {
2017 let mut preorder_visits = Vec::new();
2018 let mut postorder_visits = Vec::new();
2019
2020 graph.traverse_edges_dfs(
2021 entry_modules,
2022 &mut (),
2023 |parent, target, _| {
2024 preorder_visits.push((
2025 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2026 module_to_name.get(&target).unwrap().clone(),
2027 ));
2028 Ok(GraphTraversalAction::Continue)
2029 },
2030 |parent, target, _| {
2031 postorder_visits.push((
2032 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2033 module_to_name.get(&target).unwrap().clone(),
2034 ));
2035 Ok(())
2036 },
2037 false,
2038 )?;
2039 assert_eq!(
2040 vec![
2041 (None, rcstr!("a.js")),
2042 (Some(rcstr!("a.js")), rcstr!("b.js")),
2043 (Some(rcstr!("b.js")), rcstr!("d.js")),
2044 (Some(rcstr!("a.js")), rcstr!("c.js")),
2045 (Some(rcstr!("c.js")), rcstr!("d.js"))
2046 ],
2047 preorder_visits
2048 );
2049 assert_eq!(
2050 vec![
2051 (Some(rcstr!("b.js")), rcstr!("d.js")),
2052 (Some(rcstr!("a.js")), rcstr!("b.js")),
2053 (Some(rcstr!("c.js")), rcstr!("d.js")),
2054 (Some(rcstr!("a.js")), rcstr!("c.js")),
2055 (None, rcstr!("a.js"))
2056 ],
2057 postorder_visits
2058 );
2059 Ok(())
2060 },
2061 )
2062 .await;
2063 }
2064
2065 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2066 async fn test_traverse_dfs_from_entries_cycle() {
2067 run_graph_test(
2068 vec![rcstr!("a.js")],
2069 {
2070 let mut deps = FxHashMap::default();
2071 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2073 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2074 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2075 deps
2076 },
2077 |graph, entry_modules, module_to_name| {
2078 let mut preorder_visits = Vec::new();
2079 let mut postorder_visits = Vec::new();
2080
2081 graph.traverse_edges_dfs(
2082 entry_modules,
2083 &mut (),
2084 |parent, target, _| {
2085 preorder_visits.push((
2086 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2087 module_to_name.get(&target).unwrap().clone(),
2088 ));
2089 Ok(GraphTraversalAction::Continue)
2090 },
2091 |parent, target, _| {
2092 postorder_visits.push((
2093 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2094 module_to_name.get(&target).unwrap().clone(),
2095 ));
2096 Ok(())
2097 },
2098 false,
2099 )?;
2100 assert_eq!(
2101 vec![
2102 (None, rcstr!("a.js")),
2103 (Some(rcstr!("a.js")), rcstr!("b.js")),
2104 (Some(rcstr!("b.js")), rcstr!("c.js")),
2105 (Some(rcstr!("c.js")), rcstr!("a.js")),
2106 ],
2107 preorder_visits
2108 );
2109 assert_eq!(
2110 vec![
2111 (Some(rcstr!("c.js")), rcstr!("a.js")),
2112 (Some(rcstr!("b.js")), rcstr!("c.js")),
2113 (Some(rcstr!("a.js")), rcstr!("b.js")),
2114 (None, rcstr!("a.js"))
2115 ],
2116 postorder_visits
2117 );
2118 Ok(())
2119 },
2120 )
2121 .await;
2122 }
2123
2124 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2125 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
2126 run_graph_test(
2127 vec![rcstr!("a.js")],
2128 {
2129 let mut deps = FxHashMap::default();
2130 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2132 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2133 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2134 deps
2135 },
2136 |graph, entry_modules, module_to_name| {
2137 let mut visits = Vec::new();
2138 let mut count = 0;
2139
2140 graph.traverse_edges_fixed_point_with_priority(
2141 entry_modules.into_iter().map(|m| (m, 0)),
2142 &mut (),
2143 |parent, target, _, _| {
2144 visits.push((
2145 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2146 module_to_name.get(&target).unwrap().clone(),
2147 ));
2148 count += 1;
2149
2150 Ok(if count < 6 {
2152 GraphTraversalAction::Continue
2153 } else {
2154 GraphTraversalAction::Skip
2155 })
2156 },
2157 |_, _| Ok(0),
2158 )?;
2159 assert_eq!(
2160 vec![
2161 (None, rcstr!("a.js")),
2162 (Some(rcstr!("a.js")), rcstr!("b.js")),
2163 (Some(rcstr!("b.js")), rcstr!("c.js")),
2164 (Some(rcstr!("c.js")), rcstr!("a.js")),
2165 (Some(rcstr!("a.js")), rcstr!("b.js")),
2167 (Some(rcstr!("b.js")), rcstr!("c.js")),
2168 ],
2169 visits
2170 );
2171
2172 Ok(())
2173 },
2174 )
2175 .await;
2176 }
2177
2178 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2179 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
2180 run_graph_test(
2181 vec![rcstr!("a.js")],
2182 {
2183 let mut deps = FxHashMap::default();
2184 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2189 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
2190 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2191 deps
2192 },
2193 |graph, entry_modules, module_to_name| {
2194 let mut visits = Vec::new();
2195 let mut count = 0;
2196
2197 graph.traverse_edges_fixed_point_with_priority(
2198 entry_modules.into_iter().map(|m| (m, 0)),
2199 &mut (),
2200 |parent, target, _, _| {
2201 visits.push((
2202 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2203 module_to_name.get(&target).unwrap().clone(),
2204 ));
2205 count += 1;
2206
2207 Ok(if count < 6 {
2209 GraphTraversalAction::Continue
2210 } else {
2211 GraphTraversalAction::Skip
2212 })
2213 },
2214 |_, _| Ok(0),
2215 )?;
2216
2217 assert_eq!(
2218 vec![
2219 (None, rcstr!("a.js")),
2220 (Some(rcstr!("a.js")), rcstr!("c.js")),
2221 (Some(rcstr!("a.js")), rcstr!("b.js")),
2222 (Some(rcstr!("b.js")), rcstr!("e.js")),
2223 (Some(rcstr!("b.js")), rcstr!("d.js")),
2224 (Some(rcstr!("c.js")), rcstr!("f.js")),
2225 (Some(rcstr!("c.js")), rcstr!("e.js")),
2226 ],
2227 visits
2228 );
2229
2230 Ok(())
2231 },
2232 )
2233 .await;
2234 }
2235
2236 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2237 async fn test_traverse_cycles() {
2238 run_graph_test(
2239 vec![rcstr!("a.js")],
2240 {
2241 let mut deps = FxHashMap::default();
2242 deps.insert(
2249 rcstr!("a.js"),
2250 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2251 );
2252 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2253 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2254 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2255 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2256 deps
2257 },
2258 |graph, _, module_to_name| {
2259 let mut cycles = vec![];
2260
2261 graph.traverse_cycles(
2262 |_| true,
2263 |cycle| {
2264 cycles.push(
2265 cycle
2266 .iter()
2267 .map(|n| module_to_name.get(*n).unwrap().clone())
2268 .collect::<Vec<_>>(),
2269 );
2270 Ok(())
2271 },
2272 )?;
2273
2274 assert_eq!(
2275 cycles,
2276 vec![
2277 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2278 vec![rcstr!("s.js")]
2279 ],
2280 );
2281
2282 Ok(())
2283 },
2284 )
2285 .await;
2286 }
2287
2288 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2289 async fn test_reverse_edges_through_layered_graph() {
2290 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2291 BackendOptions::default(),
2292 noop_backing_storage(),
2293 ));
2294 tt.run_once(async move {
2295 #[turbo_tasks::value]
2296 struct ReverseTraversalResults {
2297 forward: Vec<RcStr>,
2298 reverse_from_d: Vec<RcStr>,
2299 reverse_from_b: Vec<RcStr>,
2300 }
2301
2302 #[turbo_tasks::function(operation, root)]
2303 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2304 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2305 let root = fs.root().await?;
2306
2307 let repo = TestRepo::new(
2310 &root,
2311 [("a.js", vec!["b.js", "d.js"]), ("b.js", vec!["c.js"])],
2312 );
2313 let make_module = |name| {
2314 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2315 .to_resolved()
2316 };
2317 let a_module = make_module("a.js").await?;
2318 let b_module = make_module("b.js").await?;
2319
2320 let parent_graph = SingleModuleGraph::new_with_entries(
2321 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2322 modules: vec![b_module],
2323 heuristics: EntryHeuristics::default(),
2324 }])
2325 .resolved_cell(),
2326 false,
2327 false,
2328 );
2329
2330 let module_graph = ModuleGraph::from_graphs(
2331 vec![
2332 parent_graph,
2333 SingleModuleGraph::new_with_entries_visited(
2334 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2335 modules: vec![a_module],
2336 heuristics: EntryHeuristics::default(),
2337 }])
2338 .resolved_cell(),
2339 VisitedModules::from_graph(parent_graph),
2340 false,
2341 false,
2342 ),
2343 ],
2344 None,
2345 )
2346 .connect();
2347 let child_graph = module_graph
2348 .iter_graphs()
2349 .await?
2350 .get(1)
2351 .unwrap()
2352 .connect()
2353 .await?;
2354
2355 let mut visited_forward = Vec::new();
2357 child_graph.traverse_edges_dfs(
2358 vec![a_module],
2359 &mut (),
2360 |_parent, child, _state_| {
2361 visited_forward.push(child);
2362 Ok(GraphTraversalAction::Continue)
2363 },
2364 |_, _, _| Ok(()),
2365 false,
2366 )?;
2367 let forward = visited_forward
2368 .iter()
2369 .map(|m| m.ident().to_string().owned())
2370 .try_join()
2371 .await?;
2372
2373 let d_module = child_graph
2375 .enumerate_nodes()
2376 .map(async |(_index, module)| {
2377 Ok(match module {
2378 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2379 if module.ident().to_string().owned().await? == "[test]/d.js" {
2380 Some(*module)
2381 } else {
2382 None
2383 }
2384 }
2385 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2386 ..
2387 } => None,
2388 })
2389 })
2390 .try_flat_join()
2391 .await?
2392 .into_iter()
2393 .next()
2394 .unwrap();
2395
2396 async fn get_reverse_from(
2397 graph: &ModuleGraphLayer,
2398 module: ResolvedVc<Box<dyn Module>>,
2399 ) -> Result<Vec<RcStr>> {
2400 let mut visited = Vec::new();
2401 graph.traverse_edges_reverse_dfs(
2402 vec![module],
2403 &mut (),
2404 |_parent, child, _state_| {
2405 visited.push(child);
2406 Ok(GraphTraversalAction::Continue)
2407 },
2408 |_, _, _| Ok(()),
2409 )?;
2410 visited
2411 .iter()
2412 .map(|m| m.ident().to_string().owned())
2413 .try_join()
2414 .await
2415 }
2416
2417 Ok(ReverseTraversalResults {
2418 forward,
2419 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2420 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2421 }
2422 .cell())
2423 }
2424
2425 let traversal_results = reverse_traversal_results_operation()
2426 .read_strongly_consistent()
2427 .await?;
2428
2429 assert_eq!(
2430 traversal_results.forward,
2431 vec![
2432 rcstr!("[test]/a.js"),
2433 rcstr!("[test]/b.js"),
2434 rcstr!("[test]/d.js")
2435 ]
2436 );
2437
2438 assert_eq!(
2439 traversal_results.reverse_from_d,
2440 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2441 );
2442
2443 assert_eq!(
2444 traversal_results.reverse_from_b,
2445 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2446 );
2447
2448 Ok(())
2449 })
2450 .await
2451 .unwrap();
2452 }
2453
2454 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2455 async fn test_iter_nodes_modules_through_layered_graph() {
2456 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2457 BackendOptions::default(),
2458 noop_backing_storage(),
2459 ));
2460 tt.run_once(async move {
2461 #[turbo_tasks::value]
2462 struct Results {
2463 iter_nodes: Vec<RcStr>,
2464 iter_modules: Vec<RcStr>,
2465 iter_nodes_single: Vec<Vec<RcStr>>,
2466 iter_modules_single: Vec<Vec<RcStr>>,
2467 }
2468
2469 #[turbo_tasks::function(operation, root)]
2470 async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2471 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2472 let root = fs.root().await?;
2473
2474 let repo = TestRepo::new_with_chunking_types(
2478 &root,
2479 [
2480 ("a.js", vec!["b.js"]),
2481 ("b.js", vec!["c.js"]),
2482 ("c.js", vec!["x.js"]),
2483 ("x.js", vec!["y.js", "traced.js"]),
2484 ("y.js", vec!["z.js"]),
2485 ],
2486 [(
2487 "x.js",
2488 "traced.js",
2489 ChunkingType::Traced {
2490 mode: TracedMode::Entry,
2491 },
2492 )],
2493 );
2494 let make_module = |name| {
2495 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2496 .to_resolved()
2497 };
2498 let x_module = make_module("x.js").await?;
2499 let a_module = make_module("a.js").await?;
2500
2501 let parent_graph = SingleModuleGraph::new_with_entries(
2502 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2503 modules: vec![x_module],
2504 heuristics: EntryHeuristics::default(),
2505 }])
2506 .resolved_cell(),
2507 true,
2508 false,
2509 );
2510
2511 let module_graph = ModuleGraph::from_graphs(
2512 vec![
2513 parent_graph,
2514 SingleModuleGraph::new_with_entries_visited(
2515 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2516 modules: vec![a_module],
2517 heuristics: EntryHeuristics::default(),
2518 }])
2519 .resolved_cell(),
2520 VisitedModules::from_graph(parent_graph),
2521 true,
2522 false,
2523 ),
2524 ],
2525 None,
2526 )
2527 .connect();
2528 let graph_layers = module_graph.iter_graphs().await?;
2529
2530 Ok(Results {
2531 iter_nodes: module_graph
2532 .await?
2533 .iter_reachable_nodes()?
2534 .map(async |node| {
2535 Ok(match node {
2536 SingleModuleGraphNode::Module(module) => {
2537 module.ident_string().owned().await?
2538 }
2539 SingleModuleGraphNode::VisitedModule { module, .. } => {
2540 format!("visited {}", module.ident_string().owned().await?)
2541 .into()
2542 }
2543 })
2544 })
2545 .try_join()
2546 .await?,
2547 iter_modules: module_graph
2548 .await?
2549 .iter_reachable_modules()?
2550 .map(|m| m.ident_string().owned())
2551 .try_join()
2552 .await?,
2553 iter_nodes_single: graph_layers
2554 .iter()
2555 .map(async |layer| {
2556 layer
2557 .connect()
2558 .await?
2559 .iter_reachable_nodes()?
2560 .map(async |node| {
2561 Ok(match node {
2562 SingleModuleGraphNode::Module(module) => {
2563 module.ident_string().owned().await?
2564 }
2565 SingleModuleGraphNode::VisitedModule { module, .. } => {
2566 format!(
2567 "visited {}",
2568 module.ident_string().owned().await?
2569 )
2570 .into()
2571 }
2572 })
2573 })
2574 .try_join()
2575 .await
2576 })
2577 .try_join()
2578 .await?,
2579 iter_modules_single: graph_layers
2580 .iter()
2581 .map(async |layer| {
2582 layer
2583 .connect()
2584 .await?
2585 .iter_reachable_modules()?
2586 .map(|m| m.ident_string().owned())
2587 .try_join()
2588 .await
2589 })
2590 .try_join()
2591 .await?,
2592 }
2593 .cell())
2594 }
2595
2596 let traversal_results = reverse_traversal_results_operation()
2597 .read_strongly_consistent()
2598 .await?;
2599
2600 assert_eq!(
2601 traversal_results.iter_nodes,
2602 vec![
2603 rcstr!("[test]/x.js"),
2604 rcstr!("[test]/a.js"),
2605 rcstr!("[test]/y.js"),
2606 rcstr!("[test]/b.js"),
2607 rcstr!("[test]/z.js"),
2608 rcstr!("[test]/c.js"),
2609 rcstr!("visited [test]/x.js")
2610 ]
2611 );
2612 assert_eq!(
2613 traversal_results.iter_modules,
2614 vec![
2615 rcstr!("[test]/x.js"),
2616 rcstr!("[test]/a.js"),
2617 rcstr!("[test]/y.js"),
2618 rcstr!("[test]/b.js"),
2619 rcstr!("[test]/z.js"),
2620 rcstr!("[test]/c.js")
2621 ]
2622 );
2623 assert_eq!(
2624 traversal_results.iter_nodes_single,
2625 vec![
2626 vec![
2627 rcstr!("[test]/x.js"),
2628 rcstr!("[test]/y.js"),
2629 rcstr!("[test]/z.js")
2630 ],
2631 vec![
2632 rcstr!("[test]/a.js"),
2633 rcstr!("[test]/b.js"),
2634 rcstr!("[test]/c.js"),
2635 rcstr!("visited [test]/x.js")
2636 ]
2637 ]
2638 );
2639 assert_eq!(
2640 traversal_results.iter_modules_single,
2641 vec![
2642 vec![
2643 rcstr!("[test]/x.js"),
2644 rcstr!("[test]/y.js"),
2645 rcstr!("[test]/z.js")
2646 ],
2647 vec![
2648 rcstr!("[test]/a.js"),
2649 rcstr!("[test]/b.js"),
2650 rcstr!("[test]/c.js")
2651 ]
2652 ]
2653 );
2654
2655 Ok(())
2656 })
2657 .await
2658 .unwrap();
2659 }
2660
2661 #[turbo_tasks::value(shared)]
2662 struct TestRepo {
2663 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2664 chunking_types: FxHashMap<(FileSystemPath, FileSystemPath), ChunkingType>,
2665 }
2666
2667 impl TestRepo {
2668 fn new(
2669 root: &FileSystemPath,
2670 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2671 ) -> Vc<Self> {
2672 Self::new_with_chunking_types(
2673 root,
2674 dependencies,
2675 std::iter::empty::<(RcStr, RcStr, ChunkingType)>(),
2676 )
2677 }
2678
2679 fn new_with_chunking_types(
2680 root: &FileSystemPath,
2681 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2682 chunking_types: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, ChunkingType)>,
2683 ) -> Vc<Self> {
2684 let chunking_types = chunking_types
2685 .into_iter()
2686 .map(|(from, to, ty)| {
2687 (
2688 (
2689 root.join(from.as_ref()).unwrap(),
2690 root.join(to.as_ref()).unwrap(),
2691 ),
2692 ty,
2693 )
2694 })
2695 .collect::<FxHashMap<_, _>>();
2696 Self {
2697 repo: dependencies
2698 .into_iter()
2699 .map(|(k, v)| {
2700 (
2701 root.join(k.as_ref()).unwrap(),
2702 v.iter().map(|f| root.join(f.as_ref()).unwrap()).collect(),
2703 )
2704 })
2705 .collect(),
2706 chunking_types,
2707 }
2708 .cell()
2709 }
2710 }
2711
2712 #[turbo_tasks::value]
2713 struct MockModule {
2714 path: FileSystemPath,
2715 repo: ResolvedVc<TestRepo>,
2716 }
2717 #[turbo_tasks::value_impl]
2718 impl MockModule {
2719 #[turbo_tasks::function]
2720 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2721 Self { path, repo }.cell()
2722 }
2723 }
2724
2725 #[turbo_tasks::value_impl]
2726 impl Asset for MockModule {
2727 #[turbo_tasks::function]
2728 fn content(&self) -> Vc<AssetContent> {
2729 panic!("MockModule::content shouldn't be called")
2730 }
2731 }
2732
2733 #[turbo_tasks::value_impl]
2734 impl Module for MockModule {
2735 #[turbo_tasks::function]
2736 fn ident(&self) -> Vc<AssetIdent> {
2737 AssetIdent::from_path(self.path.clone()).into_vc()
2738 }
2739
2740 #[turbo_tasks::function]
2741 fn source(&self) -> Vc<crate::source::OptionSource> {
2742 Vc::cell(None)
2743 }
2744
2745 #[turbo_tasks::function]
2746 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2747 let repo = self.repo.await?;
2748 let references = match repo.repo.get(&self.path) {
2749 Some(deps) => {
2750 deps.iter()
2751 .map(async |p| {
2752 Vc::upcast::<Box<dyn ModuleReference>>(MockModuleReference::new(
2753 ResolvedVc::upcast(
2754 MockModule::new(p.clone(), *self.repo).to_resolved().await?,
2755 ),
2756 rcstr!("normal-dep"),
2757 repo.chunking_types
2758 .get(&(self.path.clone(), p.clone()))
2759 .cloned()
2760 .unwrap_or(ChunkingType::Parallel {
2761 inherit_async: true,
2762 hoisted: false,
2763 }),
2764 ))
2765 .to_resolved()
2766 .await
2767 })
2768 .try_join()
2769 .await?
2770 }
2771 None => vec![],
2772 };
2773
2774 Ok(Vc::cell(references))
2775 }
2776 #[turbo_tasks::function]
2777 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2778 ModuleSideEffects::SideEffectful.cell()
2779 }
2780 }
2781
2782 #[turbo_tasks::value]
2783 #[derive(ValueToString)]
2784 #[value_to_string(self.description)]
2785 struct MockModuleReference {
2786 asset: ResolvedVc<Box<dyn Module>>,
2787 description: RcStr,
2788 chunking_type: ChunkingType,
2789 }
2790
2791 impl MockModuleReference {
2792 pub fn new(
2793 asset: ResolvedVc<Box<dyn Module>>,
2794 description: RcStr,
2795 chunking_type: ChunkingType,
2796 ) -> Vc<Self> {
2797 MockModuleReference {
2798 asset,
2799 description,
2800 chunking_type,
2801 }
2802 .cell()
2803 }
2804 }
2805
2806 #[turbo_tasks::value_impl]
2807 impl ModuleReference for MockModuleReference {
2808 #[turbo_tasks::function]
2809 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
2810 *ModuleResolveResult::module(self.asset)
2811 }
2812
2813 fn chunking_type(&self) -> Option<ChunkingType> {
2814 Some(self.chunking_type.clone())
2815 }
2816 }
2817
2818 async fn run_graph_test(
2832 entries: Vec<RcStr>,
2833 graph: FxHashMap<RcStr, Vec<RcStr>>,
2834 test_fn: impl FnOnce(
2835 &ModuleGraph,
2836 Vec<ResolvedVc<Box<dyn Module>>>,
2837 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2838 ) -> Result<()>
2839 + Send
2840 + 'static,
2841 ) {
2842 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
2843 struct SetupGraph {
2844 module_graph: ReadRef<ModuleGraph>,
2845 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2846 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2847 }
2848
2849 #[turbo_tasks::function(operation, root)]
2850 async fn setup_graph(
2851 entries: Vec<RcStr>,
2852 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2853 ) -> Result<Vc<SetupGraph>> {
2854 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2855 let root = fs.root().await?;
2856
2857 let repo = TestRepo::new(&root, graph_entries);
2858 let entry_modules = entries
2859 .iter()
2860 .map(|e| {
2861 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2862 .to_resolved()
2863 })
2864 .try_join()
2865 .await?;
2866 let graph = SingleModuleGraph::new_with_entries(
2867 GraphEntries::resolved_cell(GraphEntries::new(
2868 vec![ChunkGroupEntry::Entry {
2869 modules: entry_modules.clone(),
2870 heuristics: EntryHeuristics::default(),
2871 }],
2872 vec![],
2873 )),
2874 false,
2875 false,
2876 );
2877
2878 let module_to_name = graph
2882 .connect()
2883 .await?
2884 .modules
2885 .keys()
2886 .map(async |m| Ok((*m, m.ident().await?.path.path.clone())))
2887 .try_join()
2888 .await?
2889 .into_iter()
2890 .collect();
2891 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
2892 .connect()
2893 .await?;
2894
2895 Ok(SetupGraph {
2896 module_graph,
2897 entry_modules,
2898 module_to_name,
2899 }
2900 .cell())
2901 }
2902
2903 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2904 BackendOptions::default(),
2905 noop_backing_storage(),
2906 ));
2907 let graph_entries = graph.into_iter().collect::<Vec<_>>();
2908 tt.run_once(async move {
2909 let setup = setup_graph(entries, graph_entries)
2910 .read_strongly_consistent()
2911 .await?;
2912
2913 test_fn(
2914 &setup.module_graph,
2915 setup.entry_modules.clone(),
2916 setup.module_to_name.clone(),
2917 )
2918 })
2919 .await
2920 .unwrap();
2921 }
2922}