Refactor ContentSources to RouteTree (#51660)

### What?

This fixes a performance problem when many pages are involved.

fixes WEB-1067

see also https://github.com/vercel/turbo/pull/5360

### Turbopack Changes

* https://github.com/vercel/turbo/pull/5416 
* https://github.com/vercel/turbo/pull/5360
This commit is contained in:
Tobias Koppers 2023-06-30 18:03:24 +02:00 committed by GitHub
parent 4bfc1eaf54
commit d443778163
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 421 additions and 322 deletions

86
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-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"serde",
]
@ -1393,16 +1393,6 @@ dependencies = [
"syn 2.0.18",
]
[[package]]
name = "ctrlc"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04d778600249295e82b6ab12e291ed9029407efee0cfb7baf67157edc65964df"
dependencies = [
"nix",
"windows-sys 0.48.0",
]
[[package]]
name = "curl"
version = "0.4.44"
@ -3378,6 +3368,7 @@ dependencies = [
"anyhow",
"async-recursion",
"async-trait",
"const_format",
"futures",
"indexmap",
"indoc",
@ -3559,7 +3550,7 @@ dependencies = [
[[package]]
name = "node-file-trace"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"serde",
@ -7219,7 +7210,7 @@ dependencies = [
[[package]]
name = "turbo-tasks"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7250,7 +7241,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"cargo-lock",
@ -7262,7 +7253,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-bytes"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"bytes",
@ -7277,7 +7268,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"dotenvy",
@ -7291,7 +7282,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fetch"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"indexmap",
@ -7308,7 +7299,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fs"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7338,7 +7329,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-hash"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"base16",
"hex",
@ -7350,7 +7341,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@ -7364,7 +7355,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros-shared"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"proc-macro2",
"quote",
@ -7374,7 +7365,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-malloc"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"mimalloc",
]
@ -7382,7 +7373,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-memory"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7405,7 +7396,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-testing"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7417,7 +7408,7 @@ dependencies = [
[[package]]
name = "turbopack"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-recursion",
@ -7447,7 +7438,7 @@ dependencies = [
[[package]]
name = "turbopack-bench"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"chromiumoxide",
@ -7477,7 +7468,7 @@ dependencies = [
[[package]]
name = "turbopack-binding"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"auto-hash-map",
"mdxjs",
@ -7519,7 +7510,7 @@ dependencies = [
[[package]]
name = "turbopack-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"indexmap",
@ -7539,18 +7530,18 @@ dependencies = [
[[package]]
name = "turbopack-cli-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"clap 4.1.11",
"crossbeam-channel",
"crossterm",
"ctrlc",
"once_cell",
"owo-colors",
"postcard",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
"turbo-tasks",
@ -7563,7 +7554,7 @@ dependencies = [
[[package]]
name = "turbopack-core"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-trait",
@ -7591,7 +7582,7 @@ dependencies = [
[[package]]
name = "turbopack-create-test-app"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"clap 4.1.11",
@ -7604,7 +7595,7 @@ dependencies = [
[[package]]
name = "turbopack-css"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-trait",
@ -7626,7 +7617,7 @@ dependencies = [
[[package]]
name = "turbopack-dev"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"indexmap",
@ -7650,10 +7641,11 @@ dependencies = [
[[package]]
name = "turbopack-dev-server"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-compression",
"auto-hash-map",
"futures",
"hyper",
"hyper-tungstenite",
@ -7685,7 +7677,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-trait",
@ -7718,7 +7710,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-plugins"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-trait",
@ -7741,7 +7733,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-runtime"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"indoc",
@ -7758,7 +7750,7 @@ dependencies = [
[[package]]
name = "turbopack-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"indexmap",
@ -7774,7 +7766,7 @@ dependencies = [
[[package]]
name = "turbopack-image"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"base64 0.21.0",
@ -7794,7 +7786,7 @@ dependencies = [
[[package]]
name = "turbopack-json"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"serde",
@ -7809,7 +7801,7 @@ dependencies = [
[[package]]
name = "turbopack-mdx"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"mdxjs",
@ -7824,7 +7816,7 @@ dependencies = [
[[package]]
name = "turbopack-node"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"async-stream",
@ -7859,7 +7851,7 @@ dependencies = [
[[package]]
name = "turbopack-static"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"serde",
@ -7875,7 +7867,7 @@ dependencies = [
[[package]]
name = "turbopack-swc-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"swc_core",
"turbo-tasks",
@ -7886,7 +7878,7 @@ dependencies = [
[[package]]
name = "turbopack-test-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230628.2#57669dea96ee99b7e68da01ec7b7d286bc81b083"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230630.2#b36c4140a11fb2530b5a70f5c44d8a95206f9ef1"
dependencies = [
"anyhow",
"once_cell",
@ -7905,7 +7897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if 1.0.0",
"rand 0.4.6",
"rand 0.8.5",
"static_assertions",
]

View file

@ -42,11 +42,11 @@ swc_core = { version = "0.78.24" }
testing = { version = "0.33.19" }
# Turbo crates
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230628.2" }
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230630.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-230628.2" }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230630.2" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230628.2" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230630.2" }
# General Deps

View file

@ -274,7 +274,6 @@ async fn get_page_chunks_for_directory(
let PagesStructureItem {
project_path,
next_router_path,
specificity: _,
} = *item.await?;
chunks.push(get_page_chunk_for_file(
node_build_context,

View file

@ -12,6 +12,7 @@ bench = false
anyhow = { workspace = true }
async-recursion = "1.0.2"
async-trait = { workspace = true }
const_format = "0.2.30"
once_cell = { workspace = true }
qstring = { workspace = true }
regex = { workspace = true }

View file

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

View file

@ -281,23 +281,23 @@ fn parse_config_value(
"dynamic" => {
let value = eval_context.eval(init);
let Some(val) = value.as_str() else {
return invalid_config("`dynamic` needs to be a static string", &value);
invalid_config("`dynamic` needs to be a static string", &value);
return;
};
config.dynamic = match serde_json::from_value(Value::String(val.to_string())) {
Ok(dynamic) => Some(dynamic),
Err(err) => {
return invalid_config(
&format!("`dynamic` has an invalid value: {}", err),
&value,
)
invalid_config(&format!("`dynamic` has an invalid value: {}", err), &value);
return;
}
};
}
"dynamicParams" => {
let value = eval_context.eval(init);
let Some(val) = value.as_bool() else {
return invalid_config("`dynamicParams` needs to be a static boolean", &value);
invalid_config("`dynamicParams` needs to be a static boolean", &value);
return
};
config.dynamic_params = Some(val);
@ -326,39 +326,41 @@ fn parse_config_value(
"fetchCache" => {
let value = eval_context.eval(init);
let Some(val) = value.as_str() else {
return invalid_config("`fetchCache` needs to be a static string", &value);
invalid_config("`fetchCache` needs to be a static string", &value);
return;
};
config.fetch_cache = match serde_json::from_value(Value::String(val.to_string())) {
Ok(fetch_cache) => Some(fetch_cache),
Err(err) => {
return invalid_config(
invalid_config(
&format!("`fetchCache` has an invalid value: {}", err),
&value,
)
);
return;
}
};
}
"runtime" => {
let value = eval_context.eval(init);
let Some(val) = value.as_str() else {
return invalid_config("`runtime` needs to be a static string", &value);
invalid_config("`runtime` needs to be a static string", &value);
return;
};
config.runtime = match serde_json::from_value(Value::String(val.to_string())) {
Ok(runtime) => Some(runtime),
Err(err) => {
return invalid_config(
&format!("`runtime` has an invalid value: {}", err),
&value,
)
invalid_config(&format!("`runtime` has an invalid value: {}", err), &value);
return;
}
};
}
"preferredRegion" => {
let value = eval_context.eval(init);
let Some(val) = value.as_str() else {
return invalid_config("`preferredRegion` needs to be a static string", &value);
invalid_config("`preferredRegion` needs to be a static string", &value);
return;
};
config.preferred_region = Some(val.to_string());

View file

@ -32,7 +32,7 @@ use turbopack_binding::{
source::{
asset_graph::AssetGraphContentSourceVc,
combined::CombinedContentSource,
specificity::{Specificity, SpecificityElementType, SpecificityVc},
route_tree::{BaseSegment, RouteType},
ContentSourceData, ContentSourceVc, NoContentSourceVc,
},
},
@ -99,31 +99,34 @@ use crate::{
util::{render_data, NextRuntime},
};
#[turbo_tasks::function]
fn pathname_to_specificity(pathname: &str) -> SpecificityVc {
let mut current = Specificity::new();
let mut position = 0;
for segment in pathname.split('/') {
if segment.starts_with('(') && segment.ends_with(')') || segment.starts_with('@') {
fn pathname_to_segments(pathname: &str) -> Result<(Vec<BaseSegment>, RouteType)> {
let mut segments = Vec::new();
let mut split = pathname.split('/');
while let Some(segment) = split.next() {
if segment.is_empty()
|| (segment.starts_with('(') && segment.ends_with(')') || segment.starts_with('@'))
{
// ignore
} else if segment.starts_with("[[...") && segment.ends_with("]]")
|| segment.starts_with("[...") && segment.ends_with(']')
{
// optional catch all segment
current.add(position - 1, SpecificityElementType::CatchAll);
position += 1;
} else if segment.starts_with("[[") || segment.ends_with("]]") {
// optional segment
position += 1;
// (optional) catch all segment
if split.remainder().is_some() {
bail!(
"Invalid route {}, catch all segment must be the last segment",
pathname
)
}
return Ok((segments, RouteType::CatchAll));
} else if segment.starts_with('[') || segment.ends_with(']') {
current.add(position - 1, SpecificityElementType::DynamicSegment);
position += 1;
// dynamic segment
segments.push(BaseSegment::Dynamic);
} else {
// normal segment
position += 1;
segments.push(BaseSegment::Static(segment.to_string()));
}
}
SpecificityVc::cell(current)
Ok((segments, RouteType::Exact))
}
#[turbo_tasks::function]
@ -666,10 +669,13 @@ async fn create_app_page_source_for_route(
let params_matcher = NextParamsMatcherVc::new(pathname_vc);
let (base_segments, route_type) = pathname_to_segments(pathname)?;
let source = create_node_rendered_source(
project_path,
env,
pathname_to_specificity(pathname),
base_segments,
route_type,
server_root,
params_matcher.into(),
pathname_vc,
@ -713,7 +719,8 @@ async fn create_app_not_found_page_source(
let source = create_node_rendered_source(
project_path,
env,
SpecificityVc::not_found(),
Vec::new(),
RouteType::NotFound,
server_root,
NextFallbackMatcherVc::new().into(),
pathname_vc,
@ -755,10 +762,13 @@ async fn create_app_route_source_for_route(
let params_matcher = NextParamsMatcherVc::new(pathname_vc);
let (base_segments, route_type) = pathname_to_segments(pathname)?;
let source = create_node_api_source(
project_path,
env,
pathname_to_specificity(pathname),
base_segments,
route_type,
server_root,
params_matcher.into(),
pathname_vc,

View file

@ -1,6 +1,7 @@
#![feature(async_closure)]
#![feature(min_specialization)]
#![feature(box_syntax)]
#![feature(str_split_remainder)]
mod app_render;
mod app_segment_config;

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use mime::{APPLICATION_JAVASCRIPT_UTF_8, APPLICATION_JSON};
use serde::Serialize;
@ -14,8 +14,9 @@ use turbopack_binding::{
introspect::{Introspectable, IntrospectableVc},
},
dev_server::source::{
ContentSource, ContentSourceContentVc, ContentSourceData, ContentSourceResultVc,
ContentSourceVc,
route_tree::{BaseSegment, RouteTreeVc, RouteTreesVc, RouteType},
ContentSource, ContentSourceContentVc, ContentSourceData, ContentSourceVc,
GetContentSourceContent, GetContentSourceContentVc,
},
node::render::{
node_api_source::NodeApiContentSourceVc, rendered_source::NodeRenderContentSourceVc,
@ -151,16 +152,46 @@ struct BuildManifest<'a> {
routes: IndexMap<&'a String, Vec<String>>,
}
const DEV_MANIFEST_PATHNAME: &str = "_next/static/development/_devPagesManifest.json";
const BUILD_MANIFEST_PATHNAME: &str = "_next/static/development/_buildManifest.js";
const DEV_MIDDLEWARE_MANIFEST_PATHNAME: &str =
"_next/static/development/_devMiddlewareManifest.json";
#[turbo_tasks::value_impl]
impl ContentSource for DevManifestContentSource {
#[turbo_tasks::function]
fn get_routes(self_vc: DevManifestContentSourceVc) -> RouteTreeVc {
RouteTreesVc::cell(vec![
RouteTreeVc::new_route(
BaseSegment::from_static_pathname(DEV_MANIFEST_PATHNAME).collect(),
RouteType::Exact,
self_vc.into(),
),
RouteTreeVc::new_route(
BaseSegment::from_static_pathname(BUILD_MANIFEST_PATHNAME).collect(),
RouteType::Exact,
self_vc.into(),
),
RouteTreeVc::new_route(
BaseSegment::from_static_pathname(DEV_MIDDLEWARE_MANIFEST_PATHNAME).collect(),
RouteType::Exact,
self_vc.into(),
),
])
.merge()
}
}
#[turbo_tasks::value_impl]
impl GetContentSourceContent for DevManifestContentSource {
#[turbo_tasks::function]
async fn get(
self_vc: DevManifestContentSourceVc,
path: &str,
_data: turbo_tasks::Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
) -> Result<ContentSourceContentVc> {
let manifest_file = match path {
"_next/static/development/_devPagesManifest.json" => {
DEV_MANIFEST_PATHNAME => {
let pages = &*self_vc.find_routes().await?;
File::from(serde_json::to_string(&serde_json::json!({
@ -168,12 +199,12 @@ impl ContentSource for DevManifestContentSource {
}))?)
.with_content_type(APPLICATION_JSON)
}
"_next/static/development/_buildManifest.js" => {
BUILD_MANIFEST_PATHNAME => {
let build_manifest = &*self_vc.create_build_manifest().await?;
File::from(build_manifest.as_str()).with_content_type(APPLICATION_JAVASCRIPT_UTF_8)
}
"_next/static/development/_devMiddlewareManifest.json" => {
DEV_MIDDLEWARE_MANIFEST_PATHNAME => {
// If there is actual middleware, this request will have been handled by the
// node router in next-core/js/src/entry/router.ts and
// next/src/server/lib/route-resolver.ts.
@ -181,12 +212,11 @@ impl ContentSource for DevManifestContentSource {
// respond with an empty `MiddlewareMatcher[]`.
File::from("[]").with_content_type(APPLICATION_JSON)
}
_ => return Ok(ContentSourceResultVc::not_found()),
_ => bail!("unknown path: {}", path),
};
Ok(ContentSourceResultVc::exact(
ContentSourceContentVc::static_content(AssetContentVc::from(manifest_file).into())
.into(),
Ok(ContentSourceContentVc::static_content(
AssetContentVc::from(manifest_file).into(),
))
}
}

View file

@ -1,6 +1,4 @@
use std::collections::BTreeSet;
use anyhow::Result;
use anyhow::{bail, Result};
use turbo_tasks::{primitives::StringVc, Value};
use turbo_tasks_fs::FileSystem;
use turbopack_binding::turbopack::{
@ -13,13 +11,14 @@ use turbopack_binding::turbopack::{
},
dev_server::source::{
query::QueryValue,
route_tree::{RouteTreeVc, RouteType},
wrapping_source::{
encode_pathname_to_url, ContentSourceProcessor, ContentSourceProcessorVc,
WrappedContentSourceVc,
ContentSourceProcessor, ContentSourceProcessorVc, WrappedGetContentSourceContentVc,
},
ContentSource, ContentSourceContent, ContentSourceContentVc, ContentSourceData,
ContentSourceDataFilter, ContentSourceDataVary, ContentSourceResultVc, ContentSourceVc,
NeededData, ProxyResult, RewriteBuilder,
ContentSourceDataFilter, ContentSourceDataVary, ContentSourceDataVaryVc, ContentSourceVc,
GetContentSourceContent, GetContentSourceContentVc, GetContentSourceContentsVc,
ProxyResult, RewriteBuilder,
},
image::process::optimize,
};
@ -41,85 +40,92 @@ impl NextImageContentSourceVc {
#[turbo_tasks::value_impl]
impl ContentSource for NextImageContentSource {
#[turbo_tasks::function]
fn get_routes(self_vc: NextImageContentSourceVc) -> RouteTreeVc {
RouteTreeVc::new_route(Vec::new(), RouteType::Exact, self_vc.into())
}
}
#[turbo_tasks::value_impl]
impl GetContentSourceContent for NextImageContentSource {
#[turbo_tasks::function]
async fn vary(&self) -> ContentSourceDataVaryVc {
ContentSourceDataVary {
query: Some(ContentSourceDataFilter::Subset(
["url".to_string(), "w".to_string(), "q".to_string()].into(),
)),
..Default::default()
}
.cell()
}
#[turbo_tasks::function]
async fn get(
self_vc: NextImageContentSourceVc,
path: &str,
_path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
) -> Result<ContentSourceContentVc> {
let this = self_vc.await?;
let Some(query) = &data.query else {
let queries = ["url".to_string(), "w".to_string(), "q".to_string()]
.into_iter()
.collect::<BTreeSet<_>>();
return Ok(ContentSourceResultVc::need_data(Value::new(NeededData {
source: self_vc.into(),
path: path.to_string(),
vary: ContentSourceDataVary {
url: true,
query: Some(ContentSourceDataFilter::Subset(queries)),
..Default::default()
},
})));
bail!("missing query");
};
let Some(QueryValue::String(url)) = query.get("url") else {
return Ok(ContentSourceResultVc::not_found());
bail!("missing url");
};
let q = match query.get("q") {
None => 75,
Some(QueryValue::String(s)) => {
let Ok(q) = s.parse::<u8>() else {
return Ok(ContentSourceResultVc::not_found());
bail!("invalid q query argument")
};
q
}
_ => return Ok(ContentSourceResultVc::not_found()),
_ => bail!("missing q query argument"),
};
let w = match query.get("w") {
Some(QueryValue::String(s)) => {
let Ok(w) = s.parse::<u32>() else {
return Ok(ContentSourceResultVc::not_found());
bail!("invalid w query argument")
};
w
}
_ => return Ok(ContentSourceResultVc::not_found()),
_ => bail!("missing w query argument"),
};
// TODO: re-encode into next-gen formats.
if let Some(path) = url.strip_prefix('/') {
let wrapped = WrappedContentSourceVc::new(
this.asset_source,
NextImageContentSourceProcessorVc::new(path.to_string(), w, q).into(),
let sources = this.asset_source.get_routes().get(path).await?;
let sources = sources
.iter()
.map(|s| {
WrappedGetContentSourceContentVc::new(
*s,
NextImageContentSourceProcessorVc::new(path.to_string(), w, q).into(),
)
.into()
})
.collect();
let sources = GetContentSourceContentsVc::cell(sources);
return Ok(
ContentSourceContent::Rewrite(RewriteBuilder::new_sources(sources).build()).cell(),
);
return Ok(ContentSourceResultVc::exact(
ContentSourceContent::Rewrite(
RewriteBuilder::new(encode_pathname_to_url(path))
.content_source(wrapped.as_content_source())
.build(),
)
.cell()
.into(),
));
}
// TODO: This should be downloaded by the server, and resized, etc.
Ok(ContentSourceResultVc::exact(
ContentSourceContent::HttpProxy(
ProxyResult {
status: 302,
headers: vec![("Location".to_string(), url.clone())],
body: "".into(),
}
.cell(),
)
.cell()
.into(),
))
Ok(ContentSourceContent::HttpProxy(
ProxyResult {
status: 302,
headers: vec![("Location".to_string(), url.clone())],
body: "".into(),
}
.cell(),
)
.cell())
}
}

View file

@ -26,7 +26,7 @@ use turbopack_binding::{
source::{
asset_graph::AssetGraphContentSourceVc,
combined::{CombinedContentSource, CombinedContentSourceVc},
specificity::SpecificityVc,
route_tree::{BaseSegment, RouteType},
ContentSourceData, ContentSourceVc,
},
},
@ -291,7 +291,8 @@ pub async fn create_page_source(
fallback_page,
client_root,
node_root.join("force_not_found"),
SpecificityVc::exact(),
BaseSegment::from_static_pathname("_next/404").collect(),
RouteType::Exact,
NextExactMatcherVc::new(StringVc::cell("_next/404".to_string())).into(),
render_data,
)
@ -333,7 +334,8 @@ pub async fn create_page_source(
fallback_page,
client_root,
node_root.join("fallback_not_found"),
SpecificityVc::not_found(),
Vec::new(),
RouteType::NotFound,
NextFallbackMatcherVc::new().into(),
render_data,
)
@ -353,7 +355,6 @@ async fn create_page_source_for_file(
server_data_context: AssetContextVc,
client_context: AssetContextVc,
pages_dir: FileSystemPathVc,
specificity: SpecificityVc,
page_asset: AssetVc,
runtime_entries: AssetsVc,
fallback_page: DevHtmlAssetVc,
@ -402,11 +403,14 @@ async fn create_page_source_for_file(
let pathname = pathname_for_path(client_root, client_path, PathType::Page);
let route_matcher = NextParamsMatcherVc::new(pathname);
let (base_segments, route_type) = pathname_to_segments(&pathname.await?, "")?;
Ok(if is_api_path {
create_node_api_source(
project_path,
env,
specificity,
base_segments,
route_type,
client_root,
route_matcher.into(),
pathname,
@ -429,6 +433,10 @@ async fn create_page_source_for_file(
let data_pathname = pathname_for_path(client_root, client_path, PathType::Data);
let data_route_matcher =
NextPrefixSuffixParamsMatcherVc::new(data_pathname, "_next/data/development/", ".json");
let (data_base_segments, data_route_type) = pathname_to_segments(
&format!("_next/data/development/{}", data_pathname.await?),
".json",
)?;
let ssr_entry = SsrEntry {
runtime_entries,
@ -460,7 +468,8 @@ async fn create_page_source_for_file(
create_node_rendered_source(
project_path,
env,
specificity,
base_segments.clone(),
route_type.clone(),
client_root,
route_matcher.into(),
pathname,
@ -472,7 +481,8 @@ async fn create_page_source_for_file(
create_node_rendered_source(
project_path,
env,
specificity,
data_base_segments,
data_route_type,
client_root,
data_route_matcher.into(),
pathname,
@ -520,7 +530,8 @@ async fn create_not_found_page_source(
fallback_page: DevHtmlAssetVc,
client_root: FileSystemPathVc,
node_path: FileSystemPathVc,
specificity: SpecificityVc,
base_segments: Vec<BaseSegment>,
route_type: RouteType,
route_matcher: RouteMatcherVc,
render_data: JsonValueVc,
) -> Result<ContentSourceVc> {
@ -588,7 +599,8 @@ async fn create_not_found_page_source(
create_node_rendered_source(
project_path,
env,
specificity,
base_segments,
route_type,
client_root,
route_matcher,
pathname,
@ -695,7 +707,6 @@ async fn create_page_source_for_directory(
for item in items.iter() {
let PagesStructureItem {
project_path,
specificity,
next_router_path,
} = *item.await?;
let source = create_page_source_for_file(
@ -705,7 +716,6 @@ async fn create_page_source_for_directory(
server_data_context,
client_context,
pages_dir,
specificity,
SourceAssetVc::new(project_path).into(),
runtime_entries,
fallback_page,
@ -748,6 +758,37 @@ async fn create_page_source_for_directory(
Ok(CombinedContentSource { sources }.cell().into())
}
fn pathname_to_segments(pathname: &str, extension: &str) -> Result<(Vec<BaseSegment>, RouteType)> {
let mut segments = Vec::new();
let mut split = pathname.split('/');
while let Some(segment) = split.next() {
if segment.is_empty() {
// ignore
} else if segment.starts_with("[[...") && segment.ends_with("]]")
|| segment.starts_with("[...") && segment.ends_with(']')
{
// (optional) catch all segment
if split.remainder().is_some() {
bail!(
"Invalid route {}, catch all segment must be the last segment",
pathname
)
}
return Ok((segments, RouteType::CatchAll));
} else if segment.starts_with('[') || segment.ends_with(']') {
// dynamic segment
segments.push(BaseSegment::Dynamic);
} else {
// normal segment
segments.push(BaseSegment::Static(segment.to_string()));
}
}
if let Some(BaseSegment::Static(s)) = segments.last_mut() {
s.push_str(extension);
}
Ok((segments, RouteType::Exact))
}
/// The node.js renderer for SSR of pages.
#[turbo_tasks::value]
pub struct SsrEntry {

View file

@ -1,8 +1,7 @@
use anyhow::Result;
use turbo_tasks::{primitives::StringsVc, CompletionVc};
use turbopack_binding::{
turbo::tasks_fs::{DirectoryContent, DirectoryEntry, FileSystemEntryType, FileSystemPathVc},
turbopack::dev_server::source::specificity::SpecificityVc,
use turbopack_binding::turbo::tasks_fs::{
DirectoryContent, DirectoryEntry, FileSystemEntryType, FileSystemPathVc,
};
use crate::{embed_js::next_js_file_path, next_config::NextConfigVc};
@ -12,7 +11,6 @@ use crate::{embed_js::next_js_file_path, next_config::NextConfigVc};
pub struct PagesStructureItem {
pub project_path: FileSystemPathVc,
pub next_router_path: FileSystemPathVc,
pub specificity: SpecificityVc,
}
#[turbo_tasks::value_impl]
@ -21,12 +19,10 @@ impl PagesStructureItemVc {
async fn new(
project_path: FileSystemPathVc,
next_router_path: FileSystemPathVc,
specificity: SpecificityVc,
) -> Result<Self> {
Ok(PagesStructureItem {
project_path,
next_router_path,
specificity,
}
.cell())
}
@ -179,7 +175,6 @@ async fn get_pages_structure_for_root_directory(
let mut document_item = None;
let mut error_item = None;
let mut api_directory = None;
let specificity = SpecificityVc::exact();
let dir_content = project_path.read_dir().await?;
if let DirectoryContent::Entries(entries) = &*dir_content {
for (name, entry) in entries.iter() {
@ -193,34 +188,26 @@ async fn get_pages_structure_for_root_directory(
let _ = app_item.insert(PagesStructureItemVc::new(
*file_project_path,
next_router_path.join("_app"),
specificity,
));
}
"_document" => {
let _ = document_item.insert(PagesStructureItemVc::new(
*file_project_path,
next_router_path.join("_document"),
specificity,
));
}
"_error" => {
let _ = error_item.insert(PagesStructureItemVc::new(
*file_project_path,
next_router_path.join("_error"),
specificity,
));
}
basename => {
let specificity = entry_specificity(specificity, name, 0);
let next_router_path =
next_router_path_for_basename(next_router_path, basename);
items.push((
basename,
PagesStructureItemVc::new(
*file_project_path,
next_router_path,
specificity,
),
PagesStructureItemVc::new(*file_project_path, next_router_path),
));
}
}
@ -230,19 +217,16 @@ async fn get_pages_structure_for_root_directory(
let _ = api_directory.insert(get_pages_structure_for_directory(
*dir_project_path,
next_router_path.join(name),
specificity,
1,
page_extensions,
));
}
_ => {
let specificity = entry_specificity(SpecificityVc::exact(), name, 0);
children.push((
name,
get_pages_structure_for_directory(
*dir_project_path,
next_router_path.join(name),
specificity,
1,
page_extensions,
),
@ -264,7 +248,6 @@ async fn get_pages_structure_for_root_directory(
PagesStructureItemVc::new(
next_js_file_path("entry/pages/_app.tsx"),
next_router_path.join("_app"),
specificity,
)
};
@ -274,7 +257,6 @@ async fn get_pages_structure_for_root_directory(
PagesStructureItemVc::new(
next_js_file_path("entry/pages/_document.tsx"),
next_router_path.join("_document"),
specificity,
)
};
@ -284,7 +266,6 @@ async fn get_pages_structure_for_root_directory(
PagesStructureItemVc::new(
next_js_file_path("entry/pages/_error.tsx"),
next_router_path.join("_error"),
specificity,
)
};
@ -311,7 +292,6 @@ async fn get_pages_structure_for_root_directory(
async fn get_pages_structure_for_directory(
project_path: FileSystemPathVc,
next_router_path: FileSystemPathVc,
specificity: SpecificityVc,
position: u32,
page_extensions: StringsVc,
) -> Result<PagesDirectoryStructureVc> {
@ -322,7 +302,6 @@ async fn get_pages_structure_for_directory(
let dir_content = project_path.read_dir().await?;
if let DirectoryContent::Entries(entries) = &*dir_content {
for (name, entry) in entries.iter() {
let specificity = entry_specificity(specificity, name, position);
match entry {
DirectoryEntry::File(file_project_path) => {
let Some(basename) = page_basename(name, page_extensions_raw) else {
@ -334,11 +313,7 @@ async fn get_pages_structure_for_directory(
};
items.push((
basename,
PagesStructureItemVc::new(
*file_project_path,
next_router_path,
specificity,
),
PagesStructureItemVc::new(*file_project_path, next_router_path),
));
}
DirectoryEntry::Directory(dir_project_path) => {
@ -347,7 +322,6 @@ async fn get_pages_structure_for_directory(
get_pages_structure_for_directory(
*dir_project_path,
next_router_path.join(name),
specificity,
position + 1,
page_extensions,
),
@ -373,16 +347,6 @@ async fn get_pages_structure_for_directory(
.cell())
}
fn entry_specificity(specificity: SpecificityVc, name: &str, position: u32) -> SpecificityVc {
if name.starts_with("[[") || name.starts_with("[...") {
specificity.with_catch_all(position)
} else if name.starts_with('[') {
specificity.with_dynamic_segment(position)
} else {
specificity
}
}
fn page_basename<'a>(name: &'a str, page_extensions: &'a [String]) -> Option<&'a str> {
if let Some((basename, extension)) = name.rsplit_once('.') {
if page_extensions.iter().any(|allowed| allowed == extension) {

View file

@ -1,4 +1,4 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use futures::{Stream, TryStreamExt};
use indexmap::IndexSet;
use turbo_tasks::{primitives::StringVc, CompletionVc, CompletionsVc, Value};
@ -8,9 +8,10 @@ use turbopack_binding::turbopack::{
introspect::{Introspectable, IntrospectableChildrenVc, IntrospectableVc},
},
dev_server::source::{
Body, ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary,
ContentSourceResultVc, ContentSourceVc, HeaderListVc, NeededData, ProxyResult,
RewriteBuilder,
route_tree::{RouteTreeVc, RouteType},
Body, ContentSource, ContentSourceContent, ContentSourceContentVc, ContentSourceData,
ContentSourceDataVary, ContentSourceDataVaryVc, ContentSourceVc, GetContentSourceContent,
GetContentSourceContentVc, HeaderListVc, ProxyResult, RewriteBuilder,
},
node::execution_context::ExecutionContextVc,
};
@ -56,24 +57,6 @@ impl NextRouterContentSourceVc {
}
}
#[turbo_tasks::function]
fn need_data(source: ContentSourceVc, path: &str) -> ContentSourceResultVc {
ContentSourceResultVc::need_data(
NeededData {
source,
path: path.to_string(),
vary: ContentSourceDataVary {
method: true,
raw_headers: true,
raw_query: true,
body: true,
..Default::default()
},
}
.into(),
)
}
#[turbo_tasks::function]
fn routes_changed(
app_dir: OptionAppDirVc,
@ -89,22 +72,48 @@ fn routes_changed(
#[turbo_tasks::value_impl]
impl ContentSource for NextRouterContentSource {
#[turbo_tasks::function]
async fn get(
self_vc: NextRouterContentSourceVc,
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
async fn get_routes(self_vc: NextRouterContentSourceVc) -> Result<RouteTreeVc> {
let this = self_vc.await?;
// The next-dev server can currently run against projects as simple as
// `index.js`. If this isn't a Next.js project, don't try to use the Next.js
// router.
if this.app_dir.await?.is_none() && this.pages_structure.await?.is_none() {
return Ok(this
.inner
.get(path, Value::new(ContentSourceData::default())));
return Ok(this.inner.get_routes());
}
// Prefetch get_routes from inner
let _ = this.inner.get_routes();
Ok(RouteTreeVc::new_route(
Vec::new(),
RouteType::CatchAll,
self_vc.into(),
))
}
}
#[turbo_tasks::value_impl]
impl GetContentSourceContent for NextRouterContentSource {
#[turbo_tasks::function]
fn vary(&self) -> ContentSourceDataVaryVc {
ContentSourceDataVary {
method: true,
raw_headers: true,
raw_query: true,
body: true,
..Default::default()
}
.cell()
}
#[turbo_tasks::function]
async fn get(
self_vc: NextRouterContentSourceVc,
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceContentVc> {
let this = self_vc.await?;
let ContentSourceData {
method: Some(method),
raw_headers: Some(raw_headers),
@ -112,7 +121,7 @@ impl ContentSource for NextRouterContentSource {
body: Some(body),
..
} = &*data else {
return Ok(need_data(self_vc.into(), path))
bail!("missing data for router");
};
// TODO: change router so we can stream the request body to it
@ -151,30 +160,28 @@ impl ContentSource for NextRouterContentSource {
formated_query(raw_query)
)))
}
RouterResult::None => this
.inner
.get(path, Value::new(ContentSourceData::default())),
RouterResult::None => {
let rewrite =
RewriteBuilder::new_source_with_path_and_query(this.inner, format!("/{path}"));
ContentSourceContent::Rewrite(rewrite.build()).cell()
}
RouterResult::Rewrite(data) => {
let mut rewrite = RewriteBuilder::new(data.url.clone()).content_source(this.inner);
let mut rewrite =
RewriteBuilder::new_source_with_path_and_query(this.inner, data.url.clone());
if !data.headers.is_empty() {
rewrite = rewrite.response_headers(HeaderListVc::new(data.headers.clone()));
}
ContentSourceResultVc::exact(
ContentSourceContent::Rewrite(rewrite.build()).cell().into(),
)
ContentSourceContent::Rewrite(rewrite.build()).cell()
}
RouterResult::Middleware(data) => ContentSourceResultVc::exact(
ContentSourceContent::HttpProxy(
ProxyResult {
status: data.status_code,
headers: data.headers.clone(),
body: Body::from_stream(data.body.read()),
}
.cell(),
)
.cell()
.into(),
),
RouterResult::Middleware(data) => ContentSourceContent::HttpProxy(
ProxyResult {
status: data.status_code,
headers: data.headers.clone(),
body: Body::from_stream(data.body.read()),
}
.cell(),
)
.cell(),
})
}
}

View file

@ -133,7 +133,8 @@ export function waitForLoaded(iframe: HTMLIFrameElement): Promise<void> {
return new Promise((resolve) => {
if (
iframe.contentDocument != null &&
iframe.contentDocument.readyState === 'complete'
iframe.contentDocument.readyState === 'complete' &&
iframe.contentDocument.documentURI !== 'about:blank'
) {
resolve()
} else {
@ -147,11 +148,12 @@ export function waitForLoaded(iframe: HTMLIFrameElement): Promise<void> {
}
export function waitForSelector(
node: ParentNode | HTMLIFrameElement | ShadowRoot,
node: HTMLIFrameElement | ShadowRoot,
selector: string
): Promise<Element> {
return new Promise((resolve, reject) => {
const document = 'contentDocument' in node ? node.contentDocument! : node
const document =
'contentDocument' in node ? node.contentDocument!.documentElement : node
const timeout = 30000
let element = document.querySelector(selector)
if (element) {
@ -168,14 +170,20 @@ export function waitForSelector(
if (timeout) {
setTimeout(() => {
observer.disconnect()
reject(new Error(`Timed out waiting for selector "${selector}"`))
reject(
new Error(
`Timed out waiting for selector "${selector}" in "${document}"\n\nNode content: "${
'innerHTML' in document ? document.innerHTML : 'no innerHTML'
}"`
)
)
}, timeout)
}
})
}
export async function waitForErrorOverlay(
node: ParentNode | HTMLIFrameElement
node: HTMLIFrameElement
): Promise<ShadowRoot> {
let element = await waitForSelector(node, 'nextjs-portal')
return element.shadowRoot!

View file

@ -1,7 +1,10 @@
export default function RootLayout({ children }: { children: any }) {
return (
<html>
<body>{children}</body>
<body>
<h1>RootLayout</h1>
{children}
</body>
</html>
)
}

View file

@ -33,7 +33,7 @@ function runTests(
const TIMEOUT = 40000
it(
'returns a 500 status code',
'returns a 500 status code for a broken page',
async () => {
const res = await fetch('/broken')
expect(res.status).toBe(500)
@ -41,6 +41,17 @@ function runTests(
TIMEOUT
)
// The existance of this test case fixes the error overlay later.
// I think it's related to streaming the result.
it(
'returns a 200 status code for a broken app page',
async () => {
const res = await fetch('/broken-app')
expect(res.status).toBe(200)
},
TIMEOUT
)
it(
'should show error overlay for a broken page',
async () => {

View file

@ -29,6 +29,7 @@ use once_cell::sync::Lazy;
use owo_colors::OwoColorize;
use tracing_subscriber::{prelude::*, EnvFilter, Registry};
use turbo_tasks::{
primitives::StringVc,
util::{FormatBytes, FormatDuration},
StatsType, TransientInstance, TurboTasks, TurboTasksBackendApi, UpdateInfo, Value,
};
@ -60,7 +61,7 @@ use turbopack_binding::{
dev_server::{
introspect::IntrospectionSource,
source::{
combined::CombinedContentSourceVc, router::RouterContentSource,
combined::CombinedContentSourceVc, router::PrefixedRouterContentSource,
source_maps::SourceMapContentSourceVc, static_assets::StaticAssetsContentSourceVc,
ContentSourceVc,
},
@ -413,17 +414,18 @@ async fn source(
pages_structure,
)
.into();
let source = RouterContentSource {
let source = PrefixedRouterContentSource {
prefix: StringVc::empty(),
routes: vec![
("__turbopack__/".to_string(), introspect),
("__turbo_tasks__/".to_string(), viz),
("__turbopack__".to_string(), introspect),
("__turbo_tasks__".to_string(), viz),
(
"__nextjs_original-stack-frame".to_string(),
source_map_trace,
),
// TODO: Load path from next.config.js
("_next/image".to_string(), img_source),
("__turbopack_sourcemap__/".to_string(), source_maps),
("__turbopack_sourcemap__".to_string(), source_maps),
],
fallback: router_source,
}

View file

@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration};
use anyhow::Result;
use anyhow::{bail, Result};
use mime::TEXT_HTML_UTF_8;
use turbo_tasks::{get_invalidator, TurboTasks, TurboTasksBackendApi, Value};
use turbopack_binding::{
@ -14,8 +14,10 @@ use turbopack_binding::{
turbopack::{
core::asset::AssetContentVc,
dev_server::source::{
route_tree::{BaseSegment, RouteTreeVc, RouteTreesVc, RouteType},
ContentSource, ContentSourceContentVc, ContentSourceData, ContentSourceDataFilter,
ContentSourceDataVary, ContentSourceResultVc, ContentSourceVc, NeededData,
ContentSourceDataVary, ContentSourceDataVaryVc, ContentSourceVc,
GetContentSourceContent, GetContentSourceContentVc,
},
},
};
@ -34,14 +36,58 @@ impl TurboTasksSourceVc {
const INVALIDATION_INTERVAL: Duration = Duration::from_secs(3);
const GRAPH_PATH: &str = "graph";
const CALL_GRAPH_PATH: &str = "call-graph";
const TABLE_PATH: &str = "table";
const RESET_PATH: &str = "reset";
#[turbo_tasks::value_impl]
impl ContentSource for TurboTasksSource {
#[turbo_tasks::function]
fn get_routes(self_vc: TurboTasksSourceVc) -> RouteTreeVc {
RouteTreesVc::cell(vec![
RouteTreeVc::new_route(
vec![BaseSegment::Static(GRAPH_PATH.to_string())],
RouteType::Exact,
self_vc.into(),
),
RouteTreeVc::new_route(
vec![BaseSegment::Static(CALL_GRAPH_PATH.to_string())],
RouteType::Exact,
self_vc.into(),
),
RouteTreeVc::new_route(
vec![BaseSegment::Static(TABLE_PATH.to_string())],
RouteType::Exact,
self_vc.into(),
),
RouteTreeVc::new_route(
vec![BaseSegment::Static(RESET_PATH.to_string())],
RouteType::Exact,
self_vc.into(),
),
])
.merge()
}
}
#[turbo_tasks::value_impl]
impl GetContentSourceContent for TurboTasksSource {
#[turbo_tasks::function]
fn vary(&self) -> ContentSourceDataVaryVc {
ContentSourceDataVary {
query: Some(ContentSourceDataFilter::All),
..Default::default()
}
.cell()
}
#[turbo_tasks::function]
async fn get(
self_vc: TurboTasksSourceVc,
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
) -> Result<ContentSourceContentVc> {
let this = self_vc.await?;
let tt = &this.turbo_tasks;
let invalidator = get_invalidator();
@ -52,7 +98,7 @@ impl ContentSource for TurboTasksSource {
}
});
let html = match path {
"graph" => {
GRAPH_PATH => {
let mut stats = Stats::new();
let b = tt.backend();
b.with_all_cached_tasks(|task| {
@ -66,7 +112,7 @@ impl ContentSource for TurboTasksSource {
);
viz::graph::wrap_html(&graph)
}
"call-graph" => {
CALL_GRAPH_PATH => {
let mut stats = Stats::new();
let b = tt.backend();
b.with_all_cached_tasks(|task| {
@ -77,45 +123,34 @@ impl ContentSource for TurboTasksSource {
viz::graph::visualize_stats_tree(tree, ReferenceType::Child, tt.stats_type());
viz::graph::wrap_html(&graph)
}
"table" => {
if let Some(query) = &data.query {
let mut stats = Stats::new();
let b = tt.backend();
let active_only = query.contains_key("active");
let include_unloaded = query.contains_key("unloaded");
b.with_all_cached_tasks(|task| {
stats.add_id_conditional(b, task, |_, info| {
(include_unloaded || !info.unloaded) && (!active_only || info.active)
});
TABLE_PATH => {
let Some(query) = &data.query else {
bail!("Missing query");
};
let mut stats = Stats::new();
let b = tt.backend();
let active_only = query.contains_key("active");
let include_unloaded = query.contains_key("unloaded");
b.with_all_cached_tasks(|task| {
stats.add_id_conditional(b, task, |_, info| {
(include_unloaded || !info.unloaded) && (!active_only || info.active)
});
let tree = stats.treeify(ReferenceType::Dependency);
let table = viz::table::create_table(tree, tt.stats_type());
viz::table::wrap_html(&table)
} else {
return Ok(ContentSourceResultVc::need_data(Value::new(NeededData {
source: self_vc.into(),
path: path.to_string(),
vary: ContentSourceDataVary {
query: Some(ContentSourceDataFilter::All),
..Default::default()
},
})));
}
});
let tree = stats.treeify(ReferenceType::Dependency);
let table = viz::table::create_table(tree, tt.stats_type());
viz::table::wrap_html(&table)
}
"reset" => {
RESET_PATH => {
let b = tt.backend();
b.with_all_cached_tasks(|task| {
b.with_task(task, |task| task.reset_stats());
});
"Done".to_string()
}
_ => return Ok(ContentSourceResultVc::not_found()),
_ => bail!("Unknown path: {}", path),
};
Ok(ContentSourceResultVc::exact(
ContentSourceContentVc::static_content(
AssetContentVc::from(File::from(html).with_content_type(TEXT_HTML_UTF_8)).into(),
)
.into(),
Ok(ContentSourceContentVc::static_content(
AssetContentVc::from(File::from(html).with_content_type(TEXT_HTML_UTF_8)).into(),
))
}
}

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.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230628.2
'@vercel/turbopack-node': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230628.2
'@vercel/turbopack-ecmascript-runtime': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230630.2
'@vercel/turbopack-node': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230630.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.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230628.2_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230628.2'
'@vercel/turbopack-ecmascript-runtime': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230630.2_react-refresh@0.12.0'
'@vercel/turbopack-node': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230630.2'
anser: 2.1.1
css.escape: 1.5.1
next: link:../../../../next
@ -6136,7 +6136,7 @@ packages:
dependencies:
'@mdx-js/mdx': 2.2.1
source-map: 0.7.3
webpack: 5.86.0
webpack: 5.86.0_@swc+core@1.3.55
transitivePeerDependencies:
- supports-color
@ -6810,7 +6810,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-x64/1.3.55:
@ -6819,7 +6818,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf/1.3.55:
@ -6828,7 +6826,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-gnu/1.3.55:
@ -6837,7 +6834,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-musl/1.3.55:
@ -6846,7 +6842,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-gnu/1.3.55:
@ -6855,7 +6850,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-musl/1.3.55:
@ -6864,7 +6858,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-arm64-msvc/1.3.55:
@ -6873,7 +6866,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-ia32-msvc/1.3.55:
@ -6882,7 +6874,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-x64-msvc/1.3.55:
@ -6891,7 +6882,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core/1.3.55_@swc+helpers@0.5.1:
@ -6916,7 +6906,6 @@ packages:
'@swc/core-win32-arm64-msvc': 1.3.55
'@swc/core-win32-ia32-msvc': 1.3.55
'@swc/core-win32-x64-msvc': 1.3.55
dev: true
/@swc/helpers/0.4.14:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
@ -23819,7 +23808,6 @@ packages:
serialize-javascript: 6.0.1
terser: 5.17.7
webpack: 5.86.0_@swc+core@1.3.55
dev: true
/terser/5.10.0:
resolution: {integrity: sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==}
@ -25177,7 +25165,6 @@ packages:
- '@swc/core'
- esbuild
- uglify-js
dev: true
/websocket-driver/0.7.3:
resolution: {integrity: sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==}
@ -25586,9 +25573,9 @@ packages:
/zwitch/2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
'@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230628.2_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230628.2}
id: '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230628.2'
'@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230630.2_react-refresh@0.12.0':
resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230630.2}
id: '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230630.2'
name: '@vercel/turbopack-ecmascript-runtime'
version: 0.0.0
dependencies:
@ -25599,8 +25586,8 @@ packages:
- webpack
dev: false
'@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230628.2':
resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230628.2}
'@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230630.2':
resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230630.2}
name: '@vercel/turbopack-node'
version: 0.0.0
dependencies: