diff --git a/turbopack/crates/turbopack-ecmascript/src/tree_shake/graph.rs b/turbopack/crates/turbopack-ecmascript/src/tree_shake/graph.rs index a9c43851b91b6..241ed04bbfd36 100644 --- a/turbopack/crates/turbopack-ecmascript/src/tree_shake/graph.rs +++ b/turbopack/crates/turbopack-ecmascript/src/tree_shake/graph.rs @@ -713,11 +713,10 @@ impl DepGraph { graph_ix: &self.g.graph_ix, data, }; - loop { - if !optimizer.merge_single_incoming_nodes(&mut condensed) { - break; - } - } + + while optimizer.merge_single_incoming_nodes(&mut condensed) + || optimizer.merge_nodes_with_same_starting_point(&mut condensed) + {} let mut new_graph = InternedGraph::default(); diff --git a/turbopack/crates/turbopack-ecmascript/src/tree_shake/optimizations.rs b/turbopack/crates/turbopack-ecmascript/src/tree_shake/optimizations.rs index 72b5cbb5b3381..adcd59b26d009 100644 --- a/turbopack/crates/turbopack-ecmascript/src/tree_shake/optimizations.rs +++ b/turbopack/crates/turbopack-ecmascript/src/tree_shake/optimizations.rs @@ -1,7 +1,7 @@ use std::ops::Index; use petgraph::{visit::EdgeRef, Direction, Graph}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use turbo_tasks::FxIndexSet; use crate::tree_shake::graph::{Dependency, ItemData, ItemId, ItemIdGroupKind, ItemIdItemKind}; @@ -124,4 +124,76 @@ impl GraphOptimizer<'_> { did_work } + + pub(super) fn merge_nodes_with_same_starting_point( + &self, + g: &mut Graph, Dependency>, + ) -> bool + where + N: Copy, + Self: Index, + { + let mut did_work = false; + let mut nodes_to_merge = Vec::new(); + + for node in g.node_indices() { + let items = g.node_weight(node).expect("Node should exist"); + for item in items { + let item_id = &self[*item]; + if matches!(item_id, ItemId::Group(..)) { + let incoming_edges: Vec<_> = + g.edges_directed(node, Direction::Incoming).collect(); + if incoming_edges.len() == 1 && incoming_edges[0].source() == node { + nodes_to_merge.push(node); + break; + } + } + } + } + + for node in nodes_to_merge { + let mut dependencies = g + .edges_directed(node, Direction::Outgoing) + .map(|e| (e.target(), *e.weight())) + .collect::>(); + + // Handle transitive dependencies + let mut visited = FxHashSet::default(); + let mut stack = dependencies.clone(); + while let Some((dependency, _weight)) = stack.pop() { + if visited.insert(dependency) { + let transitive_deps = g + .edges_directed(dependency, Direction::Outgoing) + .map(|e| (e.target(), *e.weight())) + .collect::>(); + stack.extend(transitive_deps.clone()); + dependencies.extend(transitive_deps); + } + } + + for (dependency, weight) in dependencies { + let edge = g + .find_edge(node, dependency) + .and_then(|e| g.edge_weight_mut(e)); + match edge { + Some(v) => { + if matches!(v, Dependency::Weak) { + *v = weight; + } + } + None => { + g.add_edge(node, dependency, weight); + } + } + } + + let items = g.node_weight(node).expect("Node should exist").clone(); + g.node_weight_mut(node).unwrap().extend(items); + + g.remove_node(node).expect("Node should exist"); + did_work = true; + } + + did_work + } }