Skip to content

Commit

Permalink
Turbopack: add edge app routes (#53387)
Browse files Browse the repository at this point in the history
### What?

* adds middleware manifest and other missing items for edge app routes
* fixes react-server condition in edge context
* fixes node.js route context

---------

Co-authored-by: Alex Kirszenberg <alex.kirszenberg@vercel.com>
  • Loading branch information
sokra and alexkirsz committed Aug 7, 2023
1 parent 9483ff1 commit 25e6db4
Show file tree
Hide file tree
Showing 37 changed files with 761 additions and 93 deletions.
135 changes: 106 additions & 29 deletions packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use next_core::{
all_server_paths,
app_structure::{
Expand All @@ -18,8 +18,10 @@ use next_core::{
ClientReferenceGraph, ClientReferenceType, NextEcmascriptClientReferenceTransition,
},
next_dynamic::{NextDynamicEntries, NextDynamicTransition},
next_edge::route_regex::get_named_middleware_regex,
next_manifests::{
AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest, PagesManifest,
AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest,
EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2, PagesManifest, Regions,
},
next_server::{
get_server_module_options_context, get_server_resolve_options_context,
Expand Down Expand Up @@ -638,37 +640,105 @@ impl AppEndpoint {

let endpoint_output = match app_entry.config.await?.runtime.unwrap_or_default() {
NextRuntime::Edge => {
// create edge chunks
let chunking_context = this.app_project.project().edge_rsc_chunking_context();
let mut evaluatable_assets = this
.app_project
.edge_rsc_runtime_entries()
.await?
.clone_value();
let Some(evaluatable) = Vc::try_resolve_sidecast(app_entry.rsc_entry).await? else {
bail!("Entry module must be evaluatable");
};
evaluatable_assets.push(evaluatable);
let files = chunking_context.evaluated_chunk_group(
app_entry
.rsc_entry
.as_root_chunk(Vc::upcast(chunking_context)),
this.app_project.edge_rsc_runtime_entries(),
Vc::cell(evaluatable_assets),
);
// TODO concatenation is not good, we should use all files once next.js supports
// that output_assets.extend(files.await?.iter().copied());
let file = concatenate_output_assets(
server_path.join(format!(
"app/{original_name}.js",
output_assets.extend(files.await?.iter().copied());

let node_root_value = node_root.await?;
let files_paths_from_root = files
.await?
.iter()
.map(move |&file| {
let node_root_value = node_root_value.clone();
async move {
Ok(node_root_value
.get_path_to(&*file.ident().path().await?)
.map(|path| path.to_string()))
}
})
.try_flat_join()
.await?;

let server_path_value = server_path.await?;
let files_paths_from_server = files
.await?
.iter()
.map(move |&file| {
let server_path_value = server_path_value.clone();
async move {
Ok(server_path_value
.get_path_to(&*file.ident().path().await?)
.map(|path| path.to_string()))
}
})
.try_flat_join()
.await?;
let base_file = files_paths_from_server[0].to_string();

// create middleware manifest
// TODO(alexkirsz) This should be shared with next build.
let named_regex = get_named_middleware_regex(&app_entry.pathname);
let matchers = MiddlewareMatcher {
regexp: named_regex,
original_source: app_entry.pathname.clone(),
..Default::default()
};
let edge_function_definition = EdgeFunctionDefinition {
files: files_paths_from_root,
name: app_entry.original_name.to_string(),
page: app_entry.original_name.clone(),
regions: app_entry
.config
.await?
.preferred_region
.clone()
.map(Regions::Single),
matchers: vec![matchers],
..Default::default()
};
let middleware_manifest_v2 = MiddlewaresManifestV2 {
sorted_middleware: vec![app_entry.original_name.clone()],
middleware: Default::default(),
functions: [(app_entry.original_name.clone(), edge_function_definition)]
.into_iter()
.collect(),
};
let middleware_manifest_v2 = Vc::upcast(VirtualOutputAsset::new(
node_root.join(format!(
"server/app{original_name}/middleware-manifest.json",
original_name = app_entry.original_name
)),
files,
);
output_assets.push(file);

let app_paths_manifest_output = create_app_paths_manifest(
node_root,
&app_entry.original_name,
server_path
.await?
.get_path_to(&*file.ident().path().await?)
.expect("edge bundle path should be within app paths manifest directory")
.to_string(),
)?;
AssetContent::file(
FileContent::Content(File::from(serde_json::to_string_pretty(
&middleware_manifest_v2,
)?))
.cell(),
),
));
output_assets.push(middleware_manifest_v2);

// create app paths manifest
let app_paths_manifest_output =
create_app_paths_manifest(node_root, &app_entry.original_name, base_file)?;
output_assets.push(app_paths_manifest_output);

AppEndpointOutput::Edge {
file,
files,
output_assets: Vc::cell(output_assets),
}
}
Expand Down Expand Up @@ -772,13 +842,20 @@ impl Endpoint for AppEndpoint {
server_paths,
},
AppEndpointOutput::Edge {
file,
files,
output_assets: _,
} => WrittenEndpoint::Edge {
files: vec![node_root_ref
.get_path_to(&*file.ident().path().await?)
.context("edge chunk file path must be inside the node root")?
.to_string()],
files: files
.await?
.iter()
.map(|&file| async move {
Ok(node_root_ref
.get_path_to(&*file.ident().path().await?)
.context("edge chunk file path must be inside the node root")?
.to_string())
})
.try_join()
.await?,
global_var_name: "TODO".to_string(),
server_paths,
},
Expand All @@ -800,7 +877,7 @@ enum AppEndpointOutput {
output_assets: Vc<OutputAssets>,
},
Edge {
file: Vc<Box<dyn OutputAsset>>,
files: Vc<OutputAssets>,
output_assets: Vc<OutputAssets>,
},
}
Expand All @@ -815,7 +892,7 @@ impl AppEndpointOutput {
output_assets,
} => output_assets,
AppEndpointOutput::Edge {
file: _,
files: _,
output_assets,
} => output_assets,
}
Expand Down
62 changes: 57 additions & 5 deletions packages/next-swc/crates/next-core/src/app_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ use crate::{
route_transition::NextEdgeRouteTransition,
},
next_route_matcher::{NextFallbackMatcher, NextParamsMatcher},
next_server::context::{
get_server_compile_time_info, get_server_module_options_context,
get_server_resolve_options_context, ServerContextType,
next_server::{
context::{
get_server_compile_time_info, get_server_module_options_context,
get_server_resolve_options_context, ServerContextType,
},
route_transition::NextRouteTransition,
},
util::{render_data, NextRuntime},
};
Expand Down Expand Up @@ -231,12 +234,12 @@ fn next_server_component_transition(
execution_context: Vc<ExecutionContext>,
app_dir: Vc<FileSystemPath>,
server_root: Vc<FileSystemPath>,
mode: NextMode,
process_env: Vc<Box<dyn ProcessEnv>>,
next_config: Vc<NextConfig>,
server_addr: Vc<ServerAddr>,
ecmascript_client_reference_transition_name: Vc<String>,
) -> Vc<Box<dyn Transition>> {
let mode = NextMode::DevServer;
let ty = Value::new(ServerContextType::AppRSC {
app_dir,
client_transition: None,
Expand All @@ -261,6 +264,37 @@ fn next_server_component_transition(
)
}

#[turbo_tasks::function]
fn next_route_transition(
project_path: Vc<FileSystemPath>,
app_dir: Vc<FileSystemPath>,
process_env: Vc<Box<dyn ProcessEnv>>,
next_config: Vc<NextConfig>,
server_addr: Vc<ServerAddr>,
execution_context: Vc<ExecutionContext>,
) -> Vc<Box<dyn Transition>> {
let mode = NextMode::DevServer;
let server_ty = Value::new(ServerContextType::AppRoute { app_dir });

let server_compile_time_info = get_server_compile_time_info(mode, process_env, server_addr);

let server_resolve_options_context = get_server_resolve_options_context(
project_path,
server_ty,
mode,
next_config,
execution_context,
);

Vc::upcast(
NextRouteTransition {
server_compile_time_info,
server_resolve_options_context,
}
.cell(),
)
}

#[turbo_tasks::function]
fn next_edge_server_component_transition(
project_path: Vc<FileSystemPath>,
Expand Down Expand Up @@ -422,6 +456,17 @@ fn app_context(
execution_context,
),
);
transitions.insert(
"next-route".to_string(),
next_route_transition(
project_path,
app_dir,
env,
next_config,
server_addr,
execution_context,
),
);
transitions.insert(
"next-edge-page".to_string(),
next_edge_page_transition(
Expand All @@ -443,7 +488,6 @@ fn app_context(
execution_context,
app_dir,
server_root,
mode,
env,
next_config,
server_addr,
Expand Down Expand Up @@ -1080,6 +1124,14 @@ impl AppRoute {
Some(NextRuntime::NodeJs) | None => {
let bootstrap_asset = next_asset("entry/app/route.ts".to_string());

let entry_asset = this
.context
.with_transition("next-route".to_string())
.process(
Vc::upcast(entry_file_source),
Value::new(ReferenceType::Entry(EntryReferenceSubType::AppRoute)),
);

route_bootstrap(
entry_asset,
Vc::upcast(this.context),
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod next_client_component;
pub mod next_client_reference;
pub mod next_config;
pub mod next_dynamic;
mod next_edge;
pub mod next_edge;
mod next_font;
pub mod next_image;
mod next_import_map;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub async fn get_app_route_favicon_entry(
let file = File::from(code.build());
let source =
// TODO(alexkirsz) Figure out how to name this virtual source.
VirtualSource::new(project_root.join("todo.tsx".to_string()), AssetContent::file(file.into()));
VirtualSource::new(project_root.join("favicon-entry.tsx".to_string()), AssetContent::file(file.into()));

Ok(get_app_route_entry(
nodejs_context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ pub async fn get_app_page_entry(
project_root: Vc<FileSystemPath>,
) -> Result<Vc<AppEntry>> {
let config = parse_segment_config_from_loader_tree(loader_tree, Vc::upcast(nodejs_context));
let context = if matches!(config.await?.runtime, Some(NextRuntime::Edge)) {
let is_edge = matches!(config.await?.runtime, Some(NextRuntime::Edge));
let context = if is_edge {
edge_context
} else {
nodejs_context
Expand Down Expand Up @@ -139,6 +140,10 @@ pub async fn get_app_page_entry(
Value::new(ReferenceType::Internal(Vc::cell(inner_assets))),
);

if is_edge {
todo!("edge pages are not supported yet")
}

let Some(rsc_entry) =
Vc::try_resolve_downcast::<Box<dyn EcmascriptChunkPlaceable>>(rsc_entry).await?
else {
Expand Down
Loading

0 comments on commit 25e6db4

Please sign in to comment.