Turobpack: Next.rs API (part 1) (#52259)

### What?

Creates a NAPI api for Next.rs to be used in Next.js

### Why?

### How?



Co-authored-by: Alex Kirszenberg <1621758+alexkirsz@users.noreply.github.com>
This commit is contained in:
Tobias Koppers 2023-07-13 19:17:38 +02:00 committed by GitHub
parent 88084e6b7a
commit ca1129c463
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2206 additions and 112 deletions

88
Cargo.lock generated
View file

@ -412,7 +412,7 @@ dependencies = [
[[package]]
name = "auto-hash-map"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"serde",
]
@ -3367,6 +3367,23 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "next-api"
version = "0.1.0"
dependencies = [
"anyhow",
"futures",
"indexmap",
"next-core",
"once_cell",
"serde",
"tokio",
"tracing",
"tracing-subscriber",
"turbo-tasks",
"turbopack-binding",
]
[[package]]
name = "next-build"
version = "0.1.0"
@ -3519,6 +3536,7 @@ dependencies = [
"napi",
"napi-build",
"napi-derive",
"next-api",
"next-build",
"next-core",
"next-dev",
@ -3581,7 +3599,7 @@ dependencies = [
[[package]]
name = "node-file-trace"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"serde",
@ -7241,7 +7259,7 @@ dependencies = [
[[package]]
name = "turbo-tasks"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7272,7 +7290,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"cargo-lock",
@ -7284,7 +7302,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-bytes"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"bytes",
@ -7299,7 +7317,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"dotenvs",
@ -7313,7 +7331,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fetch"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"indexmap",
@ -7330,7 +7348,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fs"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7360,7 +7378,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-hash"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"base16",
"hex",
@ -7372,7 +7390,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@ -7386,7 +7404,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros-shared"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"proc-macro2",
"quote",
@ -7396,7 +7414,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-malloc"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"mimalloc",
]
@ -7404,7 +7422,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-memory"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7427,7 +7445,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-testing"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7440,7 +7458,7 @@ dependencies = [
[[package]]
name = "turbopack"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-recursion",
@ -7470,7 +7488,7 @@ dependencies = [
[[package]]
name = "turbopack-bench"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"chromiumoxide",
@ -7500,7 +7518,7 @@ dependencies = [
[[package]]
name = "turbopack-binding"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"auto-hash-map",
"mdxjs",
@ -7542,7 +7560,7 @@ dependencies = [
[[package]]
name = "turbopack-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"indexmap",
@ -7562,7 +7580,7 @@ dependencies = [
[[package]]
name = "turbopack-cli-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"clap 4.1.11",
@ -7586,7 +7604,7 @@ dependencies = [
[[package]]
name = "turbopack-core"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-trait",
@ -7614,7 +7632,7 @@ dependencies = [
[[package]]
name = "turbopack-create-test-app"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"clap 4.1.11",
@ -7627,7 +7645,7 @@ dependencies = [
[[package]]
name = "turbopack-css"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-trait",
@ -7649,7 +7667,7 @@ dependencies = [
[[package]]
name = "turbopack-dev"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"indexmap",
@ -7673,7 +7691,7 @@ dependencies = [
[[package]]
name = "turbopack-dev-server"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-compression",
@ -7709,7 +7727,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-trait",
@ -7742,7 +7760,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-plugins"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-trait",
@ -7765,7 +7783,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-runtime"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"indoc",
@ -7782,7 +7800,7 @@ dependencies = [
[[package]]
name = "turbopack-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"indexmap",
@ -7798,7 +7816,7 @@ dependencies = [
[[package]]
name = "turbopack-image"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"base64 0.21.0",
@ -7818,7 +7836,7 @@ dependencies = [
[[package]]
name = "turbopack-json"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"serde",
@ -7833,7 +7851,7 @@ dependencies = [
[[package]]
name = "turbopack-mdx"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"mdxjs",
@ -7848,7 +7866,7 @@ dependencies = [
[[package]]
name = "turbopack-node"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"async-stream",
@ -7883,7 +7901,7 @@ dependencies = [
[[package]]
name = "turbopack-static"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"serde",
@ -7899,7 +7917,7 @@ dependencies = [
[[package]]
name = "turbopack-swc-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"swc_core",
"turbo-tasks",
@ -7910,7 +7928,7 @@ dependencies = [
[[package]]
name = "turbopack-test-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.2#4022f2b0e5bf6183b813f3ff32267d24f058cc82"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230713.3#e3c68fac81352b24bfcfa1f14b56c0ef0a391917"
dependencies = [
"anyhow",
"once_cell",

View file

@ -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",
@ -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 = [
@ -42,11 +44,11 @@ swc_core = { version = "0.79.13" }
testing = { version = "0.33.20" }
# Turbo crates
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.2" }
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.3" }
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.2" }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.3" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.2" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230713.3" }
# General Deps

View file

@ -43,6 +43,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 }

View file

@ -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;
@ -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"));
});

View file

@ -0,0 +1,57 @@
use napi::{bindgen_prelude::External, JsFunction};
use next_api::route::{Endpoint, EndpointVc, WrittenEndpoint};
use turbopack_binding::turbopack::core::error::PrettyPrintError;
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 { endpoint.write_to_disk().strongly_consistent().await })
.await
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
// 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![()]),
)
}

View file

@ -0,0 +1,3 @@
pub mod endpoint;
pub mod project;
pub mod utils;

View file

@ -0,0 +1,198 @@
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, turbopack::core::error::PrettyPrintError,
};
use super::utils::{serde_enum_to_string, subscribe, NapiDiagnostic, NapiIssue, 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 From<NapiProjectOptions> for ProjectOptions {
fn from(val: NapiProjectOptions) -> Self {
ProjectOptions {
root_path: val.root_path,
project_path: val.project_path,
watch: val.watch,
next_config: val.next_config,
memory_limit: val.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 { ProjectVc::new(options).resolve().await })
.await
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
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),
data_endpoint: convert_endpoint(data_endpoint),
..Default::default()
},
Route::PageApi { endpoint } => NapiRoute {
pathname,
r#type: "page-api",
endpoint: convert_endpoint(endpoint),
..Default::default()
},
Route::AppPage {
html_endpoint,
rsc_endpoint,
} => NapiRoute {
pathname,
r#type: "app-page",
html_endpoint: convert_endpoint(html_endpoint),
rsc_endpoint: convert_endpoint(rsc_endpoint),
..Default::default()
},
Route::AppRoute { endpoint } => NapiRoute {
pathname,
r#type: "app-route",
endpoint: convert_endpoint(endpoint),
..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)),
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![],
}])
},
)
}

View file

@ -0,0 +1,137 @@
use std::{future::Future, ops::Deref, sync::Arc};
use anyhow::{anyhow, Context, Result};
use napi::{
bindgen_prelude::{External, ToNapiValue},
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
JsFunction, JsObject, NapiRaw, NapiValue, Status,
};
use serde::Serialize;
use turbo_tasks::{NothingVc, TaskId, TurboTasks};
use turbopack_binding::{
turbo::tasks_memory::MemoryBackend, turbopack::core::error::PrettyPrintError,
};
/// A helper type to hold both a Vc operation and the TurboTasks root process.
/// Without this, we'd need to pass both individually all over the place
#[derive(Clone)]
pub struct VcArc<T> {
turbo_tasks: Arc<TurboTasks<MemoryBackend>>,
/// The Vc. Must be resolved, otherwise you are referencing an inactive
/// operation.
vc: T,
}
impl<T> VcArc<T> {
pub fn new(turbo_tasks: Arc<TurboTasks<MemoryBackend>>, vc: T) -> Self {
Self { turbo_tasks, vc }
}
pub fn turbo_tasks(&self) -> &Arc<TurboTasks<MemoryBackend>> {
&self.turbo_tasks
}
}
impl<T> Deref for VcArc<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.vc
}
}
pub fn serde_enum_to_string<T: Serialize>(value: &T) -> Result<String> {
Ok(serde_json::to_value(value)?
.as_str()
.context("value must serialize to a string")?
.to_string())
}
/// The root of our turbopack computation.
pub struct RootTask {
#[allow(dead_code)]
turbo_tasks: Arc<TurboTasks<MemoryBackend>>,
#[allow(dead_code)]
task_id: Option<TaskId>,
}
impl Drop for RootTask {
fn drop(&mut self) {
// TODO stop the root task
}
}
#[napi]
pub fn root_task_dispose(
#[napi(ts_arg_type = "{ __napiType: \"RootTask\" }")] _root_task: External<RootTask>,
) -> napi::Result<()> {
// TODO(alexkirsz) Implement. Not panicking here to avoid crashing the process
// when testing.
eprintln!("root_task_dispose not yet implemented");
Ok(())
}
#[napi(object)]
pub struct NapiIssue {}
#[napi(object)]
pub struct NapiDiagnostic {}
pub struct TurbopackResult<T: ToNapiValue> {
pub result: T,
pub issues: Vec<NapiIssue>,
pub diagnostics: Vec<NapiDiagnostic>,
}
impl<T: ToNapiValue> ToNapiValue for TurbopackResult<T> {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
val: Self,
) -> napi::Result<napi::sys::napi_value> {
let result = T::to_napi_value(env, val.result)?;
// let issues = ToNapiValue::to_napi_value(env, val.issues)?;
// let diagnostics = ToNapiValue::to_napi_value(env, val.diagnostics)?;
let result = JsObject::from_raw(env, result)?;
let mut obj = napi::Env::from_raw(env).create_object()?;
for key in JsObject::keys(&result)? {
obj.set_named_property(&key, result.get_named_property(&key)?)?;
}
obj.set_named_property("issues", val.issues)?;
obj.set_named_property("diagnostics", val.diagnostics)?;
Ok(obj.raw())
}
}
pub fn subscribe<T: 'static + Send + Sync, F: Future<Output = Result<T>> + Send, V: ToNapiValue>(
turbo_tasks: Arc<TurboTasks<MemoryBackend>>,
func: JsFunction,
handler: impl 'static + Sync + Send + Clone + Fn() -> F,
mapper: impl 'static + Sync + Send + FnMut(ThreadSafeCallContext<T>) -> napi::Result<Vec<V>>,
) -> napi::Result<External<RootTask>> {
let func: ThreadsafeFunction<T> = func.create_threadsafe_function(0, mapper)?;
let task_id = turbo_tasks.spawn_root_task(move || {
let handler = handler.clone();
let func = func.clone();
Box::pin(async move {
let result = handler().await;
let status = func.call(
result.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string())),
ThreadsafeFunctionCallMode::NonBlocking,
);
if !matches!(status, Status::Ok) {
let error = anyhow!("Error calling JS function: {}", status);
eprintln!("{}", error);
return Err(error);
}
Ok(NothingVc::new().into())
})
});
Ok(External::new(RootTask {
turbo_tasks,
task_id: Some(task_id),
}))
}

View file

@ -0,0 +1,46 @@
[package]
name = "next-api"
version = "0.1.0"
description = "TBD"
license = "MPL-2.0"
edition = "2021"
autobenches = false
[lib]
bench = false
[features]
default = ["custom_allocator", "native-tls"]
custom_allocator = ["turbopack-binding/__turbo_tasks_malloc", "turbopack-binding/__turbo_tasks_malloc_custom_allocator"]
native-tls = ["next-core/native-tls"]
rustls-tls = ["next-core/rustls-tls"]
[dependencies]
anyhow = { workspace = true, features = ["backtrace"] }
futures = { workspace = true }
indexmap = { workspace = true }
next-core = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true, features = ["full"] }
turbopack-binding = { workspace = true, features = [
"__turbo_tasks_memory",
"__turbo_tasks_env",
"__turbo_tasks_fs",
"__turbopack",
"__turbopack_build",
"__turbopack_core",
"__turbopack_dev",
"__turbopack_env",
"__turbopack_cli_utils",
"__turbopack_node",
"__turbopack_dev_server",
]}
turbo-tasks = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
[build-dependencies]
turbopack-binding = { workspace = true, features = [
"__turbo_tasks_build"
]}

View file

@ -0,0 +1,5 @@
use turbopack_binding::turbo::tasks_build::generate_register;
fn main() {
generate_register();
}

View file

@ -0,0 +1,67 @@
use next_core::app_structure::Entrypoint;
use serde::{Deserialize, Serialize};
use turbo_tasks::{trace::TraceRawVcs, CompletionVc};
use crate::route::{Endpoint, EndpointVc, Route, RouteVc, WrittenEndpointVc};
#[turbo_tasks::function]
pub async fn app_entry_point_to_route(entrypoint: Entrypoint) -> RouteVc {
match entrypoint {
Entrypoint::AppPage { .. } => Route::AppPage {
html_endpoint: AppPageEndpoint {
ty: AppPageEndpointType::Html,
}
.cell()
.into(),
rsc_endpoint: AppPageEndpoint {
ty: AppPageEndpointType::Rsc,
}
.cell()
.into(),
},
Entrypoint::AppRoute { .. } => Route::AppRoute {
endpoint: AppRouteEndpoint.cell().into(),
},
}
.cell()
}
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Debug, TraceRawVcs)]
enum AppPageEndpointType {
Html,
Rsc,
}
#[turbo_tasks::value]
struct AppPageEndpoint {
ty: AppPageEndpointType,
}
#[turbo_tasks::value_impl]
impl Endpoint for AppPageEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> WrittenEndpointVc {
todo!()
}
#[turbo_tasks::function]
fn changed(&self) -> CompletionVc {
todo!()
}
}
#[turbo_tasks::value]
struct AppRouteEndpoint;
#[turbo_tasks::value_impl]
impl Endpoint for AppRouteEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> WrittenEndpointVc {
todo!()
}
#[turbo_tasks::function]
fn changed(&self) -> CompletionVc {
todo!()
}
}

View file

@ -0,0 +1,13 @@
#![feature(future_join)]
#![feature(min_specialization)]
mod app;
mod pages;
pub mod project;
pub mod route;
pub fn register() {
next_core::register();
turbopack_binding::turbopack::build::register();
include!(concat!(env!("OUT_DIR"), "/register.rs"));
}

View file

@ -0,0 +1,363 @@
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use next_core::{
create_page_loader_entry_module, emit_all_assets, get_asset_path_from_pathname,
pages_structure::{
PagesDirectoryStructure, PagesDirectoryStructureVc, PagesStructure, PagesStructureItem,
PagesStructureVc,
},
};
use turbo_tasks::{primitives::StringVc, CompletionVc, CompletionsVc, Value};
use turbopack_binding::{
turbo::tasks_fs::FileSystemPathVc,
turbopack::{
core::{
asset::Asset,
changed::{any_content_changed, any_content_changed_of_output_assets},
chunk::{ChunkableModule, ChunkingContext},
context::AssetContext,
file_source::FileSourceVc,
output::{OutputAssetVc, OutputAssetsVc},
reference_type::{EntryReferenceSubType, ReferenceType},
source::SourceVc,
},
ecmascript::EcmascriptModuleAssetVc,
},
};
use crate::{
project::ProjectVc,
route::{Endpoint, EndpointVc, Route, RoutesVc, WrittenEndpoint, WrittenEndpointVc},
};
#[turbo_tasks::function]
pub async fn get_pages_routes(
project: ProjectVc,
page_structure: PagesStructureVc,
) -> Result<RoutesVc> {
let PagesStructure { api, pages, .. } = *page_structure.await?;
let mut routes = IndexMap::new();
async fn add_dir_to_routes(
routes: &mut IndexMap<String, Route>,
dir: PagesDirectoryStructureVc,
make_route: impl Fn(StringVc, StringVc, FileSystemPathVc) -> Route,
) -> Result<()> {
let mut queue = vec![dir];
while let Some(dir) = queue.pop() {
let PagesDirectoryStructure {
ref items,
ref children,
next_router_path: _,
project_path: _,
} = *dir.await?;
for &item in items.iter() {
let PagesStructureItem {
next_router_path,
project_path,
original_path,
} = *item.await?;
let pathname = format!("/{}", next_router_path.await?.path);
let pathname_vc = StringVc::cell(pathname.clone());
let original_name = StringVc::cell(format!("/{}", original_path.await?.path));
let route = make_route(pathname_vc, original_name, project_path);
routes.insert(pathname, route);
}
for &child in children.iter() {
queue.push(child);
}
}
Ok(())
}
if let Some(api) = api {
add_dir_to_routes(&mut routes, api, |pathname, original_name, path| {
Route::PageApi {
endpoint: ApiEndpointVc::new(project, pathname, original_name, path).into(),
}
})
.await?;
}
if let Some(page) = pages {
add_dir_to_routes(&mut routes, page, |pathname, original_name, path| {
Route::Page {
html_endpoint: PageHtmlEndpointVc::new(project, pathname, original_name, path)
.into(),
data_endpoint: PageDataEndpointVc::new(project, pathname, original_name, path)
.into(),
}
})
.await?;
}
Ok(RoutesVc::cell(routes))
}
#[turbo_tasks::value]
struct PageHtmlEndpoint {
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
}
#[turbo_tasks::value_impl]
impl PageHtmlEndpointVc {
#[turbo_tasks::function]
fn new(
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
) -> Self {
PageHtmlEndpoint {
project,
pathname,
original_name,
path,
}
.cell()
}
#[turbo_tasks::function]
async fn source(self) -> Result<SourceVc> {
let this = self.await?;
Ok(FileSourceVc::new(this.path).into())
}
#[turbo_tasks::function]
async fn client_chunks(self) -> Result<OutputAssetsVc> {
let this = self.await?;
let client_module = create_page_loader_entry_module(
this.project.pages_client_module_context(),
self.source(),
this.pathname,
);
let Some(client_module) = EcmascriptModuleAssetVc::resolve_from(client_module).await?
else {
bail!("expected an ECMAScript module asset");
};
let client_chunking_context = this.project.client_chunking_context();
let client_entry_chunk = client_module.as_root_chunk(client_chunking_context.into());
let client_chunks = client_chunking_context.evaluated_chunk_group(
client_entry_chunk,
this.project
.pages_client_runtime_entries()
.with_entry(client_module.into()),
);
Ok(client_chunks)
}
#[turbo_tasks::function]
async fn ssr_chunk(self) -> Result<OutputAssetVc> {
let this = self.await?;
let reference_type = Value::new(ReferenceType::Entry(EntryReferenceSubType::Page));
let ssr_module = this
.project
.pages_ssr_module_context()
.process(self.source(), reference_type.clone());
let Some(ssr_module) = EcmascriptModuleAssetVc::resolve_from(ssr_module).await? else {
bail!("expected an ECMAScript module asset");
};
let asset_path = get_asset_path_from_pathname(&this.pathname.await?, ".js");
let ssr_entry_chunk_path_string = format!("server/pages{asset_path}");
let ssr_entry_chunk_path = this.project.node_root().join(&ssr_entry_chunk_path_string);
let ssr_entry_chunk = this.project.ssr_chunking_context().entry_chunk(
ssr_entry_chunk_path,
ssr_module.into(),
this.project.pages_ssr_runtime_entries(),
);
Ok(ssr_entry_chunk)
}
}
#[turbo_tasks::value_impl]
impl Endpoint for PageHtmlEndpoint {
#[turbo_tasks::function]
async fn write_to_disk(self_vc: PageHtmlEndpointVc) -> Result<WrittenEndpointVc> {
let this = self_vc.await?;
let ssr_chunk = self_vc.ssr_chunk();
let ssr_emit = emit_all_assets(
OutputAssetsVc::cell(vec![ssr_chunk]),
this.project.node_root(),
this.project.client_root().join("_next"),
this.project.node_root(),
);
let client_emit = emit_all_assets(
self_vc.client_chunks(),
this.project.node_root(),
this.project.client_root().join("_next"),
this.project.node_root(),
);
ssr_emit.await?;
client_emit.await?;
Ok(WrittenEndpoint {
server_entry_path: this
.project
.node_root()
.await?
.get_path_to(&*ssr_chunk.ident().path().await?)
.context("ssr chunk entry path must be inside the node root")?
.to_string(),
server_paths: vec![],
}
.cell())
}
#[turbo_tasks::function]
fn changed(self_vc: PageHtmlEndpointVc) -> CompletionVc {
let ssr_chunk = self_vc.ssr_chunk();
CompletionsVc::all(vec![
any_content_changed(ssr_chunk.into()),
any_content_changed_of_output_assets(self_vc.client_chunks()),
])
}
}
#[turbo_tasks::value]
struct PageDataEndpoint {
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
}
#[turbo_tasks::value_impl]
impl PageDataEndpointVc {
#[turbo_tasks::function]
fn new(
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
) -> Self {
PageDataEndpoint {
project,
pathname,
original_name,
path,
}
.cell()
}
#[turbo_tasks::function]
async fn source(self) -> Result<SourceVc> {
let this = self.await?;
Ok(FileSourceVc::new(this.path).into())
}
#[turbo_tasks::function]
async fn ssr_data_chunk(self) -> Result<OutputAssetVc> {
let this = self.await?;
let reference_type = Value::new(ReferenceType::Entry(EntryReferenceSubType::Page));
let ssr_data_module = this
.project
.pages_ssr_data_module_context()
.process(self.source(), reference_type.clone());
let Some(ssr_data_module) = EcmascriptModuleAssetVc::resolve_from(ssr_data_module).await?
else {
bail!("expected an ECMAScript module asset");
};
let asset_path = get_asset_path_from_pathname(&this.pathname.await?, ".js");
let ssr_data_entry_chunk_path_string = format!("server/pages-data/{asset_path}");
let ssr_data_entry_chunk_path = this
.project
.node_root()
.join(&ssr_data_entry_chunk_path_string);
let ssr_data_entry_chunk = this.project.ssr_data_chunking_context().entry_chunk(
ssr_data_entry_chunk_path,
ssr_data_module.into(),
this.project.pages_ssr_runtime_entries(),
);
Ok(ssr_data_entry_chunk)
}
}
#[turbo_tasks::value_impl]
impl Endpoint for PageDataEndpoint {
#[turbo_tasks::function]
async fn write_to_disk(self_vc: PageDataEndpointVc) -> Result<WrittenEndpointVc> {
let this = self_vc.await?;
let ssr_data_chunk = self_vc.ssr_data_chunk();
emit_all_assets(
OutputAssetsVc::cell(vec![ssr_data_chunk]),
this.project.node_root(),
this.project.client_root().join("_next"),
this.project.node_root(),
)
.await?;
Ok(WrittenEndpoint {
server_entry_path: this
.project
.node_root()
.await?
.get_path_to(&*ssr_data_chunk.ident().path().await?)
.context("ssr data chunk entry path must be inside the node root")?
.to_string(),
server_paths: vec![],
}
.cell())
}
#[turbo_tasks::function]
async fn changed(self_vc: PageDataEndpointVc) -> Result<CompletionVc> {
let ssr_data_chunk = self_vc.ssr_data_chunk();
Ok(any_content_changed(ssr_data_chunk.into()))
}
}
#[turbo_tasks::value]
struct ApiEndpoint {
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
}
#[turbo_tasks::value_impl]
impl ApiEndpointVc {
#[turbo_tasks::function]
fn new(
project: ProjectVc,
pathname: StringVc,
original_name: StringVc,
path: FileSystemPathVc,
) -> Self {
ApiEndpoint {
project,
pathname,
original_name,
path,
}
.cell()
}
}
#[turbo_tasks::value_impl]
impl Endpoint for ApiEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> WrittenEndpointVc {
todo!()
}
#[turbo_tasks::function]
fn changed(&self) -> CompletionVc {
todo!()
}
}

View file

@ -0,0 +1,517 @@
use std::path::MAIN_SEPARATOR;
use anyhow::{Context, Result};
use indexmap::{map::Entry, IndexMap};
use next_core::{
app_structure::{find_app_dir, get_entrypoints},
mode::NextMode,
next_client::{
get_client_chunking_context, get_client_compile_time_info,
get_client_module_options_context, get_client_resolve_options_context,
get_client_runtime_entries, ClientContextType,
},
next_config::NextConfigVc,
next_dynamic::NextDynamicTransitionVc,
next_server::{
get_server_chunking_context, get_server_compile_time_info,
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
},
pages_structure::{find_pages_structure, PagesStructureVc},
util::NextSourceConfig,
};
use serde::{Deserialize, Serialize};
use turbo_tasks::{
debug::ValueDebugFormat, trace::TraceRawVcs, NothingVc, TaskInput, TransientValue,
TryJoinIterExt, Value,
};
use turbopack_binding::{
turbo::{
tasks_env::ProcessEnvVc,
tasks_fs::{
DiskFileSystemVc, FileSystem, FileSystemPathVc, FileSystemVc, VirtualFileSystemVc,
},
},
turbopack::{
build::BuildChunkingContextVc,
core::{
chunk::{ChunkingContext, EvaluatableAssetsVc},
compile_time_info::CompileTimeInfoVc,
context::AssetContextVc,
environment::ServerAddrVc,
PROJECT_FILESYSTEM_NAME,
},
dev::DevChunkingContextVc,
ecmascript::chunk::EcmascriptChunkingContextVc,
env::dotenv::load_env,
node::execution_context::ExecutionContextVc,
turbopack::{
evaluate_context::node_build_environment,
module_options::ModuleOptionsContextVc,
resolve_options_context::ResolveOptionsContextVc,
transition::{ContextTransitionVc, TransitionsByNameVc},
ModuleAssetContextVc,
},
},
};
use crate::{
app::app_entry_point_to_route,
pages::get_pages_routes,
route::{EndpointVc, Route},
};
#[derive(Debug, Serialize, Deserialize, Clone, TaskInput)]
#[serde(rename_all = "camelCase")]
pub struct ProjectOptions {
/// 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,
/// The contents of next.config.js, serialized to JSON.
pub next_config: String,
/// Whether to watch the filesystem for file changes.
pub watch: bool,
/// An upper bound of memory that turbopack will attempt to stay under.
pub memory_limit: Option<u64>,
}
#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat)]
pub struct Middleware {
pub endpoint: EndpointVc,
pub config: NextSourceConfig,
}
#[turbo_tasks::value]
pub struct Entrypoints {
pub routes: IndexMap<String, Route>,
pub middleware: Option<Middleware>,
}
#[turbo_tasks::value]
pub struct Project {
/// 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.
root_path: String,
/// A path inside the root_path which contains the app/pages directories.
project_path: String,
/// Whether to watch the filesystem for file changes.
watch: bool,
/// Next config.
next_config: NextConfigVc,
browserslist_query: String,
mode: NextMode,
}
#[turbo_tasks::value_impl]
impl ProjectVc {
#[turbo_tasks::function]
pub async fn new(options: ProjectOptions) -> Result<Self> {
let next_config = NextConfigVc::from_string(options.next_config);
Ok(Project {
root_path: options.root_path,
project_path: options.project_path,
watch: options.watch,
next_config,
browserslist_query: "last 1 Chrome versions, last 1 Firefox versions, last 1 Safari \
versions, last 1 Edge versions"
.to_string(),
mode: NextMode::Development,
}
.cell())
}
#[turbo_tasks::function]
async fn project_fs(self) -> Result<FileSystemVc> {
let this = self.await?;
let disk_fs = DiskFileSystemVc::new(
PROJECT_FILESYSTEM_NAME.to_string(),
this.root_path.to_string(),
);
if this.watch {
disk_fs.await?.start_watching_with_invalidation_reason()?;
}
Ok(disk_fs.into())
}
#[turbo_tasks::function]
async fn client_fs(self) -> Result<FileSystemVc> {
let virtual_fs = VirtualFileSystemVc::new();
Ok(virtual_fs.into())
}
#[turbo_tasks::function]
async fn node_fs(self) -> Result<FileSystemVc> {
let this = self.await?;
let disk_fs = DiskFileSystemVc::new("node".to_string(), this.project_path.clone());
disk_fs.await?.start_watching_with_invalidation_reason()?;
Ok(disk_fs.into())
}
#[turbo_tasks::function]
pub(super) fn node_root(self) -> FileSystemPathVc {
self.node_fs().root().join(".next")
}
#[turbo_tasks::function]
pub(super) fn client_root(self) -> FileSystemPathVc {
self.client_fs().root()
}
#[turbo_tasks::function]
fn project_root_path(self) -> FileSystemPathVc {
self.project_fs().root()
}
#[turbo_tasks::function]
async fn project_path(self) -> Result<FileSystemPathVc> {
let this = self.await?;
let root = self.project_root_path();
let project_relative = this.project_path.strip_prefix(&this.root_path).unwrap();
let project_relative = project_relative
.strip_prefix(MAIN_SEPARATOR)
.unwrap_or(project_relative)
.replace(MAIN_SEPARATOR, "/");
Ok(root.join(&project_relative))
}
#[turbo_tasks::function]
async fn pages_structure(self) -> Result<PagesStructureVc> {
let this: turbo_tasks::ReadRef<Project> = self.await?;
let next_router_fs = VirtualFileSystemVc::new().as_file_system();
let next_router_root = next_router_fs.root();
Ok(find_pages_structure(
self.project_path(),
next_router_root,
this.next_config.page_extensions(),
))
}
#[turbo_tasks::function]
fn env(self) -> ProcessEnvVc {
load_env(self.project_path())
}
#[turbo_tasks::function]
async fn next_config(self) -> Result<NextConfigVc> {
Ok(self.await?.next_config)
}
#[turbo_tasks::function]
fn execution_context(self) -> ExecutionContextVc {
let node_root = self.node_root();
let node_execution_chunking_context = DevChunkingContextVc::builder(
self.project_path(),
node_root,
node_root.join("chunks"),
node_root.join("assets"),
node_build_environment(),
)
.build()
.into();
ExecutionContextVc::new(
self.project_path(),
node_execution_chunking_context,
self.env(),
)
}
#[turbo_tasks::function]
async fn client_compile_time_info(self) -> Result<CompileTimeInfoVc> {
let this = self.await?;
Ok(get_client_compile_time_info(
this.mode,
&this.browserslist_query,
))
}
#[turbo_tasks::function]
async fn server_compile_time_info(self) -> Result<CompileTimeInfoVc> {
let this = self.await?;
Ok(get_server_compile_time_info(
this.mode,
self.env(),
// TODO(alexkirsz) Fill this out.
ServerAddrVc::empty(),
))
}
#[turbo_tasks::function]
async fn pages_dir(self) -> Result<FileSystemPathVc> {
Ok(if let Some(pages) = self.pages_structure().await?.pages {
pages.project_path()
} else {
self.project_path().join("pages")
})
}
#[turbo_tasks::function]
fn pages_transitions(self) -> TransitionsByNameVc {
TransitionsByNameVc::cell(
[(
"next-dynamic".to_string(),
NextDynamicTransitionVc::new(self.pages_client_transition()).into(),
)]
.into_iter()
.collect(),
)
}
#[turbo_tasks::function]
fn pages_client_transition(self) -> ContextTransitionVc {
ContextTransitionVc::new(
self.client_compile_time_info(),
self.pages_client_module_options_context(),
self.pages_client_resolve_options_context(),
)
}
#[turbo_tasks::function]
async fn pages_client_module_options_context(self) -> Result<ModuleOptionsContextVc> {
let this = self.await?;
Ok(get_client_module_options_context(
self.project_path(),
self.execution_context(),
self.client_compile_time_info().environment(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
))
}
#[turbo_tasks::function]
async fn pages_client_resolve_options_context(self) -> Result<ResolveOptionsContextVc> {
let this = self.await?;
Ok(get_client_resolve_options_context(
self.project_path(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
self.execution_context(),
))
}
#[turbo_tasks::function]
pub(super) fn pages_client_module_context(self) -> AssetContextVc {
ModuleAssetContextVc::new(
self.pages_transitions(),
self.client_compile_time_info(),
self.pages_client_module_options_context(),
self.pages_client_resolve_options_context(),
)
.into()
}
#[turbo_tasks::function]
pub(super) fn pages_ssr_module_context(self) -> AssetContextVc {
ModuleAssetContextVc::new(
self.pages_transitions(),
self.server_compile_time_info(),
self.pages_ssr_module_options_context(),
self.pages_ssr_resolve_options_context(),
)
.into()
}
#[turbo_tasks::function]
pub(super) fn pages_ssr_data_module_context(self) -> AssetContextVc {
ModuleAssetContextVc::new(
self.pages_transitions(),
self.server_compile_time_info(),
self.pages_ssr_data_module_options_context(),
self.pages_ssr_resolve_options_context(),
)
.into()
}
#[turbo_tasks::function]
async fn pages_ssr_module_options_context(self) -> Result<ModuleOptionsContextVc> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project_path(),
self.execution_context(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
))
}
#[turbo_tasks::function]
async fn pages_ssr_data_module_options_context(self) -> Result<ModuleOptionsContextVc> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project_path(),
self.execution_context(),
Value::new(ServerContextType::PagesData {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
))
}
#[turbo_tasks::function]
async fn pages_ssr_resolve_options_context(self) -> Result<ResolveOptionsContextVc> {
let this = self.await?;
Ok(get_server_resolve_options_context(
self.project_path(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
self.execution_context(),
))
}
#[turbo_tasks::function]
pub(super) async fn pages_client_runtime_entries(self) -> Result<EvaluatableAssetsVc> {
let this = self.await?;
let client_runtime_entries = get_client_runtime_entries(
self.project_path(),
self.env(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
self.execution_context(),
);
Ok(client_runtime_entries.resolve_entries(self.pages_client_module_context()))
}
#[turbo_tasks::function]
pub(super) async fn pages_ssr_runtime_entries(self) -> Result<EvaluatableAssetsVc> {
let this = self.await?;
let ssr_runtime_entries = get_server_runtime_entries(
self.project_path(),
self.env(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.next_config(),
);
Ok(ssr_runtime_entries.resolve_entries(self.pages_ssr_module_context()))
}
#[turbo_tasks::function]
pub(super) async fn client_chunking_context(self) -> Result<EcmascriptChunkingContextVc> {
let this = self.await?;
Ok(get_client_chunking_context(
self.project_path(),
self.client_root(),
self.client_compile_time_info().environment(),
this.mode,
))
}
#[turbo_tasks::function]
pub(super) fn server_chunking_context(self) -> BuildChunkingContextVc {
get_server_chunking_context(
self.project_path(),
self.node_root(),
self.client_fs().root(),
self.server_compile_time_info().environment(),
)
}
#[turbo_tasks::function]
pub(super) async fn ssr_chunking_context(self) -> Result<BuildChunkingContextVc> {
let ssr_chunking_context = self.server_chunking_context().with_layer("ssr");
BuildChunkingContextVc::resolve_from(ssr_chunking_context)
.await?
.context("with_layer should not change the type of the chunking context")
}
#[turbo_tasks::function]
pub(super) async fn ssr_data_chunking_context(self) -> Result<BuildChunkingContextVc> {
let ssr_chunking_context = self.server_chunking_context().with_layer("ssr data");
BuildChunkingContextVc::resolve_from(ssr_chunking_context)
.await?
.context("with_layer should not change the type of the chunking context")
}
#[turbo_tasks::function]
pub(super) async fn rsc_chunking_context(self) -> Result<BuildChunkingContextVc> {
let rsc_chunking_context = self.server_chunking_context().with_layer("rsc");
BuildChunkingContextVc::resolve_from(rsc_chunking_context)
.await?
.context("with_layer should not change the type of the chunking context")
}
/// Scans the app/pages directories for entry points files (matching the
/// provided page_extensions).
#[turbo_tasks::function]
pub async fn entrypoints(self) -> Result<EntrypointsVc> {
let this = self.await?;
let mut routes = IndexMap::new();
if let Some(app_dir) = *find_app_dir(self.project_path()).await? {
let app_entrypoints = get_entrypoints(app_dir, this.next_config.page_extensions());
routes.extend(
app_entrypoints
.await?
.iter()
.map(|(pathname, app_entrypoint)| async {
Ok((
pathname.clone(),
*app_entry_point_to_route(*app_entrypoint).await?,
))
})
.try_join()
.await?,
);
}
for (pathname, page_route) in get_pages_routes(self, self.pages_structure()).await?.iter() {
match routes.entry(pathname.clone()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() = Route::Conflict;
}
Entry::Vacant(entry) => {
entry.insert(*page_route);
}
}
}
// TODO middleware
Ok(Entrypoints {
routes,
middleware: None,
}
.cell())
}
/// Emits opaque HMR events whenever a change is detected in the chunk group
/// internally known as `identifier`.
#[turbo_tasks::function]
pub fn hmr_events(self, _identifier: String, _sender: TransientValue<()>) -> NothingVc {
NothingVc::new()
}
}
#[turbo_tasks::function]
async fn project_fs(project_dir: &str, watching: bool) -> Result<FileSystemVc> {
let disk_fs =
DiskFileSystemVc::new(PROJECT_FILESYSTEM_NAME.to_string(), project_dir.to_string());
if watching {
disk_fs.await?.start_watching_with_invalidation_reason()?;
}
Ok(disk_fs.into())
}

View file

@ -0,0 +1,43 @@
use anyhow::Result;
use indexmap::IndexMap;
use turbo_tasks::CompletionVc;
#[turbo_tasks::value(shared)]
#[derive(Copy, Clone, Debug)]
pub enum Route {
Page {
html_endpoint: EndpointVc,
data_endpoint: EndpointVc,
},
PageApi {
endpoint: EndpointVc,
},
AppPage {
html_endpoint: EndpointVc,
rsc_endpoint: EndpointVc,
},
AppRoute {
endpoint: EndpointVc,
},
Conflict,
}
#[turbo_tasks::value_trait]
pub trait Endpoint {
fn write_to_disk(&self) -> WrittenEndpointVc;
fn changed(&self) -> CompletionVc;
}
#[turbo_tasks::value(shared)]
#[derive(Debug)]
pub struct WrittenEndpoint {
/// Relative to the root_path
pub server_entry_path: String,
/// Relative to the root_path
pub server_paths: Vec<String>,
}
/// The routes as map from pathname to route. (pathname includes the leading
/// slash)
#[turbo_tasks::value(transparent)]
pub struct Routes(IndexMap<String, Route>);

View file

@ -68,7 +68,11 @@ pub async fn get_page_entries(
server_compile_time_info: CompileTimeInfoVc,
next_config: NextConfigVc,
) -> Result<PageEntriesVc> {
let pages_structure = find_pages_structure(project_root, next_router_root, next_config);
let pages_structure = find_pages_structure(
project_root,
next_router_root,
next_config.page_extensions(),
);
let pages_dir = if let Some(pages) = pages_structure.await?.pages {
pages.project_path().resolve().await?

View file

@ -10,8 +10,8 @@
"check": "tsc --noEmit"
},
"dependencies": {
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2",
"@vercel/turbopack-node": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.2",
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3",
"@vercel/turbopack-node": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.3",
"anser": "^2.1.1",
"css.escape": "^1.5.1",
"next": "*",

View file

@ -16,10 +16,6 @@ const loadNextConfig = async (silent) => {
const customRoutes = await loadCustomRoutes(nextConfig)
nextConfig.headers = customRoutes.headers
nextConfig.rewrites = customRoutes.rewrites
nextConfig.redirects = customRoutes.redirects
// TODO: these functions takes arguments, have to be supported in a different way
nextConfig.exportPathMap = nextConfig.exportPathMap && {}
nextConfig.webpack = nextConfig.webpack && {}
@ -30,7 +26,10 @@ const loadNextConfig = async (silent) => {
)
}
return nextConfig
return {
customRoutes: customRoutes,
config: nextConfig,
}
}
export { loadNextConfig as default }

View file

@ -12,7 +12,7 @@ use turbo_tasks::{
debug::ValueDebugFormat,
primitives::{StringVc, StringsVc},
trace::TraceRawVcs,
CompletionVc, CompletionsVc,
CompletionVc, CompletionsVc, TaskInput, ValueToString,
};
use turbopack_binding::{
turbo::tasks_fs::{DirectoryContent, DirectoryEntry, FileSystemEntryType, FileSystemPathVc},
@ -297,11 +297,11 @@ fn match_metadata_file<'a>(
#[turbo_tasks::function]
async fn get_directory_tree(
app_dir: FileSystemPathVc,
dir: FileSystemPathVc,
page_extensions: StringsVc,
) -> Result<DirectoryTreeVc> {
let DirectoryContent::Entries(entries) = &*app_dir.read_dir().await? else {
bail!("app_dir must be a directory")
let DirectoryContent::Entries(entries) = &*dir.read_dir().await? else {
bail!("{} must be a directory", dir.to_string().await?);
};
let page_extensions_value = page_extensions.await?;
@ -451,7 +451,16 @@ async fn merge_loader_trees(
}
#[derive(
Copy, Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, Debug,
Copy,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
TraceRawVcs,
ValueDebugFormat,
Debug,
TaskInput,
)]
pub enum Entrypoint {
AppPage { loader_tree: LoaderTreeVc },

View file

@ -0,0 +1,97 @@
use anyhow::Result;
use turbo_tasks::{
graph::{AdjacencyMap, GraphTraversal},
CompletionVc, CompletionsVc, TryJoinIterExt,
};
use turbo_tasks_fs::{rebase, FileSystemPathVc};
use turbopack_binding::turbopack::core::{
asset::Asset,
output::{OutputAssetVc, OutputAssetsVc},
reference::AssetReference,
};
/// Emits all assets transitively reachable from the given chunks, that are
/// inside the node root or the client root.
#[turbo_tasks::function]
pub async fn emit_all_assets(
assets: OutputAssetsVc,
node_root: FileSystemPathVc,
client_relative_path: FileSystemPathVc,
client_output_path: FileSystemPathVc,
) -> Result<CompletionVc> {
let all_assets = all_assets_from_entries(assets).await?;
Ok(CompletionsVc::all(
all_assets
.iter()
.copied()
.map(|asset| async move {
if asset.ident().path().await?.is_inside(&*node_root.await?) {
return Ok(emit(asset));
} else if asset
.ident()
.path()
.await?
.is_inside(&*client_relative_path.await?)
{
// Client assets are emitted to the client output path, which is prefixed with
// _next. We need to rebase them to remove that prefix.
return Ok(emit_rebase(asset, client_relative_path, client_output_path));
}
Ok(CompletionVc::immutable())
})
.try_join()
.await?,
))
}
#[turbo_tasks::function]
fn emit(asset: OutputAssetVc) -> CompletionVc {
asset.content().write(asset.ident().path())
}
#[turbo_tasks::function]
fn emit_rebase(asset: OutputAssetVc, from: FileSystemPathVc, to: FileSystemPathVc) -> CompletionVc {
asset
.content()
.write(rebase(asset.ident().path(), from, to))
}
/// Walks the asset graph from multiple assets and collect all referenced
/// assets.
#[turbo_tasks::function]
async fn all_assets_from_entries(entries: OutputAssetsVc) -> Result<OutputAssetsVc> {
Ok(OutputAssetsVc::cell(
AdjacencyMap::new()
.skip_duplicates()
.visit(entries.await?.iter().copied(), get_referenced_assets)
.await
.completed()?
.into_inner()
.into_reverse_topological()
.collect(),
))
}
/// Computes the list of all chunk children of a given chunk.
async fn get_referenced_assets(
asset: OutputAssetVc,
) -> Result<impl Iterator<Item = OutputAssetVc> + Send> {
Ok(asset
.references()
.await?
.iter()
.map(|reference| async move {
let primary_assets = reference.resolve_reference().primary_assets().await?;
Ok(primary_assets.clone_value())
})
.try_join()
.await?
.into_iter()
.flatten()
.map(|asset| async move { Ok(OutputAssetVc::resolve_from(asset).await?) })
.try_join()
.await?
.into_iter()
.flatten())
}

View file

@ -11,6 +11,7 @@ pub mod app_structure;
mod babel;
mod bootstrap;
mod embed_js;
mod emit;
pub mod env;
mod fallback;
pub mod loader_tree;
@ -41,10 +42,11 @@ mod runtime;
mod sass;
mod transform_options;
pub mod url_node;
mod util;
pub mod util;
mod web_entry_source;
pub use app_source::create_app_source;
pub use emit::emit_all_assets;
pub use next_app::unsupported_dynamic_metadata_issue::{
UnsupportedDynamicMetadataIssue, UnsupportedDynamicMetadataIssueVc,
};

View file

@ -26,7 +26,7 @@ use turbopack_binding::{
use crate::{
embed_js::next_js_file,
next_config::{NextConfigVc, RewritesReadRef},
next_config::{RewritesReadRef, RewritesVc},
util::get_asset_path_from_pathname,
};
@ -35,7 +35,7 @@ use crate::{
#[turbo_tasks::value(shared)]
pub struct DevManifestContentSource {
pub page_roots: Vec<ContentSourceVc>,
pub next_config: NextConfigVc,
pub rewrites: RewritesVc,
}
#[turbo_tasks::value_impl]
@ -124,7 +124,7 @@ impl DevManifestContentSourceVc {
.collect();
let manifest = BuildManifest {
rewrites: this.next_config.rewrites().await?,
rewrites: this.rewrites.await?,
sorted_pages,
routes,
};

View file

@ -45,6 +45,34 @@ use turbopack_binding::{
use crate::{embed_js::next_asset, next_shared::transforms::ModularizeImportPackageConfig};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct NextConfigAndCustomRoutesRaw {
config: NextConfig,
custom_routes: CustomRoutesRaw,
}
#[turbo_tasks::value]
struct NextConfigAndCustomRoutes {
config: NextConfigVc,
custom_routes: CustomRoutesVc,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CustomRoutesRaw {
rewrites: Rewrites,
// unsupported
headers: Vec<Header>,
redirects: Vec<Redirect>,
}
#[turbo_tasks::value]
struct CustomRoutes {
rewrites: RewritesVc,
}
#[turbo_tasks::value(serialization = "custom", eq = "manual")]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -57,7 +85,6 @@ pub struct NextConfig {
pub images: ImageConfig,
pub page_extensions: Vec<String>,
pub react_strict_mode: Option<bool>,
pub rewrites: Rewrites,
pub transpile_packages: Option<Vec<String>>,
pub modularize_imports: Option<IndexMap<String, ModularizeImportPackageConfig>>,
sass_options: Option<serde_json::Value>,
@ -84,7 +111,6 @@ pub struct NextConfig {
// this is a function in js land
generate_build_id: Option<serde_json::Value>,
generate_etags: bool,
headers: Vec<Header>,
http_agent_options: HttpAgentConfig,
i18n: Option<I18NConfig>,
on_demand_entries: OnDemandEntriesConfig,
@ -93,7 +119,6 @@ pub struct NextConfig {
powered_by_header: bool,
production_browser_source_maps: bool,
public_runtime_config: IndexMap<String, serde_json::Value>,
redirects: Vec<Redirect>,
server_runtime_config: IndexMap<String, serde_json::Value>,
static_page_generation_timeout: f64,
swc_minify: bool,
@ -486,6 +511,13 @@ pub enum RemoveConsoleConfig {
#[turbo_tasks::value_impl]
impl NextConfigVc {
#[turbo_tasks::function]
pub fn from_string(s: String) -> Result<Self> {
let config: NextConfig = serde_json::from_str(&s)
.with_context(|| format!("failed to parse next.config.js: {}", s))?;
Ok(config.cell())
}
#[turbo_tasks::function]
pub async fn server_component_externals(self) -> Result<StringsVc> {
Ok(StringsVc::cell(
@ -545,11 +577,6 @@ impl NextConfigVc {
Ok(StringsVc::cell(self.await?.page_extensions.clone()))
}
#[turbo_tasks::function]
pub async fn rewrites(self) -> Result<RewritesVc> {
Ok(self.await?.rewrites.clone().cell())
}
#[turbo_tasks::function]
pub async fn transpile_packages(self) -> Result<StringsVc> {
Ok(StringsVc::cell(
@ -646,22 +673,41 @@ fn next_configs() -> StringsVc {
#[turbo_tasks::function]
pub async fn load_next_config(execution_context: ExecutionContextVc) -> Result<NextConfigVc> {
Ok(load_config_and_custom_routes(execution_context)
.await?
.config)
}
#[turbo_tasks::function]
pub async fn load_rewrites(execution_context: ExecutionContextVc) -> Result<RewritesVc> {
Ok(load_config_and_custom_routes(execution_context)
.await?
.custom_routes
.await?
.rewrites)
}
#[turbo_tasks::function]
async fn load_config_and_custom_routes(
execution_context: ExecutionContextVc,
) -> Result<NextConfigAndCustomRoutesVc> {
let ExecutionContext { project_path, .. } = *execution_context.await?;
let find_config_result = find_context_file(project_path, next_configs());
let config_file = match &*find_config_result.await? {
FindContextFileResult::Found(config_path, _) => Some(*config_path),
FindContextFileResult::NotFound(_) => None,
};
load_next_config_internal(execution_context, config_file)
load_next_config_and_custom_routes_internal(execution_context, config_file)
.issue_context(config_file, "Loading Next.js config")
.await
}
#[turbo_tasks::function]
pub async fn load_next_config_internal(
async fn load_next_config_and_custom_routes_internal(
execution_context: ExecutionContextVc,
config_file: Option<FileSystemPathVc>,
) -> Result<NextConfigVc> {
) -> Result<NextConfigAndCustomRoutesVc> {
let ExecutionContext {
project_path,
chunking_context,
@ -709,11 +755,24 @@ pub async fn load_next_config_internal(
.await
.context("Evaluation of Next.js config failed")?
else {
return Ok(NextConfig::default().cell());
return Ok(NextConfigAndCustomRoutes {
config: NextConfig::default().cell(),
custom_routes: CustomRoutes {
rewrites: Rewrites::default().cell(),
}
.cell(),
}
.cell());
};
let next_config: NextConfig = parse_json_with_source_context(val.to_str()?)?;
let next_config_and_custom_routes: NextConfigAndCustomRoutesRaw =
parse_json_with_source_context(val.to_str()?)?;
if let Some(turbo) = next_config.experimental.turbo.as_ref() {
if let Some(turbo) = next_config_and_custom_routes
.config
.experimental
.turbo
.as_ref()
{
if turbo.loaders.is_some() {
OutdatedConfigIssue {
path: config_file.unwrap_or(project_path),
@ -730,7 +789,14 @@ Example: loaders: { \".mdx\": [\"mdx-loader\"] } -> rules: { \"*.mdx\": [\"mdx-l
}
}
Ok(next_config.cell())
Ok(NextConfigAndCustomRoutes {
config: next_config_and_custom_routes.config.cell(),
custom_routes: CustomRoutes {
rewrites: next_config_and_custom_routes.custom_routes.rewrites.cell(),
}
.cell(),
}
.cell())
}
#[turbo_tasks::function]

View file

@ -5,7 +5,7 @@ use turbopack_binding::turbo::tasks_fs::{
DirectoryContent, DirectoryEntry, FileSystemEntryType, FileSystemPathVc,
};
use crate::{embed_js::next_js_file_path, next_config::NextConfigVc};
use crate::embed_js::next_js_file_path;
/// A final route in the pages directory.
#[turbo_tasks::value]
@ -125,7 +125,7 @@ impl PagesDirectoryStructureVc {
pub async fn find_pages_structure(
project_root: FileSystemPathVc,
next_router_root: FileSystemPathVc,
next_config: NextConfigVc,
page_extensions: StringsVc,
) -> Result<PagesStructureVc> {
let pages_root = project_root.join("pages");
let pages_root: FileSystemPathOptionVc = FileSystemPathOptionVc::cell(
@ -149,7 +149,7 @@ pub async fn find_pages_structure(
Ok(get_pages_structure_for_root_directory(
pages_root,
next_router_root,
next_config.page_extensions(),
page_extensions,
))
}

View file

@ -25,7 +25,7 @@ use next_core::{
manifest::DevManifestContentSource,
mode::NextMode,
next_client::{get_client_chunking_context, get_client_compile_time_info},
next_config::load_next_config,
next_config::{load_next_config, load_rewrites},
next_image::NextImageContentSourceVc,
pages_structure::find_pages_structure,
router_source::NextRouterContentSourceVc,
@ -335,7 +335,9 @@ async fn source(
ExecutionContextVc::new(project_path, build_chunking_context.into(), env);
let mode = NextMode::Development;
let next_config = load_next_config(execution_context.with_layer("next_config"));
let next_config_execution_context = execution_context.with_layer("next_config");
let next_config = load_next_config(next_config_execution_context);
let rewrites = load_rewrites(next_config_execution_context);
let output_root = output_fs.root().join(".next/server");
@ -367,7 +369,8 @@ async fn source(
client_compile_time_info.environment(),
mode,
);
let pages_structure = find_pages_structure(project_path, dev_server_root, next_config);
let pages_structure =
find_pages_structure(project_path, dev_server_root, next_config.page_extensions());
let page_source = create_page_source(
pages_structure,
project_path,
@ -402,7 +405,7 @@ async fn source(
StaticAssetsContentSourceVc::new(String::new(), project_path.join("public")).into();
let manifest_source = DevManifestContentSource {
page_roots: vec![page_source],
next_config,
rewrites,
}
.cell()
.into();

View file

@ -939,7 +939,7 @@ export default async function build(
ignore: [] as string[],
}))
let binding = (await loadBindings()) as any
let binding = await loadBindings()
async function turbopackBuild() {
const turboNextBuildStart = process.hrtime()

View file

@ -9,6 +9,8 @@ import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure'
import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
import { downloadWasmSwc } from '../../lib/download-wasm-swc'
import { spawn } from 'child_process'
import { NextConfigComplete, TurboLoaderItem } from '../../server/config-shared'
import { isDeepStrictEqual } from 'util'
const nextVersion = process.env.__NEXT_VERSION as string
@ -88,7 +90,38 @@ let swcHeapProfilerFlushGuard: any
let swcCrashReporterFlushGuard: any
export const lockfilePatchPromise: { cur?: Promise<void> } = {}
export async function loadBindings(): Promise<any> {
export interface Binding {
isWasm: boolean
turbo: {
startDev: any
startTrace: any
nextBuild?: any
createTurboTasks?: any
entrypoints: {
stream: any
get: any
}
mdx: {
compile: any
compileSync: any
}
createProject: (options: ProjectOptions) => Promise<Project>
}
minify: any
minifySync: any
transform: any
transformSync: any
parse: any
parseSync: any
getTargetTriple(): string | undefined
initCustomTraceSubscriber?: any
teardownTraceSubscriber?: any
initHeapProfiler?: any
teardownHeapProfiler?: any
teardownCrashReporter?: any
}
export async function loadBindings(): Promise<Binding> {
if (pendingBindings) {
return pendingBindings
}
@ -236,6 +269,379 @@ function logLoadFailure(attempts: any, triedWasm = false) {
})
}
interface ProjectOptions {
/**
* 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.
*/
rootPath: string
/**
* A path inside the root_path which contains the app/pages directories.
*/
projectPath: string
/**
* The next.config.js contents.
*/
nextConfig: NextConfigComplete
/**
* Whether to watch he filesystem for file changes.
*/
watch: boolean
/**
* An upper bound of memory that turbopack will attempt to stay under.
*/
memoryLimit?: number
}
interface Issue {}
interface Diagnostics {}
type TurbopackResult<T = {}> = T & {
issues: Issue[]
diagnostics: Diagnostics[]
}
interface Middleware {
endpoint: Endpoint
runtime: 'nodejs' | 'edge'
matcher?: string[]
}
interface Entrypoints {
routes: Map<string, Route>
middleware?: Middleware
}
interface Project {
entrypointsSubscribe(): AsyncIterableIterator<TurbopackResult<Entrypoints>>
}
type Route =
| {
type: 'conflict'
}
| {
type: 'app-page'
htmlEndpoint: Endpoint
rscEndpoint: Endpoint
}
| {
type: 'app-route'
endpoint: Endpoint
}
| {
type: 'page'
htmlEndpoint: Endpoint
dataEndpoint: Endpoint
}
| {
type: 'page-api'
endpoint: Endpoint
}
interface Endpoint {
/** Write files for the endpoint to disk. */
writeToDisk(): Promise<TurbopackResult<WrittenEndpoint>>
/**
* Listen to changes to the endpoint.
* After changed() has been awaited it will listen to changes.
* The async iterator will yield for each change.
*/
changed(): Promise<AsyncIterableIterator<TurbopackResult>>
}
interface EndpointConfig {
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
dynamicParams?: boolean
revalidate?: 'never' | 'force-cache' | number
fetchCache?:
| 'auto'
| 'default-cache'
| 'only-cache'
| 'force-cache'
| 'default-no-store'
| 'only-no-store'
| 'force-no-store'
runtime?: 'nodejs' | 'edge'
preferredRegion?: string
}
interface WrittenEndpoint {
/** The entry path for the endpoint. */
entryPath: string
/** All paths that has been written for the endpoint. */
paths: string[]
config: EndpointConfig
}
// TODO(sokra) Support wasm option.
function bindingToApi(binding: any, _wasm: boolean) {
type NativeFunction<T> = (
callback: (err: Error, value: T) => void
) => Promise<{ __napiType: 'RootTask' }>
/**
* Utility function to ensure all variants of an enum are handled.
*/
function invariant(
never: never,
computeMessage: (arg: any) => string
): never {
throw new Error(`Invariant: ${computeMessage(never)}`)
}
/**
* Calls a native function and streams the result.
* If useBuffer is true, all values will be preserved, potentially buffered
* if consumed slower than produced. Else, only the latest value will be
* preserved.
*/
function subscribe<T>(
useBuffer: boolean,
nativeFunction: NativeFunction<T>
): AsyncIterableIterator<T> {
type BufferItem =
| { err: Error; value: undefined }
| { err: undefined; value: T }
// A buffer of produced items. This will only contain values if the
// consumer is slower than the producer.
let buffer: BufferItem[] = []
// A deferred value waiting for the next produced item. This will only
// exist if the consumer is faster than the producer.
let waiting:
| {
resolve: (value: T) => void
reject: (error: Error) => void
}
| undefined
// The native function will call this every time it emits a new result. We
// either need to notify a waiting consumer, or buffer the new result until
// the consumer catches up.
const emitResult = (err: Error | undefined, value: T | undefined) => {
if (waiting) {
let { resolve, reject } = waiting
waiting = undefined
if (err) reject(err)
else resolve(value!)
} else {
const item = { err, value } as BufferItem
if (useBuffer) buffer.push(item)
else buffer[0] = item
}
}
return (async function* () {
const task = await nativeFunction(emitResult)
try {
while (true) {
if (buffer.length > 0) {
const item = buffer.shift()!
if (item.err) throw item.err
yield item.value
} else {
// eslint-disable-next-line no-loop-func
yield new Promise<T>((resolve, reject) => {
waiting = { resolve, reject }
})
}
}
} finally {
binding.rootTaskDispose(task)
}
})()
}
class ProjectImpl implements Project {
private _nativeProject: { __napiType: 'Project' }
constructor(nativeProject: { __napiType: 'Project' }) {
this._nativeProject = nativeProject
}
entrypointsSubscribe() {
type NapiEndpoint = { __napiType: 'Endpoint' }
type NapiEntrypoints = {
routes: NapiRoute[]
middleware?: NapiMiddleware
issues: Issue[]
diagnostics: Diagnostics[]
}
type NapiMiddleware = {
endpoint: NapiEndpoint
runtime: 'nodejs' | 'edge'
matcher?: string[]
}
type NapiRoute = {
pathname: string
} & (
| {
type: 'page'
htmlEndpoint: NapiEndpoint
dataEndpoint: NapiEndpoint
}
| {
type: 'page-api'
endpoint: NapiEndpoint
}
| {
type: 'app-page'
htmlEndpoint: NapiEndpoint
rscEndpoint: NapiEndpoint
}
| {
type: 'app-route'
endpoint: NapiEndpoint
}
| {
type: 'conflict'
}
)
const subscription = subscribe<NapiEntrypoints>(false, async (callback) =>
binding.projectEntrypointsSubscribe(await this._nativeProject, callback)
)
return (async function* () {
for await (const entrypoints of subscription) {
const routes = new Map()
for (const { pathname, ...nativeRoute } of entrypoints.routes) {
let route: Route
switch (nativeRoute.type) {
case 'page':
route = {
type: 'page',
htmlEndpoint: new EndpointImpl(nativeRoute.htmlEndpoint),
dataEndpoint: new EndpointImpl(nativeRoute.dataEndpoint),
}
break
case 'page-api':
route = {
type: 'page-api',
endpoint: new EndpointImpl(nativeRoute.endpoint),
}
break
case 'app-page':
route = {
type: 'app-page',
htmlEndpoint: new EndpointImpl(nativeRoute.htmlEndpoint),
rscEndpoint: new EndpointImpl(nativeRoute.rscEndpoint),
}
break
case 'app-route':
route = {
type: 'app-route',
endpoint: new EndpointImpl(nativeRoute.endpoint),
}
break
case 'conflict':
route = {
type: 'conflict',
}
break
default:
invariant(
nativeRoute,
() => `Unknown route type: ${(nativeRoute as any).type}`
)
break
}
routes.set(pathname, route)
}
const napiMiddlewareToMiddleware = (middleware: NapiMiddleware) => ({
endpoint: new EndpointImpl(middleware.endpoint),
runtime: middleware.runtime,
matcher: middleware.matcher,
})
const middleware = entrypoints.middleware
? napiMiddlewareToMiddleware(entrypoints.middleware)
: undefined
yield {
routes,
middleware,
issues: entrypoints.issues,
diagnostics: entrypoints.diagnostics,
}
}
})()
}
}
class EndpointImpl implements Endpoint {
private _nativeEndpoint: { __napiType: 'Endpoint' }
constructor(nativeEndpoint: { __napiType: 'Endpoint' }) {
this._nativeEndpoint = nativeEndpoint
}
async writeToDisk(): Promise<TurbopackResult<WrittenEndpoint>> {
return await binding.endpointWriteToDisk(this._nativeEndpoint)
}
async changed(): Promise<AsyncIterableIterator<TurbopackResult>> {
const iter = subscribe<TurbopackResult>(false, async (callback) =>
binding.endpointChangedSubscribe(await this._nativeEndpoint, callback)
)
await iter.next()
return iter
}
}
async function serializeNextConfig(
nextConfig: NextConfigComplete
): Promise<string> {
let nextConfigSerializable = nextConfig as any
nextConfigSerializable.generateBuildId =
await nextConfig.generateBuildId?.()
// TODO: these functions takes arguments, have to be supported in a different way
nextConfigSerializable.exportPathMap = {}
nextConfigSerializable.webpack = nextConfig.webpack && {}
if (nextConfig.experimental?.turbo?.loaders) {
ensureLoadersHaveSerializableOptions(
nextConfig.experimental.turbo.loaders
)
}
return JSON.stringify(nextConfigSerializable)
}
function ensureLoadersHaveSerializableOptions(
turbopackLoaders: Record<string, TurboLoaderItem[]>
) {
for (const [ext, loaderItems] of Object.entries(turbopackLoaders)) {
for (const loaderItem of loaderItems) {
if (
typeof loaderItem !== 'string' &&
!isDeepStrictEqual(loaderItem, JSON.parse(JSON.stringify(loaderItem)))
) {
throw new Error(
`loader ${loaderItem.loader} for match "${ext}" does not have serializable options. Ensure that options passed are plain JavaScript objects and values.`
)
}
}
}
}
async function createProject(options: ProjectOptions) {
const optionsForRust = options as any
optionsForRust.nextConfig = await serializeNextConfig(options.nextConfig)
return new ProjectImpl(await binding.projectNew(optionsForRust))
}
return createProject
}
async function loadWasm(importPath = '', isCustomTurbopack: boolean) {
if (wasmBindings) {
return wasmBindings
@ -314,9 +720,6 @@ async function loadWasm(importPath = '', isCustomTurbopack: boolean) {
startTrace: () => {
Log.error('Wasm binding does not support trace yet')
},
experimentalTurbo: () => {
Log.error('Wasm binding does not support this interface')
},
entrypoints: {
stream: (
turboTasks: any,
@ -577,12 +980,6 @@ function loadNative(isCustomTurbopack = false) {
)
return ret
},
experimentalTurbo: () => {
initHeapProfiler()
const ret = bindings.experimentalTurbo()
return ret
},
createTurboTasks: (memoryLimit?: number): unknown =>
bindings.createTurboTasks(memoryLimit),
entrypoints: {
@ -615,6 +1012,7 @@ function loadNative(isCustomTurbopack = false) {
)
},
},
createProject: bindingToApi(bindings, false),
},
mdx: {
compile: (src: string, options: any) =>

View file

@ -218,7 +218,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
// startTrace existed and callable
if (this.turbotrace) {
let binding = (await loadBindings()) as any
let binding = await loadBindings()
if (
!binding?.isWasm &&
typeof binding.turbo.startTrace === 'function'
@ -475,7 +475,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
})
// startTrace existed and callable
if (this.turbotrace) {
let binding = (await loadBindings()) as any
let binding = await loadBindings()
if (
!binding?.isWasm &&
typeof binding.turbo.startTrace === 'function'
@ -825,7 +825,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
if (this.turbotrace) {
compiler.hooks.afterEmit.tapPromise(PLUGIN_NAME, async () => {
let binding = (await loadBindings()) as any
let binding = await loadBindings()
if (
!binding?.isWasm &&
typeof binding.turbo.startTrace === 'function'

View file

@ -211,7 +211,6 @@ const nextDev: CliCommand = async (argv) => {
// We do not set a default host value here to prevent breaking
// some set-ups that rely on listening on other interfaces
const host = args['--hostname']
const experimentalTurbo = args['--experimental-turbo']
const devServerOptions: StartServerOptions = {
dir,
@ -221,14 +220,64 @@ const nextDev: CliCommand = async (argv) => {
hostname: host,
// This is required especially for app dir.
useWorkers: true,
isExperimentalTurbo: experimentalTurbo,
}
if (args['--turbo']) {
process.env.TURBOPACK = '1'
}
if (args['--experimental-turbo']) {
process.env.EXPERIMENTAL_TURBOPACK = '1'
}
const experimentalTurbo = !!process.env.EXPERIMENTAL_TURBOPACK
if (process.env.TURBOPACK) {
if (experimentalTurbo) {
const { loadBindings } =
require('../build/swc') as typeof import('../build/swc')
resetEnv()
let bindings = await loadBindings()
const config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
dir,
undefined,
undefined,
true
)
// Just testing code here:
const project = await bindings.turbo.createProject({
projectPath: dir,
rootPath: dir,
nextConfig: config,
watch: true,
})
const iter = project.entrypointsSubscribe()
try {
for await (const entrypoints of iter) {
for (const [pathname, route] of entrypoints.routes) {
switch (route.type) {
case 'page': {
Log.info(`writing ${pathname} to disk`)
const written = await route.htmlEndpoint.writeToDisk()
Log.info(written)
break
}
default:
Log.info(`skipping ${pathname} (${route.type})`)
break
}
}
}
} catch (e) {
console.error(e)
}
Log.error('Not supported yet')
process.exit(1)
} else if (process.env.TURBOPACK) {
isTurboSession = true
const { validateTurboNextConfig } =
@ -297,7 +346,8 @@ const nextDev: CliCommand = async (argv) => {
// Turbopack need to be in control over reading the .env files and watching them.
// So we need to start with a initial env to know which env vars are coming from the user.
resetEnv()
let bindings: any = await loadBindings()
let bindings = await loadBindings()
let server = bindings.turbo.startDev({
...devServerOptions,
showAll: args['--show-all'] ?? false,
@ -318,11 +368,6 @@ const nextDev: CliCommand = async (argv) => {
return server
} else {
if (experimentalTurbo) {
Log.error('Not supported yet')
process.exit(1)
}
let cleanupFns: (() => Promise<void> | void)[] = []
const runDevServer = async () => {
const oldCleanupFns = cleanupFns

View file

@ -85,7 +85,7 @@ type JSONValue =
| JSONValue[]
| { [k: string]: JSONValue }
type TurboLoaderItem =
export type TurboLoaderItem =
| string
| {
loader: string
@ -93,7 +93,7 @@ type TurboLoaderItem =
options: Record<string, JSONValue>
}
interface ExperimentalTurboOptions {
export interface ExperimentalTurboOptions {
/**
* (`next --turbo` only) A mapping of aliased imports to modules to load in their place.
*

View file

@ -22,7 +22,6 @@ export interface StartServerOptions {
useWorkers: boolean
allowRetry?: boolean
isTurbopack?: boolean
isExperimentalTurbo?: boolean
keepAliveTimeout?: number
onStdout?: (data: any) => void
onStderr?: (data: any) => void

View file

@ -992,8 +992,8 @@ importers:
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
'@vercel/ncc': ^0.36.0
'@vercel/turbopack-ecmascript-runtime': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2
'@vercel/turbopack-node': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.2
'@vercel/turbopack-ecmascript-runtime': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3
'@vercel/turbopack-node': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.3
anser: ^2.1.1
css.escape: ^1.5.1
find-up: ^6.3.0
@ -1005,8 +1005,8 @@ importers:
stacktrace-parser: ^0.1.10
strip-ansi: ^7.0.1
dependencies:
'@vercel/turbopack-ecmascript-runtime': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.2'
'@vercel/turbopack-ecmascript-runtime': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.3'
anser: 2.1.1
css.escape: 1.5.1
next: link:../../../../next
@ -25511,9 +25511,9 @@ packages:
/zwitch/2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2}
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.2'
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3}
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230713.3'
name: '@vercel/turbopack-ecmascript-runtime'
version: 0.0.0
dependencies:
@ -25524,8 +25524,8 @@ packages:
- webpack
dev: false
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.2':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.2}
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.3':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230713.3}
name: '@vercel/turbopack-node'
version: 0.0.0
dependencies: