Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbopack: migrate server actions to single-graph-traversal #73260

Merged
merged 7 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,8 +1120,21 @@ impl AppEndpoint {
};

let server_action_manifest_loader = if process_client_components {
let reduced_graphs = get_reduced_graphs_for_endpoint(
this.app_project.project(),
*rsc_entry,
Vc::upcast(this.app_project.client_module_context()),
);
let actions = reduced_graphs.get_server_actions_for_endpoint(
*rsc_entry,
match runtime {
NextRuntime::Edge => Vc::upcast(this.app_project.edge_rsc_module_context()),
NextRuntime::NodeJs => Vc::upcast(this.app_project.rsc_module_context()),
},
);

let server_action_manifest = create_server_actions_manifest(
*ResolvedVc::upcast(app_entry.rsc_entry),
actions,
this.app_project.project().project_path(),
node_root,
app_entry.original_name.clone(),
Expand Down
146 changes: 144 additions & 2 deletions crates/next-api/src/module_graph.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
future::Future,
hash::Hash,
Expand All @@ -9,6 +10,7 @@
use next_core::{
mode::NextMode,
next_client_reference::{find_server_entries, ServerEntries},
next_manifests::ActionLayer,
};
use petgraph::{
graph::{DiGraph, NodeIndex},
Expand All @@ -35,6 +37,7 @@
use crate::{
dynamic_imports::{map_next_dynamic, DynamicImports},
project::Project,
server_actions::{map_server_actions, to_rsc_context, AllActions, AllModuleActions},
};

#[turbo_tasks::value(transparent)]
Expand Down Expand Up @@ -316,6 +319,12 @@
.context("Couldn't find entry module in graph")
}

/// Iterate over all nodes in the graph (potentially in the whole app!).
pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ SingleModuleGraphNode> + '_ {
self.graph.node_weights()
}

/// Enumerate over all nodes in the graph (potentially in the whole app!).
pub fn enumerate_nodes(
&self,
) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
Expand Down Expand Up @@ -424,20 +433,20 @@
.collect();

let mut graphs = vec![graph];
for module in server_component_entries
.iter()
.map(|m| ResolvedVc::upcast::<Box<dyn Module>>(*m))
{
let graph = SingleModuleGraph::new_with_entries_visited(
*entry,
vec![*module],
Vc::cell(visited_modules.clone()),
)
.to_resolved()
.await?;
visited_modules.extend(graph.await?.graph.node_weights().map(|n| n.module));
graphs.push(graph);
}

Check notice on line 449 in crates/next-api/src/module_graph.rs

View workflow job for this annotation

GitHub Actions / ast-grep lint

to-resolved-in-loop

Calling `SingleModuleGraph::new_with_entries_visited(
let graph = SingleModuleGraph::new_with_entries_visited(
*entry,
vec![*entry],
Expand Down Expand Up @@ -533,13 +542,98 @@
}
}

#[turbo_tasks::value]
pub struct ServerActionsGraph {
is_single_page: bool,
graph: ResolvedVc<SingleModuleGraph>,
/// (Layer, RSC or Browser module) -> list of actions
data: ResolvedVc<AllModuleActions>,
}

#[turbo_tasks::value_impl]
impl ServerActionsGraph {
#[turbo_tasks::function]
pub async fn new_with_entries(
graph: ResolvedVc<SingleModuleGraph>,
is_single_page: bool,
) -> Result<Vc<Self>> {
let mapped = map_server_actions(*graph);

// TODO shrink graph here

Ok(ServerActionsGraph {
is_single_page,
graph,
data: mapped.to_resolved().await?,
}
.cell())
}

#[turbo_tasks::function]
pub async fn get_server_actions_for_endpoint(
&self,
entry: ResolvedVc<Box<dyn Module>>,
rsc_asset_context: Vc<Box<dyn AssetContext>>,
) -> Result<Vc<AllActions>> {
let span = tracing::info_span!("collect server actions for endpoint");
async move {
let data = &*self.data.await?;
let data = if self.is_single_page {
// The graph contains the page (= `entry`) only, no need to filter.
Cow::Borrowed(data)
} else {
// The graph contains the whole app, traverse and collect all reachable imports.
let graph = &*self.graph.await?;

let mut result = HashMap::new();
graph.traverse_from_entry(entry, |node| {
if let Some(node_data) = data.get(&node.module) {
result.insert(node.module, *node_data);
}
})?;
Cow::Owned(result)
};

let actions = data
.iter()
.map(|(module, (layer, actions))| async move {
actions
.await?
.iter()
.map(|(hash, name)| async move {
Ok((
hash.to_string(),
(
*layer,
name.to_string(),
if *layer == ActionLayer::Rsc {
*module
} else {
to_rsc_context(**module, rsc_asset_context).await?
},
),
))
})
.try_join()
.await
})
.try_flat_join()
.await?;
Ok(Vc::cell(actions.into_iter().collect()))
}
.instrument(span)
.await
}
}

/// 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
#[turbo_tasks::value]
pub struct ReducedGraphs {
next_dynamic: Vec<ResolvedVc<NextDynamicGraph>>,
server_actions: Vec<ResolvedVc<ServerActionsGraph>>,
// TODO add other graphs
}

Expand Down Expand Up @@ -578,6 +672,38 @@
.instrument(span)
.await
}

/// Returns the server actions for the given page.
#[turbo_tasks::function]
pub async fn get_server_actions_for_endpoint(
&self,
entry: Vc<Box<dyn Module>>,
rsc_asset_context: Vc<Box<dyn AssetContext>>,
) -> Result<Vc<AllActions>> {
let span = tracing::info_span!("collect all server actions for endpoint");
async move {
if let [graph] = &self.server_actions[..] {
// Just a single graph, no need to merge results
Ok(graph.get_server_actions_for_endpoint(entry, rsc_asset_context))
} else {
let result = self
.server_actions
.iter()
.map(|graph| async move {
Ok(graph
.get_server_actions_for_endpoint(entry, rsc_asset_context)
.await?
.clone_value())
})
.try_flat_join()
.await?;

Ok(Vc::cell(result.into_iter().collect()))
}
}
.instrument(span)
.await
}
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -609,7 +735,7 @@
),
};

let next_dynamic = async move {
let next_dynamic = async {
graphs
.iter()
.map(|graph| {
Expand All @@ -622,7 +748,23 @@
.instrument(tracing::info_span!("generating next/dynamic graphs"))
.await?;

Ok(ReducedGraphs { next_dynamic }.cell())
let server_actions = async {
graphs
.iter()
.map(|graph| {
ServerActionsGraph::new_with_entries(**graph, is_single_page).to_resolved()
})
.try_join()
.await
}
.instrument(tracing::info_span!("generating server actions graphs"))
.await?;

Ok(ReducedGraphs {
next_dynamic,
server_actions,
}
.cell())
}

/// Generates a [ReducedGraph] for the given project and endpoint containing information that is
Expand Down
Loading
Loading