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: add middleware support for next.rs api dev mode #54555

Merged
merged 6 commits into from
Aug 27, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 7 additions & 8 deletions packages/next-swc/crates/napi/src/next_api/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ use turbopack_binding::{
use super::{
endpoint::ExternalEndpoint,
utils::{
get_diagnostics, get_issues, serde_enum_to_string, subscribe, NapiDiagnostic, NapiIssue,
RootTask, TurbopackResult, VcArc,
get_diagnostics, get_issues, subscribe, NapiDiagnostic, NapiIssue, RootTask,
TurbopackResult, VcArc,
},
};
use crate::register;
Expand Down Expand Up @@ -265,9 +265,7 @@ impl NapiRoute {

#[napi(object)]
struct NapiMiddleware {
pub endpoint: External<VcArc<Vc<Box<dyn Endpoint>>>>,
pub runtime: String,
pub matcher: Option<Vec<String>>,
pub endpoint: External<ExternalEndpoint>,
}

impl NapiMiddleware {
Expand All @@ -276,9 +274,10 @@ impl NapiMiddleware {
turbo_tasks: &Arc<TurboTasks<MemoryBackend>>,
) -> Result<Self> {
Ok(NapiMiddleware {
endpoint: External::new(VcArc::new(turbo_tasks.clone(), value.endpoint)),
runtime: serde_enum_to_string(&value.config.runtime)?,
matcher: value.config.matcher.clone(),
endpoint: External::new(ExternalEndpoint(VcArc::new(
turbo_tasks.clone(),
value.endpoint,
))),
})
}
}
Expand Down
14 changes: 7 additions & 7 deletions packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use next_core::{
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
},
util::NextRuntime,
util::{get_asset_prefix_from_pathname, NextRuntime},
};
use serde::{Deserialize, Serialize};
use turbo_tasks::{trace::TraceRawVcs, Completion, TryFlatJoinIterExt, TryJoinIterExt, Value, Vc};
Expand Down Expand Up @@ -700,13 +700,13 @@ impl AppEndpoint {
// 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,
regexp: Some(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(),
name: app_entry.pathname.to_string(),
page: app_entry.original_name.clone(),
regions: app_entry
.config
Expand All @@ -718,16 +718,16 @@ impl AppEndpoint {
..Default::default()
};
let middleware_manifest_v2 = MiddlewaresManifestV2 {
sorted_middleware: vec![app_entry.original_name.clone()],
sorted_middleware: vec![app_entry.pathname.clone()],
middleware: Default::default(),
functions: [(app_entry.original_name.clone(), edge_function_definition)]
functions: [(app_entry.pathname.clone(), edge_function_definition)]
.into_iter()
.collect(),
};
let manifest_path_prefix = get_asset_prefix_from_pathname(&app_entry.pathname);
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
"server/app{manifest_path_prefix}/middleware-manifest.json",
)),
AssetContent::file(
FileContent::Content(File::from(serde_json::to_string_pretty(
Expand Down
1 change: 1 addition & 0 deletions packages/next-swc/crates/next-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

mod app;
mod entrypoints;
mod middleware;
mod pages;
pub mod project;
pub mod route;
Expand Down
219 changes: 219 additions & 0 deletions packages/next-swc/crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use anyhow::{bail, Context, Result};
use next_core::{
all_server_paths,
middleware::{get_middleware_module, wrap_edge_entry},
mode::NextMode,
next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2},
next_server::{get_server_runtime_entries, ServerContextType},
util::parse_config_from_source,
};
use turbo_tasks::{Completion, TryJoinIterExt, Value, Vc};
use turbopack_binding::{
turbo::tasks_fs::{File, FileContent},
turbopack::{
core::{
asset::AssetContent,
changed::any_content_changed_of_output_assets,
chunk::{ChunkableModule, ChunkingContext},
context::AssetContext,
module::Module,
output::{OutputAsset, OutputAssets},
virtual_output::VirtualOutputAsset,
},
ecmascript::chunk::EcmascriptChunkPlaceable,
},
};

use crate::{
project::Project,
route::{Endpoint, WrittenEndpoint},
};

#[turbo_tasks::value]
pub struct MiddlewareEndpoint {
project: Vc<Project>,
context: Vc<Box<dyn AssetContext>>,
userland_module: Vc<Box<dyn Module>>,
}

#[turbo_tasks::value_impl]
impl MiddlewareEndpoint {
#[turbo_tasks::function]
pub fn new(
project: Vc<Project>,
context: Vc<Box<dyn AssetContext>>,
userland_module: Vc<Box<dyn Module>>,
) -> Vc<Self> {
Self {
project,
context,
userland_module,
}
.cell()
}

#[turbo_tasks::function]
async fn edge_files(&self) -> Result<Vc<OutputAssets>> {
let module = get_middleware_module(
self.context,
self.project.project_path(),
self.userland_module,
);

let module = wrap_edge_entry(
self.context,
self.project.project_path(),
module,
"middleware".to_string(),
);

let mut evaluatable_assets = get_server_runtime_entries(
self.project.project_path(),
self.project.env(),
Value::new(ServerContextType::Middleware),
NextMode::Development,
self.project.next_config(),
)
.resolve_entries(self.context)
.await?
.clone_value();

let Some(module) =
Vc::try_resolve_downcast::<Box<dyn EcmascriptChunkPlaceable>>(module).await?
else {
bail!("Entry module must be evaluatable");
};

let Some(evaluatable) = Vc::try_resolve_sidecast(module).await? else {
bail!("Entry module must be evaluatable");
};
evaluatable_assets.push(evaluatable);

let edge_chunking_context = self.project.edge_middleware_chunking_context();

let edge_files = edge_chunking_context.evaluated_chunk_group(
module.as_root_chunk(Vc::upcast(edge_chunking_context)),
Vc::cell(evaluatable_assets),
);

Ok(edge_files)
}

#[turbo_tasks::function]
async fn output_assets(self: Vc<Self>) -> Result<Vc<OutputAssets>> {
let this = self.await?;

let config = parse_config_from_source(this.userland_module);

let mut output_assets = self.edge_files().await?.clone_value();

let node_root = this.project.node_root();

let files_paths_from_root = {
let node_root = &node_root.await?;
output_assets
.iter()
.map(|&file| async move {
Ok(node_root
.get_path_to(&*file.ident().path().await?)
.context("middleware file path must be inside the node root")?
.to_string())
})
.try_join()
.await?
};

let matchers = if let Some(matchers) = config.await?.matcher.as_ref() {
matchers
.iter()
.map(|matcher| MiddlewareMatcher {
original_source: matcher.to_string(),
..Default::default()
})
.collect()
} else {
vec![MiddlewareMatcher {
regexp: Some("^/.*$".to_string()),
original_source: "/:path*".to_string(),
..Default::default()
}]
};

let edge_function_definition = EdgeFunctionDefinition {
files: files_paths_from_root,
name: "middleware".to_string(),
page: "/".to_string(),
regions: None,
matchers,
..Default::default()
};
let middleware_manifest_v2 = MiddlewaresManifestV2 {
sorted_middleware: Default::default(),
middleware: [("/".to_string(), edge_function_definition)]
.into_iter()
.collect(),
functions: Default::default(),
};
let middleware_manifest_v2 = Vc::upcast(VirtualOutputAsset::new(
node_root.join("server/middleware/middleware-manifest.json".to_string()),
AssetContent::file(
FileContent::Content(File::from(serde_json::to_string_pretty(
&middleware_manifest_v2,
)?))
.cell(),
),
));
output_assets.push(middleware_manifest_v2);

Ok(Vc::cell(output_assets))
}
}

#[turbo_tasks::value_impl]
impl Endpoint for MiddlewareEndpoint {
#[turbo_tasks::function]
async fn write_to_disk(self: Vc<Self>) -> Result<Vc<WrittenEndpoint>> {
let this = self.await?;
let files = self.edge_files();
let output_assets = self.output_assets();
this.project
.emit_all_output_assets(Vc::cell(output_assets))
.await?;

let node_root = this.project.node_root();
let server_paths = all_server_paths(output_assets, node_root)
.await?
.clone_value();

let node_root = &node_root.await?;

let files = files
.await?
.iter()
.map(|&file| async move {
Ok(node_root
.get_path_to(&*file.ident().path().await?)
.context("middleware file path must be inside the node root")?
.to_string())
})
.try_join()
.await?;

Ok(WrittenEndpoint::Edge {
files,
global_var_name: "TODO".to_string(),
server_paths,
}
.cell())
}

#[turbo_tasks::function]
fn server_changed(self: Vc<Self>) -> Vc<Completion> {
any_content_changed_of_output_assets(self.output_assets())
}

#[turbo_tasks::function]
fn client_changed(self: Vc<Self>) -> Vc<Completion> {
Completion::new()
}
}
11 changes: 6 additions & 5 deletions packages/next-swc/crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,29 +842,30 @@ impl PageEndpoint {
let pathname = this.pathname.await?;
let named_regex = get_named_middleware_regex(&pathname);
let matchers = MiddlewareMatcher {
regexp: named_regex,
regexp: Some(named_regex),
original_source: pathname.to_string(),
..Default::default()
};
let original_name = this.original_name.await?;
let edge_function_definition = EdgeFunctionDefinition {
files: files_paths_from_root,
name: original_name.to_string(),
name: pathname.to_string(),
page: original_name.to_string(),
regions: None,
matchers: vec![matchers],
..Default::default()
};
let middleware_manifest_v2 = MiddlewaresManifestV2 {
sorted_middleware: vec![original_name.to_string()],
sorted_middleware: vec![pathname.to_string()],
middleware: Default::default(),
functions: [(original_name.to_string(), edge_function_definition)]
functions: [(pathname.to_string(), edge_function_definition)]
.into_iter()
.collect(),
};
let manifest_path_prefix = get_asset_prefix_from_pathname(&this.pathname.await?);
let middleware_manifest_v2 = Vc::upcast(VirtualOutputAsset::new(
node_root.join(format!(
"server/pages{original_name}/middleware-manifest.json"
"server/pages{manifest_path_prefix}/middleware-manifest.json"
)),
AssetContent::file(
FileContent::Content(File::from(serde_json::to_string_pretty(
Expand Down