From 4d6aaddd80759962298100c827a3ffd45a0ee3aa Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 9 Jan 2026 09:45:53 +0100 Subject: [PATCH 1/8] Turbopack: pass binding info as operation to module graph --- crates/next-api/src/app.rs | 9 +++-- crates/next-api/src/pages.rs | 9 +++-- crates/next-api/src/project.rs | 15 +++----- .../crates/turbopack-cli/src/build/mod.rs | 14 ++++---- .../turbopack-core/src/module_graph/mod.rs | 14 ++++---- .../crates/turbopack-tests/tests/execution.rs | 25 ++++++------- .../crates/turbopack-tests/tests/snapshot.rs | 36 +++++++++---------- 7 files changed, 56 insertions(+), 66 deletions(-) diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index 749ead09cf7f4..dc98407eb6797 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -973,11 +973,10 @@ impl AppProject { .await? { full_with_unused_references - .without_unused_references( - *compute_binding_usage_info(full_with_unused_references, true) - .resolve_strongly_consistent() - .await?, - ) + .without_unused_references(compute_binding_usage_info( + full_with_unused_references, + true, + )) .to_resolved() .await? } else { diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index 3b057ba0f66df..3a70bea063e33 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -754,11 +754,10 @@ impl PageEndpoint { .turbopack_remove_unused_imports(next_mode) .await? { - graph = graph.without_unused_references( - *compute_binding_usage_info(graph.to_resolved().await?, true) - .resolve_strongly_consistent() - .await?, - ); + graph = graph.without_unused_references(compute_binding_usage_info( + graph.to_resolved().await?, + true, + )); } Ok(graph) diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index 6f7f20603a4fc..c735c75b03ec1 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -2118,11 +2118,7 @@ async fn whole_app_module_graph_operation( let base = if turbopack_remove_unused_imports { // TODO suboptimal that we do compute_binding_usage_info twice (once for the base graph // and later for the full graph) - base.without_unused_references( - *compute_binding_usage_info(base.to_resolved().await?, true) - .resolve_strongly_consistent() - .await?, - ) + base.without_unused_references(compute_binding_usage_info(base.to_resolved().await?, true)) } else { base }; @@ -2143,11 +2139,10 @@ async fn whole_app_module_graph_operation( let full = if turbopack_remove_unused_imports { full_with_unused_references - .without_unused_references( - *compute_binding_usage_info(full_with_unused_references, true) - .resolve_strongly_consistent() - .await?, - ) + .without_unused_references(compute_binding_usage_info( + full_with_unused_references, + true, + )) .to_resolved() .await? } else { diff --git a/turbopack/crates/turbopack-cli/src/build/mod.rs b/turbopack/crates/turbopack-cli/src/build/mod.rs index c572585ac6912..e50aa8d758d30 100644 --- a/turbopack/crates/turbopack-cli/src/build/mod.rs +++ b/turbopack/crates/turbopack-cli/src/build/mod.rs @@ -314,11 +314,13 @@ async fn build_internal( .to_resolved() .await?, ); - let binding_usage = compute_binding_usage_info(module_graph.to_resolved().await?, true) - .resolve_strongly_consistent() + let binding_usage = compute_binding_usage_info(module_graph.to_resolved().await?, true); + let unused_references = binding_usage + .connect() + .unused_references() + .to_resolved() .await?; - let unused_references = binding_usage.unused_references().to_resolved().await?; - module_graph = module_graph.without_unused_references(*binding_usage); + module_graph = module_graph.without_unused_references(binding_usage); let chunking_context: Vc> = match target { Target::Browser => { @@ -344,7 +346,7 @@ async fn build_internal( ) .source_maps(source_maps_type) .module_id_strategy(module_id_strategy) - .export_usage(Some(binding_usage)) + .export_usage(Some(binding_usage.connect().to_resolved().await?)) .unused_references(unused_references) .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript) .minify_type(minify_type); @@ -394,7 +396,7 @@ async fn build_internal( ) .source_maps(source_maps_type) .module_id_strategy(module_id_strategy) - .export_usage(Some(binding_usage)) + .export_usage(Some(binding_usage.connect().to_resolved().await?)) .unused_references(unused_references) .minify_type(minify_type); diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index 1d07833cbc347..2ceee86bf2726 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -16,8 +16,8 @@ use serde::{Deserialize, Serialize}; use tracing::{Instrument, Level, Span}; use turbo_rcstr::RcStr; use turbo_tasks::{ - CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt, - ValueToString, Vc, + CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TaskInput, + TryJoinIterExt, ValueToString, Vc, debug::ValueDebugFormat, graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow}, trace::TraceRawVcs, @@ -639,7 +639,7 @@ impl ImportTracer for ModuleGraphImportTracer { pub struct ModuleGraph { pub graphs: Vec>, - pub binding_usage: Option>, + pub binding_usage: Option>, } #[turbo_tasks::value_impl] @@ -763,7 +763,7 @@ impl ModuleGraph { #[turbo_tasks::function] pub async fn without_unused_references( self: ResolvedVc, - binding_usage: ResolvedVc, + binding_usage: OperationVc, ) -> Result> { Ok(Self { graphs: self.await?.graphs.clone(), @@ -782,7 +782,7 @@ impl ModuleGraph { skip_visited_module_children: false, graph_idx_override: None, binding_usage: if let Some(binding_usage) = this.binding_usage { - Some(binding_usage.await?) + Some(binding_usage.connect().await?) } else { None }, @@ -810,7 +810,7 @@ impl ModuleGraph { pub struct SingleModuleGraphWithBindingUsage { pub graph: ResolvedVc, pub graph_idx: u32, - pub binding_usage: Option>, + pub binding_usage: Option>, } impl SingleModuleGraphWithBindingUsage { @@ -820,7 +820,7 @@ impl SingleModuleGraphWithBindingUsage { skip_visited_module_children: true, graph_idx_override: Some(self.graph_idx), binding_usage: if let Some(binding_usage) = &self.binding_usage { - Some(binding_usage.await?) + Some(binding_usage.connect().await?) } else { None }, diff --git a/turbopack/crates/turbopack-tests/tests/execution.rs b/turbopack/crates/turbopack-tests/tests/execution.rs index 33a6fa9ac29d2..f147d5b3004ce 100644 --- a/turbopack/crates/turbopack-tests/tests/execution.rs +++ b/turbopack/crates/turbopack-tests/tests/execution.rs @@ -482,19 +482,15 @@ async fn run_test_operation(prepared_test: ResolvedVc) -> Result) -> Result Result> { ); let binding_usage = if options.remove_unused_imports || options.remove_unused_exports { - Some( - compute_binding_usage_info( - module_graph.to_resolved().await?, - options.remove_unused_imports, - ) - .resolve_strongly_consistent() - .await?, - ) + Some(compute_binding_usage_info( + module_graph.to_resolved().await?, + options.remove_unused_imports, + )) } else { None }; if options.remove_unused_imports { - module_graph = module_graph.without_unused_references(*binding_usage.unwrap()); + module_graph = module_graph.without_unused_references(binding_usage.unwrap()); } let chunk_root_path = project_path.join("output")?; @@ -477,11 +473,11 @@ async fn run_test_operation(resource: RcStr) -> Result> { ) .minify_type(options.minify_type) .module_merging(options.scope_hoisting) - .export_usage( - options - .remove_unused_exports - .then(|| binding_usage.unwrap()), - ) + .export_usage(if options.remove_unused_exports { + Some(binding_usage.unwrap().connect().to_resolved().await?) + } else { + None + }) .debug_ids(options.enable_debug_ids) .source_map_source_type(options.source_map_source_type); @@ -489,6 +485,7 @@ async fn run_test_operation(resource: RcStr) -> Result> { builder = builder.unused_references( binding_usage .unwrap() + .connect() .unused_references() .to_resolved() .await?, @@ -523,11 +520,11 @@ async fn run_test_operation(resource: RcStr) -> Result> { ) .minify_type(options.minify_type) .module_merging(options.scope_hoisting) - .export_usage( - options - .remove_unused_exports - .then(|| binding_usage.unwrap()), - ) + .export_usage(if options.remove_unused_exports { + Some(binding_usage.unwrap().connect().to_resolved().await?) + } else { + None + }) .debug_ids(options.enable_debug_ids) .source_map_source_type(options.source_map_source_type); @@ -535,6 +532,7 @@ async fn run_test_operation(resource: RcStr) -> Result> { builder = builder.unused_references( binding_usage .unwrap() + .connect() .unused_references() .to_resolved() .await?, From 624c0888083e5aac43c0d80aaaf55f1a23994580 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 9 Jan 2026 14:24:39 +0100 Subject: [PATCH 2/8] Turbopack: pass single module graph as operation to module graph --- crates/next-api/src/app.rs | 6 +- crates/next-api/src/client_references.rs | 7 +- crates/next-api/src/dynamic_imports.rs | 9 ++- crates/next-api/src/module_graph.rs | 6 +- crates/next-api/src/pages.rs | 2 +- crates/next-api/src/project.rs | 7 +- crates/next-api/src/server_actions.rs | 7 +- .../turbopack-core/src/module_graph/mod.rs | 75 ++++++++++--------- 8 files changed, 68 insertions(+), 51 deletions(-) diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index dc98407eb6797..13966cb739cbc 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -924,12 +924,12 @@ impl AppProject { // Only propagate the visited_modules of the parent layout(s), not // across siblings such as loading.js and // page.js. - visited_modules.concatenate(graph) + VisitedModules::concatenate(visited_modules, graph) } else { // Prevents graph index from getting out of sync. // TODO We should remove VisitedModule entirely in favor of lookups // in SingleModuleGraph - visited_modules.with_incremented_index() + VisitedModules::with_incremented_index(visited_modules) }; } visited_modules @@ -951,7 +951,7 @@ impl AppProject { should_read_binding_usage, ); graphs.push(graph); - visited_modules = visited_modules.concatenate(graph); + visited_modules = VisitedModules::concatenate(visited_modules, graph); let base = ModuleGraph::from_graphs(graphs.clone()); let additional_entries = endpoint.additional_entries(base); diff --git a/crates/next-api/src/client_references.rs b/crates/next-api/src/client_references.rs index f37d22296750f..125366c73540f 100644 --- a/crates/next-api/src/client_references.rs +++ b/crates/next-api/src/client_references.rs @@ -6,7 +6,8 @@ use next_core::{ }; use rustc_hash::FxHashMap; use turbo_tasks::{ - NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, + NonLocalValue, OperationVc, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, + trace::TraceRawVcs, }; use turbopack_core::{module::Module, module_graph::SingleModuleGraph}; use turbopack_css::chunk::CssChunkPlaceable; @@ -29,9 +30,9 @@ pub struct ClientReferenceData(FxHashMap>, ClientMani #[turbo_tasks::function] pub async fn map_client_references( - graph: Vc, + graph: OperationVc, ) -> Result> { - let graph = graph.await?; + let graph = graph.connect().await?; let manifest = graph .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/dynamic_imports.rs b/crates/next-api/src/dynamic_imports.rs index de4e53480d4ac..87f4ee8fad5f6 100644 --- a/crates/next-api/src/dynamic_imports.rs +++ b/crates/next-api/src/dynamic_imports.rs @@ -26,8 +26,8 @@ use next_core::{ next_dynamic::NextDynamicEntryModule, }; use turbo_tasks::{ - FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc, - debug::ValueDebugFormat, trace::TraceRawVcs, + FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TryFlatJoinIterExt, + TryJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, }; use turbopack_core::{ chunk::{ @@ -122,8 +122,11 @@ pub struct DynamicImportEntries( ); #[turbo_tasks::function] -pub async fn map_next_dynamic(graph: Vc) -> Result> { +pub async fn map_next_dynamic( + graph: OperationVc, +) -> Result> { let actions = graph + .connect() .await? .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/module_graph.rs b/crates/next-api/src/module_graph.rs index b7124c18a7e29..267f7d421db26 100644 --- a/crates/next-api/src/module_graph.rs +++ b/crates/next-api/src/module_graph.rs @@ -133,7 +133,7 @@ impl NextDynamicGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_next_dynamic(*graph.graph); + let mapped = map_next_dynamic(graph.graph); Ok(NextDynamicGraph { is_single_page, @@ -319,7 +319,7 @@ impl ServerActionsGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_server_actions(*graph.graph); + let mapped = map_server_actions(graph.graph); Ok(ServerActionsGraph { is_single_page, @@ -521,7 +521,7 @@ impl ClientReferencesGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_client_references(*graph.graph); + let mapped = map_client_references(graph.graph); Ok(Self { is_single_page, diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index 3a70bea063e33..879575a9c49df 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -736,7 +736,7 @@ impl PageEndpoint { should_read_binding_usage, ); graphs.push(graph); - visited_modules = visited_modules.concatenate(graph); + visited_modules = VisitedModules::concatenate(visited_modules, graph); } let graph = SingleModuleGraph::new_with_entries_visited_intern( diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index c735c75b03ec1..10855344e2a48 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -2102,7 +2102,7 @@ async fn whole_app_module_graph_operation( let should_trace = next_mode_ref.is_production(); let should_read_binding_usage = next_mode_ref.is_production(); let base_single_module_graph = SingleModuleGraph::new_with_entries( - project.get_all_entries(), + project.get_all_entries().to_resolved().await?, should_trace, should_read_binding_usage, ); @@ -2123,7 +2123,10 @@ async fn whole_app_module_graph_operation( base }; - let additional_entries = project.get_all_additional_entries(base); + let additional_entries = project + .get_all_additional_entries(base) + .to_resolved() + .await?; let additional_module_graph = SingleModuleGraph::new_with_entries_visited( additional_entries, diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index 189c8a2fd05cd..6ad484105e9ca 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -19,7 +19,7 @@ use swc_core::{ }, }; use turbo_rcstr::{RcStr, rcstr}; -use turbo_tasks::{FxIndexMap, ResolvedVc, TryFlatJoinIterExt, Vc}; +use turbo_tasks::{FxIndexMap, OperationVc, ResolvedVc, TryFlatJoinIterExt, Vc}; use turbo_tasks_fs::{self, File, FileContent, FileSystemPath, rope::RopeBuilder}; use turbopack_core::{ asset::AssetContent, @@ -453,8 +453,11 @@ pub struct AllModuleActions( ); #[turbo_tasks::function] -pub async fn map_server_actions(graph: Vc) -> Result> { +pub async fn map_server_actions( + graph: OperationVc, +) -> Result> { let actions = graph + .connect() .await? .iter_nodes() .map(async |module| { diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index 2ceee86bf2726..67a32e65305aa 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -126,7 +126,7 @@ pub struct VisitedModules { #[turbo_tasks::value_impl] impl VisitedModules { - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub fn empty() -> Vc { Self { modules: Default::default(), @@ -135,10 +135,11 @@ impl VisitedModules { .cell() } - #[turbo_tasks::function] - pub async fn from_graph(graph: Vc) -> Result> { + #[turbo_tasks::function(operation)] + pub async fn from_graph(graph: OperationVc) -> Result> { Ok(Self { modules: graph + .connect() .await? .enumerate_nodes() .flat_map(|(node_idx, module)| match module { @@ -157,19 +158,24 @@ impl VisitedModules { .cell()) } - #[turbo_tasks::function] - pub fn with_incremented_index(&self) -> Result> { + #[turbo_tasks::function(operation)] + pub async fn with_incremented_index(this: OperationVc) -> Result> { + let this = this.connect().await?; Ok(Self { - modules: self.modules.clone(), - next_graph_idx: self.next_graph_idx + 1, + modules: this.modules.clone(), + next_graph_idx: this.next_graph_idx + 1, } .cell()) } - #[turbo_tasks::function] - pub async fn concatenate(&self, graph: Vc) -> Result> { - let graph = graph.await?; - let iter = self + #[turbo_tasks::function(operation)] + pub async fn concatenate( + this: OperationVc, + graph: OperationVc, + ) -> Result> { + let graph = graph.connect().await?; + let this = this.connect().await?; + let iter = this .modules .iter() .map(|(module, idx)| (*module, *idx)) @@ -180,7 +186,7 @@ impl VisitedModules { SingleModuleGraphNode::Module(module) => Some(( *module, GraphNodeIndex { - graph_idx: self.next_graph_idx, + graph_idx: this.next_graph_idx, node_idx, }, )), @@ -189,7 +195,7 @@ impl VisitedModules { ); let mut map = FxIndexMap::with_capacity_and_hasher( - self.modules.len() + graph.number_of_modules, + this.modules.len() + graph.number_of_modules, Default::default(), ); for (k, v) in iter { @@ -199,7 +205,7 @@ impl VisitedModules { Ok(Self { modules: map, - next_graph_idx: self.next_graph_idx + 1, + next_graph_idx: this.next_graph_idx + 1, } .cell()) } @@ -637,7 +643,7 @@ impl ImportTracer for ModuleGraphImportTracer { #[turbo_tasks::value(shared)] #[derive(Clone, Default)] pub struct ModuleGraph { - pub graphs: Vec>, + pub graphs: Vec>, pub binding_usage: Option>, } @@ -645,7 +651,7 @@ pub struct ModuleGraph { #[turbo_tasks::value_impl] impl ModuleGraph { #[turbo_tasks::function] - pub fn from_graphs(graphs: Vec>) -> Vc { + pub fn from_graphs(graphs: Vec>) -> Vc { Self { graphs, binding_usage: None, @@ -654,7 +660,7 @@ impl ModuleGraph { } #[turbo_tasks::function] - pub fn from_single_graph(graph: ResolvedVc) -> Vc { + pub fn from_single_graph(graph: OperationVc) -> Vc { Self { graphs: vec![graph], binding_usage: None, @@ -669,7 +675,7 @@ impl ModuleGraph { include_binding_usage: bool, ) -> Vc { Self::from_single_graph(SingleModuleGraph::new_with_entries( - Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]), + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![module])]), include_traced, include_binding_usage, )) @@ -677,7 +683,7 @@ impl ModuleGraph { #[turbo_tasks::function] pub fn from_modules( - modules: Vc, + modules: ResolvedVc, include_traced: bool, include_binding_usage: bool, ) -> Vc { @@ -778,7 +784,7 @@ impl ModuleGraph { pub async fn read_graphs(self: Vc) -> Result { let this = self.await?; Ok(ModuleGraphRef { - graphs: this.graphs.iter().try_join().await?, + graphs: this.graphs.iter().map(|g| g.connect()).try_join().await?, skip_visited_module_children: false, graph_idx_override: None, binding_usage: if let Some(binding_usage) = this.binding_usage { @@ -808,7 +814,7 @@ impl ModuleGraph { Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode, )] pub struct SingleModuleGraphWithBindingUsage { - pub graph: ResolvedVc, + pub graph: OperationVc, pub graph_idx: u32, pub binding_usage: Option>, } @@ -816,7 +822,7 @@ pub struct SingleModuleGraphWithBindingUsage { impl SingleModuleGraphWithBindingUsage { pub async fn read(self: &SingleModuleGraphWithBindingUsage) -> Result { Ok(ModuleGraphRef { - graphs: vec![self.graph.await?], + graphs: vec![self.graph.connect().await?], skip_visited_module_children: true, graph_idx_override: Some(self.graph_idx), binding_usage: if let Some(binding_usage) = &self.binding_usage { @@ -1387,9 +1393,9 @@ impl ModuleGraphRef { #[turbo_tasks::value_impl] impl SingleModuleGraph { - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub async fn new_with_entries( - entries: Vc, + entries: ResolvedVc, include_traced: bool, include_binding_usage: bool, ) -> Result> { @@ -1402,33 +1408,33 @@ impl SingleModuleGraph { .await } - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub async fn new_with_entries_visited( - entries: Vc, - visited_modules: Vc, + entries: ResolvedVc, + visited_modules: OperationVc, include_traced: bool, include_binding_usage: bool, ) -> Result> { SingleModuleGraph::new_inner( &*entries.await?, - &visited_modules.await?.modules, + &visited_modules.connect().await?.modules, include_traced, include_binding_usage, ) .await } - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub async fn new_with_entries_visited_intern( // This must not be a Vc> to ensure layout segment optimization hits the cache entries: GraphEntriesT, - visited_modules: Vc, + visited_modules: OperationVc, include_traced: bool, include_binding_usage: bool, ) -> Result> { SingleModuleGraph::new_inner( &entries, - &visited_modules.await?.modules, + &visited_modules.connect().await?.modules, include_traced, include_binding_usage, ) @@ -1999,7 +2005,7 @@ pub mod tests { let b_module = make_module("b.js").await?; let parent_graph = SingleModuleGraph::new_with_entries( - GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(vec![b_module])])), + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]), false, false, ); @@ -2007,7 +2013,7 @@ pub mod tests { let module_graph = ModuleGraph::from_graphs(vec![ parent_graph, SingleModuleGraph::new_with_entries_visited( - GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(vec![a_module])])), + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]), VisitedModules::from_graph(parent_graph), false, false, @@ -2236,7 +2242,7 @@ pub mod tests { .try_join() .await?; let graph = SingleModuleGraph::new_with_entries( - GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry( + GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry( entry_modules.clone(), )])), false, @@ -2247,6 +2253,7 @@ pub mod tests { // Technically they could always pull this name off of the // `module.ident().await?.path.path` themselves but you cannot `await` in visitors. let module_to_name = graph + .connect() .await? .modules .keys() From 74ad367a2658da5540f56d2862f3e82b67bbedb6 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 9 Jan 2026 15:11:07 +0100 Subject: [PATCH 3/8] avoid pub fields for SingleModuleGraphWithBindingUsage --- crates/next-api/src/client_references.rs | 9 ++++----- crates/next-api/src/dynamic_imports.rs | 10 +++++----- crates/next-api/src/module_graph.rs | 6 +++--- crates/next-api/src/server_actions.rs | 10 ++++++---- .../crates/turbopack-core/src/module_graph/mod.rs | 12 ++++++++---- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/crates/next-api/src/client_references.rs b/crates/next-api/src/client_references.rs index 125366c73540f..7707d52217e41 100644 --- a/crates/next-api/src/client_references.rs +++ b/crates/next-api/src/client_references.rs @@ -6,10 +6,9 @@ use next_core::{ }; use rustc_hash::FxHashMap; use turbo_tasks::{ - NonLocalValue, OperationVc, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, - trace::TraceRawVcs, + NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, }; -use turbopack_core::{module::Module, module_graph::SingleModuleGraph}; +use turbopack_core::{module::Module, module_graph::SingleModuleGraphWithBindingUsage}; use turbopack_css::chunk::CssChunkPlaceable; #[derive( @@ -30,9 +29,9 @@ pub struct ClientReferenceData(FxHashMap>, ClientMani #[turbo_tasks::function] pub async fn map_client_references( - graph: OperationVc, + graph: SingleModuleGraphWithBindingUsage, ) -> Result> { - let graph = graph.connect().await?; + let graph = graph.read().await?; let manifest = graph .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/dynamic_imports.rs b/crates/next-api/src/dynamic_imports.rs index 87f4ee8fad5f6..4cd61acc0c89c 100644 --- a/crates/next-api/src/dynamic_imports.rs +++ b/crates/next-api/src/dynamic_imports.rs @@ -26,8 +26,8 @@ use next_core::{ next_dynamic::NextDynamicEntryModule, }; use turbo_tasks::{ - FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TryFlatJoinIterExt, - TryJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, + FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc, + debug::ValueDebugFormat, trace::TraceRawVcs, }; use turbopack_core::{ chunk::{ @@ -35,7 +35,7 @@ use turbopack_core::{ availability_info::AvailabilityInfo, }, module::Module, - module_graph::{ModuleGraph, SingleModuleGraph}, + module_graph::{ModuleGraph, SingleModuleGraphWithBindingUsage}, output::{OutputAssetsReference, OutputAssetsWithReferenced}, }; @@ -123,10 +123,10 @@ pub struct DynamicImportEntries( #[turbo_tasks::function] pub async fn map_next_dynamic( - graph: OperationVc, + graph: SingleModuleGraphWithBindingUsage, ) -> Result> { let actions = graph - .connect() + .read() .await? .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/module_graph.rs b/crates/next-api/src/module_graph.rs index 267f7d421db26..f73e1d7819cd3 100644 --- a/crates/next-api/src/module_graph.rs +++ b/crates/next-api/src/module_graph.rs @@ -133,7 +133,7 @@ impl NextDynamicGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_next_dynamic(graph.graph); + let mapped = map_next_dynamic(graph); Ok(NextDynamicGraph { is_single_page, @@ -319,7 +319,7 @@ impl ServerActionsGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_server_actions(graph.graph); + let mapped = map_server_actions(graph); Ok(ServerActionsGraph { is_single_page, @@ -521,7 +521,7 @@ impl ClientReferencesGraph { graph: SingleModuleGraphWithBindingUsage, is_single_page: bool, ) -> Result> { - let mapped = map_client_references(graph.graph); + let mapped = map_client_references(graph); Ok(Self { is_single_page, diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index 6ad484105e9ca..64381e0b74be1 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -19,7 +19,7 @@ use swc_core::{ }, }; use turbo_rcstr::{RcStr, rcstr}; -use turbo_tasks::{FxIndexMap, OperationVc, ResolvedVc, TryFlatJoinIterExt, Vc}; +use turbo_tasks::{FxIndexMap, ResolvedVc, TryFlatJoinIterExt, Vc}; use turbo_tasks_fs::{self, File, FileContent, FileSystemPath, rope::RopeBuilder}; use turbopack_core::{ asset::AssetContent, @@ -30,7 +30,9 @@ use turbopack_core::{ file_source::FileSource, ident::AssetIdent, module::Module, - module_graph::{ModuleGraph, SingleModuleGraph, async_module_info::AsyncModulesInfo}, + module_graph::{ + ModuleGraph, SingleModuleGraphWithBindingUsage, async_module_info::AsyncModulesInfo, + }, output::OutputAsset, reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType}, resolve::ModulePart, @@ -454,10 +456,10 @@ pub struct AllModuleActions( #[turbo_tasks::function] pub async fn map_server_actions( - graph: OperationVc, + graph: SingleModuleGraphWithBindingUsage, ) -> Result> { let actions = graph - .connect() + .read() .await? .iter_nodes() .map(async |module| { diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index 67a32e65305aa..b0c8a6d2404d2 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -811,12 +811,12 @@ impl ModuleGraph { } #[derive( - Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode, + Clone, Copy, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode, )] pub struct SingleModuleGraphWithBindingUsage { - pub graph: OperationVc, - pub graph_idx: u32, - pub binding_usage: Option>, + graph: OperationVc, + graph_idx: u32, + binding_usage: Option>, } impl SingleModuleGraphWithBindingUsage { @@ -911,6 +911,10 @@ impl ModuleGraphRef { self.graphs.iter().flat_map(|g| g.enumerate_nodes()) } + pub fn iter_nodes(&self) -> impl Iterator>> + '_ { + self.graphs.iter().flat_map(|g| g.iter_nodes()) + } + /// Iterate the edges of a node REVERSED! fn iter_graphs_neighbors_rev<'a>( &'a self, From fb2d37d2f416fb75eeae52ac21a29bc50d6bb108 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 9 Jan 2026 16:02:27 +0100 Subject: [PATCH 4/8] Turbopack: snapshot the ModuleGraph to avoid (eventual) inconsistent graphs --- crates/next-api/src/analyze.rs | 2 +- crates/next-api/src/client_references.rs | 6 +- crates/next-api/src/dynamic_imports.rs | 7 +- crates/next-api/src/module_graph.rs | 64 ++--- crates/next-api/src/routes_hashes_manifest.rs | 2 +- crates/next-api/src/server_actions.rs | 9 +- crates/next-api/src/webpack_stats.rs | 2 +- .../src/module_graph/async_module_info.rs | 12 +- .../src/module_graph/binding_usage_info.rs | 2 +- .../src/module_graph/chunk_group_info.rs | 4 +- .../src/module_graph/merged_modules.rs | 2 +- .../turbopack-core/src/module_graph/mod.rs | 218 +++++++++--------- .../src/module_graph/module_batches.rs | 6 +- .../module_graph/side_effect_module_info.rs | 12 +- .../crates/turbopack/src/global_module_ids.rs | 2 +- 15 files changed, 179 insertions(+), 171 deletions(-) diff --git a/crates/next-api/src/analyze.rs b/crates/next-api/src/analyze.rs index fd617edb1cfc3..1609eeaf91a25 100644 --- a/crates/next-api/src/analyze.rs +++ b/crates/next-api/src/analyze.rs @@ -425,7 +425,7 @@ pub async fn analyze_module_graphs(module_graphs: Vc) -> Result>, ClientMani #[turbo_tasks::function] pub async fn map_client_references( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, ) -> Result> { - let graph = graph.read().await?; + let graph = graph.await?; let manifest = graph .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/dynamic_imports.rs b/crates/next-api/src/dynamic_imports.rs index 4cd61acc0c89c..0ce73a946d621 100644 --- a/crates/next-api/src/dynamic_imports.rs +++ b/crates/next-api/src/dynamic_imports.rs @@ -35,7 +35,7 @@ use turbopack_core::{ availability_info::AvailabilityInfo, }, module::Module, - module_graph::{ModuleGraph, SingleModuleGraphWithBindingUsage}, + module_graph::ModuleGraph, output::{OutputAssetsReference, OutputAssetsWithReferenced}, }; @@ -122,11 +122,8 @@ pub struct DynamicImportEntries( ); #[turbo_tasks::function] -pub async fn map_next_dynamic( - graph: SingleModuleGraphWithBindingUsage, -) -> Result> { +pub async fn map_next_dynamic(graph: ResolvedVc) -> Result> { let actions = graph - .read() .await? .iter_nodes() .map(|module| async move { diff --git a/crates/next-api/src/module_graph.rs b/crates/next-api/src/module_graph.rs index f73e1d7819cd3..db402bd5ddfb8 100644 --- a/crates/next-api/src/module_graph.rs +++ b/crates/next-api/src/module_graph.rs @@ -23,7 +23,7 @@ use turbopack_core::{ context::AssetContext, issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString}, module::Module, - module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraphWithBindingUsage}, + module_graph::{GraphTraversalAction, ModuleGraph}, }; use turbopack_css::{CssModuleAsset, ModuleCssAsset}; @@ -35,7 +35,7 @@ use crate::{ #[turbo_tasks::value] pub struct NextDynamicGraph { - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, /// list of NextDynamicEntryModules @@ -52,12 +52,13 @@ impl NextDynamicGraphs { graphs: ResolvedVc, is_single_page: bool, ) -> Result> { - let graphs_ref = &graphs.await?; + let graphs_ref = &graphs.iter_graphs().await?; let next_dynamic = async { graphs_ref - .iter_graphs() + .iter() .map(|graph| { - NextDynamicGraph::new_with_entries(graph, is_single_page).to_resolved() + NextDynamicGraph::new_with_entries(graph.connect(), is_single_page) + .to_resolved() }) .try_join() .await @@ -130,10 +131,10 @@ pub struct DynamicImportEntriesWithImporter( impl NextDynamicGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { - let mapped = map_next_dynamic(graph); + let mapped = map_next_dynamic(*graph); Ok(NextDynamicGraph { is_single_page, @@ -151,7 +152,7 @@ impl NextDynamicGraph { let span = tracing::info_span!("collect next/dynamic imports for endpoint"); async move { let data = &*self.data.await?; - let graph = self.graph.read().await?; + let graph = self.graph.await?; #[derive(Clone, PartialEq, Eq)] enum VisitState { @@ -232,7 +233,7 @@ impl NextDynamicGraph { #[turbo_tasks::value] pub struct ServerActionsGraph { - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, /// (Layer, RSC or Browser module) -> list of actions @@ -249,12 +250,13 @@ impl ServerActionsGraphs { graphs: ResolvedVc, is_single_page: bool, ) -> Result> { - let graphs_ref = &graphs.await?; + let graphs_ref = &graphs.iter_graphs().await?; let server_actions = async { graphs_ref - .iter_graphs() + .iter() .map(|graph| { - ServerActionsGraph::new_with_entries(graph, is_single_page).to_resolved() + ServerActionsGraph::new_with_entries(graph.connect(), is_single_page) + .to_resolved() }) .try_join() .await @@ -316,10 +318,10 @@ impl ServerActionsGraphs { impl ServerActionsGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { - let mapped = map_server_actions(graph); + let mapped = map_server_actions(*graph); Ok(ServerActionsGraph { is_single_page, @@ -343,7 +345,7 @@ impl ServerActionsGraph { Cow::Borrowed(data) } else { // The graph contains the whole app, traverse and collect all reachable imports. - let graph = self.graph.read().await?; + let graph = self.graph.await?; if !graph.graphs.first().unwrap().has_entry_module(entry) { // the graph doesn't contain the entry, e.g. for the additional module graph @@ -407,7 +409,7 @@ impl ServerActionsGraph { #[turbo_tasks::value] pub struct ClientReferencesGraph { is_single_page: bool, - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, /// List of client references (modules that entries into the client graph) data: ResolvedVc, @@ -423,12 +425,13 @@ impl ClientReferencesGraphs { graphs: ResolvedVc, is_single_page: bool, ) -> Result> { - let graphs_ref = &graphs.await?; + let graphs_ref = graphs.iter_graphs().await?; let client_references = async { graphs_ref - .iter_graphs() + .iter() .map(|graph| { - ClientReferencesGraph::new_with_entries(graph, is_single_page).to_resolved() + ClientReferencesGraph::new_with_entries(graph.connect(), is_single_page) + .to_resolved() }) .try_join() .await @@ -518,10 +521,10 @@ impl ClientReferencesGraphs { impl ClientReferencesGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { - let mapped = map_client_references(graph); + let mapped = map_client_references(*graph); Ok(Self { is_single_page, @@ -539,7 +542,7 @@ impl ClientReferencesGraph { let span = tracing::info_span!("collect client references for endpoint"); async move { let data = &*self.data.await?; - let graph = self.graph.read().await?; + let graph = self.graph.await?; let entries = if !self.is_single_page { if !graph.graphs.first().unwrap().has_entry_module(entry) { @@ -766,12 +769,12 @@ struct ModuleNameMap(#[bincode(with = "turbo_bincode::indexmap")] pub FxModuleNa #[tracing::instrument(level = "info", name = "validate pages css imports", skip_all)] #[turbo_tasks::function] async fn validate_pages_css_imports_individual( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, is_single_page: bool, entry: Vc>, app_module: ResolvedVc>, ) -> Result<()> { - let graph = graph.read().await?; + let graph = graph.await?; let entry = entry.to_resolved().await?; let entries = if !is_single_page { @@ -869,12 +872,17 @@ pub async fn validate_pages_css_imports( entry: Vc>, app_module: Vc>, ) -> Result<()> { - let graphs = &graph.await?; + let graphs = graph.iter_graphs().await?; graphs - .iter_graphs() + .iter() .map(|graph| { - validate_pages_css_imports_individual(graph, is_single_page, entry, app_module) - .as_side_effect() + validate_pages_css_imports_individual( + graph.connect(), + is_single_page, + entry, + app_module, + ) + .as_side_effect() }) .try_join() .await?; diff --git a/crates/next-api/src/routes_hashes_manifest.rs b/crates/next-api/src/routes_hashes_manifest.rs index 87597db9a742c..95f28f7b01b53 100644 --- a/crates/next-api/src/routes_hashes_manifest.rs +++ b/crates/next-api/src/routes_hashes_manifest.rs @@ -62,7 +62,7 @@ pub async fn endpoint_hashes( let mut all_modules = FxIndexSet::default(); - let module_graph = module_graph.read_graphs().await?; + let module_graph = module_graph.await?; module_graph.traverse_nodes_dfs( modules, diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index 64381e0b74be1..5fbdd688646cd 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -30,9 +30,7 @@ use turbopack_core::{ file_source::FileSource, ident::AssetIdent, module::Module, - module_graph::{ - ModuleGraph, SingleModuleGraphWithBindingUsage, async_module_info::AsyncModulesInfo, - }, + module_graph::{ModuleGraph, async_module_info::AsyncModulesInfo}, output::OutputAsset, reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType}, resolve::ModulePart, @@ -455,11 +453,8 @@ pub struct AllModuleActions( ); #[turbo_tasks::function] -pub async fn map_server_actions( - graph: SingleModuleGraphWithBindingUsage, -) -> Result> { +pub async fn map_server_actions(graph: ResolvedVc) -> Result> { let actions = graph - .read() .await? .iter_nodes() .map(async |module| { diff --git a/crates/next-api/src/webpack_stats.rs b/crates/next-api/src/webpack_stats.rs index bed5c32e4ad9e..12385ee987691 100644 --- a/crates/next-api/src/webpack_stats.rs +++ b/crates/next-api/src/webpack_stats.rs @@ -56,7 +56,7 @@ where }; let asset_reasons = { - let module_graph = module_graph.read_graphs().await?; + let module_graph = module_graph.await?; let mut edges = vec![]; module_graph.traverse_edges_unordered(|parent, current| { if let Some((parent_node, r)) = parent { diff --git a/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs b/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs index 881fbda7bdd6c..45ca5b11dff8e 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs @@ -4,7 +4,7 @@ use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, Vc}; use crate::{ module::{Module, Modules}, - module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraphWithBindingUsage}, + module_graph::{GraphTraversalAction, ModuleGraph}, }; #[turbo_tasks::value(transparent)] @@ -41,21 +41,19 @@ pub async fn compute_async_module_info( ) -> Result> { // Layout segment optimization, we can individually compute the async modules for each graph. let mut result: Vc = Vc::cell(Default::default()); - let graphs = graphs.await?; - for graph in graphs.iter_graphs() { - result = compute_async_module_info_single(graph, result); + for graph in graphs.iter_graphs().await? { + result = compute_async_module_info_single(graph.connect(), result); } Ok(result) } #[turbo_tasks::function] async fn compute_async_module_info_single( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, parent_async_modules: Vc, ) -> Result> { let parent_async_modules = parent_async_modules.await?; - let graph = graph.read().await?; - + let graph = graph.await?; let self_async_modules = graph .enumerate_nodes() .map(async |(_, node)| { diff --git a/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs b/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs index e86386a6c5dde..abe61a2c09dca 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs @@ -128,7 +128,7 @@ pub async fn compute_binding_usage_info( without_unused_references" ); } - let graph_ref = graph.read_graphs().await?; + let graph_ref = graph.await?; let side_effect_free_modules = if remove_unused_imports { let side_effect_free_modules = compute_side_effect_free_module_info(*graph).await?; span.record("side_effect_free_modules", side_effect_free_modules.len()); diff --git a/turbopack/crates/turbopack-core/src/module_graph/chunk_group_info.rs b/turbopack/crates/turbopack-core/src/module_graph/chunk_group_info.rs index a37e2886f0b15..231e93a7a9931 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/chunk_group_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/chunk_group_info.rs @@ -19,7 +19,7 @@ use turbo_tasks::{ use crate::{ chunk::ChunkingType, module::Module, - module_graph::{GraphTraversalAction, ModuleGraphRef, RefData}, + module_graph::{GraphTraversalAction, ModuleGraph, RefData}, }; #[derive(Clone, Debug, Default, PartialEq, TraceRawVcs, ValueDebugFormat, Encode, Decode)] @@ -372,7 +372,7 @@ impl Ord for TraversalPriority { } } -pub async fn compute_chunk_group_info(graph: &ModuleGraphRef) -> Result> { +pub async fn compute_chunk_group_info(graph: &ModuleGraph) -> Result> { let span_outer = tracing::info_span!( "compute chunk group info", module_count = tracing::field::Empty, diff --git a/turbopack/crates/turbopack-core/src/module_graph/merged_modules.rs b/turbopack/crates/turbopack-core/src/module_graph/merged_modules.rs index 0dfe2f9e8541a..9c8d25e489bb2 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/merged_modules.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/merged_modules.rs @@ -74,7 +74,7 @@ pub async fn compute_merged_modules(module_graph: Vc) -> Result(); diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index b0c8a6d2404d2..e4f6b2eb22b31 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize}; use tracing::{Instrument, Level, Span}; use turbo_rcstr::RcStr; use turbo_tasks::{ - CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TaskInput, + CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, debug::ValueDebugFormat, graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow}, @@ -640,32 +640,56 @@ impl ImportTracer for ModuleGraphImportTracer { } } -#[turbo_tasks::value(shared)] +/// The ReadRef version of ModuleGraphBase. This is better for eventual consistency, as the graphs +/// aren't awaited multiple times within the same task. +#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")] #[derive(Clone, Default)] pub struct ModuleGraph { - pub graphs: Vec>, + input_graphs: Vec>, + input_binding_usage: Option>, + + pub graphs: Vec>, + // Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single + // module graph usecases, this is what you want. For the whole graph, there should be an error. + skip_visited_module_children: bool, - pub binding_usage: Option>, + pub graph_idx_override: Option, + + pub binding_usage: Option>, } #[turbo_tasks::value_impl] impl ModuleGraph { #[turbo_tasks::function] - pub fn from_graphs(graphs: Vec>) -> Vc { - Self { - graphs, - binding_usage: None, - } - .cell() + pub async fn from_single_graph(graph: OperationVc) -> Result> { + let graph = Self::create(vec![graph], None) + .read_strongly_consistent() + .await?; + Ok(ReadRef::cell(graph)) } #[turbo_tasks::function] - pub fn from_single_graph(graph: OperationVc) -> Vc { - Self { - graphs: vec![graph], - binding_usage: None, - } - .cell() + pub async fn from_graphs(graphs: Vec>) -> Result> { + let graph = Self::create(graphs, None) + .read_strongly_consistent() + .await?; + Ok(ReadRef::cell(graph)) + } + + /// Analyze the module graph and remove unused references (by determining the used exports and + /// removing unused imports). + /// + /// In particular, this removes ChunkableModuleReference-s that list only unused exports in the + /// `import_usage()` + #[turbo_tasks::function] + pub async fn from_graphs_without_unused_references( + graphs: Vec>, + binding_usage: OperationVc, + ) -> Result> { + let graph = Self::create(graphs, Some(binding_usage)) + .read_strongly_consistent() + .await?; + Ok(ReadRef::cell(graph)) } #[turbo_tasks::function] @@ -694,9 +718,50 @@ impl ModuleGraph { )) } + #[turbo_tasks::function(operation)] + async fn create( + graphs: Vec>, + binding_usage: Option>, + ) -> Result> { + Ok(ModuleGraph { + input_graphs: graphs.clone(), + input_binding_usage: binding_usage, + graphs: graphs.iter().map(|g| g.connect()).try_join().await?, + skip_visited_module_children: false, + graph_idx_override: None, + binding_usage: if let Some(binding_usage) = binding_usage { + Some(binding_usage.connect().await?) + } else { + None + }, + } + .cell()) + } + + #[turbo_tasks::function(operation)] + async fn create_layer( + graph: OperationVc, + graph_idx: u32, + binding_usage: Option>, + ) -> Result> { + Ok(Self { + input_graphs: vec![graph.clone()], + input_binding_usage: binding_usage, + graphs: vec![graph.connect().await?], + skip_visited_module_children: true, + graph_idx_override: Some(graph_idx), + binding_usage: if let Some(binding_usage) = binding_usage { + Some(binding_usage.connect().await?) + } else { + None + }, + } + .cell()) + } + #[turbo_tasks::function] pub async fn chunk_group_info(self: Vc) -> Result> { - compute_chunk_group_info(&self.read_graphs().await?).await + compute_chunk_group_info(&*self.await?).await } #[turbo_tasks::function] @@ -740,7 +805,7 @@ impl ModuleGraph { self: Vc, module: ResolvedVc>, ) -> Result> { - let graph_ref = self.read_graphs().await?; + let graph_ref = self.await?; let async_modules_info = self.async_module_info().await?; let entry = graph_ref.get_entry(module)?; @@ -761,93 +826,34 @@ impl ModuleGraph { Ok(AsyncModuleInfo::new(referenced_modules)) } - /// Analyze the module graph and remove unused references (by determining the used exports and - /// removing unused imports). - /// - /// In particular, this removes ChunkableModuleReference-s that list only unused exports in the - /// `import_usage()` + // TODO remove this method and use `from_graphs_without_unused_references` directly #[turbo_tasks::function] - pub async fn without_unused_references( - self: ResolvedVc, + pub fn without_unused_references( + &self, binding_usage: OperationVc, - ) -> Result> { - Ok(Self { - graphs: self.await?.graphs.clone(), - binding_usage: Some(binding_usage), - } - .cell()) - } -} - -impl ModuleGraph { - /// Reads the ModuleGraph into a ModuleGraphRef, awaiting all underlying graphs. - pub async fn read_graphs(self: Vc) -> Result { - let this = self.await?; - Ok(ModuleGraphRef { - graphs: this.graphs.iter().map(|g| g.connect()).try_join().await?, - skip_visited_module_children: false, - graph_idx_override: None, - binding_usage: if let Some(binding_usage) = this.binding_usage { - Some(binding_usage.connect().await?) - } else { - None - }, - }) + ) -> Vc { + Self::from_graphs_without_unused_references(self.input_graphs.clone(), binding_usage) } /// Returns the underlying graphs as a list, to be used for individual graph traversals. - pub fn iter_graphs( - self: &ModuleGraph, - ) -> impl Iterator { - self.graphs - .iter() - .enumerate() - .map(|(graph_idx, graph)| SingleModuleGraphWithBindingUsage { - graph: *graph, - graph_idx: graph_idx as u32, - binding_usage: self.binding_usage, - }) - } -} - -#[derive( - Clone, Copy, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode, -)] -pub struct SingleModuleGraphWithBindingUsage { - graph: OperationVc, - graph_idx: u32, - binding_usage: Option>, -} - -impl SingleModuleGraphWithBindingUsage { - pub async fn read(self: &SingleModuleGraphWithBindingUsage) -> Result { - Ok(ModuleGraphRef { - graphs: vec![self.graph.connect().await?], - skip_visited_module_children: true, - graph_idx_override: Some(self.graph_idx), - binding_usage: if let Some(binding_usage) = &self.binding_usage { - Some(binding_usage.connect().await?) - } else { - None - }, - }) + #[turbo_tasks::function] + pub fn iter_graphs(&self) -> Vc { + Vc::cell( + self.input_graphs + .iter() + .enumerate() + .map(|(graph_idx, graph)| { + ModuleGraph::create_layer(*graph, graph_idx as u32, self.input_binding_usage) + }) + .collect(), + ) } } -/// The ReadRef version of ModuleGraph. This is better for eventual consistency, as the graphs -/// aren't awaited multiple times within the same task. -pub struct ModuleGraphRef { - pub graphs: Vec>, - // Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single - // module graph usecases, this is what you want. For the whole graph, there should be an error. - skip_visited_module_children: bool, - - pub graph_idx_override: Option, - - pub binding_usage: Option>, -} +#[turbo_tasks::value(transparent)] +pub struct ModuleGraphLayers(Vec>); -impl ModuleGraphRef { +impl ModuleGraph { fn get_entry(&self, entry: ResolvedVc>) -> Result { if self.graph_idx_override.is_some() { debug_assert_eq!(self.graphs.len(), 1,); @@ -1681,8 +1687,8 @@ pub mod tests { ident::AssetIdent, module::{Module, ModuleSideEffects}, module_graph::{ - GraphEntries, GraphTraversalAction, ModuleGraph, ModuleGraphRef, SingleModuleGraph, - VisitedModules, chunk_group_info::ChunkGroupEntry, + GraphEntries, GraphTraversalAction, ModuleGraph, SingleModuleGraph, VisitedModules, + chunk_group_info::ChunkGroupEntry, }, reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference}, resolve::ExportUsage, @@ -2022,9 +2028,15 @@ pub mod tests { false, false, ), - ]) - .await?; - let child_graph = module_graph.iter_graphs().nth(1).unwrap().read().await?; + ]); + let child_graph = module_graph + .iter_graphs() + .await? + .iter() + .nth(1) + .unwrap() + .connect() + .await?; // test traversing forward from a in the child graph { let mut visited_forward = Vec::new(); @@ -2210,7 +2222,7 @@ pub mod tests { entries: Vec, graph: FxHashMap>, test_fn: impl FnOnce( - ModuleGraphRef, + &ModuleGraph, Vec>>, FxHashMap>, RcStr>, ) -> Result<()> @@ -2267,7 +2279,7 @@ pub mod tests { .into_iter() .collect(); test_fn( - ModuleGraph::from_single_graph(graph).read_graphs().await?, + &*ModuleGraph::from_single_graph(graph).await?, entry_modules, module_to_name, ) diff --git a/turbopack/crates/turbopack-core/src/module_graph/module_batches.rs b/turbopack/crates/turbopack-core/src/module_graph/module_batches.rs index 686ce61f9d208..708cf74c55304 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/module_batches.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/module_batches.rs @@ -21,7 +21,7 @@ use crate::{ chunk::{ChunkableModule, ChunkingType}, module::Module, module_graph::{ - GraphTraversalAction, ModuleGraph, ModuleGraphRef, + GraphTraversalAction, ModuleGraph, chunk_group_info::{ChunkGroupInfo, ChunkGroupKey, RoaringBitmapWrapper}, module_batch::{ModuleBatch, ModuleBatchGroup, ModuleOrBatch}, traced_di_graph::{TracedDiGraph, iter_neighbors_rev}, @@ -281,7 +281,7 @@ impl PreBatches { &mut self, entry: ResolvedVc>, chunk_group_info: &ChunkGroupInfo, - module_graph: &ModuleGraphRef, + module_graph: &ModuleGraph, queue: &mut VecDeque<(ResolvedVc>, PreBatchIndex)>, ) -> Result> { let mut state = TraversalState { @@ -349,7 +349,7 @@ pub async fn compute_module_batches( let span = outer_span.clone(); async move { let chunk_group_info = module_graph.chunk_group_info().await?; - let module_graph = module_graph.read_graphs().await?; + let module_graph = module_graph.await?; let mut pre_batches = PreBatches::new(); diff --git a/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs b/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs index b5d267d9cc0b4..00801530884d6 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs @@ -4,7 +4,7 @@ use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; use crate::{ module::{Module, ModuleSideEffects}, - module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraphWithBindingUsage}, + module_graph::{GraphTraversalAction, ModuleGraph}, }; /// This lists all the modules that are side effect free @@ -29,21 +29,19 @@ pub async fn compute_side_effect_free_module_info( // Layout segment optimization, we can individually compute the side effect free modules for // each graph. let mut result: Vc = Vc::cell(Default::default()); - let graphs = graphs.await?; - for graph in graphs.iter_graphs() { - result = compute_side_effect_free_module_info_single(graph, result); + for graph in graphs.iter_graphs().await? { + result = compute_side_effect_free_module_info_single(graph.connect(), result); } Ok(result) } #[turbo_tasks::function] async fn compute_side_effect_free_module_info_single( - graph: SingleModuleGraphWithBindingUsage, + graph: ResolvedVc, parent_side_effect_free_modules: Vc, ) -> Result> { let parent_side_effect_free_modules = parent_side_effect_free_modules.await?; - let graph = graph.read().await?; - + let graph = graph.await?; let module_side_effects = graph .enumerate_nodes() .map(async |(_, node)| { diff --git a/turbopack/crates/turbopack/src/global_module_ids.rs b/turbopack/crates/turbopack/src/global_module_ids.rs index d800e5eca15ac..f1b53ea6a7e0f 100644 --- a/turbopack/crates/turbopack/src/global_module_ids.rs +++ b/turbopack/crates/turbopack/src/global_module_ids.rs @@ -19,7 +19,7 @@ pub async fn get_global_module_id_strategy( ) -> Result> { let span = tracing::info_span!("compute module id map"); async move { - let module_graph = module_graph.read_graphs().await?; + let module_graph = module_graph.await?; let graphs = &module_graph.graphs; // All modules in the graph From 2d8489412c9c9b524007ff7630dfda6855896fa3 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 9 Jan 2026 18:49:00 +0100 Subject: [PATCH 5/8] cleanup --- crates/next-api/src/app.rs | 35 ++++---- crates/next-api/src/pages.rs | 22 ++--- crates/next-api/src/project.rs | 71 ++++++++-------- crates/next-core/src/next_font/google/mod.rs | 9 +- .../crates/turbopack-cli/src/build/mod.rs | 23 ++--- .../turbopack-cli/src/dev/web_entry_source.rs | 11 ++- .../src/module_graph/binding_usage_info.rs | 11 +-- .../turbopack-core/src/module_graph/mod.rs | 85 +++++++++---------- .../turbopack-node/src/transforms/postcss.rs | 13 ++- .../turbopack-node/src/transforms/webpack.rs | 13 ++- .../crates/turbopack-tests/tests/execution.rs | 21 +++-- .../crates/turbopack-tests/tests/snapshot.rs | 17 ++-- 12 files changed, 179 insertions(+), 152 deletions(-) diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index 13966cb739cbc..a11692e9556ee 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -954,7 +954,7 @@ impl AppProject { visited_modules = VisitedModules::concatenate(visited_modules, graph); let base = ModuleGraph::from_graphs(graphs.clone()); - let additional_entries = endpoint.additional_entries(base); + let additional_entries = endpoint.additional_entries(base.connect()); let additional_module_graph = SingleModuleGraph::new_with_entries_visited_intern( additional_entries.owned().await?, visited_modules, @@ -963,30 +963,29 @@ impl AppProject { ); graphs.push(additional_module_graph); - let full_with_unused_references = - ModuleGraph::from_graphs(graphs).to_resolved().await?; - - let full = if *self + let remove_unused_imports = *self .project .next_config() .turbopack_remove_unused_imports(next_mode) - .await? - { - full_with_unused_references - .without_unused_references(compute_binding_usage_info( - full_with_unused_references, - true, - )) - .to_resolved() - .await? + .await?; + + let binding_usage_info = remove_unused_imports.then(|| { + compute_binding_usage_info( + ModuleGraph::from_graphs(graphs.clone()), + should_read_binding_usage, + ) + }); + + let full = if let Some(binding_usage_info) = binding_usage_info { + ModuleGraph::from_graphs_without_unused_references(graphs, binding_usage_info) } else { - full_with_unused_references + ModuleGraph::from_graphs(graphs) }; Ok(BaseAndFullModuleGraph { - base: base.to_resolved().await?, - full_with_unused_references, - full, + base: base.connect().to_resolved().await?, + full: full.connect().to_resolved().await?, + binding_usage_info, } .cell()) } diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index 879575a9c49df..d27f4c701269d 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -747,20 +747,20 @@ impl PageEndpoint { ); graphs.push(graph); - let mut graph = ModuleGraph::from_graphs(graphs); - - if *project + let remove_unused_imports = *project .next_config() .turbopack_remove_unused_imports(next_mode) - .await? - { - graph = graph.without_unused_references(compute_binding_usage_info( - graph.to_resolved().await?, - true, - )); - } + .await?; + + let graph = if remove_unused_imports { + let graph = ModuleGraph::from_graphs(graphs.clone()); + let binding_usage_info = compute_binding_usage_info(graph, true); + ModuleGraph::from_graphs_without_unused_references(graphs, binding_usage_info) + } else { + ModuleGraph::from_graphs(graphs) + }; - Ok(graph) + Ok(graph.connect()) } else { Ok(*project.whole_app_module_graphs().await?.full) } diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index 10855344e2a48..f4aa5b5b6675b 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -1163,7 +1163,12 @@ impl Project { ) -> Result> { Ok(if *self.per_page_module_graph().await? { let is_production = self.next_mode().await?.is_production(); - ModuleGraph::from_entry_module(*entry, is_production, is_production) + ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entry( + ChunkGroupEntry::Entry(vec![entry]), + is_production, + is_production, + )) + .connect() } else { *self.whole_app_module_graphs().await?.full }) @@ -1182,11 +1187,12 @@ impl Project { .copied() .map(ResolvedVc::upcast) .collect(); - ModuleGraph::from_modules( - Vc::cell(vec![ChunkGroupEntry::Entry(entries)]), + ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries( + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(entries)]), is_production, is_production, - ) + )) + .connect() } else { *self.whole_app_module_graphs().await?.full }) @@ -2034,19 +2040,11 @@ impl Project { /// Compute the used exports and unused imports for each module. #[turbo_tasks::function] async fn binding_usage_info(self: Vc) -> Result> { - let remove_unused_imports = *self - .next_config() - .turbopack_remove_unused_imports(self.next_mode()) - .await?; - let module_graphs = self.whole_app_module_graphs().await?; - Ok(*compute_binding_usage_info( - module_graphs.full_with_unused_references, - remove_unused_imports, - ) - // As a performance optimization, we resolve strongly consistently - .resolve_strongly_consistent() - .await?) + Ok(module_graphs + .binding_usage_info + .context("No binding usage info")? + .connect()) } /// Compute the used exports for each module. @@ -2118,13 +2116,17 @@ async fn whole_app_module_graph_operation( let base = if turbopack_remove_unused_imports { // TODO suboptimal that we do compute_binding_usage_info twice (once for the base graph // and later for the full graph) - base.without_unused_references(compute_binding_usage_info(base.to_resolved().await?, true)) + let binding_usage_info = compute_binding_usage_info(base, true); + ModuleGraph::from_single_graph_without_unused_references( + base_single_module_graph, + binding_usage_info, + ) } else { base }; let additional_entries = project - .get_all_additional_entries(base) + .get_all_additional_entries(base.connect()) .to_resolved() .await?; @@ -2135,27 +2137,23 @@ async fn whole_app_module_graph_operation( should_read_binding_usage, ); - let full_with_unused_references = - ModuleGraph::from_graphs(vec![base_single_module_graph, additional_module_graph]) - .to_resolved() - .await?; + let graphs = vec![base_single_module_graph, additional_module_graph]; - let full = if turbopack_remove_unused_imports { - full_with_unused_references - .without_unused_references(compute_binding_usage_info( - full_with_unused_references, - true, - )) - .to_resolved() - .await? + let (full, binding_usage_info) = if turbopack_remove_unused_imports { + let full_with_unused_references = ModuleGraph::from_graphs(graphs.clone()); + let binding_usage_info = compute_binding_usage_info(full_with_unused_references, true); + ( + ModuleGraph::from_graphs_without_unused_references(graphs, binding_usage_info), + Some(binding_usage_info), + ) } else { - full_with_unused_references + (ModuleGraph::from_graphs(graphs), None) }; Ok(BaseAndFullModuleGraph { - base: base.to_resolved().await?, - full_with_unused_references, - full, + base: base.connect().to_resolved().await?, + full: full.connect().to_resolved().await?, + binding_usage_info, } .cell()) } @@ -2164,11 +2162,10 @@ async fn whole_app_module_graph_operation( pub struct BaseAndFullModuleGraph { /// The base module graph generated from the entry points. pub base: ResolvedVc, - /// The base graph plus any modules that were generated from additional entries (for which the - /// base graph is needed). - pub full_with_unused_references: ResolvedVc, /// `full_with_unused_references` but with unused references removed. pub full: ResolvedVc, + /// Information about binding usage in the module graph. + pub binding_usage_info: Option>, } #[turbo_tasks::function] diff --git a/crates/next-core/src/next_font/google/mod.rs b/crates/next-core/src/next_font/google/mod.rs index 4b4a16e7ae722..9061a73acc28f 100644 --- a/crates/next-core/src/next_font/google/mod.rs +++ b/crates/next-core/src/next_font/google/mod.rs @@ -21,7 +21,7 @@ use turbopack_core::{ context::AssetContext, ident::Layer, issue::{IssueExt, IssueSeverity, StyledString}, - module_graph::ModuleGraph, + module_graph::{ModuleGraph, SingleModuleGraph}, reference_type::{InnerAssets, ReferenceType}, resolve::{ ResolveResult, @@ -760,7 +760,12 @@ async fn get_mock_stylesheet( .module(); let entries = get_evaluate_entries(mocked_response_asset, asset_context, None); - let module_graph = ModuleGraph::from_modules(entries.graph_entries(), false, false); + let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries( + entries.graph_entries().to_resolved().await?, + false, + false, + )); + let module_graph = module_graph.connect(); let root = mock_fs.root().owned().await?; let val = evaluate( diff --git a/turbopack/crates/turbopack-cli/src/build/mod.rs b/turbopack/crates/turbopack-cli/src/build/mod.rs index e50aa8d758d30..27976920211e8 100644 --- a/turbopack/crates/turbopack-cli/src/build/mod.rs +++ b/turbopack/crates/turbopack-cli/src/build/mod.rs @@ -29,7 +29,7 @@ use turbopack_core::{ issue::{IssueReporter, IssueSeverity, handle_issues}, module::Module, module_graph::{ - ModuleGraph, + ModuleGraph, SingleModuleGraph, binding_usage_info::compute_binding_usage_info, chunk_group_info::{ChunkGroup, ChunkGroupEntry}, }, @@ -304,23 +304,26 @@ async fn build_internal( .instrument(tracing::info_span!("resolve entries")) .await?; - let mut module_graph = ModuleGraph::from_modules( - Vc::cell(vec![ChunkGroupEntry::Entry(entries.clone())]), + let single_graph = SingleModuleGraph::new_with_entries( + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(entries.clone())]), false, true, ); - let module_id_strategy = ResolvedVc::upcast( - get_global_module_id_strategy(module_graph) - .to_resolved() - .await?, - ); - let binding_usage = compute_binding_usage_info(module_graph.to_resolved().await?, true); + let mut module_graph = ModuleGraph::from_single_graph(single_graph); + let binding_usage = compute_binding_usage_info(module_graph, true); let unused_references = binding_usage .connect() .unused_references() .to_resolved() .await?; - module_graph = module_graph.without_unused_references(binding_usage); + module_graph = + ModuleGraph::from_single_graph_without_unused_references(single_graph, binding_usage); + let module_graph = module_graph.connect(); + let module_id_strategy = ResolvedVc::upcast( + get_global_module_id_strategy(module_graph) + .to_resolved() + .await?, + ); let chunking_context: Vc> = match target { Target::Browser => { diff --git a/turbopack/crates/turbopack-cli/src/dev/web_entry_source.rs b/turbopack/crates/turbopack-cli/src/dev/web_entry_source.rs index 2d64e40e59b87..6c1a2005e6da1 100644 --- a/turbopack/crates/turbopack-cli/src/dev/web_entry_source.rs +++ b/turbopack/crates/turbopack-cli/src/dev/web_entry_source.rs @@ -12,7 +12,7 @@ use turbopack_core::{ environment::Environment, file_source::FileSource, module::Module, - module_graph::{ModuleGraph, chunk_group_info::ChunkGroupEntry}, + module_graph::{ModuleGraph, SingleModuleGraph, chunk_group_info::ChunkGroupEntry}, reference_type::{EntryReferenceSubType, ReferenceType}, resolve::{ origin::{PlainResolveOrigin, ResolveOrigin, ResolveOriginExt}, @@ -161,13 +161,12 @@ pub async fn create_web_entry_source( .map(|&entry| ResolvedVc::upcast(entry)), ) .collect::>>>(); - let module_graph = ModuleGraph::from_modules( - Vc::cell(vec![ChunkGroupEntry::Entry(all_modules)]), + let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries( + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(all_modules)]), false, false, - ) - .to_resolved() - .await?; + )); + let module_graph = module_graph.connect().to_resolved().await?; let entries: Vec<_> = entries .into_iter() diff --git a/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs b/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs index abe61a2c09dca..742985e92cc8a 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs @@ -5,7 +5,7 @@ use auto_hash_map::AutoSet; use rustc_hash::{FxHashMap, FxHashSet}; use tracing::Instrument; use turbo_rcstr::RcStr; -use turbo_tasks::{ResolvedVc, Vc}; +use turbo_tasks::{OperationVc, ResolvedVc, Vc}; use crate::{ chunk::chunking_context::UnusedReferences, @@ -90,7 +90,7 @@ impl BindingUsageInfo { #[turbo_tasks::function(operation)] pub async fn compute_binding_usage_info( - graph: ResolvedVc, + graph: OperationVc, remove_unused_imports: bool, ) -> Result> { let span_outer = tracing::info_span!( @@ -111,7 +111,9 @@ pub async fn compute_binding_usage_info( let mut unused_references_edges = FxHashSet::default(); let mut unused_references = FxHashSet::default(); - if graph.await?.binding_usage.is_some() { + let graph = graph.connect(); + let graph_ref = graph.await?; + if graph_ref.binding_usage.is_some() { // If the graph already has binding usage info, return it directly. This is // unfortunately easy to do with // ``` @@ -128,9 +130,8 @@ pub async fn compute_binding_usage_info( without_unused_references" ); } - let graph_ref = graph.await?; let side_effect_free_modules = if remove_unused_imports { - let side_effect_free_modules = compute_side_effect_free_module_info(*graph).await?; + let side_effect_free_modules = compute_side_effect_free_module_info(graph).await?; span.record("side_effect_free_modules", side_effect_free_modules.len()); Some(side_effect_free_modules) } else { diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index e4f6b2eb22b31..5e8b0dfcbade2 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -269,7 +269,7 @@ impl SingleModuleGraph { /// nodes listed in `visited_modules` /// The resulting graph's outgoing edges are in reverse order. async fn new_inner( - entries: &GraphEntriesT, + entries: Vec, visited_modules: &FxIndexMap>, GraphNodeIndex>, include_traced: bool, include_binding_usage: bool, @@ -660,7 +660,7 @@ pub struct ModuleGraph { #[turbo_tasks::value_impl] impl ModuleGraph { - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub async fn from_single_graph(graph: OperationVc) -> Result> { let graph = Self::create(vec![graph], None) .read_strongly_consistent() @@ -668,7 +668,7 @@ impl ModuleGraph { Ok(ReadRef::cell(graph)) } - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] pub async fn from_graphs(graphs: Vec>) -> Result> { let graph = Self::create(graphs, None) .read_strongly_consistent() @@ -681,7 +681,23 @@ impl ModuleGraph { /// /// In particular, this removes ChunkableModuleReference-s that list only unused exports in the /// `import_usage()` - #[turbo_tasks::function] + #[turbo_tasks::function(operation)] + pub async fn from_single_graph_without_unused_references( + graph: OperationVc, + binding_usage: OperationVc, + ) -> Result> { + let graph = Self::create(vec![graph], Some(binding_usage)) + .read_strongly_consistent() + .await?; + Ok(ReadRef::cell(graph)) + } + + /// Analyze the module graph and remove unused references (by determining the used exports and + /// removing unused imports). + /// + /// In particular, this removes ChunkableModuleReference-s that list only unused exports in the + /// `import_usage()` + #[turbo_tasks::function(operation)] pub async fn from_graphs_without_unused_references( graphs: Vec>, binding_usage: OperationVc, @@ -692,32 +708,6 @@ impl ModuleGraph { Ok(ReadRef::cell(graph)) } - #[turbo_tasks::function] - pub fn from_entry_module( - module: ResolvedVc>, - include_traced: bool, - include_binding_usage: bool, - ) -> Vc { - Self::from_single_graph(SingleModuleGraph::new_with_entries( - ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![module])]), - include_traced, - include_binding_usage, - )) - } - - #[turbo_tasks::function] - pub fn from_modules( - modules: ResolvedVc, - include_traced: bool, - include_binding_usage: bool, - ) -> Vc { - Self::from_single_graph(SingleModuleGraph::new_with_entries( - modules, - include_traced, - include_binding_usage, - )) - } - #[turbo_tasks::function(operation)] async fn create( graphs: Vec>, @@ -826,15 +816,6 @@ impl ModuleGraph { Ok(AsyncModuleInfo::new(referenced_modules)) } - // TODO remove this method and use `from_graphs_without_unused_references` directly - #[turbo_tasks::function] - pub fn without_unused_references( - &self, - binding_usage: OperationVc, - ) -> Vc { - Self::from_graphs_without_unused_references(self.input_graphs.clone(), binding_usage) - } - /// Returns the underlying graphs as a list, to be used for individual graph traversals. #[turbo_tasks::function] pub fn iter_graphs(&self) -> Vc { @@ -1403,6 +1384,21 @@ impl ModuleGraph { #[turbo_tasks::value_impl] impl SingleModuleGraph { + #[turbo_tasks::function(operation)] + pub async fn new_with_entry( + entry: ChunkGroupEntry, + include_traced: bool, + include_binding_usage: bool, + ) -> Result> { + SingleModuleGraph::new_inner( + vec![entry], + &Default::default(), + include_traced, + include_binding_usage, + ) + .await + } + #[turbo_tasks::function(operation)] pub async fn new_with_entries( entries: ResolvedVc, @@ -1410,7 +1406,7 @@ impl SingleModuleGraph { include_binding_usage: bool, ) -> Result> { SingleModuleGraph::new_inner( - &*entries.await?, + entries.owned().await?, &Default::default(), include_traced, include_binding_usage, @@ -1426,7 +1422,7 @@ impl SingleModuleGraph { include_binding_usage: bool, ) -> Result> { SingleModuleGraph::new_inner( - &*entries.await?, + entries.owned().await?, &visited_modules.connect().await?.modules, include_traced, include_binding_usage, @@ -1443,7 +1439,7 @@ impl SingleModuleGraph { include_binding_usage: bool, ) -> Result> { SingleModuleGraph::new_inner( - &entries, + entries, &visited_modules.connect().await?.modules, include_traced, include_binding_usage, @@ -2028,7 +2024,8 @@ pub mod tests { false, false, ), - ]); + ]) + .connect(); let child_graph = module_graph .iter_graphs() .await? @@ -2279,7 +2276,7 @@ pub mod tests { .into_iter() .collect(); test_fn( - &*ModuleGraph::from_single_graph(graph).await?, + &*ModuleGraph::from_single_graph(graph).connect().await?, entry_modules, module_to_name, ) diff --git a/turbopack/crates/turbopack-node/src/transforms/postcss.rs b/turbopack/crates/turbopack-node/src/transforms/postcss.rs index 2f58b7991ad18..673573e3bb8f3 100644 --- a/turbopack/crates/turbopack-node/src/transforms/postcss.rs +++ b/turbopack/crates/turbopack-node/src/transforms/postcss.rs @@ -16,7 +16,7 @@ use turbopack_core::{ context::{AssetContext, ProcessResult}, file_source::FileSource, ident::AssetIdent, - module_graph::ModuleGraph, + module_graph::{ModuleGraph, SingleModuleGraph}, reference_type::{EntryReferenceSubType, InnerAssets, ReferenceType}, resolve::{FindContextFileResult, find_context_file_or_package_key, options::ImportMapping}, source::Source, @@ -501,9 +501,14 @@ impl PostCssTransformedAsset { .to_resolved() .await?; - let module_graph = ModuleGraph::from_modules(entries.graph_entries(), false, false) - .to_resolved() - .await?; + let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries( + entries.graph_entries().to_resolved().await?, + false, + false, + )) + .connect() + .to_resolved() + .await?; let css_fs_path = self.source.ident().path(); diff --git a/turbopack/crates/turbopack-node/src/transforms/webpack.rs b/turbopack/crates/turbopack-node/src/transforms/webpack.rs index 7b3e2b928e9a4..5d1b6d7bfdfa7 100644 --- a/turbopack/crates/turbopack-node/src/transforms/webpack.rs +++ b/turbopack/crates/turbopack-node/src/transforms/webpack.rs @@ -30,7 +30,7 @@ use turbopack_core::{ Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource, OptionStyledString, StyledString, }, - module_graph::ModuleGraph, + module_graph::{ModuleGraph, SingleModuleGraph}, reference_type::{InnerAssets, ReferenceType}, resolve::{ options::{ConditionValue, ResolveInPackage, ResolveIntoPackage, ResolveOptions}, @@ -257,9 +257,14 @@ impl WebpackLoadersProcessedAsset { .to_resolved() .await?; - let module_graph = ModuleGraph::from_modules(entries.graph_entries(), false, false) - .to_resolved() - .await?; + let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries( + entries.graph_entries().to_resolved().await?, + false, + false, + )) + .connect() + .to_resolved() + .await?; let resource_fs_path = self.source.ident().path().await?; let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path) else { diff --git a/turbopack/crates/turbopack-tests/tests/execution.rs b/turbopack/crates/turbopack-tests/tests/execution.rs index f147d5b3004ce..d67c07e3968ba 100644 --- a/turbopack/crates/turbopack-tests/tests/execution.rs +++ b/turbopack/crates/turbopack-tests/tests/execution.rs @@ -38,7 +38,9 @@ use turbopack_core::{ file_source::FileSource, ident::Layer, issue::{CollectibleIssuesExt, IssueFilter}, - module_graph::{ModuleGraph, binding_usage_info::compute_binding_usage_info}, + module_graph::{ + ModuleGraph, SingleModuleGraph, binding_usage_info::compute_binding_usage_info, + }, reference_type::{InnerAssets, ReferenceType}, resolve::{ ExternalTraced, ExternalType, @@ -479,19 +481,28 @@ async fn run_test_operation(prepared_test: ResolvedVc) -> Result Result> { bail!("Entry module is not chunkable, so it can't be used to bootstrap the application") }; - let mut module_graph = ModuleGraph::from_modules( - Vc::cell(vec![ChunkGroupEntry::Entry(entry_modules.clone())]), + let single_graph = SingleModuleGraph::new_with_entries( + ResolvedVc::cell(vec![ChunkGroupEntry::Entry(entry_modules.clone())]), false, true, ); + let mut module_graph = ModuleGraph::from_single_graph(single_graph); let binding_usage = if options.remove_unused_imports || options.remove_unused_exports { Some(compute_binding_usage_info( - module_graph.to_resolved().await?, + module_graph, options.remove_unused_imports, )) } else { None }; - if options.remove_unused_imports { - module_graph = module_graph.without_unused_references(binding_usage.unwrap()); + if options.remove_unused_imports + && let Some(binding_usage) = binding_usage + { + module_graph = + ModuleGraph::from_single_graph_without_unused_references(single_graph, binding_usage); } + let module_graph = module_graph.connect(); let chunk_root_path = project_path.join("output")?; let static_root_path = project_path.join("static")?; From 1a45569b85e2fe09aa7ebebaabceb9d95df5d810 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 13 Jan 2026 16:40:21 +0100 Subject: [PATCH 6/8] Use separate type for module graph layer to make it more clear --- crates/next-api/src/client_references.rs | 4 +- crates/next-api/src/dynamic_imports.rs | 6 +- crates/next-api/src/module_graph.rs | 16 +-- crates/next-api/src/server_actions.rs | 6 +- .../src/module_graph/async_module_info.rs | 4 +- .../turbopack-core/src/module_graph/mod.rs | 111 +++++++++++------- .../module_graph/side_effect_module_info.rs | 4 +- 7 files changed, 93 insertions(+), 58 deletions(-) diff --git a/crates/next-api/src/client_references.rs b/crates/next-api/src/client_references.rs index d935750e3ea11..bd2f137c3eb35 100644 --- a/crates/next-api/src/client_references.rs +++ b/crates/next-api/src/client_references.rs @@ -8,7 +8,7 @@ use rustc_hash::FxHashMap; use turbo_tasks::{ NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, }; -use turbopack_core::{module::Module, module_graph::ModuleGraph}; +use turbopack_core::{module::Module, module_graph::ModuleGraphLayer}; use turbopack_css::chunk::CssChunkPlaceable; #[derive( @@ -29,7 +29,7 @@ pub struct ClientReferenceData(FxHashMap>, ClientMani #[turbo_tasks::function] pub async fn map_client_references( - graph: ResolvedVc, + graph: ResolvedVc, ) -> Result> { let graph = graph.await?; let manifest = graph diff --git a/crates/next-api/src/dynamic_imports.rs b/crates/next-api/src/dynamic_imports.rs index 0ce73a946d621..7b1f46845b6b5 100644 --- a/crates/next-api/src/dynamic_imports.rs +++ b/crates/next-api/src/dynamic_imports.rs @@ -35,7 +35,7 @@ use turbopack_core::{ availability_info::AvailabilityInfo, }, module::Module, - module_graph::ModuleGraph, + module_graph::{ModuleGraph, ModuleGraphLayer}, output::{OutputAssetsReference, OutputAssetsWithReferenced}, }; @@ -122,7 +122,9 @@ pub struct DynamicImportEntries( ); #[turbo_tasks::function] -pub async fn map_next_dynamic(graph: ResolvedVc) -> Result> { +pub async fn map_next_dynamic( + graph: ResolvedVc, +) -> Result> { let actions = graph .await? .iter_nodes() diff --git a/crates/next-api/src/module_graph.rs b/crates/next-api/src/module_graph.rs index db402bd5ddfb8..8f2c154aa7149 100644 --- a/crates/next-api/src/module_graph.rs +++ b/crates/next-api/src/module_graph.rs @@ -23,7 +23,7 @@ use turbopack_core::{ context::AssetContext, issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString}, module::Module, - module_graph::{GraphTraversalAction, ModuleGraph}, + module_graph::{GraphTraversalAction, ModuleGraph, ModuleGraphLayer}, }; use turbopack_css::{CssModuleAsset, ModuleCssAsset}; @@ -35,7 +35,7 @@ use crate::{ #[turbo_tasks::value] pub struct NextDynamicGraph { - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, /// list of NextDynamicEntryModules @@ -131,7 +131,7 @@ pub struct DynamicImportEntriesWithImporter( impl NextDynamicGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { let mapped = map_next_dynamic(*graph); @@ -233,7 +233,7 @@ impl NextDynamicGraph { #[turbo_tasks::value] pub struct ServerActionsGraph { - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, /// (Layer, RSC or Browser module) -> list of actions @@ -318,7 +318,7 @@ impl ServerActionsGraphs { impl ServerActionsGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { let mapped = map_server_actions(*graph); @@ -409,7 +409,7 @@ impl ServerActionsGraph { #[turbo_tasks::value] pub struct ClientReferencesGraph { is_single_page: bool, - graph: ResolvedVc, + graph: ResolvedVc, /// List of client references (modules that entries into the client graph) data: ResolvedVc, @@ -521,7 +521,7 @@ impl ClientReferencesGraphs { impl ClientReferencesGraph { #[turbo_tasks::function] pub async fn new_with_entries( - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, ) -> Result> { let mapped = map_client_references(*graph); @@ -769,7 +769,7 @@ struct ModuleNameMap(#[bincode(with = "turbo_bincode::indexmap")] pub FxModuleNa #[tracing::instrument(level = "info", name = "validate pages css imports", skip_all)] #[turbo_tasks::function] async fn validate_pages_css_imports_individual( - graph: ResolvedVc, + graph: ResolvedVc, is_single_page: bool, entry: Vc>, app_module: ResolvedVc>, diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index 5fbdd688646cd..0a99389a66d08 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -30,7 +30,7 @@ use turbopack_core::{ file_source::FileSource, ident::AssetIdent, module::Module, - module_graph::{ModuleGraph, async_module_info::AsyncModulesInfo}, + module_graph::{ModuleGraph, ModuleGraphLayer, async_module_info::AsyncModulesInfo}, output::OutputAsset, reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType}, resolve::ModulePart, @@ -453,7 +453,9 @@ pub struct AllModuleActions( ); #[turbo_tasks::function] -pub async fn map_server_actions(graph: ResolvedVc) -> Result> { +pub async fn map_server_actions( + graph: ResolvedVc, +) -> Result> { let actions = graph .await? .iter_nodes() diff --git a/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs b/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs index 45ca5b11dff8e..09a6bf328e2cb 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/async_module_info.rs @@ -4,7 +4,7 @@ use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, Vc}; use crate::{ module::{Module, Modules}, - module_graph::{GraphTraversalAction, ModuleGraph}, + module_graph::{GraphTraversalAction, ModuleGraph, ModuleGraphLayer}, }; #[turbo_tasks::value(transparent)] @@ -49,7 +49,7 @@ pub async fn compute_async_module_info( #[turbo_tasks::function] async fn compute_async_module_info_single( - graph: ResolvedVc, + graph: ResolvedVc, parent_async_modules: Vc, ) -> Result> { let parent_async_modules = parent_async_modules.await?; diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index 5e8b0dfcbade2..f7945d3f3f3ca 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -2,6 +2,7 @@ use core::panic; use std::{ collections::{BinaryHeap, VecDeque}, future::Future, + ops::Deref, }; use anyhow::{Context, Result, bail}; @@ -643,19 +644,11 @@ impl ImportTracer for ModuleGraphImportTracer { /// The ReadRef version of ModuleGraphBase. This is better for eventual consistency, as the graphs /// aren't awaited multiple times within the same task. #[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")] -#[derive(Clone, Default)] pub struct ModuleGraph { input_graphs: Vec>, input_binding_usage: Option>, - pub graphs: Vec>, - // Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single - // module graph usecases, this is what you want. For the whole graph, there should be an error. - skip_visited_module_children: bool, - - pub graph_idx_override: Option, - - pub binding_usage: Option>, + snapshot: ModuleGraphSnapshot, } #[turbo_tasks::value_impl] @@ -716,34 +709,15 @@ impl ModuleGraph { Ok(ModuleGraph { input_graphs: graphs.clone(), input_binding_usage: binding_usage, - graphs: graphs.iter().map(|g| g.connect()).try_join().await?, - skip_visited_module_children: false, - graph_idx_override: None, - binding_usage: if let Some(binding_usage) = binding_usage { - Some(binding_usage.connect().await?) - } else { - None - }, - } - .cell()) - } - - #[turbo_tasks::function(operation)] - async fn create_layer( - graph: OperationVc, - graph_idx: u32, - binding_usage: Option>, - ) -> Result> { - Ok(Self { - input_graphs: vec![graph.clone()], - input_binding_usage: binding_usage, - graphs: vec![graph.connect().await?], - skip_visited_module_children: true, - graph_idx_override: Some(graph_idx), - binding_usage: if let Some(binding_usage) = binding_usage { - Some(binding_usage.connect().await?) - } else { - None + snapshot: ModuleGraphSnapshot { + graphs: graphs.iter().map(|g| g.connect()).try_join().await?, + skip_visited_module_children: false, + graph_idx_override: None, + binding_usage: if let Some(binding_usage) = binding_usage { + Some(binding_usage.connect().await?) + } else { + None + }, }, } .cell()) @@ -824,17 +798,74 @@ impl ModuleGraph { .iter() .enumerate() .map(|(graph_idx, graph)| { - ModuleGraph::create_layer(*graph, graph_idx as u32, self.input_binding_usage) + ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage) }) .collect(), ) } } +impl Deref for ModuleGraph { + type Target = ModuleGraphSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")] +pub struct ModuleGraphLayer { + snapshot: ModuleGraphSnapshot, +} + +#[turbo_tasks::value_impl] +impl ModuleGraphLayer { + #[turbo_tasks::function(operation)] + async fn new( + graph: OperationVc, + graph_idx: u32, + binding_usage: Option>, + ) -> Result> { + Ok(Self { + snapshot: ModuleGraphSnapshot { + graphs: vec![graph.connect().await?], + skip_visited_module_children: true, + graph_idx_override: Some(graph_idx), + binding_usage: if let Some(binding_usage) = binding_usage { + Some(binding_usage.connect().await?) + } else { + None + }, + }, + } + .cell()) + } +} + +impl Deref for ModuleGraphLayer { + type Target = ModuleGraphSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + #[turbo_tasks::value(transparent)] -pub struct ModuleGraphLayers(Vec>); +pub struct ModuleGraphLayers(Vec>); -impl ModuleGraph { +#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)] +pub struct ModuleGraphSnapshot { + pub graphs: Vec>, + // Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single + // module graph usecases, this is what you want. For the whole graph, there should be an error. + skip_visited_module_children: bool, + + pub graph_idx_override: Option, + + pub binding_usage: Option>, +} + +impl ModuleGraphSnapshot { fn get_entry(&self, entry: ResolvedVc>) -> Result { if self.graph_idx_override.is_some() { debug_assert_eq!(self.graphs.len(), 1,); diff --git a/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs b/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs index 00801530884d6..2034fbdd24eca 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/side_effect_module_info.rs @@ -4,7 +4,7 @@ use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; use crate::{ module::{Module, ModuleSideEffects}, - module_graph::{GraphTraversalAction, ModuleGraph}, + module_graph::{GraphTraversalAction, ModuleGraph, ModuleGraphLayer}, }; /// This lists all the modules that are side effect free @@ -37,7 +37,7 @@ pub async fn compute_side_effect_free_module_info( #[turbo_tasks::function] async fn compute_side_effect_free_module_info_single( - graph: ResolvedVc, + graph: ResolvedVc, parent_side_effect_free_modules: Vc, ) -> Result> { let parent_side_effect_free_modules = parent_side_effect_free_modules.await?; From 2732dd177e47300238f19e99474e6417733a0483 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 13 Jan 2026 16:40:32 +0100 Subject: [PATCH 7/8] improve readablility --- crates/next-api/src/app.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index a11692e9556ee..0fc4375f261a4 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -969,17 +969,21 @@ impl AppProject { .turbopack_remove_unused_imports(next_mode) .await?; - let binding_usage_info = remove_unused_imports.then(|| { - compute_binding_usage_info( - ModuleGraph::from_graphs(graphs.clone()), + let (full, binding_usage_info) = if remove_unused_imports { + let full_with_unused_references = ModuleGraph::from_graphs(graphs.clone()); + let binding_usage_info = compute_binding_usage_info( + full_with_unused_references, should_read_binding_usage, + ); + ( + ModuleGraph::from_graphs_without_unused_references( + graphs, + binding_usage_info, + ), + Some(binding_usage_info), ) - }); - - let full = if let Some(binding_usage_info) = binding_usage_info { - ModuleGraph::from_graphs_without_unused_references(graphs, binding_usage_info) } else { - ModuleGraph::from_graphs(graphs) + (ModuleGraph::from_graphs(graphs), None) }; Ok(BaseAndFullModuleGraph { From 236485f798c849c68dabb6aa80745d632a7a57c5 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 16 Jan 2026 08:01:08 +0100 Subject: [PATCH 8/8] clippy --- turbopack/crates/turbopack-core/src/module_graph/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/turbopack/crates/turbopack-core/src/module_graph/mod.rs b/turbopack/crates/turbopack-core/src/module_graph/mod.rs index f7945d3f3f3ca..c82604a82524b 100644 --- a/turbopack/crates/turbopack-core/src/module_graph/mod.rs +++ b/turbopack/crates/turbopack-core/src/module_graph/mod.rs @@ -2060,8 +2060,7 @@ pub mod tests { let child_graph = module_graph .iter_graphs() .await? - .iter() - .nth(1) + .get(1) .unwrap() .connect() .await?;