Skip to content

Commit

Permalink
add support for middleware to next.rs api
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Aug 25, 2023
1 parent dbfcfd7 commit a5fc2d8
Show file tree
Hide file tree
Showing 29 changed files with 735 additions and 232 deletions.
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
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ 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()
};
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(format!("server/middleware/middleware-manifest.json")),
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()
}
}
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ 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()
};
Expand Down
Loading

0 comments on commit a5fc2d8

Please sign in to comment.