Repository: vercel/next.js
Pull request: Turbopack: simplify `ReducedGraphs` and `find_server_entries`

- Renamed `ReducedGraphs` to `GlobalInformation`, these graphs never ended up getting reduced
- Simplify and rename `VisitClientReference`, as its only remaining use is finding server entries (layout segments), and not client references anymore.

Closes PACK-4971

Changed files:
filename: crates/next-api/src/app.rs
patch: @@ -76,7 +76,7 @@ use crate::{
     dynamic_imports::{NextDynamicChunkAvailability, collect_next_dynamic_chunks},
     font::create_font_manifest,
     loadable_manifest::create_react_loadable_manifest,
-    module_graph::get_reduced_graphs_for_endpoint,
+    module_graph::get_global_information_for_endpoint,
     nft_json::NftJsonAsset,
     paths::{
         all_paths_in_root, all_server_paths, get_asset_paths_from_root, get_js_paths_from_root,
@@ -1261,15 +1261,15 @@ impl AppEndpoint {
         }
         let client_shared_availability_info = client_shared_chunk_group.availability_info;
 
-        let reduced_graphs = get_reduced_graphs_for_endpoint(
+        let global_information = get_global_information_for_endpoint(
             *module_graphs.base,
             *project.per_page_module_graph().await?,
         );
-        let next_dynamic_imports = reduced_graphs
+        let next_dynamic_imports = global_information
             .get_next_dynamic_imports_for_endpoint(*rsc_entry)
             .await?;
 
-        let client_references = reduced_graphs
+        let client_references = global_information
             .get_client_references_for_endpoint(
                 *rsc_entry,
                 matches!(this.ty, AppEndpointType::Page { .. }),
@@ -1419,7 +1419,7 @@ impl AppEndpoint {
             }
         }
 
-        let actions = reduced_graphs.get_server_actions_for_endpoint(
+        let actions = global_information.get_server_actions_for_endpoint(
             *rsc_entry,
             match runtime {
                 NextRuntime::Edge => Vc::upcast(this.app_project.edge_rsc_module_context()),
@@ -2044,7 +2044,7 @@ impl Endpoint for AppEndpoint {
         let rsc_entry = app_entry.rsc_entry;
         let runtime = app_entry.config.await?.runtime.unwrap_or_default();
 
-        let actions = get_reduced_graphs_for_endpoint(
+        let actions = get_global_information_for_endpoint(
             graph,
             *this.app_project.project().per_page_module_graph().await?,
         )

filename: crates/next-api/src/module_graph.rs
patch: @@ -5,7 +5,7 @@ use either::Either;
 use next_core::{
     next_client_reference::{
         ClientReference, ClientReferenceGraphResult, ClientReferenceType, ServerEntries,
-        VisitedClientReferenceGraphNodes, find_server_entries,
+        find_server_entries,
     },
     next_dynamic::NextDynamicEntryModule,
     next_manifests::ActionLayer,
@@ -57,30 +57,6 @@ impl NextDynamicGraph {
     ) -> Result<Vc<Self>> {
         let mapped = map_next_dynamic(*graph);
 
-        // TODO shrink graph here, using the information from
-        //  - `mapped` (which lists the relevant nodes)
-        //  - `graph.entries` (which lists the page/route/... entries we need to keep)
-
-        // This would clone the graph and allow changing the node weights. We can probably get away
-        // with keeping the sidecar information separate from the graph itself, though.
-        //
-        // let mut reduced_modules: FxHashMap<Vc<Box<dyn Module>>, NodeIndex<u32>> =
-        // FxHashMap::default(); let mut reduced_graph = DiGraph::new();
-        // for idx in graph.node_indices() {
-        //     let weight = *graph.node_weight(idx).unwrap();
-        //     let new_idx = reduced_graph.add_node(weight);
-        //     reduced_modules.insert(weight, new_idx);
-        //     for e in graph.edges_directed(idx, petgraph::Direction::Outgoing) {
-        //         let target_weight = *graph.node_weight(e.target()).context("Missing
-        // target")?;         if let Some(new_target_idx) =
-        // reduced_modules.get(&target_weight) {
-        // reduced_graph.add_edge(new_idx, *new_target_idx, ());         } else {
-        //             let new_idx = reduced_graph.add_node(target_weight);
-        //             reduced_modules.insert(target_weight, new_idx);
-        //         }
-        //     }
-        // }
-
         Ok(NextDynamicGraph {
             is_single_page,
             graph,
@@ -185,8 +161,6 @@ impl ServerActionsGraph {
     ) -> Result<Vc<Self>> {
         let mapped = map_server_actions(*graph);
 
-        // TODO shrink graph here
-
         Ok(ServerActionsGraph {
             is_single_page,
             graph,
@@ -283,8 +257,6 @@ impl ClientReferencesGraph {
         // already, which saves us a traversal.
         let mapped = map_client_references(*graph);
 
-        // TODO shrink graph here
-
         Ok(Self {
             is_single_page,
             graph,
@@ -390,10 +362,6 @@ impl ClientReferencesGraph {
                 client_references_by_server_component,
                 server_utils: vec![],
                 server_component_entries: vec![],
-                // TODO remove
-                visited_nodes: VisitedClientReferenceGraphNodes::empty()
-                    .to_resolved()
-                    .await?,
             }
             .cell())
         }
@@ -564,20 +532,19 @@ async fn validate_pages_css_imports(
 /// The consumers of this shouldn't need to care about the exact contents since it's abstracted away
 /// by the accessor functions, but
 /// - In dev, contains information about the modules of the current endpoint only
-/// - In prod, there is a single `ReducedGraphs` for the whole app, containing all pages
+/// - In prod, there is a single `GlobalBuildInformation` for the whole app, containing all pages
 #[turbo_tasks::value]
-pub struct ReducedGraphs {
+pub struct GlobalBuildInformation {
     next_dynamic: Vec<ResolvedVc<NextDynamicGraph>>,
     server_actions: Vec<ResolvedVc<ServerActionsGraph>>,
     client_references: Vec<ResolvedVc<ClientReferencesGraph>>,
     // Data for some more ad-hoc operations
     bare_graphs: ResolvedVc<ModuleGraph>,
     is_single_page: bool,
-    // TODO add other graphs
 }
 
 #[turbo_tasks::value_impl]
-impl ReducedGraphs {
+impl GlobalBuildInformation {
     #[turbo_tasks::function]
     pub async fn new(graphs: Vc<ModuleGraph>, is_single_page: bool) -> Result<Vc<Self>> {
         let graphs_ref = &graphs.await?.graphs;
@@ -809,24 +776,25 @@ impl ReducedGraphs {
 }
 
 #[turbo_tasks::function(operation)]
-fn get_reduced_graphs_for_endpoint_inner_operation(
+fn get_global_information_for_endpoint_inner_operation(
     module_graph: ResolvedVc<ModuleGraph>,
     is_single_page: bool,
-) -> Vc<ReducedGraphs> {
-    ReducedGraphs::new(*module_graph, is_single_page)
+) -> Vc<GlobalBuildInformation> {
+    GlobalBuildInformation::new(*module_graph, is_single_page)
 }
 
-/// Generates a [ReducedGraph] for the given project and endpoint containing information that is
-/// either global (module ids, chunking) or computed globally as a performance optimization (client
-/// references, etc).
+/// Generates a [GlobalBuildInformation] for the given project and endpoint containing information
+/// that is either global (module ids, chunking) or computed globally as a performance optimization
+/// (client references, etc).
 #[turbo_tasks::function]
-pub async fn get_reduced_graphs_for_endpoint(
+pub async fn get_global_information_for_endpoint(
     module_graph: ResolvedVc<ModuleGraph>,
     is_single_page: bool,
-) -> Result<Vc<ReducedGraphs>> {
+) -> Result<Vc<GlobalBuildInformation>> {
     // TODO get rid of this function once everything inside of
-    // `get_reduced_graphs_for_endpoint_inner` calls `take_collectibles()` when needed
-    let result_op = get_reduced_graphs_for_endpoint_inner_operation(module_graph, is_single_page);
+    // `get_global_information_for_endpoint_inner` calls `take_collectibles()` when needed
+    let result_op =
+        get_global_information_for_endpoint_inner_operation(module_graph, is_single_page);
     let result_vc = if !is_single_page {
         let result_vc = result_op.resolve_strongly_consistent().await?;
         let _issues = result_op.take_collectibles::<Box<dyn Issue>>();

filename: crates/next-api/src/pages.rs
patch: @@ -70,7 +70,7 @@ use crate::{
     },
     font::create_font_manifest,
     loadable_manifest::create_react_loadable_manifest,
-    module_graph::get_reduced_graphs_for_endpoint,
+    module_graph::get_global_information_for_endpoint,
     nft_json::NftJsonAsset,
     paths::{
         all_paths_in_root, all_server_paths, get_asset_paths_from_root, get_js_paths_from_root,
@@ -1012,7 +1012,7 @@ impl PageEndpoint {
 
                 let client_module_graph = self.client_module_graph();
 
-                let reduced_graphs = get_reduced_graphs_for_endpoint(
+                let global_information = get_global_information_for_endpoint(
                     client_module_graph,
                     *project.per_page_module_graph().await?,
                 );
@@ -1043,12 +1043,12 @@ impl PageEndpoint {
                         .await?
                         .module();
 
-                    reduced_graphs
+                    global_information
                         .validate_pages_css_imports(self.client_module(), app_module)
                         .await?;
                 }
 
-                let next_dynamic_imports = reduced_graphs
+                let next_dynamic_imports = global_information
                     .get_next_dynamic_imports_for_endpoint(self.client_module())
                     .await?;
                 Some((next_dynamic_imports, client_availability_info))

filename: crates/next-core/src/next_client_reference/visit_client_reference.rs
patch: @@ -11,7 +11,6 @@ use turbo_tasks::{
     graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
     trace::TraceRawVcs,
 };
-use turbo_tasks_fs::FileSystemPath;
 use turbopack::css::chunk::CssChunkPlaceable;
 use turbopack_core::{
     chunk::ChunkingType, module::Module, reference::primary_chunkable_referenced_modules,
@@ -73,7 +72,7 @@ pub enum ClientReferenceType {
 }
 
 #[turbo_tasks::value(shared)]
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
 pub struct ClientReferenceGraphResult {
     pub client_references: Vec<ClientReference>,
     /// Only the [`ClientReferenceType::EcmascriptClientReference`]s are listed in this map.
@@ -82,23 +81,10 @@ pub struct ClientReferenceGraphResult {
         FxIndexMap<Option<ResolvedVc<NextServerComponentModule>>, Vec<ResolvedVc<Box<dyn Module>>>>,
     pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
     pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
-    pub visited_nodes: ResolvedVc<VisitedClientReferenceGraphNodes>,
-}
-
-impl Default for ClientReferenceGraphResult {
-    fn default() -> Self {
-        ClientReferenceGraphResult {
-            client_references: Default::default(),
-            client_references_by_server_component: Default::default(),
-            server_component_entries: Default::default(),
-            server_utils: Default::default(),
-            visited_nodes: VisitedClientReferenceGraphNodes(Default::default()).resolved_cell(),
-        }
-    }
 }
 
 #[turbo_tasks::value(shared)]
-pub struct VisitedClientReferenceGraphNodes(FxHashSet<VisitClientReferenceNode>);
+pub struct VisitedClientReferenceGraphNodes(FxHashSet<FindServerEntriesNode>);
 
 #[turbo_tasks::value_impl]
 impl VisitedClientReferenceGraphNodes {
@@ -138,8 +124,6 @@ impl ClientReferenceGraphResult {
         self.server_component_entries
             .extend(other.server_component_entries.iter().copied());
         self.server_utils.extend(other.server_utils.iter().copied());
-        // This is merged already by `client_reference_graph` itself
-        self.visited_nodes = other.visited_nodes;
     }
 }
 
@@ -150,27 +134,22 @@ pub struct ServerEntries {
     pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
 }
 
+/// For a given RSC entry, finds all server components (i.e. layout segments) and server utils that
+/// are referenced by the entry.
 #[turbo_tasks::function]
 pub async fn find_server_entries(
     entry: ResolvedVc<Box<dyn Module>>,
     include_traced: bool,
 ) -> Result<Vc<ServerEntries>> {
     async move {
-        let entry_path = entry.ident().path().await?.clone_value();
         let graph = AdjacencyMap::new()
             .skip_duplicates()
             .visit(
-                vec![VisitClientReferenceNode {
-                    state: { VisitClientReferenceNodeState::Entry { entry_path } },
-                    ty: VisitClientReferenceNodeType::Internal(
-                        entry,
-                        entry.ident().to_string().await?,
-                    ),
-                }],
-                VisitClientReference {
-                    stop_at_server_entries: true,
-                    include_traced,
-                },
+                vec![FindServerEntriesNode::Internal(
+                    entry,
+                    entry.ident().to_string().await?,
+                )],
+                FindServerEntries { include_traced },
             )
             .await
             .completed()?
@@ -179,15 +158,14 @@ pub async fn find_server_entries(
         let mut server_component_entries = vec![];
         let mut server_utils = vec![];
         for node in graph.postorder_topological() {
-            match &node.ty {
-                VisitClientReferenceNodeType::ServerUtilEntry(server_util, _) => {
+            match node {
+                FindServerEntriesNode::ServerUtilEntry(server_util, _) => {
                     server_utils.push(*server_util);
                 }
-                VisitClientReferenceNodeType::ServerComponentEntry(server_component, _) => {
+                FindServerEntriesNode::ServerComponentEntry(server_component, _) => {
                     server_component_entries.push(*server_component);
                 }
-                VisitClientReferenceNodeType::Internal(_, _)
-                | VisitClientReferenceNodeType::ClientReference(_, _) => {}
+                FindServerEntriesNode::Internal(_, _) | FindServerEntriesNode::ClientReference => {}
             }
         }
 
@@ -201,61 +179,9 @@ pub async fn find_server_entries(
     .await
 }
 
-struct VisitClientReference {
+struct FindServerEntries {
     /// Whether to walk ChunkingType::Traced references
     include_traced: bool,
-    /// Used to discover ServerComponents and ServerUtils
-    stop_at_server_entries: bool,
-}
-
-#[derive(
-    Clone,
-    Eq,
-    PartialEq,
-    Hash,
-    Serialize,
-    Deserialize,
-    Debug,
-    ValueDebugFormat,
-    TraceRawVcs,
-    NonLocalValue,
-)]
-struct VisitClientReferenceNode {
-    state: VisitClientReferenceNodeState,
-    ty: VisitClientReferenceNodeType,
-}
-
-#[derive(
-    Clone,
-    Eq,
-    PartialEq,
-    Hash,
-    Serialize,
-    Deserialize,
-    Debug,
-    ValueDebugFormat,
-    TraceRawVcs,
-    NonLocalValue,
-)]
-enum VisitClientReferenceNodeState {
-    Entry {
-        entry_path: FileSystemPath,
-    },
-    InServerComponent {
-        server_component: ResolvedVc<NextServerComponentModule>,
-    },
-    InServerUtil,
-}
-impl VisitClientReferenceNodeState {
-    fn server_component(&self) -> Option<ResolvedVc<NextServerComponentModule>> {
-        match self {
-            VisitClientReferenceNodeState::Entry { .. } => None,
-            VisitClientReferenceNodeState::InServerComponent { server_component } => {
-                Some(*server_component)
-            }
-            VisitClientReferenceNodeState::InServerUtil => None,
-        }
-    }
 }
 
 #[derive(
@@ -270,56 +196,46 @@ impl VisitClientReferenceNodeState {
     TraceRawVcs,
     NonLocalValue,
 )]
-enum VisitClientReferenceNodeType {
-    ClientReference(ClientReference, ReadRef<RcStr>),
+enum FindServerEntriesNode {
+    ClientReference,
     ServerComponentEntry(ResolvedVc<NextServerComponentModule>, ReadRef<RcStr>),
     ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, ReadRef<RcStr>),
     Internal(ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>),
 }
 
-impl Visit<VisitClientReferenceNode> for VisitClientReference {
-    type Edge = VisitClientReferenceNode;
+impl Visit<FindServerEntriesNode> for FindServerEntries {
+    type Edge = FindServerEntriesNode;
     type EdgesIntoIter = Vec<Self::Edge>;
     type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
 
-    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<VisitClientReferenceNode> {
-        if self.stop_at_server_entries
-            && matches!(
-                edge.ty,
-                VisitClientReferenceNodeType::ServerUtilEntry(..)
-                    | VisitClientReferenceNodeType::ServerComponentEntry(..)
-            )
-        {
-            return VisitControlFlow::Skip(edge);
-        }
-
-        match edge.ty {
-            VisitClientReferenceNodeType::ClientReference(..) => VisitControlFlow::Skip(edge),
-            VisitClientReferenceNodeType::Internal(..)
-            | VisitClientReferenceNodeType::ServerUtilEntry(..)
-            | VisitClientReferenceNodeType::ServerComponentEntry(..) => {
-                VisitControlFlow::Continue(edge)
-            }
+    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<FindServerEntriesNode> {
+        match edge {
+            FindServerEntriesNode::Internal(..) => VisitControlFlow::Continue(edge),
+            FindServerEntriesNode::ClientReference
+            | FindServerEntriesNode::ServerUtilEntry(..)
+            | FindServerEntriesNode::ServerComponentEntry(..) => VisitControlFlow::Skip(edge),
         }
     }
 
-    fn edges(&mut self, node: &VisitClientReferenceNode) -> Self::EdgesFuture {
+    fn edges(&mut self, node: &FindServerEntriesNode) -> Self::EdgesFuture {
         let node = node.clone();
         let include_traced = self.include_traced;
         async move {
-            let parent_module = match node.ty {
+            let parent_module = match node {
                 // This should never occur since we always skip visiting these
                 // nodes' edges.
-                VisitClientReferenceNodeType::ClientReference(..) => return Ok(vec![]),
-                VisitClientReferenceNodeType::Internal(module, _) => module,
-                VisitClientReferenceNodeType::ServerUtilEntry(module, _) => {
-                    ResolvedVc::upcast(module)
+                FindServerEntriesNode::ClientReference => {
+                    unreachable!("ClientReference node should not be visited")
                 }
-                VisitClientReferenceNodeType::ServerComponentEntry(module, _) => {
+                FindServerEntriesNode::Internal(module, _) => module,
+                FindServerEntriesNode::ServerUtilEntry(module, _) => ResolvedVc::upcast(module),
+                FindServerEntriesNode::ServerComponentEntry(module, _) => {
                     ResolvedVc::upcast(module)
                 }
             };
 
+            // Pass include_traced to reuse the same cached `primary_chunkable_referenced_modules`
+            // task result, but the traced references will be filtered out again afterwards.
             let referenced_modules =
                 primary_chunkable_referenced_modules(*parent_module, include_traced).await?;
 
@@ -330,79 +246,37 @@ impl Visit<VisitClientReferenceNode> for VisitClientReference {
                     _ => Some(modules.iter()),
                 })
                 .flatten()
-                .map(|module| {
-                    let node_state = node.state.clone();
-
-                    async move {
-                        if let Some(client_reference_module) = ResolvedVc::try_downcast_type::<
-                            EcmascriptClientReferenceModule,
-                        >(*module)
-                        {
-                            return Ok(VisitClientReferenceNode {
-                                state: node_state.clone(),
-                                ty: VisitClientReferenceNodeType::ClientReference(
-                                    ClientReference {
-                                        server_component: node_state.clone().server_component(),
-                                        ty: ClientReferenceType::EcmascriptClientReference(
-                                            client_reference_module,
-                                        ),
-                                    },
-                                    client_reference_module.ident().to_string().await?,
-                                ),
-                            });
-                        }
-
-                        if let Some(client_reference_module) =
-                            ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
-                        {
-                            return Ok(VisitClientReferenceNode {
-                                state: node_state.clone(),
-                                ty: VisitClientReferenceNodeType::ClientReference(
-                                    ClientReference {
-                                        server_component: node_state.clone().server_component(),
-                                        ty: ClientReferenceType::CssClientReference(
-                                            client_reference_module.await?.client_module,
-                                        ),
-                                    },
-                                    client_reference_module.ident().to_string().await?,
-                                ),
-                            });
-                        }
-
-                        if let Some(server_component_asset) =
-                            ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
-                        {
-                            return Ok(VisitClientReferenceNode {
-                                state: VisitClientReferenceNodeState::InServerComponent {
-                                    server_component: server_component_asset,
-                                },
-                                ty: VisitClientReferenceNodeType::ServerComponentEntry(
-                                    server_component_asset,
-                                    server_component_asset.ident().to_string().await?,
-                                ),
-                            });
-                        }
+                .map(async |module| {
+                    if ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
+                        .is_some()
+                        || ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
+                            .is_some()
+                    {
+                        return Ok(FindServerEntriesNode::ClientReference);
+                    }
 
-                        if let Some(server_util_module) =
-                            ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
-                        {
-                            return Ok(VisitClientReferenceNode {
-                                state: VisitClientReferenceNodeState::InServerUtil,
-                                ty: VisitClientReferenceNodeType::ServerUtilEntry(
-                                    server_util_module,
-                                    module.ident().to_string().await?,
-                                ),
-                            });
-                        }
+                    if let Some(server_component_asset) =
+                        ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
+                    {
+                        return Ok(FindServerEntriesNode::ServerComponentEntry(
+                            server_component_asset,
+                            server_component_asset.ident().to_string().await?,
+                        ));
+                    }
 
-                        Ok(VisitClientReferenceNode {
-                            state: node_state,
-                            ty: VisitClientReferenceNodeType::Internal(
-                                *module,
-                                module.ident().to_string().await?,
-                            ),
-                        })
+                    if let Some(server_util_module) =
+                        ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
+                    {
+                        return Ok(FindServerEntriesNode::ServerUtilEntry(
+                            server_util_module,
+                            module.ident().to_string().await?,
+                        ));
                     }
+
+                    Ok(FindServerEntriesNode::Internal(
+                        *module,
+                        module.ident().to_string().await?,
+                    ))
                 });
 
             let assets = referenced_modules.try_join().await?;
@@ -411,18 +285,18 @@ impl Visit<VisitClientReferenceNode> for VisitClientReference {
         }
     }
 
-    fn span(&mut self, node: &VisitClientReferenceNode) -> tracing::Span {
-        match &node.ty {
-            VisitClientReferenceNodeType::ClientReference(_, name) => {
-                tracing::info_span!("client reference", name = name.to_string())
+    fn span(&mut self, node: &FindServerEntriesNode) -> tracing::Span {
+        match node {
+            FindServerEntriesNode::ClientReference => {
+                tracing::info_span!("client reference")
             }
-            VisitClientReferenceNodeType::Internal(_, name) => {
+            FindServerEntriesNode::Internal(_, name) => {
                 tracing::info_span!("module", name = name.to_string())
             }
-            VisitClientReferenceNodeType::ServerUtilEntry(_, name) => {
+            FindServerEntriesNode::ServerUtilEntry(_, name) => {
                 tracing::info_span!("server util", name = name.to_string())
             }
-            VisitClientReferenceNodeType::ServerComponentEntry(_, name) => {
+            FindServerEntriesNode::ServerComponentEntry(_, name) => {
                 tracing::info_span!("layout segment", name = name.to_string())
             }
         }