Turbopack: Experimental dev app pages support (#52680)

This implements app pages and routes for the Nexturbo API.

## Turbopack updates

* https://github.com/vercel/turbo/pull/5527 <!-- Alex Kirszenberg -
AdjacencyMap::reverse_topological (+ fixes) -->
This commit is contained in:
Alex Kirszenberg 2023-07-17 18:03:14 +02:00 committed by GitHub
parent e5b35894a7
commit dd56a77e91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1634 additions and 868 deletions

75
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-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"serde",
]
@ -3377,6 +3377,7 @@ dependencies = [
"next-core",
"once_cell",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
@ -3416,12 +3417,14 @@ dependencies = [
"anyhow",
"async-recursion",
"async-trait",
"base64 0.21.0",
"const_format",
"futures",
"indexmap",
"indoc",
"lazy_static",
"mime",
"mime_guess",
"next-transform-dynamic",
"next-transform-font",
"next-transform-strip-page-exports",
@ -3599,7 +3602,7 @@ dependencies = [
[[package]]
name = "node-file-trace"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"serde",
@ -7259,7 +7262,7 @@ dependencies = [
[[package]]
name = "turbo-tasks"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-trait",
@ -7291,7 +7294,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"cargo-lock",
@ -7303,7 +7306,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-bytes"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"bytes",
@ -7318,7 +7321,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"dotenvs",
@ -7332,7 +7335,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fetch"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"indexmap",
@ -7349,7 +7352,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fs"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7379,7 +7382,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-hash"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"base16",
"hex",
@ -7391,7 +7394,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@ -7405,7 +7408,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros-shared"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"proc-macro2",
"quote",
@ -7415,7 +7418,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-malloc"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"mimalloc",
]
@ -7423,7 +7426,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-memory"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7446,7 +7449,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-testing"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7459,7 +7462,7 @@ dependencies = [
[[package]]
name = "turbopack"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-recursion",
@ -7489,7 +7492,7 @@ dependencies = [
[[package]]
name = "turbopack-bench"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"chromiumoxide",
@ -7519,7 +7522,7 @@ dependencies = [
[[package]]
name = "turbopack-binding"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"auto-hash-map",
"mdxjs",
@ -7561,7 +7564,7 @@ dependencies = [
[[package]]
name = "turbopack-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"indexmap",
@ -7581,7 +7584,7 @@ dependencies = [
[[package]]
name = "turbopack-cli-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"clap 4.1.11",
@ -7605,7 +7608,7 @@ dependencies = [
[[package]]
name = "turbopack-core"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-trait",
@ -7633,7 +7636,7 @@ dependencies = [
[[package]]
name = "turbopack-create-test-app"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"clap 4.1.11",
@ -7646,7 +7649,7 @@ dependencies = [
[[package]]
name = "turbopack-css"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-trait",
@ -7668,7 +7671,7 @@ dependencies = [
[[package]]
name = "turbopack-dev"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"indexmap",
@ -7692,7 +7695,7 @@ dependencies = [
[[package]]
name = "turbopack-dev-server"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-compression",
@ -7728,7 +7731,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-trait",
@ -7761,7 +7764,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-plugins"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-trait",
@ -7784,7 +7787,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-runtime"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"indoc",
@ -7801,7 +7804,7 @@ dependencies = [
[[package]]
name = "turbopack-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"indexmap",
@ -7817,7 +7820,7 @@ dependencies = [
[[package]]
name = "turbopack-image"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"base64 0.21.0",
@ -7837,7 +7840,7 @@ dependencies = [
[[package]]
name = "turbopack-json"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"serde",
@ -7852,7 +7855,7 @@ dependencies = [
[[package]]
name = "turbopack-mdx"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"mdxjs",
@ -7867,7 +7870,7 @@ dependencies = [
[[package]]
name = "turbopack-node"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"async-stream",
@ -7902,7 +7905,7 @@ dependencies = [
[[package]]
name = "turbopack-static"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"serde",
@ -7918,7 +7921,7 @@ dependencies = [
[[package]]
name = "turbopack-swc-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"swc_core",
"turbo-tasks",
@ -7929,7 +7932,7 @@ dependencies = [
[[package]]
name = "turbopack-test-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230716.2#8433a321545de30374b5374320625b92c663f5dc"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230717.2#b0695455647275362c780c85f7e0bcbb3d8d64ec"
dependencies = [
"anyhow",
"once_cell",
@ -7949,7 +7952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if 1.0.0",
"rand 0.8.5",
"rand 0.4.6",
"static_assertions",
]

View file

@ -44,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-230716.2" }
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230717.2" }
# [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-230716.2" }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230717.2" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230716.2" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230717.2" }
# General Deps

View file

@ -22,6 +22,7 @@ indexmap = { workspace = true }
next-core = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["full"] }
turbopack-binding = { workspace = true, features = [
"__turbo_tasks_memory",

View file

@ -1,28 +1,349 @@
use next_core::app_structure::Entrypoint;
use anyhow::{Context, Result};
use next_core::{
app_structure::{
get_entrypoints, Entrypoint as AppEntrypoint, Entrypoints as AppEntrypoints, LoaderTree,
},
emit_all_assets,
mode::NextMode,
next_app::{
get_app_client_references_chunks, get_app_client_shared_chunks, get_app_page_entry,
get_app_route_entry, AppEntry,
},
next_client::{
get_client_module_options_context, get_client_resolve_options_context,
get_client_runtime_entries, ClientContextType,
},
next_client_reference::{
ClientReferenceGraph, ClientReferenceType, NextEcmascriptClientReferenceTransition,
},
next_dynamic::{NextDynamicEntries, NextDynamicTransition},
next_manifests::{
AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest, PagesManifest,
},
next_server::{
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
},
};
use serde::{Deserialize, Serialize};
use turbo_tasks::{trace::TraceRawVcs, Completion, Vc};
use turbo_tasks::{trace::TraceRawVcs, Completion, TryJoinIterExt, Value, Vc};
use turbopack_binding::{
turbo::{
tasks_env::{CustomProcessEnv, ProcessEnv},
tasks_fs::{File, FileSystemPath},
},
turbopack::{
core::{
asset::{Asset, AssetContent},
changed::any_content_changed_of_output_assets,
chunk::EvaluatableAssets,
file_source::FileSource,
output::{OutputAsset, OutputAssets},
raw_output::RawOutput,
virtual_source::VirtualSource,
},
turbopack::{
module_options::ModuleOptionsContext, resolve_options_context::ResolveOptionsContext,
transition::ContextTransition, ModuleAssetContext,
},
},
};
use crate::route::{Endpoint, Route, WrittenEndpoint};
use crate::{
project::Project,
route::{Endpoint, Route, Routes, WrittenEndpoint},
};
#[turbo_tasks::value]
pub struct AppProject {
project: Vc<Project>,
app_dir: Vc<FileSystemPath>,
mode: NextMode,
}
#[turbo_tasks::value(transparent)]
pub struct OptionAppProject(Option<Vc<AppProject>>);
impl AppProject {
fn client_ty(self: Vc<Self>) -> ClientContextType {
ClientContextType::App {
app_dir: self.app_dir(),
}
}
fn rsc_ty(self: Vc<Self>) -> ServerContextType {
ServerContextType::AppRSC {
app_dir: self.app_dir(),
client_transition: Some(Vc::upcast(self.client_transition())),
ecmascript_client_reference_transition_name: Some(self.client_transition_name()),
}
}
fn ssr_ty(self: Vc<Self>) -> ServerContextType {
ServerContextType::AppSSR {
app_dir: self.app_dir(),
}
}
}
const ECMASCRIPT_CLIENT_TRANSITION_NAME: &str = "next-ecmascript-client-reference";
#[turbo_tasks::value_impl]
impl AppProject {
#[turbo_tasks::function]
pub fn new(project: Vc<Project>, app_dir: Vc<FileSystemPath>, mode: NextMode) -> Vc<Self> {
AppProject {
project,
app_dir,
mode,
}
.cell()
}
#[turbo_tasks::function]
fn project(&self) -> Vc<Project> {
self.project
}
#[turbo_tasks::function]
fn app_dir(&self) -> Vc<FileSystemPath> {
self.app_dir
}
#[turbo_tasks::function]
fn app_entrypoints(&self) -> Vc<AppEntrypoints> {
get_entrypoints(self.app_dir, self.project.next_config().page_extensions())
}
#[turbo_tasks::function]
async fn client_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_client_module_options_context(
self.project().project_path(),
self.project().execution_context(),
self.project().client_compile_time_info().environment(),
Value::new(self.client_ty()),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn client_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
let this = self.await?;
Ok(get_client_resolve_options_context(
self.project().project_path(),
Value::new(self.client_ty()),
this.mode,
self.project().next_config(),
self.project().execution_context(),
))
}
#[turbo_tasks::function]
fn client_transition_name(self: Vc<Self>) -> Vc<String> {
Vc::cell(ECMASCRIPT_CLIENT_TRANSITION_NAME.to_string())
}
#[turbo_tasks::function]
fn client_transition(self: Vc<Self>) -> Vc<ContextTransition> {
ContextTransition::new(
self.project().client_compile_time_info(),
self.client_module_options_context(),
self.client_resolve_options_context(),
)
}
#[turbo_tasks::function]
async fn rsc_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project().project_path(),
self.project().execution_context(),
Value::new(self.rsc_ty()),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn rsc_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
let this = self.await?;
Ok(get_server_resolve_options_context(
self.project().project_path(),
Value::new(self.rsc_ty()),
this.mode,
self.project().next_config(),
self.project().execution_context(),
))
}
#[turbo_tasks::function]
fn rsc_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
let transitions = [
(
ECMASCRIPT_CLIENT_TRANSITION_NAME.to_string(),
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
self.client_transition(),
self.ssr_transition(),
)),
),
(
"next-dynamic".to_string(),
Vc::upcast(NextDynamicTransition::new(self.client_transition())),
),
]
.into_iter()
.collect();
ModuleAssetContext::new(
Vc::cell(transitions),
self.project().server_compile_time_info(),
self.rsc_module_options_context(),
self.rsc_resolve_options_context(),
)
}
#[turbo_tasks::function]
fn client_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
ModuleAssetContext::new(
Vc::cell(Default::default()),
self.project().client_compile_time_info(),
self.client_module_options_context(),
self.client_resolve_options_context(),
)
}
#[turbo_tasks::function]
async fn ssr_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project().project_path(),
self.project().execution_context(),
Value::new(self.ssr_ty()),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn ssr_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
let this = self.await?;
Ok(get_server_resolve_options_context(
self.project().project_path(),
Value::new(self.ssr_ty()),
this.mode,
self.project().next_config(),
self.project().execution_context(),
))
}
#[turbo_tasks::function]
fn ssr_transition(self: Vc<Self>) -> Vc<ContextTransition> {
ContextTransition::new(
self.project().server_compile_time_info(),
self.ssr_module_options_context(),
self.ssr_resolve_options_context(),
)
}
#[turbo_tasks::function]
async fn rsc_runtime_entries(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
let this = self.await?;
Ok(get_server_runtime_entries(
self.project().project_path(),
// TODO(alexkirsz) Should we pass env here or EnvMap::empty, as is done in
// app_source?
self.project().env(),
Value::new(self.rsc_ty()),
this.mode,
self.project().next_config(),
)
.resolve_entries(Vc::upcast(self.rsc_module_context())))
}
#[turbo_tasks::function]
fn client_env(self: Vc<Self>) -> Vc<Box<dyn ProcessEnv>> {
Vc::upcast(CustomProcessEnv::new(
self.project().env(),
self.project().next_config().env(),
))
}
#[turbo_tasks::function]
async fn client_runtime_entries(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
let this = self.await?;
Ok(get_client_runtime_entries(
self.project().project_path(),
self.client_env(),
Value::new(self.client_ty()),
this.mode,
self.project().next_config(),
self.project().execution_context(),
)
.resolve_entries(Vc::upcast(self.client_module_context())))
}
#[turbo_tasks::function]
pub async fn routes(self: Vc<Self>) -> Result<Vc<Routes>> {
let app_entrypoints = self.app_entrypoints();
Ok(Vc::cell(
app_entrypoints
.await?
.iter()
.map(|(pathname, app_entrypoint)| async {
Ok((
pathname.clone(),
*app_entry_point_to_route(self, *app_entrypoint, pathname.clone()).await?,
))
})
.try_join()
.await?
.into_iter()
.collect(),
))
}
}
#[turbo_tasks::function]
pub async fn app_entry_point_to_route(entrypoint: Entrypoint) -> Vc<Route> {
pub async fn app_entry_point_to_route(
app_project: Vc<AppProject>,
entrypoint: AppEntrypoint,
pathname: String,
) -> Vc<Route> {
match entrypoint {
Entrypoint::AppPage { .. } => Route::AppPage {
AppEntrypoint::AppPage { loader_tree } => Route::AppPage {
html_endpoint: Vc::upcast(
AppPageEndpoint {
ty: AppPageEndpointType::Html,
AppEndpoint {
ty: AppEndpointType::Page {
ty: AppPageEndpointType::Html,
loader_tree,
},
app_project,
pathname: pathname.clone(),
}
.cell(),
),
rsc_endpoint: Vc::upcast(
AppPageEndpoint {
ty: AppPageEndpointType::Rsc,
AppEndpoint {
ty: AppEndpointType::Page {
ty: AppPageEndpointType::Rsc,
loader_tree,
},
app_project,
pathname,
}
.cell(),
),
},
Entrypoint::AppRoute { .. } => Route::AppRoute {
endpoint: Vc::upcast(AppRouteEndpoint.cell()),
AppEntrypoint::AppRoute { path } => Route::AppRoute {
endpoint: Vc::upcast(
AppEndpoint {
ty: AppEndpointType::Route { path },
app_project,
pathname: pathname.clone(),
}
.cell(),
),
},
}
.cell()
@ -34,36 +355,288 @@ enum AppPageEndpointType {
Rsc,
}
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Debug, TraceRawVcs)]
enum AppEndpointType {
Page {
ty: AppPageEndpointType,
loader_tree: Vc<LoaderTree>,
},
Route {
path: Vc<FileSystemPath>,
},
}
#[turbo_tasks::value]
struct AppPageEndpoint {
ty: AppPageEndpointType,
struct AppEndpoint {
ty: AppEndpointType,
app_project: Vc<AppProject>,
pathname: String,
}
#[turbo_tasks::value_impl]
impl Endpoint for AppPageEndpoint {
impl AppEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> Vc<WrittenEndpoint> {
todo!()
fn client_relative_path(&self) -> Vc<FileSystemPath> {
self.app_project
.project()
.client_root()
.join("_next".to_string())
}
#[turbo_tasks::function]
fn changed(&self) -> Vc<Completion> {
todo!()
fn app_page_entry(&self, loader_tree: Vc<LoaderTree>) -> Vc<AppEntry> {
get_app_page_entry(
self.app_project.rsc_module_context(),
loader_tree,
self.app_project.app_dir(),
self.pathname.clone(),
self.app_project.project().project_path(),
)
}
#[turbo_tasks::function]
fn app_route_entry(&self, path: Vc<FileSystemPath>) -> Vc<AppEntry> {
get_app_route_entry(
self.app_project.rsc_module_context(),
Vc::upcast(FileSource::new(path)),
self.pathname.clone(),
self.app_project.project().project_path(),
)
}
#[turbo_tasks::function]
async fn output(self: Vc<Self>) -> Result<Vc<AppEndpointOutput>> {
let this = self.await?;
let app_entry = match this.ty {
AppEndpointType::Page { ty: _, loader_tree } => self.app_page_entry(loader_tree),
// NOTE(alexkirsz) For routes, technically, a lot of the following code is not needed,
// as we know we won't have any client references. However, for now, for simplicity's
// sake, we just do the same thing as for pages.
AppEndpointType::Route { path } => self.app_route_entry(path),
};
let node_root = this.app_project.project().node_root();
let client_relative_path = self.client_relative_path();
let client_relative_path_ref = client_relative_path.await?;
let server_path = node_root.join("server".to_string());
let mut output_assets = vec![];
let client_shared_chunks = get_app_client_shared_chunks(
this.app_project.client_runtime_entries(),
this.app_project.project().client_chunking_context(),
);
let mut client_shared_chunks_paths = vec![];
for chunk in client_shared_chunks.await?.iter().copied() {
output_assets.push(chunk);
let chunk_path = chunk.ident().path().await?;
if chunk_path.extension_ref() == Some("js") {
if let Some(chunk_path) = client_relative_path_ref.get_path_to(&chunk_path) {
client_shared_chunks_paths.push(chunk_path.to_string());
}
}
}
let app_entry = app_entry.await?;
let rsc_entry = app_entry.rsc_entry;
let rsc_entry_asset = Vc::upcast(rsc_entry);
let client_reference_graph = ClientReferenceGraph::new(Vc::cell(vec![rsc_entry_asset]));
let client_reference_types = client_reference_graph.types();
let client_references = client_reference_graph.entry(rsc_entry_asset);
let app_ssr_entries: Vec<_> = client_reference_types
.await?
.iter()
.map(|client_reference_ty| async move {
let ClientReferenceType::EcmascriptClientReference(entry) = client_reference_ty
else {
return Ok(None);
};
Ok(Some(entry.await?.ssr_module))
})
.try_join()
.await?
.into_iter()
.flatten()
.collect();
let app_node_entries: Vec<_> = app_ssr_entries.iter().copied().chain([rsc_entry]).collect();
// TODO(alexkirsz) Handle dynamic entries and dynamic chunks.
let _dynamic_entries = NextDynamicEntries::from_entries(Vc::cell(
app_node_entries.iter().copied().map(Vc::upcast).collect(),
))
.await?;
let rsc_chunk = this
.app_project
.project()
.rsc_chunking_context()
.entry_chunk(
server_path.join(format!(
"app/{original_name}.js",
original_name = app_entry.original_name
)),
app_entry.rsc_entry,
this.app_project.rsc_runtime_entries(),
);
output_assets.push(rsc_chunk);
let app_entry_client_references = client_reference_graph
.entry(Vc::upcast(app_entry.rsc_entry))
.await?;
let client_references_chunks = get_app_client_references_chunks(
client_reference_types,
this.app_project.project().client_chunking_context(),
this.app_project.project().ssr_chunking_context(),
);
let client_references_chunks_ref = client_references_chunks.await?;
let mut entry_client_chunks = vec![];
// TODO(alexkirsz) In which manifest does this go?
let mut entry_ssr_chunks = vec![];
for client_reference in app_entry_client_references.iter() {
let client_reference_chunks = client_references_chunks_ref
.get(client_reference.ty())
.expect("client reference should have corresponding chunks");
entry_client_chunks
.extend(client_reference_chunks.client_chunks.await?.iter().copied());
entry_ssr_chunks.extend(client_reference_chunks.ssr_chunks.await?.iter().copied());
}
output_assets.extend(entry_client_chunks.iter().copied());
output_assets.extend(entry_ssr_chunks.iter().copied());
let entry_client_chunks_paths = entry_client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
let mut entry_client_chunks_paths: Vec<_> = entry_client_chunks_paths
.iter()
.map(|path| {
client_relative_path_ref
.get_path_to(path)
.expect("asset path should be inside client root")
.to_string()
})
.collect();
entry_client_chunks_paths.extend(client_shared_chunks_paths.iter().cloned());
let app_build_manifest = AppBuildManifest {
pages: [(app_entry.original_name.clone(), entry_client_chunks_paths)]
.into_iter()
.collect(),
};
let app_build_manifest_output = Vc::upcast(RawOutput::new(Vc::upcast(VirtualSource::new(
node_root.join("server/app-build-manifest.json".to_string()),
AssetContent::file(
File::from(serde_json::to_string_pretty(&app_build_manifest)?).into(),
),
))));
output_assets.push(app_build_manifest_output);
let app_paths_manifest = AppPathsManifest {
node_server_app_paths: PagesManifest {
pages: [(
app_entry.original_name.clone(),
server_path
.await?
.get_path_to(&*rsc_chunk.ident().path().await?)
.expect("RSC chunk path should be within app paths manifest directory")
.to_string(),
)]
.into_iter()
.collect(),
},
..Default::default()
};
let app_paths_manifest_output = Vc::upcast(RawOutput::new(Vc::upcast(VirtualSource::new(
node_root.join("server/app-paths-manifest.json".to_string()),
AssetContent::file(
File::from(serde_json::to_string_pretty(&app_paths_manifest)?).into(),
),
))));
output_assets.push(app_paths_manifest_output);
let build_manifest = BuildManifest {
root_main_files: client_shared_chunks_paths,
..Default::default()
};
let build_manifest_output = Vc::upcast(RawOutput::new(Vc::upcast(VirtualSource::new(
node_root.join("build-manifest.json".to_string()),
AssetContent::file(File::from(serde_json::to_string_pretty(&build_manifest)?).into()),
))));
output_assets.push(build_manifest_output);
let entry_manifest = ClientReferenceManifest::build_output(
node_root,
client_relative_path,
app_entry.original_name.clone(),
client_references,
client_references_chunks,
this.app_project.project().client_chunking_context(),
Vc::upcast(this.app_project.project().ssr_chunking_context()),
);
output_assets.push(entry_manifest);
Ok(AppEndpointOutput {
rsc_chunk,
output_assets: Vc::cell(output_assets),
}
.cell())
}
}
#[turbo_tasks::value_impl]
impl Endpoint for AppEndpoint {
#[turbo_tasks::function]
async fn write_to_disk(self: Vc<Self>) -> Result<Vc<WrittenEndpoint>> {
let output = self.output();
let this = self.await?;
let node_root = this.app_project.project().node_root();
let output = output.await?;
let node_root_ref = node_root.await?;
emit_all_assets(
output.output_assets,
this.app_project.project().node_root(),
self.client_relative_path(),
this.app_project.project().node_root(),
)
.await?;
Ok(WrittenEndpoint {
server_entry_path: node_root_ref
.get_path_to(&*output.rsc_chunk.ident().path().await?)
.context("rsc chunk entry path must be inside the node root")?
.to_string(),
server_paths: vec![],
}
.cell())
}
#[turbo_tasks::function]
async fn changed(self: Vc<Self>) -> Result<Vc<Completion>> {
let output = self.output();
Ok(any_content_changed_of_output_assets(
output.await?.output_assets,
))
}
}
#[turbo_tasks::value]
struct AppRouteEndpoint;
#[turbo_tasks::value_impl]
impl Endpoint for AppRouteEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> Vc<WrittenEndpoint> {
todo!()
}
#[turbo_tasks::function]
fn changed(&self) -> Vc<Completion> {
todo!()
}
struct AppEndpointOutput {
rsc_chunk: Vc<Box<dyn OutputAsset>>,
output_assets: Vc<OutputAssets>,
}

View file

@ -0,0 +1,9 @@
use indexmap::IndexMap;
use crate::{project::Middleware, route::Route};
#[turbo_tasks::value(shared)]
pub struct Entrypoints {
pub routes: IndexMap<String, Route>,
pub middleware: Option<Middleware>,
}

View file

@ -3,6 +3,7 @@
#![feature(async_fn_in_trait)]
mod app;
mod entrypoints;
mod pages;
pub mod project;
pub mod route;

View file

@ -2,16 +2,28 @@ 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, PagesStructure, PagesStructureItem},
mode::NextMode,
next_client::{
get_client_module_options_context, get_client_resolve_options_context,
get_client_runtime_entries, ClientContextType,
},
next_dynamic::NextDynamicTransition,
next_server::{
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
},
pages_structure::{
find_pages_structure, PagesDirectoryStructure, PagesStructure, PagesStructureItem,
},
};
use turbo_tasks::{Completion, Completions, Value, Vc};
use turbopack_binding::{
turbo::tasks_fs::FileSystemPath,
turbo::tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem},
turbopack::{
core::{
asset::Asset,
changed::{any_content_changed, any_content_changed_of_output_assets},
chunk::{ChunkableModule, ChunkingContext},
chunk::{ChunkableModule, ChunkingContext, EvaluatableAssets},
context::AssetContext,
file_source::FileSource,
output::{OutputAsset, OutputAssets},
@ -19,6 +31,12 @@ use turbopack_binding::{
source::Source,
},
ecmascript::EcmascriptModuleAsset,
turbopack::{
module_options::ModuleOptionsContext,
resolve_options_context::ResolveOptionsContext,
transition::{ContextTransition, TransitionsByName},
ModuleAssetContext,
},
},
};
@ -27,77 +45,266 @@ use crate::{
route::{Endpoint, Route, Routes, WrittenEndpoint},
};
#[turbo_tasks::function]
pub async fn get_pages_routes(
#[turbo_tasks::value]
pub struct PagesProject {
project: Vc<Project>,
page_structure: Vc<PagesStructure>,
) -> Result<Vc<Routes>> {
let PagesStructure { api, pages, .. } = *page_structure.await?;
let mut routes = IndexMap::new();
async fn add_dir_to_routes(
routes: &mut IndexMap<String, Route>,
dir: Vc<PagesDirectoryStructure>,
make_route: impl Fn(Vc<String>, Vc<String>, Vc<FileSystemPath>) -> 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 = Vc::cell(pathname.clone());
let original_name = Vc::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);
mode: NextMode,
}
#[turbo_tasks::value_impl]
impl PagesProject {
#[turbo_tasks::function]
pub async fn new(project: Vc<Project>, mode: NextMode) -> Result<Vc<Self>> {
Ok(PagesProject { project, mode }.cell())
}
#[turbo_tasks::function]
pub async fn routes(self: Vc<Self>) -> Result<Vc<Routes>> {
let PagesStructure { api, pages, .. } = &*self.pages_structure().await?;
let mut routes = IndexMap::new();
async fn add_dir_to_routes(
routes: &mut IndexMap<String, Route>,
dir: Vc<PagesDirectoryStructure>,
make_route: impl Fn(Vc<String>, Vc<String>, Vc<FileSystemPath>) -> 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 = Vc::cell(pathname.clone());
let original_name = Vc::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(())
}
Ok(())
if let Some(api) = api {
add_dir_to_routes(&mut routes, *api, |pathname, original_name, path| {
Route::PageApi {
endpoint: Vc::upcast(PageApiEndpoint::new(self, pathname, original_name, path)),
}
})
.await?;
}
if let Some(page) = pages {
add_dir_to_routes(&mut routes, *page, |pathname, original_name, path| {
Route::Page {
html_endpoint: Vc::upcast(PageHtmlEndpoint::new(
self,
pathname,
original_name,
path,
)),
data_endpoint: Vc::upcast(PageDataEndpoint::new(
self,
pathname,
original_name,
path,
)),
}
})
.await?;
}
Ok(Vc::cell(routes))
}
if let Some(api) = api {
add_dir_to_routes(&mut routes, api, |pathname, original_name, path| {
Route::PageApi {
endpoint: Vc::upcast(ApiEndpoint::new(project, pathname, original_name, path)),
}
#[turbo_tasks::function]
fn project(&self) -> Vc<Project> {
self.project
}
#[turbo_tasks::function]
fn pages_structure(&self) -> Vc<PagesStructure> {
let next_router_fs = Vc::upcast::<Box<dyn FileSystem>>(VirtualFileSystem::new());
let next_router_root = next_router_fs.root();
find_pages_structure(
self.project.project_path(),
next_router_root,
self.project.next_config().page_extensions(),
)
}
#[turbo_tasks::function]
async fn pages_dir(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
Ok(if let Some(pages) = self.pages_structure().await?.pages {
pages.project_path()
} else {
self.project().project_path().join("pages".to_string())
})
.await?;
}
if let Some(page) = pages {
add_dir_to_routes(&mut routes, page, |pathname, original_name, path| {
Route::Page {
html_endpoint: Vc::upcast(PageHtmlEndpoint::new(
project,
pathname,
original_name,
path,
)),
data_endpoint: Vc::upcast(PageDataEndpoint::new(
project,
pathname,
original_name,
path,
)),
}
})
.await?;
#[turbo_tasks::function]
fn transitions(self: Vc<Self>) -> Vc<TransitionsByName> {
Vc::cell(
[(
"next-dynamic".to_string(),
Vc::upcast(NextDynamicTransition::new(self.client_transition())),
)]
.into_iter()
.collect(),
)
}
#[turbo_tasks::function]
fn client_transition(self: Vc<Self>) -> Vc<ContextTransition> {
ContextTransition::new(
self.project().client_compile_time_info(),
self.client_module_options_context(),
self.client_resolve_options_context(),
)
}
#[turbo_tasks::function]
async fn client_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_client_module_options_context(
self.project().project_path(),
self.project().execution_context(),
self.project().client_compile_time_info().environment(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn client_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
let this = self.await?;
Ok(get_client_resolve_options_context(
self.project().project_path(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
self.project().execution_context(),
))
}
#[turbo_tasks::function]
pub(super) fn client_module_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.transitions(),
self.project().client_compile_time_info(),
self.client_module_options_context(),
self.client_resolve_options_context(),
))
}
#[turbo_tasks::function]
pub(super) fn ssr_module_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.transitions(),
self.project().server_compile_time_info(),
self.ssr_module_options_context(),
self.ssr_resolve_options_context(),
))
}
#[turbo_tasks::function]
pub(super) fn ssr_data_module_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.transitions(),
self.project().server_compile_time_info(),
self.ssr_data_module_options_context(),
self.ssr_resolve_options_context(),
))
}
#[turbo_tasks::function]
async fn ssr_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project().project_path(),
self.project().execution_context(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn ssr_data_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
let this = self.await?;
Ok(get_server_module_options_context(
self.project().project_path(),
self.project().execution_context(),
Value::new(ServerContextType::PagesData {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
))
}
#[turbo_tasks::function]
async fn ssr_resolve_options_context(self: Vc<Self>) -> Result<Vc<ResolveOptionsContext>> {
let this = self.await?;
Ok(get_server_resolve_options_context(
self.project().project_path(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
self.project().execution_context(),
))
}
#[turbo_tasks::function]
async fn client_runtime_entries(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
let this = self.await?;
let client_runtime_entries = get_client_runtime_entries(
self.project().project_path(),
self.project().env(),
Value::new(ClientContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
self.project().execution_context(),
);
Ok(client_runtime_entries.resolve_entries(self.client_module_context()))
}
#[turbo_tasks::function]
async fn ssr_runtime_entries(self: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
let this = self.await?;
let ssr_runtime_entries = get_server_runtime_entries(
self.project().project_path(),
self.project().env(),
Value::new(ServerContextType::Pages {
pages_dir: self.pages_dir(),
}),
this.mode,
self.project().next_config(),
);
Ok(ssr_runtime_entries.resolve_entries(self.ssr_module_context()))
}
Ok(Vc::cell(routes))
}
#[turbo_tasks::value]
struct PageHtmlEndpoint {
project: Vc<Project>,
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
@ -107,13 +314,13 @@ struct PageHtmlEndpoint {
impl PageHtmlEndpoint {
#[turbo_tasks::function]
fn new(
project: Vc<Project>,
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
) -> Vc<Self> {
PageHtmlEndpoint {
project,
pages_project,
pathname,
original_name,
path,
@ -132,7 +339,7 @@ impl PageHtmlEndpoint {
let this = self.await?;
let client_module = create_page_loader_entry_module(
this.project.pages_client_module_context(),
this.pages_project.client_module_context(),
self.source(),
this.pathname,
);
@ -143,14 +350,14 @@ impl PageHtmlEndpoint {
bail!("expected an ECMAScript module asset");
};
let client_chunking_context = this.project.client_chunking_context();
let client_chunking_context = this.pages_project.project().client_chunking_context();
let client_entry_chunk = client_module.as_root_chunk(Vc::upcast(client_chunking_context));
let client_chunks = client_chunking_context.evaluated_chunk_group(
client_entry_chunk,
this.project
.pages_client_runtime_entries()
this.pages_project
.client_runtime_entries()
.with_entry(Vc::upcast(client_module)),
);
@ -163,8 +370,8 @@ impl PageHtmlEndpoint {
let reference_type = Value::new(ReferenceType::Entry(EntryReferenceSubType::Page));
let ssr_module = this
.project
.pages_ssr_module_context()
.pages_project
.ssr_module_context()
.process(self.source(), reference_type.clone());
let Some(ssr_module) =
@ -176,12 +383,20 @@ impl PageHtmlEndpoint {
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,
Vc::upcast(ssr_module),
this.project.pages_ssr_runtime_entries(),
);
let ssr_entry_chunk_path = this
.pages_project
.project()
.node_root()
.join(ssr_entry_chunk_path_string);
let ssr_entry_chunk = this
.pages_project
.project()
.ssr_chunking_context()
.entry_chunk(
ssr_entry_chunk_path,
Vc::upcast(ssr_module),
this.pages_project.ssr_runtime_entries(),
);
Ok(ssr_entry_chunk)
}
@ -195,15 +410,15 @@ impl Endpoint for PageHtmlEndpoint {
let ssr_chunk = self.ssr_chunk();
let ssr_emit = emit_all_assets(
Vc::cell(vec![ssr_chunk]),
this.project.node_root(),
this.project.client_root().join("_next".to_string()),
this.project.node_root(),
this.pages_project.project().node_root(),
this.pages_project.project().client_relative_path(),
this.pages_project.project().node_root(),
);
let client_emit = emit_all_assets(
self.client_chunks(),
this.project.node_root(),
this.project.client_root().join("_next".to_string()),
this.project.node_root(),
this.pages_project.project().node_root(),
this.pages_project.project().client_relative_path(),
this.pages_project.project().node_root(),
);
ssr_emit.await?;
@ -211,7 +426,8 @@ impl Endpoint for PageHtmlEndpoint {
Ok(WrittenEndpoint {
server_entry_path: this
.project
.pages_project
.project()
.node_root()
.await?
.get_path_to(&*ssr_chunk.ident().path().await?)
@ -234,7 +450,7 @@ impl Endpoint for PageHtmlEndpoint {
#[turbo_tasks::value]
struct PageDataEndpoint {
project: Vc<Project>,
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
@ -244,13 +460,13 @@ struct PageDataEndpoint {
impl PageDataEndpoint {
#[turbo_tasks::function]
fn new(
project: Vc<Project>,
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
) -> Vc<Self> {
PageDataEndpoint {
project,
pages_project,
pathname,
original_name,
path,
@ -270,8 +486,8 @@ impl PageDataEndpoint {
let reference_type = Value::new(ReferenceType::Entry(EntryReferenceSubType::Page));
let ssr_data_module = this
.project
.pages_ssr_data_module_context()
.pages_project
.ssr_data_module_context()
.process(self.source(), reference_type.clone());
let Some(ssr_data_module) =
@ -284,14 +500,19 @@ impl PageDataEndpoint {
let ssr_data_entry_chunk_path_string = format!("server/pages-data/{asset_path}");
let ssr_data_entry_chunk_path = this
.project
.pages_project
.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,
Vc::upcast(ssr_data_module),
this.project.pages_ssr_runtime_entries(),
);
let ssr_data_entry_chunk = this
.pages_project
.project()
.ssr_data_chunking_context()
.entry_chunk(
ssr_data_entry_chunk_path,
Vc::upcast(ssr_data_module),
this.pages_project.ssr_runtime_entries(),
);
Ok(ssr_data_entry_chunk)
}
@ -305,15 +526,16 @@ impl Endpoint for PageDataEndpoint {
let ssr_data_chunk = self.ssr_data_chunk();
emit_all_assets(
Vc::cell(vec![ssr_data_chunk]),
this.project.node_root(),
this.project.client_root().join("_next".to_string()),
this.project.node_root(),
this.pages_project.project().node_root(),
this.pages_project.project().client_relative_path(),
this.pages_project.project().node_root(),
)
.await?;
Ok(WrittenEndpoint {
server_entry_path: this
.project
.pages_project
.project()
.node_root()
.await?
.get_path_to(&*ssr_data_chunk.ident().path().await?)
@ -332,41 +554,105 @@ impl Endpoint for PageDataEndpoint {
}
#[turbo_tasks::value]
struct ApiEndpoint {
project: Vc<Project>,
struct PageApiEndpoint {
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
}
#[turbo_tasks::value_impl]
impl ApiEndpoint {
impl PageApiEndpoint {
#[turbo_tasks::function]
fn new(
project: Vc<Project>,
pages_project: Vc<PagesProject>,
pathname: Vc<String>,
original_name: Vc<String>,
path: Vc<FileSystemPath>,
) -> Vc<Self> {
ApiEndpoint {
project,
PageApiEndpoint {
pages_project,
pathname,
original_name,
path,
}
.cell()
}
#[turbo_tasks::function]
fn source(&self) -> Vc<Box<dyn Source>> {
Vc::upcast(FileSource::new(self.path))
}
#[turbo_tasks::function]
async fn api_chunk(self: Vc<Self>) -> Result<Vc<Box<dyn OutputAsset>>> {
let this = self.await?;
let reference_type = Value::new(ReferenceType::Entry(EntryReferenceSubType::PagesApi));
let api_module = this
.pages_project
.ssr_module_context()
.process(self.source(), reference_type.clone());
let Some(api_module) =
Vc::try_resolve_downcast_type::<EcmascriptModuleAsset>(api_module).await?
else {
bail!("expected an ECMAScript module asset");
};
let asset_path = get_asset_path_from_pathname(&this.pathname.await?, ".js");
let api_entry_chunk_path_string = format!("server/pages/{asset_path}");
let api_entry_chunk_path = this
.pages_project
.project()
.node_root()
.join(api_entry_chunk_path_string);
let api_entry_chunk = this
.pages_project
.project()
.ssr_chunking_context()
.entry_chunk(
api_entry_chunk_path,
Vc::upcast(api_module),
this.pages_project.ssr_runtime_entries(),
);
Ok(api_entry_chunk)
}
}
#[turbo_tasks::value_impl]
impl Endpoint for ApiEndpoint {
impl Endpoint for PageApiEndpoint {
#[turbo_tasks::function]
fn write_to_disk(&self) -> Vc<WrittenEndpoint> {
todo!()
async fn write_to_disk(self: Vc<Self>) -> Result<Vc<WrittenEndpoint>> {
let this = self.await?;
let api_chunk = self.api_chunk();
emit_all_assets(
Vc::cell(vec![api_chunk]),
this.pages_project.project().node_root(),
this.pages_project.project().client_relative_path(),
this.pages_project.project().node_root(),
)
.await?;
Ok(WrittenEndpoint {
server_entry_path: this
.pages_project
.project()
.node_root()
.await?
.get_path_to(&*api_chunk.ident().path().await?)
.context("API chunk entry path must be inside the node root")?
.to_string(),
server_paths: vec![],
}
.cell())
}
#[turbo_tasks::function]
fn changed(&self) -> Vc<Completion> {
todo!()
fn changed(self: Vc<Self>) -> Vc<Completion> {
let api_chunk = self.api_chunk();
any_content_changed(Vc::upcast(api_chunk))
}
}

View file

@ -3,27 +3,16 @@ use std::path::MAIN_SEPARATOR;
use anyhow::{Context, Result};
use indexmap::{map::Entry, IndexMap};
use next_core::{
app_structure::{find_app_dir, get_entrypoints},
app_structure::find_app_dir,
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_client::{get_client_chunking_context, get_client_compile_time_info},
next_config::NextConfig,
next_dynamic::NextDynamicTransition,
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, PagesStructure},
next_server::{get_server_chunking_context, get_server_compile_time_info},
util::NextSourceConfig,
};
use serde::{Deserialize, Serialize};
use turbo_tasks::{
debug::ValueDebugFormat, trace::TraceRawVcs, unit, TaskInput, TransientValue, TryJoinIterExt,
Value, Vc,
debug::ValueDebugFormat, trace::TraceRawVcs, unit, TaskInput, TransientValue, Vc,
};
use turbopack_binding::{
turbo::{
@ -33,29 +22,21 @@ use turbopack_binding::{
turbopack::{
build::BuildChunkingContext,
core::{
chunk::{ChunkingContext, EvaluatableAssets},
compile_time_info::CompileTimeInfo,
context::AssetContext,
environment::ServerAddr,
chunk::ChunkingContext, compile_time_info::CompileTimeInfo, environment::ServerAddr,
PROJECT_FILESYSTEM_NAME,
},
dev::DevChunkingContext,
ecmascript::chunk::EcmascriptChunkingContext,
env::dotenv::load_env,
node::execution_context::ExecutionContext,
turbopack::{
evaluate_context::node_build_environment,
module_options::ModuleOptionsContext,
resolve_options_context::ResolveOptionsContext,
transition::{ContextTransition, TransitionsByName},
ModuleAssetContext,
},
turbopack::evaluate_context::node_build_environment,
},
};
use crate::{
app::app_entry_point_to_route,
pages::get_pages_routes,
app::{AppProject, OptionAppProject},
entrypoints::Entrypoints,
pages::PagesProject,
route::{Endpoint, Route},
};
@ -85,12 +66,6 @@ pub struct Middleware {
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
@ -129,6 +104,24 @@ impl Project {
.cell())
}
#[turbo_tasks::function]
async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
let this = self.await?;
let app_dir = find_app_dir(self.project_path()).await?;
Ok(Vc::cell(if let Some(app_dir) = &*app_dir {
Some(AppProject::new(self, *app_dir, this.mode))
} else {
None
}))
}
#[turbo_tasks::function]
async fn pages_project(self: Vc<Self>) -> Result<Vc<PagesProject>> {
let this = self.await?;
Ok(PagesProject::new(self, this.mode))
}
#[turbo_tasks::function]
async fn project_fs(self: Vc<Self>) -> Result<Vc<Box<dyn FileSystem>>> {
let this = self.await?;
@ -172,7 +165,12 @@ impl Project {
}
#[turbo_tasks::function]
async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
pub(super) fn client_relative_path(self: Vc<Self>) -> Vc<FileSystemPath> {
self.client_root().join("_next".to_string())
}
#[turbo_tasks::function]
pub(super) async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
let this = self.await?;
let root = self.project_root_path();
let project_relative = this.project_path.strip_prefix(&this.root_path).unwrap();
@ -184,29 +182,17 @@ impl Project {
}
#[turbo_tasks::function]
async fn pages_structure(self: Vc<Self>) -> Result<Vc<PagesStructure>> {
let this: turbo_tasks::ReadRef<Project> = self.await?;
let next_router_fs = Vc::upcast::<Box<dyn FileSystem>>(VirtualFileSystem::new());
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: Vc<Self>) -> Vc<Box<dyn ProcessEnv>> {
pub(super) fn env(self: Vc<Self>) -> Vc<Box<dyn ProcessEnv>> {
load_env(self.project_path())
}
#[turbo_tasks::function]
async fn next_config(self: Vc<Self>) -> Result<Vc<NextConfig>> {
pub(super) async fn next_config(self: Vc<Self>) -> Result<Vc<NextConfig>> {
Ok(self.await?.next_config)
}
#[turbo_tasks::function]
fn execution_context(self: Vc<Self>) -> Vc<ExecutionContext> {
pub(super) fn execution_context(self: Vc<Self>) -> Vc<ExecutionContext> {
let node_root = self.node_root();
let node_execution_chunking_context = Vc::upcast(
@ -228,16 +214,12 @@ impl Project {
}
#[turbo_tasks::function]
async fn client_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
let this = self.await?;
Ok(get_client_compile_time_info(
this.mode,
this.browserslist_query.clone(),
))
pub(super) fn client_compile_time_info(&self) -> Vc<CompileTimeInfo> {
get_client_compile_time_info(self.mode, self.browserslist_query.clone())
}
#[turbo_tasks::function]
async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
let this = self.await?;
Ok(get_server_compile_time_info(
this.mode,
@ -247,178 +229,6 @@ impl Project {
))
}
#[turbo_tasks::function]
async fn pages_dir(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
Ok(if let Some(pages) = self.pages_structure().await?.pages {
pages.project_path()
} else {
self.project_path().join("pages".to_string())
})
}
#[turbo_tasks::function]
fn pages_transitions(self: Vc<Self>) -> Vc<TransitionsByName> {
Vc::cell(
[(
"next-dynamic".to_string(),
Vc::upcast(NextDynamicTransition::new(self.pages_client_transition())),
)]
.into_iter()
.collect(),
)
}
#[turbo_tasks::function]
fn pages_client_transition(self: Vc<Self>) -> Vc<ContextTransition> {
ContextTransition::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: Vc<Self>,
) -> Result<Vc<ModuleOptionsContext>> {
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: Vc<Self>,
) -> Result<Vc<ResolveOptionsContext>> {
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: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.pages_transitions(),
self.client_compile_time_info(),
self.pages_client_module_options_context(),
self.pages_client_resolve_options_context(),
))
}
#[turbo_tasks::function]
pub(super) fn pages_ssr_module_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.pages_transitions(),
self.server_compile_time_info(),
self.pages_ssr_module_options_context(),
self.pages_ssr_resolve_options_context(),
))
}
#[turbo_tasks::function]
pub(super) fn pages_ssr_data_module_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
Vc::upcast(ModuleAssetContext::new(
self.pages_transitions(),
self.server_compile_time_info(),
self.pages_ssr_data_module_options_context(),
self.pages_ssr_resolve_options_context(),
))
}
#[turbo_tasks::function]
async fn pages_ssr_module_options_context(self: Vc<Self>) -> Result<Vc<ModuleOptionsContext>> {
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: Vc<Self>,
) -> Result<Vc<ModuleOptionsContext>> {
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: Vc<Self>,
) -> Result<Vc<ResolveOptionsContext>> {
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: Vc<Self>,
) -> Result<Vc<EvaluatableAssets>> {
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: Vc<Self>) -> Result<Vc<EvaluatableAssets>> {
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: Vc<Self>,
@ -474,25 +284,16 @@ impl Project {
/// provided page_extensions).
#[turbo_tasks::function]
pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
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?,
);
let app_project = self.app_project();
let pages_project = self.pages_project();
if let Some(app_project) = &*app_project.await? {
let app_routes = app_project.routes();
routes.extend(app_routes.await?.iter().map(|(k, v)| (k.clone(), *v)));
}
for (pathname, page_route) in get_pages_routes(self, self.pages_structure()).await?.iter() {
for (pathname, page_route) in pages_project.routes().await?.iter() {
match routes.entry(pathname.clone()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() = Route::Conflict;
@ -502,6 +303,7 @@ impl Project {
}
}
}
// TODO middleware
Ok(Entrypoints {
routes,

View file

@ -8,7 +8,6 @@ use turbopack_binding::turbo::{
};
pub mod build_options;
pub mod manifests;
pub(crate) mod next_app;
pub(crate) mod next_build;
pub(crate) mod next_pages;

View file

@ -1,78 +1,44 @@
use std::collections::HashMap;
use anyhow::{Context, Result};
use indexmap::IndexMap;
use indoc::formatdoc;
use anyhow::Result;
use next_core::{
app_structure::{find_app_dir_if_enabled, get_entrypoints, get_global_metadata, Entrypoint},
mode::NextMode,
next_app::{
get_app_client_shared_chunks, get_app_page_entry, get_app_route_entry,
get_app_route_favicon_entry, AppEntry, ClientReferencesChunks,
},
next_client::{
get_client_module_options_context, get_client_resolve_options_context,
get_client_runtime_entries, ClientContextType,
},
next_client_reference::{
ClientReference, ClientReferenceType, NextEcmascriptClientReferenceTransition,
},
next_client_reference::{ClientReferenceGraph, NextEcmascriptClientReferenceTransition},
next_config::NextConfig,
next_dynamic::NextDynamicTransition,
next_manifests::{AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest},
next_server::{
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
},
};
use turbo_tasks::{TryJoinIterExt, Value, ValueToString, Vc};
use turbo_tasks::{TryJoinIterExt, Value, Vc};
use turbopack_binding::{
turbo::{
tasks_env::{CustomProcessEnv, ProcessEnv},
tasks_fs::{File, FileSystemPath},
tasks_fs::FileSystemPath,
},
turbopack::{
build::BuildChunkingContext,
core::{
asset::{Asset, AssetContent},
chunk::{
availability_info::AvailabilityInfo, ChunkingContext, EvaluatableAssets,
ModuleId as TurbopackModuleId,
},
compile_time_info::CompileTimeInfo,
file_source::FileSource,
output::{OutputAsset, OutputAssets},
raw_output::RawOutput,
virtual_source::VirtualSource,
},
ecmascript::{
chunk::{
EcmascriptChunk, EcmascriptChunkItemExt, EcmascriptChunkPlaceable,
EcmascriptChunkingContext,
},
utils::StringifyJs,
asset::Asset, chunk::EvaluatableAssets, compile_time_info::CompileTimeInfo,
file_source::FileSource, output::OutputAsset,
},
ecmascript::chunk::EcmascriptChunkingContext,
node::execution_context::ExecutionContext,
turbopack::{transition::ContextTransition, ModuleAssetContext},
},
};
use super::{
app_client_reference::ClientReferenceChunks, app_page_entry::get_app_page_entry,
app_route_entry::get_app_route_entry, app_route_favicon_entry::get_app_route_favicon_entry,
};
use crate::manifests::{
AppBuildManifest, AppPathsManifest, BuildManifest, ClientReferenceManifest, ManifestNode,
ManifestNodeEntry, ModuleId,
};
/// The entry module asset for a Next.js app route or page.
#[turbo_tasks::value(shared)]
pub struct AppEntry {
/// The pathname of the route or page.
pub pathname: String,
/// The original Next.js name of the route or page. This is used instead of
/// the pathname to refer to this entry.
pub original_name: String,
/// The RSC module asset for the route or page.
pub rsc_entry: Vc<Box<dyn EcmascriptChunkPlaceable>>,
}
#[turbo_tasks::value]
pub struct AppEntries {
/// All app entries.
@ -223,19 +189,19 @@ pub async fn get_app_entries(
.iter()
.map(|(pathname, entrypoint)| async move {
Ok(match entrypoint {
Entrypoint::AppPage { loader_tree } => {
get_app_page_entry(rsc_context, *loader_tree, app_dir, pathname, project_root)
.await?
}
Entrypoint::AppRoute { path } => {
get_app_route_entry(
rsc_context,
Vc::upcast(FileSource::new(*path)),
pathname,
project_root,
)
.await?
}
Entrypoint::AppPage { loader_tree } => get_app_page_entry(
rsc_context,
*loader_tree,
app_dir,
pathname.clone(),
project_root,
),
Entrypoint::AppRoute { path } => get_app_route_entry(
rsc_context,
Vc::upcast(FileSource::new(*path)),
pathname.clone(),
project_root,
),
})
})
.try_join()
@ -245,7 +211,11 @@ pub async fn get_app_entries(
let global_metadata = global_metadata.await?;
if let Some(favicon) = global_metadata.favicon {
entries.push(get_app_route_favicon_entry(rsc_context, favicon, project_root).await?);
entries.push(get_app_route_favicon_entry(
rsc_context,
favicon,
project_root,
));
}
let client_context = ModuleAssetContext::new(
@ -276,20 +246,20 @@ pub async fn get_app_entries(
/// manifests.
pub async fn compute_app_entries_chunks(
app_entries: &AppEntries,
app_client_references_by_entry: &IndexMap<Vc<Box<dyn Asset>>, Vec<ClientReference>>,
app_client_references_chunks: &IndexMap<ClientReferenceType, ClientReferenceChunks>,
app_client_reference_graph: Vc<ClientReferenceGraph>,
app_client_references_chunks: Vc<ClientReferencesChunks>,
rsc_chunking_context: Vc<BuildChunkingContext>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
ssr_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
node_root: Vc<FileSystemPath>,
client_relative_path: &FileSystemPath,
client_relative_path: Vc<FileSystemPath>,
app_paths_manifest_dir_path: &FileSystemPath,
app_build_manifest: &mut AppBuildManifest,
build_manifest: &mut BuildManifest,
app_paths_manifest: &mut AppPathsManifest,
all_chunks: &mut Vec<Vc<Box<dyn OutputAsset>>>,
) -> Result<()> {
let node_root_ref = node_root.await?;
let client_relative_path_ref = client_relative_path.await?;
let app_client_shared_chunks =
get_app_client_shared_chunks(app_entries.client_runtime_entries, client_chunking_context);
@ -300,19 +270,21 @@ pub async fn compute_app_entries_chunks(
let chunk_path = chunk.ident().path().await?;
if chunk_path.extension_ref() == Some("js") {
if let Some(chunk_path) = client_relative_path.get_path_to(&chunk_path) {
if let Some(chunk_path) = client_relative_path.await?.get_path_to(&chunk_path) {
app_shared_client_chunks_paths.push(chunk_path.to_string());
build_manifest.root_main_files.push(chunk_path.to_string());
}
}
}
let app_client_references_chunks_ref = app_client_references_chunks.await?;
for app_entry in app_entries.entries.iter().copied() {
let app_entry = app_entry.await?;
let app_entry_client_references = app_client_references_by_entry
.get(&Vc::upcast(app_entry.rsc_entry))
.expect("app entry should have a corresponding client references list");
let app_entry_client_references = app_client_reference_graph
.entry(Vc::upcast(app_entry.rsc_entry))
.await?;
let rsc_chunk = rsc_chunking_context.entry_chunk(
node_root.join(format!(
@ -325,10 +297,11 @@ pub async fn compute_app_entries_chunks(
all_chunks.push(rsc_chunk);
let mut app_entry_client_chunks = vec![];
// TODO(alexkirsz) In which manifest should this go?
let mut app_entry_ssr_chunks = vec![];
for client_reference in app_entry_client_references {
let client_reference_chunks = app_client_references_chunks
for client_reference in app_entry_client_references.iter() {
let client_reference_chunks = app_client_references_chunks_ref
.get(client_reference.ty())
.expect("client reference should have corresponding chunks");
app_entry_client_chunks
@ -344,7 +317,7 @@ pub async fn compute_app_entries_chunks(
let mut app_entry_client_chunks_paths: Vec<_> = app_entry_client_chunks_paths
.iter()
.map(|path| {
client_relative_path
client_relative_path_ref
.get_path_to(path)
.expect("asset path should be inside client root")
.to_string()
@ -365,232 +338,18 @@ pub async fn compute_app_entries_chunks(
.to_string(),
);
let mut entry_manifest: ClientReferenceManifest = Default::default();
for app_client_reference in app_entry_client_references {
let app_client_reference_ty = app_client_reference.ty();
let app_client_reference_chunks = app_client_references_chunks
.get(app_client_reference.ty())
.context("client reference chunks not found")?;
let client_reference_chunks = app_client_references_chunks
.get(app_client_reference_ty)
.context("client reference chunks not found")?;
let client_chunks = &client_reference_chunks.client_chunks.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
if let Some(server_component) = app_client_reference.server_component() {
let server_component_name = server_component
.server_path()
.with_extension("".to_string())
.to_string()
.await?;
let entry_css_files = entry_manifest
.entry_css_files
.entry(server_component_name.clone_value())
.or_insert_with(Default::default);
match app_client_reference_ty {
ClientReferenceType::CssClientReference(_) => {
entry_css_files.extend(
client_chunks_paths
.iter()
.filter_map(|chunk_path| {
client_relative_path.get_path_to(chunk_path)
})
.map(ToString::to_string),
);
}
ClientReferenceType::EcmascriptClientReference(_) => {
entry_css_files.extend(
client_chunks_paths
.iter()
.filter_map(|chunk_path| {
if chunk_path.extension_ref() == Some("css") {
client_relative_path.get_path_to(chunk_path)
} else {
None
}
})
.map(ToString::to_string),
);
}
}
}
match app_client_reference_ty {
ClientReferenceType::CssClientReference(_) => {}
ClientReferenceType::EcmascriptClientReference(ecmascript_client_reference) => {
let client_chunks = &app_client_reference_chunks.client_chunks.await?;
let ssr_chunks = &app_client_reference_chunks.ssr_chunks.await?;
let ecmascript_client_reference = ecmascript_client_reference.await?;
let client_module_id = ecmascript_client_reference
.client_module
.as_chunk_item(client_chunking_context)
.id()
.await?;
let ssr_module_id = ecmascript_client_reference
.ssr_module
.as_chunk_item(ssr_chunking_context)
.id()
.await?;
let server_path = ecmascript_client_reference
.server_ident
.path()
.to_string()
.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
let client_chunks_paths: Vec<String> = client_chunks_paths
.iter()
.filter_map(|chunk_path| client_relative_path.get_path_to(chunk_path))
.map(ToString::to_string)
.collect::<Vec<_>>();
let ssr_chunks_paths = ssr_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
let ssr_chunks_paths = ssr_chunks_paths
.iter()
.filter_map(|chunk_path| node_root_ref.get_path_to(chunk_path))
.map(ToString::to_string)
.collect::<Vec<_>>();
let mut ssr_manifest_node = ManifestNode::default();
entry_manifest.client_modules.module_exports.insert(
get_client_reference_module_key(&server_path, "*"),
ManifestNodeEntry {
name: "*".to_string(),
id: (&*client_module_id).into(),
chunks: client_chunks_paths.clone(),
// TODO(WEB-434)
r#async: false,
},
);
ssr_manifest_node.module_exports.insert(
"*".to_string(),
ManifestNodeEntry {
name: "*".to_string(),
id: (&*ssr_module_id).into(),
chunks: ssr_chunks_paths.clone(),
// TODO(WEB-434)
r#async: false,
},
);
entry_manifest
.ssr_module_mapping
.insert((&*client_module_id).into(), ssr_manifest_node);
}
}
}
let client_reference_manifest_json = serde_json::to_string(&entry_manifest).unwrap();
let client_reference_manifest_source = VirtualSource::new(
node_root.join(format!(
"server/app/{original_name}_client-reference-manifest.js",
original_name = app_entry.original_name
)),
AssetContent::file(
File::from(formatdoc! {
r#"
globalThis.__RSC_MANIFEST = globalThis.__RSC_MANIFEST || {{}};
globalThis.__RSC_MANIFEST[{original_name}] = {manifest}
"#,
original_name = StringifyJs(&app_entry.original_name),
manifest = StringifyJs(&client_reference_manifest_json)
})
.into(),
),
let entry_manifest = ClientReferenceManifest::build_output(
node_root,
client_relative_path,
app_entry.original_name.clone(),
app_client_reference_graph.entry(Vc::upcast(app_entry.rsc_entry)),
app_client_references_chunks,
client_chunking_context,
ssr_chunking_context,
);
all_chunks.push(Vc::upcast(RawOutput::new(Vc::upcast(
client_reference_manifest_source,
))));
all_chunks.push(entry_manifest);
}
Ok(())
}
#[turbo_tasks::function]
pub async fn get_app_shared_client_chunk(
app_client_runtime_entries: Vc<EvaluatableAssets>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
) -> Result<Vc<EcmascriptChunk>> {
let client_runtime_entries: Vec<_> = app_client_runtime_entries
.await?
.iter()
.map(|entry| async move {
Ok(Vc::try_resolve_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(*entry).await?)
})
.try_join()
.await?
.into_iter()
.flatten()
.collect();
Ok(EcmascriptChunk::new_normalized(
client_chunking_context,
// TODO(alexkirsz) Should this accept Evaluatable instead?
Vc::cell(client_runtime_entries),
None,
Value::new(AvailabilityInfo::Untracked),
))
}
#[turbo_tasks::function]
pub async fn get_app_client_shared_chunks(
app_client_runtime_entries: Vc<EvaluatableAssets>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
) -> Result<Vc<OutputAssets>> {
if app_client_runtime_entries.await?.is_empty() {
return Ok(OutputAssets::empty());
}
let app_client_shared_chunk =
get_app_shared_client_chunk(app_client_runtime_entries, client_chunking_context);
let app_client_shared_chunks = client_chunking_context.evaluated_chunk_group(
Vc::upcast(app_client_shared_chunk),
app_client_runtime_entries,
);
Ok(app_client_shared_chunks)
}
/// See next.js/packages/next/src/lib/client-reference.ts
fn get_client_reference_module_key(server_path: &str, export_name: &str) -> String {
if export_name == "*" {
server_path.to_string()
} else {
format!("{}#{}", server_path, export_name)
}
}
impl From<&TurbopackModuleId> for ModuleId {
fn from(module_id: &TurbopackModuleId) -> Self {
match module_id {
TurbopackModuleId::String(string) => ModuleId::String(string.clone()),
TurbopackModuleId::Number(number) => ModuleId::Number(*number as _),
}
}
}

View file

@ -1,5 +1 @@
pub(crate) mod app_client_reference;
pub(crate) mod app_entries;
pub(crate) mod app_page_entry;
pub(crate) mod app_route_entry;
pub(crate) mod app_route_favicon_entry;

View file

@ -8,10 +8,16 @@ use anyhow::{anyhow, bail, Context, Result};
use dunce::canonicalize;
use next_core::{
mode::NextMode,
next_app::get_app_client_references_chunks,
next_client::{get_client_chunking_context, get_client_compile_time_info},
next_client_reference::{ClientReferenceType, ClientReferencesByEntry},
next_client_reference::{ClientReferenceGraph, ClientReferenceType},
next_config::load_next_config,
next_dynamic::NextDynamicEntries,
next_manifests::{
AppBuildManifest, AppPathsManifest, BuildManifest, ClientBuildManifest, FontManifest,
MiddlewaresManifest, NextFontManifest, PagesManifest, ReactLoadableManifest,
ServerReferenceManifest,
},
next_server::{get_server_chunking_context, get_server_compile_time_info},
url_node::get_sorted_routes,
{self},
@ -45,15 +51,7 @@ use turbopack_binding::{
use crate::{
build_options::{BuildContext, BuildOptions},
manifests::{
AppBuildManifest, AppPathsManifest, BuildManifest, ClientBuildManifest, FontManifest,
MiddlewaresManifest, NextFontManifest, PagesManifest, ReactLoadableManifest,
ServerReferenceManifest,
},
next_app::{
app_client_reference::compute_app_client_references_chunks,
app_entries::{compute_app_entries_chunks, get_app_entries},
},
next_app::app_entries::{compute_app_entries_chunks, get_app_entries},
next_pages::page_entries::{compute_page_entries_chunks, get_page_entries},
};
@ -167,26 +165,16 @@ pub(crate) async fn next_build(options: TransientInstance<BuildOptions>) -> Resu
.try_join()
.await?;
let app_client_references_by_entry = ClientReferencesByEntry::new(Vc::cell(
let app_client_references = ClientReferenceGraph::new(Vc::cell(
app_rsc_entries.iter().copied().map(Vc::upcast).collect(),
))
.await?;
let app_client_references: HashSet<_> = app_client_references_by_entry
.values()
.flatten()
.copied()
.collect();
));
// The same client reference can occur from two different server components.
// Here, we're only interested in deduped client references.
let app_client_reference_tys: HashSet<_> = app_client_references
.iter()
.map(|client_reference| client_reference.ty())
.copied()
.collect();
let app_client_reference_tys = app_client_references.types();
let app_ssr_entries: Vec<_> = app_client_reference_tys
.await?
.iter()
.map(|client_reference_ty| async move {
let ClientReferenceType::EcmascriptClientReference(entry) = client_reference_ty else {
@ -298,26 +286,32 @@ pub(crate) async fn next_build(options: TransientInstance<BuildOptions>) -> Resu
// APP CLIENT REFERENCES CHUNKING
let app_client_references_chunks = compute_app_client_references_chunks(
&app_client_reference_tys,
let app_client_references_chunks = get_app_client_references_chunks(
app_client_reference_tys,
client_chunking_context,
ssr_chunking_context,
&mut all_chunks,
)
.await?;
);
let app_client_references_chunks_ref = app_client_references_chunks.await?;
for app_client_reference_chunks in app_client_references_chunks_ref.values() {
let client_chunks = &app_client_reference_chunks.client_chunks.await?;
let ssr_chunks = &app_client_reference_chunks.ssr_chunks.await?;
all_chunks.extend(client_chunks.iter().copied());
all_chunks.extend(ssr_chunks.iter().copied());
}
// APP RSC CHUNKING
// TODO(alexkirsz) Do some of that in parallel with the above.
compute_app_entries_chunks(
&app_entries,
&app_client_references_by_entry,
&app_client_references_chunks,
app_client_references,
app_client_references_chunks,
rsc_chunking_context,
client_chunking_context,
Vc::upcast(ssr_chunking_context),
node_root,
&client_relative_path_ref,
client_relative_path,
&app_paths_manifest_dir_path,
&mut app_build_manifest,
&mut build_manifest,

View file

@ -8,6 +8,7 @@ use next_core::{
},
next_config::NextConfig,
next_dynamic::NextDynamicTransition,
next_manifests::{BuildManifest, PagesManifest},
next_server::{
get_server_module_options_context, get_server_resolve_options_context,
get_server_runtime_entries, ServerContextType,
@ -41,8 +42,6 @@ use turbopack_binding::{
},
};
use crate::manifests::{BuildManifest, PagesManifest};
#[turbo_tasks::value]
pub struct PageEntries {
pub entries: Vec<Vc<PageEntry>>,

View file

@ -12,6 +12,7 @@ bench = false
anyhow = { workspace = true }
async-recursion = { workspace = true }
async-trait = { workspace = true }
base64 = "0.21.0"
const_format = "0.2.30"
once_cell = { workspace = true }
qstring = { workspace = true }
@ -20,6 +21,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
mime = { workspace = true }
mime_guess = "2.0.4"
indoc = { workspace = true }
allsorts = { workspace = true }
futures = { workspace = true }

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-230716.2",
"@vercel/turbopack-node": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230716.2",
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2",
"@vercel/turbopack-node": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230717.2",
"anser": "^2.1.1",
"css.escape": "^1.5.1",
"next": "*",

View file

@ -65,6 +65,7 @@ use crate::{
fallback::get_fallback_page,
loader_tree::{LoaderTreeModule, ServerComponentTransition},
mode::NextMode,
next_app::UnsupportedDynamicMetadataIssue,
next_client::{
context::{
get_client_assets_path, get_client_module_options_context,
@ -89,7 +90,6 @@ use crate::{
get_server_resolve_options_context, ServerContextType,
},
util::{render_data, NextRuntime},
UnsupportedDynamicMetadataIssue,
};
fn pathname_to_segments(pathname: &str) -> Result<(Vec<BaseSegment>, RouteType)> {

View file

@ -94,7 +94,7 @@ pub enum MetadataWithAltItem {
}
/// A single metadata file.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TaskInput, TraceRawVcs)]
pub enum MetadataItem {
Static { path: Vc<FileSystemPath> },
Dynamic { path: Vc<FileSystemPath> },

View file

@ -12,6 +12,9 @@ use turbopack_binding::turbopack::core::{
/// Emits all assets transitively reachable from the given chunks, that are
/// inside the node root or the client root.
///
/// Assets inside the given client root are rebased to the given client output
/// path.
#[turbo_tasks::function]
pub async fn emit_all_assets(
assets: Vc<OutputAssets>,

View file

@ -12,14 +12,14 @@ mod app_source;
pub mod app_structure;
mod babel;
mod bootstrap;
pub mod dev_manifest;
mod embed_js;
mod emit;
pub mod env;
mod fallback;
pub mod loader_tree;
pub mod manifest;
pub mod mode;
pub(crate) mod next_app;
pub mod next_app;
mod next_build;
pub mod next_client;
pub mod next_client_chunks;
@ -31,6 +31,7 @@ mod next_edge;
mod next_font;
pub mod next_image;
mod next_import_map;
pub mod next_manifests;
mod next_route_matcher;
pub mod next_server;
pub mod next_server_component;
@ -49,7 +50,6 @@ 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;
pub use page_loader::create_page_loader_entry_module;
pub use page_source::create_page_source;
pub use turbopack_binding::{turbopack::node::source_map, *};

View file

@ -1,33 +1,44 @@
use std::collections::HashSet;
use anyhow::Result;
use indexmap::IndexMap;
use next_core::{
next_client_reference::ClientReferenceType,
{self},
};
use turbo_tasks::{TryJoinIterExt, Vc};
use serde::{Deserialize, Serialize};
use turbo_tasks::{debug::ValueDebugFormat, trace::TraceRawVcs, TryJoinIterExt, Vc};
use turbopack_binding::turbopack::{
build::BuildChunkingContext,
core::{
chunk::{ChunkableModule, ChunkingContext},
output::{OutputAsset, OutputAssets},
output::OutputAssets,
},
ecmascript::chunk::EcmascriptChunkingContext,
};
/// Computes all client references chunks, and adds them to the relevant
/// manifests.
use crate::next_client_reference::{ClientReferenceType, ClientReferenceTypes};
/// Contains the chunks corresponding to a client reference.
#[derive(
Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat,
)]
pub struct ClientReferenceChunks {
/// Chunks to be loaded on the client.
pub client_chunks: Vc<OutputAssets>,
/// Chunks to be loaded on the server for SSR.
pub ssr_chunks: Vc<OutputAssets>,
}
#[turbo_tasks::value(transparent)]
pub struct ClientReferencesChunks(IndexMap<ClientReferenceType, ClientReferenceChunks>);
/// Computes all client references chunks.
///
/// This returns a map from client reference type to the chunks that reference
/// type needs to load.
pub async fn compute_app_client_references_chunks(
app_client_reference_types: &HashSet<ClientReferenceType>,
#[turbo_tasks::function]
pub async fn get_app_client_references_chunks(
app_client_reference_types: Vc<ClientReferenceTypes>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
ssr_chunking_context: Vc<BuildChunkingContext>,
all_chunks: &mut Vec<Vc<Box<dyn OutputAsset>>>,
) -> Result<IndexMap<ClientReferenceType, ClientReferenceChunks>> {
) -> Result<Vc<ClientReferencesChunks>> {
let app_client_references_chunks: IndexMap<_, _> = app_client_reference_types
.await?
.iter()
.map(|client_reference_ty| async move {
Ok((
@ -64,28 +75,5 @@ pub async fn compute_app_client_references_chunks(
.into_iter()
.collect();
for (app_client_reference_ty, app_client_reference_chunks) in &app_client_references_chunks {
match app_client_reference_ty {
ClientReferenceType::EcmascriptClientReference(_) => {
let client_chunks = &app_client_reference_chunks.client_chunks.await?;
let ssr_chunks = &app_client_reference_chunks.ssr_chunks.await?;
all_chunks.extend(client_chunks.iter().copied());
all_chunks.extend(ssr_chunks.iter().copied());
}
ClientReferenceType::CssClientReference(_) => {
let client_chunks = &app_client_reference_chunks.client_chunks.await?;
all_chunks.extend(client_chunks.iter().copied());
}
}
}
Ok(app_client_references_chunks)
}
/// Contains the chunks corresponding to a client reference.
pub struct ClientReferenceChunks {
/// Chunks to be loaded on the client.
pub client_chunks: Vc<OutputAssets>,
/// Chunks to be loaded on the server for SSR.
pub ssr_chunks: Vc<OutputAssets>,
Ok(Vc::cell(app_client_references_chunks))
}

View file

@ -0,0 +1,55 @@
use anyhow::Result;
use turbo_tasks::{TryJoinIterExt, Value, Vc};
use turbopack_binding::turbopack::{
core::{
chunk::{availability_info::AvailabilityInfo, ChunkingContext, EvaluatableAssets},
output::OutputAssets,
},
ecmascript::chunk::{EcmascriptChunk, EcmascriptChunkPlaceable, EcmascriptChunkingContext},
};
#[turbo_tasks::function]
pub async fn get_app_shared_client_chunk(
app_client_runtime_entries: Vc<EvaluatableAssets>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
) -> Result<Vc<EcmascriptChunk>> {
let client_runtime_entries: Vec<_> = app_client_runtime_entries
.await?
.iter()
.map(|entry| async move {
Ok(Vc::try_resolve_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(*entry).await?)
})
.try_join()
.await?
.into_iter()
.flatten()
.collect();
Ok(EcmascriptChunk::new_normalized(
client_chunking_context,
// TODO(alexkirsz) Should this accept Evaluatable instead?
Vc::cell(client_runtime_entries),
None,
Value::new(AvailabilityInfo::Untracked),
))
}
#[turbo_tasks::function]
pub async fn get_app_client_shared_chunks(
app_client_runtime_entries: Vc<EvaluatableAssets>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
) -> Result<Vc<OutputAssets>> {
if app_client_runtime_entries.await?.is_empty() {
return Ok(OutputAssets::empty());
}
let app_client_shared_chunk =
get_app_shared_client_chunk(app_client_runtime_entries, client_chunking_context);
let app_client_shared_chunks = client_chunking_context.evaluated_chunk_group(
Vc::upcast(app_client_shared_chunk),
app_client_runtime_entries,
);
Ok(app_client_shared_chunks)
}

View file

@ -0,0 +1,14 @@
use turbo_tasks::Vc;
use turbopack_binding::turbopack::ecmascript::chunk::EcmascriptChunkPlaceable;
/// The entry module asset for a Next.js app route or page.
#[turbo_tasks::value(shared)]
pub struct AppEntry {
/// The pathname of the route or page.
pub pathname: String,
/// The original Next.js name of the route or page. This is used instead of
/// the pathname to refer to this entry.
pub original_name: String,
/// The RSC module asset for the route or page.
pub rsc_entry: Vc<Box<dyn EcmascriptChunkPlaceable>>,
}

View file

@ -3,7 +3,6 @@ use std::io::Write;
use anyhow::{bail, Result};
use base64::{display::Base64Display, engine::general_purpose::STANDARD};
use indoc::writedoc;
use next_core::app_structure::MetadataItem;
use turbo_tasks::{ValueToString, Vc};
use turbopack_binding::{
turbo::tasks_fs::{rope::RopeBuilder, File, FileContent, FileSystemPath},
@ -14,10 +13,12 @@ use turbopack_binding::{
},
};
use super::{app_entries::AppEntry, app_route_entry::get_app_route_entry};
use super::app_route_entry::get_app_route_entry;
use crate::{app_structure::MetadataItem, next_app::AppEntry};
/// Computes the entry for a Next.js favicon file.
pub(super) async fn get_app_route_favicon_entry(
#[turbo_tasks::function]
pub async fn get_app_route_favicon_entry(
rsc_context: Vc<ModuleAssetContext>,
favicon: MetadataItem,
project_root: Vc<FileSystemPath>,
@ -77,12 +78,11 @@ pub(super) async fn get_app_route_favicon_entry(
// TODO(alexkirsz) Figure out how to name this virtual source.
VirtualSource::new(project_root.join("todo.tsx".to_string()), AssetContent::file(file.into()));
get_app_route_entry(
Ok(get_app_route_entry(
rsc_context,
Vc::upcast(source),
// TODO(alexkirsz) Get this from the metadata?
"/favicon.ico",
"/favicon.ico".to_string(),
project_root,
)
.await
))
}

View file

@ -2,13 +2,6 @@ use std::io::Write;
use anyhow::{bail, Result};
use indoc::writedoc;
use next_core::{
app_structure::LoaderTree,
loader_tree::{LoaderTreeModule, ServerComponentTransition},
mode::NextMode,
next_server_component::NextServerComponentTransition,
UnsupportedDynamicMetadataIssue,
};
use turbo_tasks::{TryJoinIterExt, Value, ValueToString, Vc};
use turbopack_binding::{
turbo::tasks_fs::{rope::RopeBuilder, File, FileSystemPath},
@ -22,14 +15,22 @@ use turbopack_binding::{
},
};
use super::app_entries::AppEntry;
use super::app_entry::AppEntry;
use crate::{
app_structure::LoaderTree,
loader_tree::{LoaderTreeModule, ServerComponentTransition},
mode::NextMode,
next_app::UnsupportedDynamicMetadataIssue,
next_server_component::NextServerComponentTransition,
};
/// Computes the entry for a Next.js app page.
pub(super) async fn get_app_page_entry(
#[turbo_tasks::function]
pub async fn get_app_page_entry(
context: Vc<ModuleAssetContext>,
loader_tree: Vc<LoaderTree>,
app_dir: Vc<FileSystemPath>,
pathname: &str,
pathname: String,
project_root: Vc<FileSystemPath>,
) -> Result<Vc<AppEntry>> {
let server_component_transition = Vc::upcast(NextServerComponentTransition::new());
@ -70,7 +71,7 @@ pub(super) async fn get_app_page_entry(
// NOTE(alexkirsz) Keep in sync with
// next.js/packages/next/src/build/webpack/loaders/next-app-loader.ts
// TODO(alexkirsz) Support custom global error.
let original_name = get_original_page_name(pathname);
let original_name = get_original_page_name(&pathname);
writedoc!(
result,

View file

@ -20,19 +20,20 @@ use turbopack_binding::{
},
};
use super::app_entries::AppEntry;
use crate::next_app::AppEntry;
/// Computes the entry for a Next.js app route.
pub(super) async fn get_app_route_entry(
#[turbo_tasks::function]
pub async fn get_app_route_entry(
rsc_context: Vc<ModuleAssetContext>,
source: Vc<Box<dyn Source>>,
pathname: &str,
pathname: String,
project_root: Vc<FileSystemPath>,
) -> Result<Vc<AppEntry>> {
let mut result = RopeBuilder::default();
let kind = "app-route";
let original_name = get_original_route_name(pathname);
let original_name = get_original_route_name(&pathname);
let path = source.ident().path();
let options = AppRouteRouteModuleOptions {

View file

@ -1 +1,17 @@
pub(crate) mod app_client_references_chunks;
pub(crate) mod app_client_shared_chunks;
pub(crate) mod app_entry;
pub(crate) mod app_favicon_entry;
pub(crate) mod app_page_entry;
pub(crate) mod app_route_entry;
pub(crate) mod unsupported_dynamic_metadata_issue;
pub use app_client_references_chunks::{
get_app_client_references_chunks, ClientReferenceChunks, ClientReferencesChunks,
};
pub use app_client_shared_chunks::get_app_client_shared_chunks;
pub use app_entry::AppEntry;
pub use app_favicon_entry::get_app_route_favicon_entry;
pub use app_page_entry::get_app_page_entry;
pub use app_route_entry::get_app_route_entry;
pub use unsupported_dynamic_metadata_issue::UnsupportedDynamicMetadataIssue;

View file

@ -7,4 +7,7 @@ pub use ecmascript_client_reference::{
ecmascript_client_reference_module::EcmascriptClientReferenceModule,
ecmascript_client_reference_transition::NextEcmascriptClientReferenceTransition,
};
pub use visit_client_reference::{ClientReference, ClientReferenceType, ClientReferencesByEntry};
pub use visit_client_reference::{
ClientReference, ClientReferenceGraph, ClientReferenceType, ClientReferenceTypes,
ClientReferences,
};

View file

@ -1,7 +1,7 @@
use std::future::Future;
use anyhow::Result;
use indexmap::IndexMap;
use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use turbo_tasks::{
debug::ValueDebugFormat,
@ -47,12 +47,20 @@ pub enum ClientReferenceType {
}
#[turbo_tasks::value(transparent)]
pub struct ClientReferencesByEntry(IndexMap<Vc<Box<dyn Asset>>, Vec<ClientReference>>);
pub struct ClientReferences(Vec<ClientReference>);
#[turbo_tasks::value(transparent)]
pub struct ClientReferenceTypes(IndexSet<ClientReferenceType>);
#[turbo_tasks::value(transparent)]
pub struct ClientReferenceGraph {
graph: AdjacencyMap<VisitClientReferenceNode>,
}
#[turbo_tasks::value_impl]
impl ClientReferencesByEntry {
impl ClientReferenceGraph {
#[turbo_tasks::function]
pub async fn new(entries: Vc<Assets>) -> Result<Vc<ClientReferencesByEntry>> {
pub async fn new(entries: Vc<Assets>) -> Result<Vc<Self>> {
let entries = entries.await?;
let graph = AdjacencyMap::new()
@ -72,42 +80,69 @@ impl ClientReferencesByEntry {
.completed()?
.into_inner();
let client_references = entries
.iter()
.copied()
.map(|entry| {
let mut entry_client_references = vec![];
for node in graph.reverse_topological_from_node(&VisitClientReferenceNode {
server_component: None,
ty: VisitClientReferenceNodeType::Internal(entry),
}) {
match &node.ty {
VisitClientReferenceNodeType::Internal(_asset) => {
// No-op. These nodes are only useful during graph
// traversal.
}
VisitClientReferenceNodeType::ClientReference(client_reference) => {
entry_client_references.push(*client_reference);
}
}
}
(entry, entry_client_references)
})
.collect();
Ok(ClientReferenceGraph { graph }.cell())
}
Ok(Vc::cell(client_references))
#[turbo_tasks::function]
pub async fn types(self: Vc<Self>) -> Result<Vc<ClientReferenceTypes>> {
let this = self.await?;
let mut client_reference_types = IndexSet::new();
for node in this.graph.reverse_topological() {
match &node.ty {
VisitClientReferenceNodeType::Internal(_asset) => {
// No-op. These nodes are only useful during graph
// traversal.
}
VisitClientReferenceNodeType::ClientReference(client_reference) => {
client_reference_types.insert(*client_reference.ty());
}
}
}
Ok(Vc::cell(client_reference_types))
}
#[turbo_tasks::function]
pub async fn entry(self: Vc<Self>, entry: Vc<Box<dyn Asset>>) -> Result<Vc<ClientReferences>> {
let this = self.await?;
let mut entry_client_references = vec![];
for node in this
.graph
.reverse_topological_from_node(&VisitClientReferenceNode {
server_component: None,
ty: VisitClientReferenceNodeType::Internal(entry),
})
{
match &node.ty {
VisitClientReferenceNodeType::Internal(_asset) => {
// No-op. These nodes are only useful during graph
// traversal.
}
VisitClientReferenceNodeType::ClientReference(client_reference) => {
entry_client_references.push(*client_reference);
}
}
}
Ok(Vc::cell(entry_client_references))
}
}
struct VisitClientReference;
#[derive(Clone, Eq, PartialEq, Hash)]
#[derive(
Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, ValueDebugFormat, TraceRawVcs,
)]
struct VisitClientReferenceNode {
server_component: Option<Vc<NextServerComponentModule>>,
ty: VisitClientReferenceNodeType,
}
#[derive(Clone, Eq, PartialEq, Hash)]
#[derive(
Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, ValueDebugFormat, TraceRawVcs,
)]
enum VisitClientReferenceNodeType {
ClientReference(ClientReference),
Internal(Vc<Box<dyn Asset>>),

View file

@ -0,0 +1,217 @@
use anyhow::{Context, Result};
use indoc::formatdoc;
use turbo_tasks::{TryJoinIterExt, ValueToString, Vc};
use turbo_tasks_fs::{File, FileSystemPath};
use turbopack_binding::turbopack::{
core::{
asset::{Asset, AssetContent},
chunk::ModuleId as TurbopackModuleId,
output::OutputAsset,
raw_output::RawOutput,
virtual_source::VirtualSource,
},
ecmascript::{
chunk::{EcmascriptChunkItemExt, EcmascriptChunkPlaceable, EcmascriptChunkingContext},
utils::StringifyJs,
},
};
use super::{ClientReferenceManifest, ManifestNode, ManifestNodeEntry, ModuleId};
use crate::{
next_app::ClientReferencesChunks,
next_client_reference::{ClientReferenceType, ClientReferences},
};
#[turbo_tasks::value_impl]
impl ClientReferenceManifest {
#[turbo_tasks::function]
pub async fn build_output(
node_root: Vc<FileSystemPath>,
client_relative_path: Vc<FileSystemPath>,
entry_name: String,
client_references: Vc<ClientReferences>,
client_references_chunks: Vc<ClientReferencesChunks>,
client_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
ssr_chunking_context: Vc<Box<dyn EcmascriptChunkingContext>>,
) -> Result<Vc<Box<dyn OutputAsset>>> {
let mut entry_manifest: ClientReferenceManifest = Default::default();
let client_references_chunks = client_references_chunks.await?;
let client_relative_path = client_relative_path.await?;
let node_root_ref = node_root.await?;
for app_client_reference in client_references.await?.iter() {
let app_client_reference_ty = app_client_reference.ty();
let app_client_reference_chunks = client_references_chunks
.get(app_client_reference.ty())
.context("client reference chunks not found")?;
let client_reference_chunks = client_references_chunks
.get(app_client_reference_ty)
.context("client reference chunks not found")?;
let client_chunks = &client_reference_chunks.client_chunks.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
if let Some(server_component) = app_client_reference.server_component() {
let server_component_name = server_component
.server_path()
.with_extension("".to_string())
.to_string()
.await?;
let entry_css_files = entry_manifest
.entry_css_files
.entry(server_component_name.clone_value())
.or_insert_with(Default::default);
match app_client_reference_ty {
ClientReferenceType::CssClientReference(_) => {
entry_css_files.extend(
client_chunks_paths
.iter()
.filter_map(|chunk_path| {
client_relative_path.get_path_to(chunk_path)
})
.map(ToString::to_string),
);
}
ClientReferenceType::EcmascriptClientReference(_) => {
entry_css_files.extend(
client_chunks_paths
.iter()
.filter_map(|chunk_path| {
if chunk_path.extension_ref() == Some("css") {
client_relative_path.get_path_to(chunk_path)
} else {
None
}
})
.map(ToString::to_string),
);
}
}
}
match app_client_reference_ty {
ClientReferenceType::CssClientReference(_) => {}
ClientReferenceType::EcmascriptClientReference(ecmascript_client_reference) => {
let client_chunks = &app_client_reference_chunks.client_chunks.await?;
let ssr_chunks = &app_client_reference_chunks.ssr_chunks.await?;
let ecmascript_client_reference = ecmascript_client_reference.await?;
let client_module_id = ecmascript_client_reference
.client_module
.as_chunk_item(client_chunking_context)
.id()
.await?;
let ssr_module_id = ecmascript_client_reference
.ssr_module
.as_chunk_item(ssr_chunking_context)
.id()
.await?;
let server_path = ecmascript_client_reference
.server_ident
.path()
.to_string()
.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
let client_chunks_paths: Vec<String> = client_chunks_paths
.iter()
.filter_map(|chunk_path| client_relative_path.get_path_to(chunk_path))
.map(ToString::to_string)
.collect::<Vec<_>>();
let ssr_chunks_paths = ssr_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;
let ssr_chunks_paths = ssr_chunks_paths
.iter()
.filter_map(|chunk_path| node_root_ref.get_path_to(chunk_path))
.map(ToString::to_string)
.collect::<Vec<_>>();
let mut ssr_manifest_node = ManifestNode::default();
entry_manifest.client_modules.module_exports.insert(
get_client_reference_module_key(&server_path, "*"),
ManifestNodeEntry {
name: "*".to_string(),
id: (&*client_module_id).into(),
chunks: client_chunks_paths.clone(),
// TODO(WEB-434)
r#async: false,
},
);
ssr_manifest_node.module_exports.insert(
"*".to_string(),
ManifestNodeEntry {
name: "*".to_string(),
id: (&*ssr_module_id).into(),
chunks: ssr_chunks_paths.clone(),
// TODO(WEB-434)
r#async: false,
},
);
entry_manifest
.ssr_module_mapping
.insert((&*client_module_id).into(), ssr_manifest_node);
}
}
}
let client_reference_manifest_json = serde_json::to_string(&entry_manifest).unwrap();
Ok(Vc::upcast(RawOutput::new(Vc::upcast(VirtualSource::new(
node_root.join(format!(
"server/app/{entry_name}_client-reference-manifest.js",
)),
AssetContent::file(
File::from(formatdoc! {
r#"
globalThis.__RSC_MANIFEST = globalThis.__RSC_MANIFEST || {{}};
globalThis.__RSC_MANIFEST[{entry_name}] = {manifest}
"#,
entry_name = StringifyJs(&entry_name),
manifest = StringifyJs(&client_reference_manifest_json)
})
.into(),
),
)))))
}
}
impl From<&TurbopackModuleId> for ModuleId {
fn from(module_id: &TurbopackModuleId) -> Self {
match module_id {
TurbopackModuleId::String(string) => ModuleId::String(string.clone()),
TurbopackModuleId::Number(number) => ModuleId::Number(*number as _),
}
}
}
/// See next.js/packages/next/src/lib/client-reference.ts
pub fn get_client_reference_module_key(server_path: &str, export_name: &str) -> String {
if export_name == "*" {
server_path.to_string()
} else {
format!("{}#{}", server_path, export_name)
}
}

View file

@ -1,10 +1,13 @@
//! Type definitions for the Next.js manifest formats.
pub(crate) mod client_reference_manifest;
use std::collections::HashMap;
use next_core::next_config::Rewrites;
use serde::Serialize;
use crate::next_config::Rewrites;
#[derive(Serialize, Default, Debug)]
pub struct PagesManifest {
#[serde(flatten)]

View file

@ -24,7 +24,7 @@ use indexmap::IndexMap;
use next_core::{
app_structure::find_app_dir_if_enabled,
create_app_source, create_page_source, create_web_entry_source,
manifest::DevManifestContentSource,
dev_manifest::DevManifestContentSource,
mode::NextMode,
next_client::{get_client_chunking_context, get_client_compile_time_info},
next_config::{load_next_config, load_rewrites},

View file

@ -265,6 +265,12 @@ const nextDev: CliCommand = async (argv) => {
Log.info(written)
break
}
case 'app-page': {
Log.info(`writing ${pathname} to disk`)
const written = await route.rscEndpoint.writeToDisk()
Log.info(written)
break
}
default:
Log.info(`skipping ${pathname} (${route.type})`)
break

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-230716.2
'@vercel/turbopack-node': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230716.2
'@vercel/turbopack-ecmascript-runtime': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2
'@vercel/turbopack-node': https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230717.2
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-230716.2_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230716.2'
'@vercel/turbopack-ecmascript-runtime': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230717.2'
anser: 2.1.1
css.escape: 1.5.1
next: link:../../../../next
@ -25498,9 +25498,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-230716.2_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230716.2}
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230716.2'
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2}
id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230717.2'
name: '@vercel/turbopack-ecmascript-runtime'
version: 0.0.0
dependencies:
@ -25511,8 +25511,8 @@ packages:
- webpack
dev: false
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230716.2':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230716.2}
'@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230717.2':
resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-node/js?turbopack-230717.2}
name: '@vercel/turbopack-node'
version: 0.0.0
dependencies: