Skip to content

Commit

Permalink
Experimental Next.rs API
Browse files Browse the repository at this point in the history
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
3 people committed Jul 13, 2023
1 parent 38dafa1 commit a95453e
Show file tree
Hide file tree
Showing 30 changed files with 2,144 additions and 64 deletions.
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"packages/next-swc/crates/core",
"packages/next-swc/crates/napi",
"packages/next-swc/crates/wasm",
"packages/next-swc/crates/next-api",
"packages/next-swc/crates/next-build",
"packages/next-swc/crates/next-core",
"packages/next-swc/crates/next-dev",
Expand All @@ -26,6 +27,7 @@ lto = true

[workspace.dependencies]
# Workspace crates
next-api = { path = "packages/next-swc/crates/next-api", default-features = false }
next-build = { path = "packages/next-swc/crates/next-build", default-features = false }
next-core = { path = "packages/next-swc/crates/next-core", default-features = false }
next-dev = { path = "packages/next-swc/crates/next-dev", default-features = false, features = [
Expand Down
1 change: 1 addition & 0 deletions packages/next-swc/crates/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ napi = { version = "2", default-features = false, features = [
napi-derive = "2"
next-swc = { version = "0.0.0", path = "../core" }
next-dev = { workspace = true }
next-api = { workspace = true }
next-build = { workspace = true }
next-core = { workspace = true }
turbo-tasks = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions packages/next-swc/crates/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use turbopack_binding::swc::core::{
pub mod app_structure;
pub mod mdx;
pub mod minify;
pub mod next_api;
pub mod parse;
pub mod transform;
pub mod turbopack;
Expand Down Expand Up @@ -116,6 +117,7 @@ static REGISTER_ONCE: Once = Once::new();

fn register() {
REGISTER_ONCE.call_once(|| {
::next_api::register();
next_core::register();
include!(concat!(env!("OUT_DIR"), "/register.rs"));
});
Expand Down
55 changes: 55 additions & 0 deletions packages/next-swc/crates/napi/src/next_api/endpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use napi::{bindgen_prelude::External, JsFunction};
use next_api::route::{Endpoint, EndpointVc, WrittenEndpoint};

use super::utils::{subscribe, NapiDiagnostic, NapiIssue, RootTask, VcArc};

#[napi(object)]
pub struct NapiWrittenEndpoint {
pub server_entry_path: String,
pub server_paths: Vec<String>,
pub issues: Vec<NapiIssue>,
pub diagnostics: Vec<NapiDiagnostic>,
}

impl From<&WrittenEndpoint> for NapiWrittenEndpoint {
fn from(written_endpoint: &WrittenEndpoint) -> Self {
Self {
server_entry_path: written_endpoint.server_entry_path.clone(),
server_paths: written_endpoint.server_paths.clone(),
issues: vec![],
diagnostics: vec![],
}
}
}

#[napi]
pub async fn endpoint_write_to_disk(
#[napi(ts_arg_type = "{ __napiType: \"Endpoint\" }")] endpoint: External<VcArc<EndpointVc>>,
) -> napi::Result<NapiWrittenEndpoint> {
let turbo_tasks = endpoint.turbo_tasks().clone();
let endpoint = **endpoint;
let written = turbo_tasks
.run_once(async move { Ok(endpoint.write_to_disk().strongly_consistent().await?) })
.await?;
// TODO peek_issues and diagnostics
Ok((&*written).into())
}

#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn endpoint_changed_subscribe(
#[napi(ts_arg_type = "{ __napiType: \"Endpoint\" }")] endpoint: External<VcArc<EndpointVc>>,
func: JsFunction,
) -> napi::Result<External<RootTask>> {
let turbo_tasks = endpoint.turbo_tasks().clone();
let endpoint = **endpoint;
subscribe(
turbo_tasks,
func,
move || async move {
endpoint.changed().await?;
// TODO peek_issues and diagnostics
Ok(())
},
|_ctx| Ok(vec![()]),
)
}
3 changes: 3 additions & 0 deletions packages/next-swc/crates/napi/src/next_api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod endpoint;
pub mod project;
pub mod utils;
195 changes: 195 additions & 0 deletions packages/next-swc/crates/napi/src/next_api/project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::sync::Arc;

use anyhow::Result;
use napi::{bindgen_prelude::External, JsFunction};
use next_api::{
project::{Middleware, ProjectOptions, ProjectVc},
route::{EndpointVc, Route},
};
use turbo_tasks::TurboTasks;
use turbopack_binding::turbo::tasks_memory::MemoryBackend;

use super::utils::{serde_enum_to_string, subscribe, RootTask, VcArc};
use crate::register;

#[napi(object)]
pub struct NapiProjectOptions {
/// A root path from which all files must be nested under. Trying to access
/// a file outside this root will fail. Think of this as a chroot.
pub root_path: String,

/// A path inside the root_path which contains the app/pages directories.
pub project_path: String,

/// Whether to watch he filesystem for file changes.
pub watch: bool,

/// The contents of next.config.js, serialized to JSON.
pub next_config: String,

/// An upper bound of memory that turbopack will attempt to stay under.
pub memory_limit: Option<f64>,
}

impl Into<ProjectOptions> for NapiProjectOptions {
fn into(self) -> ProjectOptions {
ProjectOptions {
root_path: self.root_path,
project_path: self.project_path,
watch: self.watch,
next_config: self.next_config,
memory_limit: self.memory_limit.map(|m| m as _),
}
}
}

#[napi(ts_return_type = "{ __napiType: \"Project\" }")]
pub async fn project_new(options: NapiProjectOptions) -> napi::Result<External<VcArc<ProjectVc>>> {
register();
let turbo_tasks = TurboTasks::new(MemoryBackend::new(
options
.memory_limit
.map(|m| m as usize)
.unwrap_or(usize::MAX),
));
let options = options.into();
let project = turbo_tasks
.run_once(async move { Ok(ProjectVc::new(options).resolve().await?) })
.await?;
Ok(External::new_with_size_hint(
VcArc::new(turbo_tasks, project),
100,
))
}

#[napi(object)]
#[derive(Default)]
struct NapiRoute {
/// The relative path from project_path to the route file
pub pathname: String,

/// The type of route, eg a Page or App
pub r#type: &'static str,

// Different representations of the endpoint
pub endpoint: Option<External<VcArc<EndpointVc>>>,
pub html_endpoint: Option<External<VcArc<EndpointVc>>>,
pub rsc_endpoint: Option<External<VcArc<EndpointVc>>>,
pub data_endpoint: Option<External<VcArc<EndpointVc>>>,
}

impl NapiRoute {
fn from_route(
pathname: String,
value: Route,
turbo_tasks: &Arc<TurboTasks<MemoryBackend>>,
) -> Self {
let convert_endpoint =
|endpoint: EndpointVc| Some(External::new(VcArc::new(turbo_tasks.clone(), endpoint)));
match value {
Route::Page {
html_endpoint,
data_endpoint,
} => NapiRoute {
pathname,
r#type: "page",
html_endpoint: convert_endpoint(html_endpoint.clone()),
data_endpoint: convert_endpoint(data_endpoint.clone()),
..Default::default()
},
Route::PageApi { endpoint } => NapiRoute {
pathname,
r#type: "page-api",
endpoint: convert_endpoint(endpoint.clone()),
..Default::default()
},
Route::AppPage {
html_endpoint,
rsc_endpoint,
} => NapiRoute {
pathname,
r#type: "app-page",
html_endpoint: convert_endpoint(html_endpoint.clone()),
rsc_endpoint: convert_endpoint(rsc_endpoint.clone()),
..Default::default()
},
Route::AppRoute { endpoint } => NapiRoute {
pathname,
r#type: "app-route",
endpoint: convert_endpoint(endpoint.clone()),
..Default::default()
},
Route::Conflict => NapiRoute {
pathname,
r#type: "conflict",
..Default::default()
},
}
}
}

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

impl NapiMiddleware {
fn from_middleware(
value: &Middleware,
turbo_tasks: &Arc<TurboTasks<MemoryBackend>>,
) -> Result<Self> {
Ok(NapiMiddleware {
endpoint: External::new(VcArc::new(turbo_tasks.clone(), value.endpoint.clone())),
runtime: serde_enum_to_string(&value.config.runtime)?,
matcher: value.config.matcher.clone(),
})
}
}

#[napi(object)]
struct NapiEntrypoints {
pub routes: Vec<NapiRoute>,
pub middleware: Option<NapiMiddleware>,
pub issues: Vec<NapiIssue>,
pub diagnostics: Vec<NapiDiagnostic>,
}

#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn project_entrypoints_subscribe(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<VcArc<ProjectVc>>,
func: JsFunction,
) -> napi::Result<External<RootTask>> {
let turbo_tasks = project.turbo_tasks().clone();
let project = **project;
subscribe(
turbo_tasks.clone(),
func,
move || async move {
let entrypoints = project.entrypoints();
let entrypoints = entrypoints.strongly_consistent().await?;
// TODO peek_issues and diagnostics
Ok(entrypoints)
},
move |ctx| {
let entrypoints = ctx.value;
Ok(vec![NapiEntrypoints {
routes: entrypoints
.routes
.iter()
.map(|(pathname, &route)| {
NapiRoute::from_route(pathname.clone(), route, &turbo_tasks)
})
.collect::<Vec<_>>(),
middleware: entrypoints
.middleware
.as_ref()
.map(|m| NapiMiddleware::from_middleware(&m, &turbo_tasks))
.transpose()?,
issues: vec![],
diagnostics: vec![],
}])
},
)
}

0 comments on commit a95453e

Please sign in to comment.