Skip to content
New issue

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

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

Already on GitHub? # to your account

Turbopack: add edge app routes #53387

Merged
merged 17 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
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 @@ -646,37 +648,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 @@ -783,13 +853,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 @@ -811,7 +888,7 @@ enum AppEndpointOutput {
output_assets: Vc<OutputAssets>,
},
Edge {
file: Vc<Box<dyn OutputAsset>>,
files: Vc<OutputAssets>,
output_assets: Vc<OutputAssets>,
},
}
Expand All @@ -826,7 +903,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