From 8275758bf14e5c3dac5112f5a98ac90f9e11691b Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 22 Jun 2023 17:12:01 +0000 Subject: [PATCH] Revert "Revert "add edge rendering for app dir for Turbopack" (#51659)" This reverts commit d0e7d04dc5777de23be8fc5eb475c89a00071dd1. --- Cargo.lock | 70 ++--- Cargo.toml | 6 +- .../next-swc/crates/next-core/js/package.json | 4 +- .../js/src/entry/app-edge-renderer.tsx | 82 ++++++ .../next-core/js/src/entry/app-renderer.tsx | 263 +++--------------- .../next-core/js/src/entry/app/app-entry.tsx | 1 + .../js/src/entry/app/edge-page-bootstrap.ts | 106 +++++++ .../next-core/js/src/entry/app/edge-route.ts | 7 +- .../js/src/entry/app/layout-entry.tsx | 25 -- .../next-core/js/src/entry/app/manifest.ts | 127 +++++++++ .../next-core/js/src/entry/app/route.ts | 47 +--- .../js/src/entry/server-edge-api.tsx | 7 +- .../js/src/internal/api-server-handler.ts | 237 ++++++---------- .../crates/next-core/js/src/internal/edge.ts | 27 +- .../js/src/internal/nodejs-proxy-handler.ts | 52 ++++ .../js/src/internal/operation-stream.ts | 155 +++++++++++ .../crates/next-core/js/tsconfig.json | 3 +- .../next-core/js/types/compiled-next.d.ts | 1 - .../crates/next-core/js/types/rust.d.ts | 11 + .../crates/next-core/src/app_render/mod.rs | 2 +- ...rs => next_server_component_transition.rs} | 18 +- .../next-core/src/app_segment_config.rs | 146 ++++++++-- .../crates/next-core/src/app_source.rs | 220 +++++++++++---- .../crates/next-core/src/bootstrap.rs | 15 +- .../crates/next-core/src/next_edge/mod.rs | 3 +- .../src/next_edge/page_transition.rs | 100 +++++++ .../{transition.rs => route_transition.rs} | 4 +- .../crates/next-core/src/next_import_map.rs | 18 +- .../crates/next-core/src/page_source.rs | 12 +- .../next-swc/crates/next-core/src/router.rs | 6 +- .../next-dev-tests/tests/integration.rs | 171 ++++++------ .../next/import/conditions/input/app/test.js | 15 +- ...rror resolving commonjs request-b2593b.txt | 35 +++ ...rror resolving commonjs request-dd84e7.txt | 41 +++ .../build/webpack/loaders/next-app-loader.ts | 29 +- .../loaders/next-edge-ssr-loader/render.ts | 2 +- .../src/server/app-render/action-handler.ts | 2 +- .../create-server-components-renderer.tsx | 13 +- .../next/src/server/app-render/entry-base.ts | 52 ++++ packages/next/src/server/render-result.ts | 12 +- pnpm-lock.yaml | 33 +-- 41 files changed, 1414 insertions(+), 766 deletions(-) create mode 100644 packages/next-swc/crates/next-core/js/src/entry/app-edge-renderer.tsx create mode 100644 packages/next-swc/crates/next-core/js/src/entry/app/app-entry.tsx create mode 100644 packages/next-swc/crates/next-core/js/src/entry/app/edge-page-bootstrap.ts delete mode 100644 packages/next-swc/crates/next-core/js/src/entry/app/layout-entry.tsx create mode 100644 packages/next-swc/crates/next-core/js/src/entry/app/manifest.ts create mode 100644 packages/next-swc/crates/next-core/js/src/internal/nodejs-proxy-handler.ts create mode 100644 packages/next-swc/crates/next-core/js/src/internal/operation-stream.ts rename packages/next-swc/crates/next-core/src/app_render/{next_layout_entry_transition.rs => next_server_component_transition.rs} (79%) create mode 100644 packages/next-swc/crates/next-core/src/next_edge/page_transition.rs rename packages/next-swc/crates/next-core/src/next_edge/{transition.rs => route_transition.rs} (96%) create mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-b2593b.txt create mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-dd84e7.txt create mode 100644 packages/next/src/server/app-render/entry-base.ts diff --git a/Cargo.lock b/Cargo.lock index ff5c4de7ffc5..b2b660cdfac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,7 +400,7 @@ dependencies = [ [[package]] name = "auto-hash-map" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "serde", ] @@ -3463,7 +3463,7 @@ dependencies = [ [[package]] name = "node-file-trace" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "serde", @@ -6412,6 +6412,7 @@ dependencies = [ "swc_common", "swc_ecma_ast", "swc_plugin_proxy", + "tokio", "tracing", "wasmer", "wasmer-cache", @@ -7103,7 +7104,7 @@ dependencies = [ [[package]] name = "turbo-tasks" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "auto-hash-map", @@ -7134,7 +7135,7 @@ dependencies = [ [[package]] name = "turbo-tasks-build" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "cargo-lock", @@ -7146,7 +7147,7 @@ dependencies = [ [[package]] name = "turbo-tasks-bytes" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "bytes", @@ -7161,7 +7162,7 @@ dependencies = [ [[package]] name = "turbo-tasks-env" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "dotenvy", @@ -7175,7 +7176,7 @@ dependencies = [ [[package]] name = "turbo-tasks-fetch" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "indexmap", @@ -7192,7 +7193,7 @@ dependencies = [ [[package]] name = "turbo-tasks-fs" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "auto-hash-map", @@ -7222,7 +7223,7 @@ dependencies = [ [[package]] name = "turbo-tasks-hash" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "base16", "hex", @@ -7234,7 +7235,7 @@ dependencies = [ [[package]] name = "turbo-tasks-macros" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -7248,7 +7249,7 @@ dependencies = [ [[package]] name = "turbo-tasks-macros-shared" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "proc-macro2", "quote", @@ -7258,7 +7259,7 @@ dependencies = [ [[package]] name = "turbo-tasks-malloc" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "mimalloc", ] @@ -7266,7 +7267,7 @@ dependencies = [ [[package]] name = "turbo-tasks-memory" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "auto-hash-map", @@ -7289,7 +7290,7 @@ dependencies = [ [[package]] name = "turbo-tasks-testing" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "auto-hash-map", @@ -7301,7 +7302,7 @@ dependencies = [ [[package]] name = "turbopack" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-recursion", @@ -7331,7 +7332,7 @@ dependencies = [ [[package]] name = "turbopack-bench" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "chromiumoxide", @@ -7361,7 +7362,7 @@ dependencies = [ [[package]] name = "turbopack-binding" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "auto-hash-map", "mdxjs", @@ -7423,7 +7424,7 @@ dependencies = [ [[package]] name = "turbopack-cli-utils" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "clap 4.1.11", @@ -7447,7 +7448,7 @@ dependencies = [ [[package]] name = "turbopack-core" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-trait", @@ -7475,7 +7476,7 @@ dependencies = [ [[package]] name = "turbopack-create-test-app" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "clap 4.1.11", @@ -7488,7 +7489,7 @@ dependencies = [ [[package]] name = "turbopack-css" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-trait", @@ -7510,7 +7511,7 @@ dependencies = [ [[package]] name = "turbopack-dev" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "indexmap", @@ -7534,7 +7535,7 @@ dependencies = [ [[package]] name = "turbopack-dev-server" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-compression", @@ -7569,7 +7570,7 @@ dependencies = [ [[package]] name = "turbopack-ecmascript" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-trait", @@ -7602,7 +7603,7 @@ dependencies = [ [[package]] name = "turbopack-ecmascript-plugins" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-trait", @@ -7625,7 +7626,7 @@ dependencies = [ [[package]] name = "turbopack-ecmascript-runtime" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "indoc", @@ -7642,7 +7643,7 @@ dependencies = [ [[package]] name = "turbopack-env" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "indexmap", @@ -7658,7 +7659,7 @@ dependencies = [ [[package]] name = "turbopack-image" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "base64 0.21.0", @@ -7678,7 +7679,7 @@ dependencies = [ [[package]] name = "turbopack-json" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "serde", @@ -7693,7 +7694,7 @@ dependencies = [ [[package]] name = "turbopack-mdx" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "mdxjs", @@ -7708,7 +7709,7 @@ dependencies = [ [[package]] name = "turbopack-node" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "async-stream", @@ -7743,7 +7744,7 @@ dependencies = [ [[package]] name = "turbopack-static" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "serde", @@ -7759,7 +7760,7 @@ dependencies = [ [[package]] name = "turbopack-swc-utils" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "swc_core", "turbo-tasks", @@ -7770,7 +7771,7 @@ dependencies = [ [[package]] name = "turbopack-test-utils" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230615.1#1ff1956dc18ff1805b2ac87f21f79e1abea75fc8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230621.2#ee6683e43e31a470e13f0bc16dbaa375face74b5" dependencies = [ "anyhow", "once_cell", @@ -8450,6 +8451,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "serde", + "serde_cbor", "serde_derive", "serde_json", "serde_yaml 0.8.26", diff --git a/Cargo.toml b/Cargo.toml index bf88e1d48f27..e6a9e1826711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,11 +42,11 @@ swc_core = { version = "0.76.46" } testing = { version = "0.33.13" } # Turbo crates -turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230615.1" } +turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230621.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-230615.1" } +turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230621.2" } # [TODO]: need to refactor embed_directory! macro usage in next-core -turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230615.1" } +turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230621.2" } # General Deps diff --git a/packages/next-swc/crates/next-core/js/package.json b/packages/next-swc/crates/next-core/js/package.json index bcc996c35d06..4afd515f4566 100644 --- a/packages/next-swc/crates/next-core/js/package.json +++ b/packages/next-swc/crates/next-core/js/package.json @@ -10,8 +10,8 @@ "check": "tsc --noEmit" }, "dependencies": { - "@vercel/turbopack-ecmascript-runtime": "https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230615.1", - "@vercel/turbopack-node": "https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230615.1", + "@vercel/turbopack-ecmascript-runtime": "https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2", + "@vercel/turbopack-node": "https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230621.2", "anser": "^2.1.1", "css.escape": "^1.5.1", "next": "*", diff --git a/packages/next-swc/crates/next-core/js/src/entry/app-edge-renderer.tsx b/packages/next-swc/crates/next-core/js/src/entry/app-edge-renderer.tsx new file mode 100644 index 000000000000..fdec9ffc360f --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/entry/app-edge-renderer.tsx @@ -0,0 +1,82 @@ +// IPC need to be the first import to allow it to catch errors happening during +// the other imports +import startOperationStreamHandler from '../internal/operation-stream' + +import { join } from 'path' +import { parse as parseUrl } from 'node:url' + +import { runEdgeFunction } from '../internal/edge' +import { headersFromEntries, initProxiedHeaders } from '../internal/headers' +import { NodeNextRequest } from 'next/dist/server/base-http/node' + +import type { IncomingMessage } from 'node:http' +import type { RenderData } from 'types/turbopack' + +import chunkGroup from 'INNER_EDGE_CHUNK_GROUP' +import { attachRequestMeta } from '../internal/next-request-helpers' +import { Readable } from 'stream' + +startOperationStreamHandler(async (renderData: RenderData, respond) => { + const { response } = await runOperation(renderData) + + if (response == null) { + throw new Error('no html returned') + } + + const channel = respond({ + status: response.status, + // @ts-expect-error Headers is iterable since node.js 18 + headers: [...response.headers], + }) + + if (response.body) { + const reader = response.body.getReader() + for (;;) { + let { done, value } = await reader.read() + if (done) { + break + } + channel.chunk(Buffer.from(value!)) + } + } + + channel.end() +}) + +async function runOperation(renderData: RenderData) { + const edgeInfo = { + name: 'edge', + paths: chunkGroup + .filter((chunk) => chunk.endsWith('.js')) + .map((chunk: string) => join(process.cwd(), '.next/server/app', chunk)), + wasm: [], + env: Object.keys(process.env), + assets: [], + } + + const parsedUrl = parseUrl(renderData.originalUrl, true) + const incoming = new Readable() as IncomingMessage + incoming.push(null) + incoming.url = renderData.originalUrl + incoming.method = renderData.method + incoming.headers = initProxiedHeaders( + headersFromEntries(renderData.rawHeaders), + renderData.data?.serverInfo + ) + const req = new NodeNextRequest(incoming) + attachRequestMeta(req, parsedUrl, req.headers.host!) + + const res = await runEdgeFunction({ + edgeInfo, + outputDir: 'edge-pages', + req, + query: renderData.rawQuery, + params: renderData.params, + path: renderData.path, + onWarning(warning) { + console.warn(warning) + }, + }) + + return res as { response: Response } +} diff --git a/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx b/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx index 759e360f7268..211bbcc0bd4e 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx +++ b/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx @@ -1,22 +1,11 @@ -// Provided by the rust generate code -type FileType = - | 'layout' - | 'template' - | 'error' - | 'loading' - | 'not-found' - | 'head' -declare global { - // an tree of all layouts and the page - const LOADER_TREE: LoaderTree - // array of chunks for the bootstrap script - const BOOTSTRAP: string[] - const IPC: Ipc -} +// IPC need to be the first import to allow it to catch errors happening during +// the other imports +import startOperationStreamHandler from '../internal/operation-stream' + +import '../polyfill/app-polyfills.ts' -import type { Ipc } from '@vercel/turbopack-node/ipc/index' import type { IncomingMessage } from 'node:http' -import type { ClientReferenceManifest } from 'next/dist/build/webpack/plugins/flight-manifest-plugin' + import type { RenderData } from 'types/turbopack' import type { RenderOpts } from 'next/dist/server/app-render/types' @@ -25,211 +14,39 @@ import { RSC_VARY_HEADER } from 'next/dist/client/components/app-router-headers' import { headersFromEntries, initProxiedHeaders } from '../internal/headers' import { parse, ParsedUrlQuery } from 'node:querystring' import { PassThrough } from 'node:stream' -;('TURBOPACK { transition: next-layout-entry; chunking-type: isolatedParallel }') -// @ts-ignore -import layoutEntry from './app/layout-entry' +;('TURBOPACK { chunking-type: isolatedParallel }') +import entry from 'APP_ENTRY' +import BOOTSTRAP from 'APP_BOOTSTRAP' import { createServerResponse } from '../internal/http' +import { createManifests, installRequireAndChunkLoad } from './app/manifest' -globalThis.__next_require__ = (data) => { - const [, , ssr_id] = JSON.parse(data) - return __turbopack_require__(ssr_id) -} -globalThis.__next_chunk_load__ = () => Promise.resolve() +installRequireAndChunkLoad() process.env.__NEXT_NEW_LINK_BEHAVIOR = 'true' -const ipc = IPC as Ipc - -type IpcIncomingMessage = { - type: 'headers' - data: RenderData -} - -type IpcOutgoingMessage = - | { - type: 'headers' - data: { - status: number - headers: [string, string][] - } - } - | { - type: 'bodyChunk' - data: number[] - } - | { - type: 'bodyEnd' - } - const MIME_TEXT_HTML_UTF8 = 'text/html; charset=utf-8' -;(async () => { - while (true) { - const msg = await ipc.recv() - - let renderData: RenderData - switch (msg.type) { - case 'headers': { - renderData = msg.data - break - } - default: { - console.error('unexpected message type', msg.type) - process.exit(1) - } - } +startOperationStreamHandler(async (renderData: RenderData, respond) => { + const result = await runOperation(renderData) - const result = await runOperation(renderData) - - if (result == null) { - throw new Error('no html returned') - } - - ipc.send({ - type: 'headers', - data: { - status: result.statusCode, - headers: result.headers, - }, - }) - - for await (const chunk of result.body) { - ipc.send({ - type: 'bodyChunk', - data: (chunk as Buffer).toJSON().data, - }) - } - - ipc.send({ type: 'bodyEnd' }) + if (result == null) { + throw new Error('no html returned') } -})().catch((err) => { - ipc.sendError(err) -}) -// TODO expose these types in next.js -type ComponentModule = () => any -type ModuleReference = [componentModule: ComponentModule, filePath: string] -export type ComponentsType = { - [componentKey in FileType]?: ModuleReference -} & { - page?: ModuleReference -} -type LoaderTree = [ - segment: string, - parallelRoutes: { [parallelRouterKey: string]: LoaderTree }, - components: ComponentsType -] + const channel = respond({ + status: result.statusCode, + headers: result.headers, + }) -async function runOperation(renderData: RenderData) { - const proxyMethodsForModule = ( - id: string - ): ProxyHandler => { - return { - get(_target, prop: string) { - return { - id, - chunks: JSON.parse(id)[1], - name: prop, - } - }, - } + for await (const chunk of result.body) { + channel.chunk(chunk as Buffer) } - const proxyMethodsNested = ( - type: 'ssrModuleMapping' | 'clientModules' | 'entryCSSFiles' - ): ProxyHandler< - | ClientReferenceManifest['ssrModuleMapping'] - | ClientReferenceManifest['clientModules'] - | ClientReferenceManifest['entryCSSFiles'] - > => { - return { - get(_target, key: string) { - if (type === 'ssrModuleMapping') { - return new Proxy({}, proxyMethodsForModule(key as string)) - } - if (type === 'clientModules') { - // The key is a `${file}#${name}`, but `file` can contain `#` itself. - // There are 2 possibilities: - // "file#" => id = "file", name = "" - // "file#foo" => id = "file", name = "foo" - const pos = key.lastIndexOf('#') - let id = key - let name = '' - if (pos !== -1) { - id = key.slice(0, pos) - name = key.slice(pos + 1) - } else { - throw new Error('keys need to be formatted as {file}#{name}') - } - - return { - id, - name, - chunks: JSON.parse(id)[1], - } - } - if (type === 'entryCSSFiles') { - const cssChunks = JSON.parse(key) - // TODO(WEB-856) subscribe to changes - return { - modules: [], - files: cssChunks.filter(filterAvailable).map(toPath), - } - } - }, - } - } + channel.end() +}) - const proxyMethods = (): ProxyHandler => { - const clientModulesProxy = new Proxy( - {}, - proxyMethodsNested('clientModules') - ) - const ssrModuleMappingProxy = new Proxy( - {}, - proxyMethodsNested('ssrModuleMapping') - ) - const entryCSSFilesProxy = new Proxy( - {}, - proxyMethodsNested('entryCSSFiles') - ) - return { - get(_target: any, prop: string) { - if (prop === 'ssrModuleMapping') { - return ssrModuleMappingProxy - } - if (prop === 'clientModules') { - return clientModulesProxy - } - if (prop === 'entryCSSFiles') { - return entryCSSFilesProxy - } - }, - } - } - const availableModules = new Set() - const toPath = (chunk: ChunkData) => - typeof chunk === 'string' ? chunk : chunk.path - /// determines if a chunk is needed based on the current available modules - const filterAvailable = (chunk: ChunkData) => { - if (typeof chunk === 'string') { - return true - } else { - let includedList = chunk.included || [] - if (includedList.length === 0) { - return true - } - let needed = false - for (const item of includedList) { - if (!availableModules.has(item)) { - availableModules.add(item) - needed = true - } - } - return needed - } - } - const manifest: ClientReferenceManifest = new Proxy({} as any, proxyMethods()) +async function runOperation(renderData: RenderData) { + const { clientReferenceManifest } = createManifests() const req: IncomingMessage = { url: renderData.originalUrl, @@ -266,12 +83,14 @@ async function runOperation(renderData: RenderData) { ampFirstPages: [], }, ComponentMod: { - ...layoutEntry, - default: undefined, - tree: LOADER_TREE, + ...entry, + __next_app__: { + require: __next_require__, + loadChunk: __next_chunk_load__, + }, pages: ['page.js'], }, - clientReferenceManifest: manifest, + clientReferenceManifest, runtime: 'nodejs', serverComponents: true, assetPrefix: '', @@ -304,23 +123,3 @@ async function runOperation(renderData: RenderData) { body, } } - -// This utility is based on https://github.com/zertosh/htmlescape -// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE - -const ESCAPE_LOOKUP = { - '&': '\\u0026', - '>': '\\u003e', - '<': '\\u003c', - '\u2028': '\\u2028', - '\u2029': '\\u2029', -} - -const ESCAPE_REGEX = /[&><\u2028\u2029]/g - -export function htmlEscapeJsonString(str: string) { - return str.replace( - ESCAPE_REGEX, - (match) => ESCAPE_LOOKUP[match as keyof typeof ESCAPE_LOOKUP] - ) -} diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/app-entry.tsx b/packages/next-swc/crates/next-core/js/src/entry/app/app-entry.tsx new file mode 100644 index 000000000000..b445020fe0de --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/entry/app/app-entry.tsx @@ -0,0 +1 @@ +// This file is generated by app_source.rs diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/edge-page-bootstrap.ts b/packages/next-swc/crates/next-core/js/src/entry/app/edge-page-bootstrap.ts new file mode 100644 index 000000000000..deaa19a0a228 --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/entry/app/edge-page-bootstrap.ts @@ -0,0 +1,106 @@ +import { adapter } from 'next/dist/server/web/adapter' +import { RSC_VARY_HEADER } from 'next/dist/client/components/app-router-headers' +import { IncrementalCache } from 'next/dist/server/lib/incremental-cache' +import { renderToHTMLOrFlight } from 'next/dist/server/app-render/app-render' +;('TURBOPACK { chunking-type: isolatedParallel }') +import entry from 'APP_ENTRY' +import BOOTSTRAP from 'APP_BOOTSTRAP' +import { createManifests, installRequireAndChunkLoad } from './manifest' +import type { NextRequest, NextFetchEvent } from 'next/server' +import type { RenderOpts } from 'next/dist/server/app-render/types' +import type { ParsedUrlQuery } from 'querystring' + +installRequireAndChunkLoad() + +// avoid limiting stack traces to 10 lines +Error.stackTraceLimit = 100 + +const { clientReferenceManifest } = createManifests() + +const MIME_TEXT_HTML_UTF8 = 'text/html; charset=utf-8' + +async function render(request: NextRequest, event: NextFetchEvent) { + const renderOpt: Omit< + RenderOpts, + 'App' | 'Document' | 'Component' | 'pathname' + > & { params: ParsedUrlQuery } = { + // TODO(WEB-1195) params + params: {}, + supportsDynamicHTML: true, + dev: true, + buildId: 'development', + buildManifest: { + polyfillFiles: [], + rootMainFiles: BOOTSTRAP.filter((path) => path.endsWith('.js')), + devFiles: [], + ampDevFiles: [], + lowPriorityFiles: [], + pages: { + '/_app': [], + }, + ampFirstPages: [], + }, + ComponentMod: { + ...entry, + __next_app__: { + require: __next_require__, + loadChunk: __next_chunk_load__, + }, + pages: ['page.js'], + }, + clientReferenceManifest, + runtime: 'nodejs', + serverComponents: true, + assetPrefix: '', + pageConfig: {}, + reactLoadableManifest: {}, + // TODO nextConfigOutput + nextConfigOutput: undefined, + } + + const tranform = new TransformStream() + const response = new Response(tranform.readable) + + let { pathname, search: query } = new URL(request.url, 'next://') + + const result = await renderToHTMLOrFlight( + // @ts-expect-error - TODO renderToHTMLOrFlight types should accept web platform types + request, + response, + pathname, + // TODO(WEB-1195) query + {}, + renderOpt as any as RenderOpts + ) + + response.headers.append( + 'Content-Type', + result.contentType || MIME_TEXT_HTML_UTF8 + ) + response.headers.append('Vary', RSC_VARY_HEADER) + + const writer = tranform.writable.getWriter() + result.pipe({ + write: (chunk: Uint8Array) => writer.write(chunk), + end: () => writer.close(), + destroy: (reason?: Error) => writer.abort(reason), + }) + + return response +} + +// adapter uses this to detect edge rendering +self.__BUILD_MANIFEST = {} + +// @ts-expect-error - exposed for edge support +globalThis._ENTRIES = { + middleware_edge: { + default: function (opts: any) { + return adapter({ + ...opts, + IncrementalCache, + handler: render, + }) + }, + }, +} diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/edge-route.ts b/packages/next-swc/crates/next-core/js/src/entry/app/edge-route.ts index afff164d101c..32c118745eb6 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/app/edge-route.ts +++ b/packages/next-swc/crates/next-core/js/src/entry/app/edge-route.ts @@ -12,7 +12,7 @@ import { NodeNextResponse, } from 'next/dist/server/base-http/node' -import { runEdgeFunction } from '../../internal/edge' +import { runEdgeFunction, updateResponse } from '../../internal/edge' import { attachRequestMeta } from '../../internal/next-request-helpers' import chunkGroup from 'ROUTE_CHUNK_GROUP' @@ -34,11 +34,10 @@ startHandler(async ({ request, response, query, params, path }) => { assets: [], } - await runEdgeFunction({ + const result = await runEdgeFunction({ edgeInfo, outputDir: 'app', req, - res, query, params, path, @@ -46,4 +45,6 @@ startHandler(async ({ request, response, query, params, path }) => { console.warn(warning) }, }) + + updateResponse(res, result) }) diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/layout-entry.tsx b/packages/next-swc/crates/next-core/js/src/entry/app/layout-entry.tsx deleted file mode 100644 index f9b0a9dd3c87..000000000000 --- a/packages/next-swc/crates/next-core/js/src/entry/app/layout-entry.tsx +++ /dev/null @@ -1,25 +0,0 @@ -export { default as AppRouter } from 'next/dist/client/components/app-router' -export { default as LayoutRouter } from 'next/dist/client/components/layout-router' -export { default as RenderFromTemplateContext } from 'next/dist/client/components/render-from-template-context' -export { default as GlobalError } from 'next/dist/client/components/error-boundary' - -export { staticGenerationAsyncStorage } from 'next/dist/client/components/static-generation-async-storage' - -export { requestAsyncStorage } from 'next/dist/client/components/request-async-storage' -export { actionAsyncStorage } from 'next/dist/client/components/action-async-storage' - -export { staticGenerationBailout } from 'next/dist/client/components/static-generation-bailout' -export { default as StaticGenerationSearchParamsBailoutProvider } from 'next/dist/client/components/static-generation-searchparams-bailout-provider' -export { createSearchParamsBailoutProxy } from 'next/dist/client/components/searchparams-bailout-proxy' - -import * as serverHooks from 'next/dist/client/components/hooks-server-context' -export { serverHooks } -export { - renderToReadableStream, - decodeReply, -} from 'next/dist/compiled/react-server-dom-webpack/server.edge' -export { - preloadStyle, - preloadFont, - preconnect, -} from 'next/dist/server/app-render/rsc/preloads' diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/manifest.ts b/packages/next-swc/crates/next-core/js/src/entry/app/manifest.ts new file mode 100644 index 000000000000..912f3ae75d95 --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/entry/app/manifest.ts @@ -0,0 +1,127 @@ +import type { ClientReferenceManifest } from 'next/dist/build/webpack/plugins/flight-manifest-plugin' + +export function createManifests() { + const proxyMethodsForModule = ( + id: string + ): ProxyHandler => { + return { + get(_target, prop: string) { + return { + id, + chunks: JSON.parse(id)[1], + name: prop, + } + }, + } + } + + const availableModules = new Set() + const toPath = (chunk: ChunkData) => + typeof chunk === 'string' ? chunk : chunk.path + /// determines if a chunk is needed based on the current available modules + const filterAvailable = (chunk: ChunkData) => { + if (typeof chunk === 'string') { + return true + } else { + let includedList = chunk.included || [] + if (includedList.length === 0) { + return true + } + let needed = false + for (const item of includedList) { + if (!availableModules.has(item)) { + availableModules.add(item) + needed = true + } + } + return needed + } + } + + const proxyMethodsNested = ( + type: 'ssrModuleMapping' | 'clientModules' | 'entryCSSFiles' + ): ProxyHandler< + | ClientReferenceManifest['ssrModuleMapping'] + | ClientReferenceManifest['clientModules'] + | ClientReferenceManifest['entryCSSFiles'] + > => { + return { + get(_target, key: string) { + if (type === 'ssrModuleMapping') { + return new Proxy({}, proxyMethodsForModule(key as string)) + } + if (type === 'clientModules') { + // The key is a `${file}#${name}`, but `file` can contain `#` itself. + // There are 2 possibilities: + // "file#" => id = "file", name = "" + // "file#foo" => id = "file", name = "foo" + const pos = key.lastIndexOf('#') + let id = key + let name = '' + if (pos !== -1) { + id = key.slice(0, pos) + name = key.slice(pos + 1) + } else { + throw new Error('keys need to be formatted as {file}#{name}') + } + + return { + id, + name, + chunks: JSON.parse(id)[1], + } + } + if (type === 'entryCSSFiles') { + const cssChunks = JSON.parse(key) + // TODO(WEB-856) subscribe to changes + return { + modules: [], + files: cssChunks.filter(filterAvailable).map(toPath), + } + } + }, + } + } + const proxyMethods = (): ProxyHandler => { + const clientModulesProxy = new Proxy( + {}, + proxyMethodsNested('clientModules') + ) + const ssrModuleMappingProxy = new Proxy( + {}, + proxyMethodsNested('ssrModuleMapping') + ) + const entryCSSFilesProxy = new Proxy( + {}, + proxyMethodsNested('entryCSSFiles') + ) + return { + get(_target: any, prop: string) { + if (prop === 'ssrModuleMapping') { + return ssrModuleMappingProxy + } + if (prop === 'clientModules') { + return clientModulesProxy + } + if (prop === 'entryCSSFiles') { + return entryCSSFilesProxy + } + }, + } + } + + const clientReferenceManifest: ClientReferenceManifest = new Proxy( + {} as any, + proxyMethods() + ) + + return { clientReferenceManifest } +} + +export function installRequireAndChunkLoad() { + globalThis.__next_require__ = (data) => { + const [, , ssr_id] = JSON.parse(data) + return __turbopack_require__(ssr_id) + } + globalThis.__next_chunk_load__ = () => Promise.resolve() +} diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/route.ts b/packages/next-swc/crates/next-core/js/src/entry/app/route.ts index 37c7f31f07de..9f268d1fe86e 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/app/route.ts +++ b/packages/next-swc/crates/next-core/js/src/entry/app/route.ts @@ -1,20 +1,6 @@ // IPC need to be the first import to allow it to catch errors happening during // the other imports -import startHandler from '../../internal/api-server-handler' - -import '../../polyfill/app-polyfills' - -import { parse as parseUrl } from 'node:url' - -import { - NodeNextRequest, - NodeNextResponse, -} from 'next/dist/server/base-http/node' -import { sendResponse } from 'next/dist/server/send-response' -import { NextRequestAdapter } from 'next/dist/server/web/spec-extension/adapters/next-request' -import { RouteHandlerManagerContext } from 'next/dist/server/future/route-handler-managers/route-handler-manager' - -import { attachRequestMeta } from '../../internal/next-request-helpers' +import startHandler from '../../internal/nodejs-proxy-handler' import RouteModule from 'ROUTE_MODULE' import * as userland from 'ENTRY' @@ -34,33 +20,4 @@ const routeModule = new RouteModule({ nextConfigOutput: undefined, }) -startHandler(async ({ request, response, query, params, path }) => { - const req = new NodeNextRequest(request) - const res = new NodeNextResponse(response) - - const parsedUrl = parseUrl(req.url!, true) - attachRequestMeta(req, parsedUrl, request.headers.host!) - - const context: RouteHandlerManagerContext = { - params, - prerenderManifest: { - version: -1 as any, // letting us know this doesn't conform to spec - routes: {}, - dynamicRoutes: {}, - notFoundRoutes: [], - preview: { - previewModeId: 'development-id', - } as any, - }, - staticGenerationContext: { - supportsDynamicHTML: true, - }, - } - - const routeResponse = await routeModule.handle( - NextRequestAdapter.fromNodeNextRequest(req), - context - ) - - await sendResponse(req, res, routeResponse) -}) +startHandler(routeModule) diff --git a/packages/next-swc/crates/next-core/js/src/entry/server-edge-api.tsx b/packages/next-swc/crates/next-core/js/src/entry/server-edge-api.tsx index 2988e767bb0d..8edab71c6dd5 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/server-edge-api.tsx +++ b/packages/next-swc/crates/next-core/js/src/entry/server-edge-api.tsx @@ -13,7 +13,7 @@ import { } from 'next/dist/server/base-http/node' import { attachRequestMeta } from '../internal/next-request-helpers' -import { runEdgeFunction } from '../internal/edge' +import { runEdgeFunction, updateResponse } from '../internal/edge' import chunkGroup from 'INNER_EDGE_CHUNK_GROUP' @@ -34,11 +34,10 @@ startHandler(async ({ request, response, query, params, path }) => { assets: [], } - await runEdgeFunction({ + const result = await runEdgeFunction({ edgeInfo, outputDir: 'pages', req, - res, query, params, path, @@ -46,4 +45,6 @@ startHandler(async ({ request, response, query, params, path }) => { console.warn(warning) }, }) + + await updateResponse(res, result) }) diff --git a/packages/next-swc/crates/next-core/js/src/internal/api-server-handler.ts b/packages/next-swc/crates/next-core/js/src/internal/api-server-handler.ts index 1a55c3d1effa..53f6c76f19a2 100644 --- a/packages/next-swc/crates/next-core/js/src/internal/api-server-handler.ts +++ b/packages/next-swc/crates/next-core/js/src/internal/api-server-handler.ts @@ -1,48 +1,15 @@ // IPC need to be the first import to allow it to catch errors happening during // the other imports -import { IPC } from '@vercel/turbopack-node/ipc/index' +import startOperationStreamHandler from './operation-stream' import type { ClientRequest, IncomingMessage, Server } from 'node:http' import type { ServerResponse } from 'node:http' import { Buffer } from 'node:buffer' -import type { Ipc } from '@vercel/turbopack-node/ipc/index' - import type { RenderData } from 'types/turbopack' import { createServer, makeRequest } from '../internal/server' import { toPairs } from '../internal/headers' -const ipc = IPC as Ipc - -type IpcIncomingMessage = - | { - type: 'headers' - data: RenderData - } - | { - type: 'bodyChunk' - data: number[] - } - | { type: 'bodyEnd' } - -type IpcOutgoingMessage = - | { - type: 'headers' - data: ResponseHeaders - } - | { - type: 'bodyChunk' - data: number[] - } - | { - type: 'bodyEnd' - } - -type ResponseHeaders = { - status: number - headers: [string, string][] -} - type Handler = (data: { request: IncomingMessage response: ServerResponse @@ -58,134 +25,96 @@ type Operation = { server: Server } -export default function startHandler(handler: Handler): void { - ;(async () => { - while (true) { - let operationPromise: Promise | null = null - - const msg = await ipc.recv() +type ResponseHeaders = { + status: number + headers: [string, string][] +} - switch (msg.type) { - case 'headers': { - operationPromise = createOperation(msg.data) - break +export default function startHandler(handler: Handler): void { + startOperationStreamHandler( + async (renderData: RenderData, respond, reportError) => { + const operationPromise = (async function createOperation() { + const server = await createServer() + + const { + clientRequest, + clientResponsePromise, + serverRequest, + serverResponse, + } = await makeRequest( + server, + renderData.method, + renderData.path, + renderData.rawQuery, + renderData.rawHeaders, + renderData.data?.serverInfo + ) + + return { + clientRequest, + server, + clientResponsePromise, + apiOperation: handler({ + request: serverRequest, + response: serverResponse, + query: renderData.rawQuery, + params: renderData.params, + path: renderData.path, + }), } - default: { - console.error('unexpected message type', msg.type) - process.exit(1) + })() + + function handleClientResponse( + server: Server, + clientResponse: IncomingMessage + ) { + const responseHeaders: ResponseHeaders = { + status: clientResponse.statusCode!, + headers: toPairs(clientResponse.rawHeaders), } + + const channel = respond(responseHeaders) + + clientResponse.on('data', (chunk) => { + channel.chunk(chunk) + }) + + clientResponse.once('end', () => { + channel.end() + server.close() + }) + + clientResponse.once('error', (err) => { + reportError(err) + }) } - let body = Buffer.alloc(0) - let operation: Operation - loop: while (true) { - const msg = await ipc.recv() - - switch (msg.type) { - case 'bodyChunk': { - body = Buffer.concat([body, Buffer.from(msg.data)]) - break - } - case 'bodyEnd': { - operation = await operationPromise - break loop - } - default: { - console.error('unexpected message type', msg.type) - process.exit(1) - } + /** + * Ends an operation by writing the response body to the client and waiting for the Next.js API resolver to finish. + */ + async function endOperation(operation: Operation, body: Buffer) { + operation.clientRequest.end(body) + + try { + await operation.apiOperation + } catch (error) { + await reportError(error) + return } } - await Promise.all([ - endOperation(operation, body), - operation.clientResponsePromise.then((clientResponse) => - handleClientResponse(operation.server, clientResponse) - ), - ]) - } - })().catch((err) => { - ipc.sendError(err) - }) - - async function createOperation(renderData: RenderData): Promise { - const server = await createServer() - - const { - clientRequest, - clientResponsePromise, - serverRequest, - serverResponse, - } = await makeRequest( - server, - renderData.method, - renderData.path, - renderData.rawQuery, - renderData.rawHeaders, - renderData.data?.serverInfo - ) - - return { - clientRequest, - server, - clientResponsePromise, - apiOperation: handler({ - request: serverRequest, - response: serverResponse, - query: renderData.rawQuery, - params: renderData.params, - path: renderData.path, - }), - } - } - - function handleClientResponse( - server: Server, - clientResponse: IncomingMessage - ) { - const responseHeaders: ResponseHeaders = { - status: clientResponse.statusCode!, - headers: toPairs(clientResponse.rawHeaders), - } - - ipc.send({ - type: 'headers', - data: responseHeaders, - }) - - clientResponse.on('data', (chunk) => { - ipc.send({ - type: 'bodyChunk', - data: chunk.toJSON().data, - }) - }) - - clientResponse.once('end', () => { - ipc.send({ type: 'bodyEnd' }) - server.close() - }) - - clientResponse.once('error', (err) => { - ipc.sendError(err) - }) - } - - /** - * Ends an operation by writing the response body to the client and waiting for the Next.js API resolver to finish. - */ - async function endOperation(operation: Operation, body: Buffer) { - operation.clientRequest.end(body) - - try { - await operation.apiOperation - } catch (error) { - if (error instanceof Error) { - await ipc.sendError(error) - } else { - await ipc.sendError(new Error('an unknown error occurred')) + return { + streamRequest: false, + onBody: async (body) => { + const operation = await operationPromise + await Promise.all([ + endOperation(operation, body), + operation.clientResponsePromise.then((clientResponse) => + handleClientResponse(operation.server, clientResponse) + ), + ]) + }, } - - return } - } + ) } diff --git a/packages/next-swc/crates/next-core/js/src/internal/edge.ts b/packages/next-swc/crates/next-core/js/src/internal/edge.ts index 386840001cce..614c3530ba1d 100644 --- a/packages/next-swc/crates/next-core/js/src/internal/edge.ts +++ b/packages/next-swc/crates/next-core/js/src/internal/edge.ts @@ -10,7 +10,7 @@ import type { } from 'next/dist/server/base-http/node' import { parse, ParsedUrlQuery } from 'querystring' import type { Params } from 'next/dist/shared/lib/router/utils/route-matcher' -import { FetchEventResult } from 'next/dist/server/web/types' +import type { FetchEventResult } from 'next/dist/server/web/types' import { getCloneableBody } from 'next/dist/server/body-streams' // This is an adapted version of a similar function in next-dev-server. @@ -19,7 +19,6 @@ export async function runEdgeFunction({ edgeInfo, outputDir, req, - res, query, params, path, @@ -34,12 +33,11 @@ export async function runEdgeFunction({ } outputDir: string req: BaseNextRequest | NodeNextRequest - res: BaseNextResponse | NodeNextResponse query: string path: string params: Params | undefined onWarning?: (warning: Error) => void -}): Promise { +}): Promise { // For edge to "fetch" we must always provide an absolute URL const initialUrl = new URL(path, 'http://n') const parsedQuery = parse(query) @@ -83,21 +81,28 @@ export async function runEdgeFunction({ onWarning, })) as FetchEventResult - res.statusCode = result.response.status - res.statusMessage = result.response.statusText + return result +} + +export async function updateResponse( + response: BaseNextResponse | NodeNextResponse, + result: FetchEventResult +) { + response.statusCode = result.response.status + response.statusMessage = result.response.statusText result.response.headers.forEach((value: string, key) => { // the append handling is special cased for `set-cookie` if (key.toLowerCase() === 'set-cookie') { - res.setHeader(key, value) + response.setHeader(key, value) } else { - res.appendHeader(key, value) + response.appendHeader(key, value) } }) if (result.response.body) { // TODO(gal): not sure that we always need to stream - const nodeResStream = (res as NodeNextResponse).originalResponse + const nodeResStream = (response as NodeNextResponse).originalResponse const { consumeUint8ArrayReadableStream, } = require('next/dist/compiled/edge-runtime') @@ -111,10 +116,8 @@ export async function runEdgeFunction({ nodeResStream.end() } } else { - ;(res as NodeNextResponse).originalResponse.end() + ;(response as NodeNextResponse).originalResponse.end() } - - return result } function stringifyUrlQueryParam(param: unknown): string { diff --git a/packages/next-swc/crates/next-core/js/src/internal/nodejs-proxy-handler.ts b/packages/next-swc/crates/next-core/js/src/internal/nodejs-proxy-handler.ts new file mode 100644 index 000000000000..2f5f56e74cdb --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/internal/nodejs-proxy-handler.ts @@ -0,0 +1,52 @@ +// IPC need to be the first import to allow it to catch errors happening during +// the other imports +import startHandler from './api-server-handler' + +import '../polyfill/app-polyfills' + +import { parse as parseUrl } from 'node:url' + +import { + NodeNextRequest, + NodeNextResponse, +} from 'next/dist/server/base-http/node' +import { sendResponse } from 'next/dist/server/send-response' +import { NextRequestAdapter } from 'next/dist/server/web/spec-extension/adapters/next-request' +import { RouteHandlerManagerContext } from 'next/dist/server/future/route-handler-managers/route-handler-manager' + +import { attachRequestMeta } from './next-request-helpers' + +import type { RouteModule } from 'next/dist/server/future/route-modules/route-module' + +export default (routeModule: RouteModule) => { + startHandler(async ({ request, response, params }) => { + const req = new NodeNextRequest(request) + const res = new NodeNextResponse(response) + + const parsedUrl = parseUrl(req.url!, true) + attachRequestMeta(req, parsedUrl, request.headers.host!) + + const context: RouteHandlerManagerContext = { + params, + prerenderManifest: { + version: -1 as any, // letting us know this doesn't conform to spec + routes: {}, + dynamicRoutes: {}, + notFoundRoutes: [], + preview: { + previewModeId: 'development-id', + } as any, + }, + staticGenerationContext: { + supportsDynamicHTML: true, + }, + } + + const routeResponse = await routeModule.handle( + NextRequestAdapter.fromNodeNextRequest(req), + context + ) + + await sendResponse(req, res, routeResponse) + }) +} diff --git a/packages/next-swc/crates/next-core/js/src/internal/operation-stream.ts b/packages/next-swc/crates/next-core/js/src/internal/operation-stream.ts new file mode 100644 index 000000000000..77f5a7d89aea --- /dev/null +++ b/packages/next-swc/crates/next-core/js/src/internal/operation-stream.ts @@ -0,0 +1,155 @@ +// IPC need to be the first import to allow it to catch errors happening during +// the other imports +import { IPC } from '@vercel/turbopack-node/ipc/index' + +import { Buffer } from 'node:buffer' + +import type { Ipc } from '@vercel/turbopack-node/ipc/index' + +type Operation = + | { + streamRequest: true + onChunk?: (chunk: Buffer) => Promise + onEnd?: () => Promise + } + | { + streamRequest?: false + onBody?: (body: Buffer) => Promise + } + +type IpcIncomingMessage = + | { + type: 'headers' + data: T + } + | { + type: 'bodyChunk' + data: number[] + } + | { + type: 'bodyText' + data: number[] + } + | { type: 'bodyEnd' } + +type IpcOutgoingMessage = + | { + type: 'headers' + data: R + } + | { + type: 'bodyChunk' + data: number[] + } + | { + type: 'bodyEnd' + } + +interface Response { + chunk: (data: Buffer) => Promise + end: () => Promise +} + +export default function startHandler( + createOperation: ( + data: T, + respond: (data: R) => Response, + reportError: (error: unknown) => Promise + ) => Promise +) { + const ipc = IPC as Ipc, IpcOutgoingMessage> + + ;(async () => { + while (true) { + let operation: Operation | void + + { + const msg = await ipc.recv() + + switch (msg.type) { + case 'headers': { + operation = await createOperation( + msg.data, + (data) => { + ipc.send({ + type: 'headers', + data, + }) + return { + chunk: (buf: Buffer) => { + return ipc.send({ + type: 'bodyChunk', + data: buf.toJSON().data, + }) + }, + end: () => { + return ipc.send({ type: 'bodyEnd' }) + }, + } + }, + (error) => { + return ipc.sendError( + error instanceof Error + ? error + : new Error(`an unknown error occurred: ${error}`) + ) + } + ) + break + } + default: { + console.error('unexpected message type', msg.type) + process.exit(1) + } + } + } + + if (operation) { + if (operation.streamRequest) { + loop: while (true) { + const msg = await ipc.recv() + + switch (msg.type) { + case 'bodyChunk': + case 'bodyText': { + await operation.onChunk?.(Buffer.from(msg.data)) + break + } + case 'bodyEnd': { + await operation.onEnd?.() + break loop + } + default: { + console.error('unexpected message type', msg.type) + process.exit(1) + } + } + } + } else { + let body = Buffer.alloc(0) + loop: while (true) { + const msg = await ipc.recv() + + switch (msg.type) { + case 'bodyChunk': + case 'bodyText': { + body = Buffer.concat([body, Buffer.from(msg.data)]) + break + } + case 'bodyEnd': { + await operation.onBody?.(body) + break loop + } + default: { + console.error('unexpected message type', msg.type) + process.exit(1) + } + } + } + } + } + } + })().catch((err) => { + ipc.sendError(err) + }) +} diff --git a/packages/next-swc/crates/next-core/js/tsconfig.json b/packages/next-swc/crates/next-core/js/tsconfig.json index abf31fb964f8..a10e86411737 100644 --- a/packages/next-swc/crates/next-core/js/tsconfig.json +++ b/packages/next-swc/crates/next-core/js/tsconfig.json @@ -27,8 +27,7 @@ "@vercel/turbopack-next/pages/_app": ["node_modules/next/app"], "@vercel/turbopack-next/pages/_document": ["node_modules/next/document"], "@vercel/turbopack-next/pages/_error": ["node_modules/next/error"], - "@vercel/turbopack-next/internal/_error": ["node_modules/next/error"], - "next/*": ["node_modules/next/*"] + "@vercel/turbopack-next/internal/_error": ["node_modules/next/error"] }, "resolveJsonModule": true, "types": ["react/next"], diff --git a/packages/next-swc/crates/next-core/js/types/compiled-next.d.ts b/packages/next-swc/crates/next-core/js/types/compiled-next.d.ts index 7dc4c97f0788..bc9f894f61ea 100644 --- a/packages/next-swc/crates/next-core/js/types/compiled-next.d.ts +++ b/packages/next-swc/crates/next-core/js/types/compiled-next.d.ts @@ -1,2 +1 @@ declare module 'next/dist/compiled/react-server-dom-webpack/client' -declare module 'next/dist/client/app-call-server' diff --git a/packages/next-swc/crates/next-core/js/types/rust.d.ts b/packages/next-swc/crates/next-core/js/types/rust.d.ts index 70713bedd664..8718b03cf82f 100644 --- a/packages/next-swc/crates/next-core/js/types/rust.d.ts +++ b/packages/next-swc/crates/next-core/js/types/rust.d.ts @@ -80,6 +80,17 @@ declare module 'BOOTSTRAP_CONFIG' { export const KIND: RouteKind } +declare module 'APP_BOOTSTRAP' { + const chunks: Array + export default chunks +} + +declare module 'APP_ENTRY' { + export const tree: any + export const pathname: string + // and more... +} + declare module 'CLIENT_MODULE' { export const __turbopack_module_id__: string } diff --git a/packages/next-swc/crates/next-core/src/app_render/mod.rs b/packages/next-swc/crates/next-core/src/app_render/mod.rs index e1436002e3e0..85695272c205 100644 --- a/packages/next-swc/crates/next-core/src/app_render/mod.rs +++ b/packages/next-swc/crates/next-core/src/app_render/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use turbopack_binding::turbo::tasks_fs::FileSystemPathVc; -pub mod next_layout_entry_transition; +pub mod next_server_component_transition; #[turbo_tasks::value(shared)] pub struct LayoutSegment { diff --git a/packages/next-swc/crates/next-core/src/app_render/next_layout_entry_transition.rs b/packages/next-swc/crates/next-core/src/app_render/next_server_component_transition.rs similarity index 79% rename from packages/next-swc/crates/next-core/src/app_render/next_layout_entry_transition.rs rename to packages/next-swc/crates/next-core/src/app_render/next_server_component_transition.rs index 472b2a9f554f..4b205a93f21e 100644 --- a/packages/next-swc/crates/next-core/src/app_render/next_layout_entry_transition.rs +++ b/packages/next-swc/crates/next-core/src/app_render/next_server_component_transition.rs @@ -13,7 +13,10 @@ use turbopack_binding::{ }, }; -use crate::next_client_component::with_client_chunks::WithClientChunksAsset; +use crate::next_client_component::{ + with_chunking_context_scope_asset::WithChunkingContextScopeAsset, + with_client_chunks::WithClientChunksAsset, +}; #[turbo_tasks::value(shared)] pub struct NextServerComponentTransition { @@ -59,10 +62,15 @@ impl Transition for NextServerComponentTransition { bail!("Not an ecmascript module"); }; - Ok(WithClientChunksAsset { - asset, - // next.js code already adds _next prefix - server_root: self.server_root.join("_next"), + Ok(WithChunkingContextScopeAsset { + asset: WithClientChunksAsset { + asset, + // next.js code already adds _next prefix + server_root: self.server_root.join("_next"), + } + .cell() + .into(), + layer: "rsc".to_string(), } .cell() .into()) diff --git a/packages/next-swc/crates/next-core/src/app_segment_config.rs b/packages/next-swc/crates/next-core/src/app_segment_config.rs index d771881d144a..2078129095ff 100644 --- a/packages/next-swc/crates/next-core/src/app_segment_config.rs +++ b/packages/next-swc/crates/next-core/src/app_segment_config.rs @@ -1,21 +1,24 @@ use std::ops::Deref; -use anyhow::Result; +use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; use serde_json::Value; use swc_core::{ common::{source_map::Pos, Span, Spanned}, ecma::ast::{Expr, Ident, Program}, }; -use turbo_tasks::{primitives::StringVc, trace::TraceRawVcs}; +use turbo_tasks::{primitives::StringVc, trace::TraceRawVcs, TryJoinIterExt}; use turbo_tasks_fs::FileSystemPathVc; use turbopack_binding::turbopack::{ core::{ asset::{Asset, AssetVc}, + context::{AssetContext, AssetContextVc}, ident::AssetIdentVc, issue::{ Issue, IssueSeverity, IssueSeverityVc, IssueSourceVc, IssueVc, OptionIssueSourceVc, }, + reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType}, + source_asset::SourceAssetVc, }, ecmascript::{ analyzer::{graph::EvalContext, JsValue}, @@ -24,7 +27,7 @@ use turbopack_binding::turbopack::{ }, }; -use crate::util::NextRuntime; +use crate::{app_structure::LoaderTreeVc, util::NextRuntime}; #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, TraceRawVcs, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] @@ -50,15 +53,14 @@ pub enum NextSegmentFetchCache { } #[turbo_tasks::value] -#[derive(Debug)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Default)] pub struct NextSegmentConfig { - pub dynamic: NextSegmentDynamic, - pub dynamic_params: bool, - pub revalidate: bool, - pub fetch_cache: NextSegmentFetchCache, - pub runtime: NextRuntime, - pub referred_region: String, + pub dynamic: Option, + pub dynamic_params: Option, + pub revalidate: Option, + pub fetch_cache: Option, + pub runtime: Option, + pub preferred_region: Option, } #[turbo_tasks::value_impl] @@ -69,16 +71,73 @@ impl NextSegmentConfigVc { } } -impl Default for NextSegmentConfig { - fn default() -> Self { - NextSegmentConfig { - dynamic: Default::default(), - dynamic_params: true, - revalidate: false, - fetch_cache: Default::default(), - runtime: Default::default(), - referred_region: "auto".to_string(), +impl NextSegmentConfig { + /// Applies the parent config to this config, setting any unset values to + /// the parent's values. + pub fn apply_parent_config(&mut self, parent: &Self) { + let NextSegmentConfig { + dynamic, + dynamic_params, + revalidate, + fetch_cache, + runtime, + preferred_region, + } = self; + *dynamic = dynamic.or(parent.dynamic); + *dynamic_params = dynamic_params.or(parent.dynamic_params); + *revalidate = revalidate.or(parent.revalidate); + *fetch_cache = fetch_cache.or(parent.fetch_cache); + *runtime = runtime.or(parent.runtime); + *preferred_region = preferred_region.take().or(parent.preferred_region.clone()); + } + + /// Applies a config from a paralllel route to this config, returning an + /// error if there are conflicting values. + pub fn apply_parallel_config(&mut self, parallel_config: &Self) -> Result<()> { + fn merge_parallel( + a: &mut Option, + b: &Option, + name: &str, + ) -> Result<()> { + match (a.as_ref(), b) { + (Some(a), Some(b)) => { + if *a != *b { + bail!( + "Sibling segment configs have conflicting values for {}", + name + ) + } + } + (None, Some(b)) => { + *a = Some(b.clone()); + } + _ => {} + } + Ok(()) } + let Self { + dynamic, + dynamic_params, + revalidate, + fetch_cache, + runtime, + preferred_region, + } = self; + merge_parallel(dynamic, ¶llel_config.dynamic, "dynamic")?; + merge_parallel( + dynamic_params, + ¶llel_config.dynamic_params, + "dynamicParams", + )?; + merge_parallel(revalidate, ¶llel_config.revalidate, "revalidate")?; + merge_parallel(fetch_cache, ¶llel_config.fetch_cache, "fetchCache")?; + merge_parallel(runtime, ¶llel_config.runtime, "runtime")?; + merge_parallel( + preferred_region, + ¶llel_config.preferred_region, + "referredRegion", + )?; + Ok(()) } } @@ -216,7 +275,7 @@ fn parse_config_value( }; config.dynamic = match serde_json::from_value(Value::String(val.to_string())) { - Ok(dynamic) => dynamic, + Ok(dynamic) => Some(dynamic), Err(err) => { return invalid_config( &format!("`dynamic` has an invalid value: {}", err), @@ -231,7 +290,7 @@ fn parse_config_value( return invalid_config("`dynamicParams` needs to be a static boolean", &value); }; - config.dynamic_params = val; + config.dynamic_params = Some(val); } "revalidate" => { let value = eval_context.eval(init); @@ -239,7 +298,7 @@ fn parse_config_value( return invalid_config("`revalidate` needs to be a static boolean", &value); }; - config.revalidate = val; + config.revalidate = Some(val); } "fetchCache" => { let value = eval_context.eval(init); @@ -248,7 +307,7 @@ fn parse_config_value( }; config.fetch_cache = match serde_json::from_value(Value::String(val.to_string())) { - Ok(fetch_cache) => fetch_cache, + Ok(fetch_cache) => Some(fetch_cache), Err(err) => { return invalid_config( &format!("`fetchCache` has an invalid value: {}", err), @@ -264,7 +323,7 @@ fn parse_config_value( }; config.runtime = match serde_json::from_value(Value::String(val.to_string())) { - Ok(runtime) => runtime, + Ok(runtime) => Some(runtime), Err(err) => { return invalid_config( &format!("`runtime` has an invalid value: {}", err), @@ -279,8 +338,43 @@ fn parse_config_value( return invalid_config("`preferredRegion` needs to be a static string", &value); }; - config.referred_region = val.to_string(); + config.preferred_region = Some(val.to_string()); } _ => {} } } + +#[turbo_tasks::function] +pub async fn parse_segment_config_from_loader_tree( + loader_tree: LoaderTreeVc, + context: AssetContextVc, +) -> Result { + let loader_tree = loader_tree.await?; + let components = loader_tree.components.await?; + let mut config = NextSegmentConfig::default(); + let parallel_configs = loader_tree + .parallel_routes + .values() + .copied() + .map(|tree| parse_segment_config_from_loader_tree(tree, context)) + .try_join() + .await?; + for tree in parallel_configs { + config.apply_parallel_config(&tree)?; + } + for component in [components.page, components.default, components.layout] + .into_iter() + .flatten() + { + config.apply_parent_config( + &*parse_segment_config_from_source(context.process( + SourceAssetVc::new(component).into(), + turbo_tasks::Value::new(ReferenceType::EcmaScriptModules( + EcmaScriptModulesReferenceSubType::Undefined, + )), + )) + .await?, + ); + } + Ok(config.cell()) +} diff --git a/packages/next-swc/crates/next-core/src/app_source.rs b/packages/next-swc/crates/next-core/src/app_source.rs index d5a2953d5ff8..5d8f6807322f 100644 --- a/packages/next-swc/crates/next-core/src/app_source.rs +++ b/packages/next-swc/crates/next-core/src/app_source.rs @@ -1,14 +1,15 @@ -use std::{collections::HashMap, io::Write, iter::once}; +use std::{collections::HashMap, io::Write as _, iter::once}; use anyhow::{bail, Result}; use async_recursion::async_recursion; use indexmap::{indexmap, IndexMap}; +use indoc::indoc; use turbo_tasks::{primitives::JsonValueVc, TryJoinIterExt, ValueToString}; use turbopack_binding::{ turbo::{ tasks::{primitives::StringVc, Value}, tasks_env::{CustomProcessEnvVc, EnvMapVc, ProcessEnvVc}, - tasks_fs::{rope::RopeBuilder, File, FileContent, FileSystemPathVc}, + tasks_fs::{rope::RopeBuilder, File, FileSystemPathVc}, }, turbopack::{ core::{ @@ -59,14 +60,14 @@ use turbopack_binding::{ }; use crate::{ - app_render::next_layout_entry_transition::NextServerComponentTransition, - app_segment_config::parse_segment_config_from_source, + app_render::next_server_component_transition::NextServerComponentTransition, + app_segment_config::{parse_segment_config_from_loader_tree, parse_segment_config_from_source}, app_structure::{ get_entrypoints, get_global_metadata, Components, Entrypoint, GlobalMetadataVc, LoaderTree, LoaderTreeVc, Metadata, MetadataItem, MetadataWithAltItem, OptionAppDirVc, }, bootstrap::{route_bootstrap, BootstrapConfigVc}, - embed_js::{next_asset, next_js_file, next_js_file_path}, + embed_js::{next_asset, next_js_file_path}, env::env_for_js, fallback::get_fallback_page, mode::NextMode, @@ -86,7 +87,8 @@ use crate::{ next_config::NextConfigVc, next_edge::{ context::{get_edge_compile_time_info, get_edge_resolve_options_context}, - transition::NextEdgeTransition, + page_transition::NextEdgePageTransition, + route_transition::NextEdgeRouteTransition, }, next_image::module::{BlurPlaceholderMode, StructuredImageModuleType}, next_route_matcher::{NextFallbackMatcherVc, NextParamsMatcherVc}, @@ -200,7 +202,7 @@ fn next_ssr_client_module_transition( } #[turbo_tasks::function] -fn next_layout_entry_transition( +fn next_server_component_transition( project_path: FileSystemPathVc, execution_context: ExecutionContextVc, app_dir: FileSystemPathVc, @@ -227,6 +229,37 @@ fn next_layout_entry_transition( .into() } +#[turbo_tasks::function] +fn next_edge_server_component_transition( + project_path: FileSystemPathVc, + execution_context: ExecutionContextVc, + app_dir: FileSystemPathVc, + server_root: FileSystemPathVc, + next_config: NextConfigVc, + server_addr: ServerAddrVc, +) -> TransitionVc { + let ty = Value::new(ServerContextType::AppRSC { app_dir }); + let mode = NextMode::Development; + let rsc_compile_time_info = get_edge_compile_time_info( + project_path, + server_addr, + Value::new(EnvironmentIntention::ServerRendering), + ); + let rsc_resolve_options_context = + get_edge_resolve_options_context(project_path, ty, next_config, execution_context); + let rsc_module_options_context = + get_server_module_options_context(project_path, execution_context, ty, mode, next_config); + + NextServerComponentTransition { + rsc_compile_time_info, + rsc_module_options_context, + rsc_resolve_options_context, + server_root, + } + .cell() + .into() +} + #[turbo_tasks::function] fn next_edge_route_transition( project_path: FileSystemPathVc, @@ -252,12 +285,12 @@ fn next_edge_route_transition( get_client_assets_path(server_root, Value::new(ClientContextType::App { app_dir })), edge_compile_time_info.environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("app_source")) .build(); let edge_resolve_options_context = get_edge_resolve_options_context(project_path, server_ty, next_config, execution_context); - NextEdgeTransition { + NextEdgeRouteTransition { edge_compile_time_info, edge_chunking_context, edge_module_options_context: None, @@ -271,6 +304,49 @@ fn next_edge_route_transition( .into() } +#[turbo_tasks::function] +fn next_edge_page_transition( + project_path: FileSystemPathVc, + app_dir: FileSystemPathVc, + server_root: FileSystemPathVc, + next_config: NextConfigVc, + server_addr: ServerAddrVc, + output_path: FileSystemPathVc, + execution_context: ExecutionContextVc, +) -> TransitionVc { + let server_ty = Value::new(ServerContextType::AppRoute { app_dir }); + + let edge_compile_time_info = get_edge_compile_time_info( + project_path, + server_addr, + Value::new(EnvironmentIntention::ServerRendering), + ); + + let edge_chunking_context = DevChunkingContextVc::builder( + project_path, + output_path.join("edge-pages"), + output_path.join("edge-pages/chunks"), + get_client_assets_path(server_root, Value::new(ClientContextType::App { app_dir })), + edge_compile_time_info.environment(), + ) + .layer("ssr") + .reference_chunk_source_maps(should_debug("app_source")) + .build(); + let edge_resolve_options_context = + get_edge_resolve_options_context(project_path, server_ty, next_config, execution_context); + + NextEdgePageTransition { + edge_compile_time_info, + edge_chunking_context, + edge_module_options_context: None, + edge_resolve_options_context, + output_path, + bootstrap_asset: next_asset("entry/app/edge-page-bootstrap.ts"), + } + .cell() + .into() +} + #[allow(clippy::too_many_arguments)] #[turbo_tasks::function] fn app_context( @@ -302,8 +378,20 @@ fn app_context( ), ); transitions.insert( - "next-layout-entry".to_string(), - next_layout_entry_transition( + "next-edge-page".to_string(), + next_edge_page_transition( + project_path, + app_dir, + server_root, + next_config, + server_addr, + output_path, + execution_context, + ), + ); + transitions.insert( + "next-server-component".to_string(), + next_server_component_transition( project_path, execution_context, app_dir, @@ -313,6 +401,17 @@ fn app_context( server_addr, ), ); + transitions.insert( + "next-edge-server-component".to_string(), + next_edge_server_component_transition( + project_path, + execution_context, + app_dir, + server_root, + next_config, + server_addr, + ), + ); transitions.insert( "server-to-client".to_string(), next_server_to_client_transition, @@ -716,6 +815,14 @@ impl AppRendererVc { (context_ssr, intermediate_output_path) }; + let config = parse_segment_config_from_loader_tree(loader_tree, context); + + let runtime = config.await?.runtime; + let rsc_transition = match runtime { + Some(NextRuntime::NodeJs) | None => "next-server-component", + Some(NextRuntime::Edge) => "next-edge-server-component", + }; + struct State { inner_assets: IndexMap, counter: usize, @@ -723,6 +830,7 @@ impl AppRendererVc { loader_tree_code: String, context: AssetContextVc, unsupported_metadata: Vec, + rsc_transition: &'static str, } impl State { @@ -740,6 +848,7 @@ impl AppRendererVc { loader_tree_code: String::new(), context, unsupported_metadata: Vec::new(), + rsc_transition, }; fn write_component( @@ -767,7 +876,7 @@ import {}, {{ chunks as {} }} from "COMPONENT_{}"; state.inner_assets.insert( format!("COMPONENT_{i}"), - state.context.with_transition("next-layout-entry").process( + state.context.with_transition(state.rsc_transition).process( SourceAssetVc::new(component).into(), Value::new(ReferenceType::EcmaScriptModules( EcmaScriptModulesReferenceSubType::Undefined, @@ -958,7 +1067,7 @@ import {}, {{ chunks as {} }} from "COMPONENT_{}"; walk_tree(&mut state, loader_tree).await?; let State { - mut inner_assets, + inner_assets, imports, loader_tree_code, unsupported_metadata, @@ -975,41 +1084,29 @@ import {}, {{ chunks as {} }} from "COMPONENT_{}"; .emit(); } - // IPC need to be the first import to allow it to catch errors happening during - // the other imports - let mut result = - RopeBuilder::from("import { IPC } from \"@vercel/turbopack-node/ipc/index\";\n"); - - let import_path = next_js_file_path("entry") - .await? - .get_relative_path_to(&*next_js_file_path("polyfill/app-polyfills.ts").await?) - .unwrap(); - writeln!(result, "import \"{import_path}\";\n",)?; + let mut result = RopeBuilder::from(indoc! {" + \"TURBOPACK { chunking-type: isolatedParallel; transition: next-edge-server-component }\"; + import GlobalErrorMod from \"next/dist/client/components/error-boundary\" + const { GlobalError } = GlobalErrorMod; + \"TURBOPACK { chunking-type: isolatedParallel; transition: next-edge-server-component }\"; + import base from \"next/dist/server/app-render/entry-base\"\n + "}); for import in imports { writeln!(result, "{import}")?; } - writeln!(result, "const LOADER_TREE = {loader_tree_code};\n")?; - writeln!(result, "import BOOTSTRAP from \"BOOTSTRAP\";\n")?; - - inner_assets.insert( - "BOOTSTRAP".to_string(), - context.with_transition("next-client").process( - SourceAssetVc::new(next_js_file_path("entry/app/hydrate.tsx")).into(), - Value::new(ReferenceType::EcmaScriptModules( - EcmaScriptModulesReferenceSubType::Undefined, - )), - ), - ); - - let base_code = next_js_file("entry/app-renderer.tsx"); - if let FileContent::Content(base_file) = &*base_code.await? { - result += base_file.content() - } + writeln!(result, "const tree = {loader_tree_code};\n")?; + writeln!(result, "const pathname = '';\n")?; + writeln!( + result, + // Need this hack because "export *" from CommonJS will trigger a warning + // otherwise + "__turbopack_export_value__({{ tree, GlobalError, pathname, ...base }});\n" + )?; let file = File::from(result.build()); - let asset = VirtualAssetVc::new(next_js_file_path("entry/app-renderer.tsx"), file.into()); + let asset = VirtualAssetVc::new(next_js_file_path("entry/app-entry.tsx"), file.into()); let chunking_context = DevChunkingContextVc::builder( project_path, @@ -1019,15 +1116,38 @@ import {}, {{ chunks as {} }} from "COMPONENT_{}"; context.compile_time_info().environment(), ) .layer("ssr") - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("app_source")) .build(); - let module = context.process( - asset.into(), - Value::new(ReferenceType::Internal(InnerAssetsVc::cell(inner_assets))), - ); + let renderer_module = match runtime { + Some(NextRuntime::NodeJs) | None => context.process( + SourceAssetVc::new(next_js_file_path("entry/app-renderer.tsx")).into(), + Value::new(ReferenceType::Internal(InnerAssetsVc::cell(indexmap! { + "APP_ENTRY".to_string() => context.with_transition(rsc_transition).process( + asset.into(), + Value::new(ReferenceType::Internal(InnerAssetsVc::cell(inner_assets))), + ), + "APP_BOOTSTRAP".to_string() => context.with_transition("next-client").process( + SourceAssetVc::new(next_js_file_path("entry/app/hydrate.tsx")).into(), + Value::new(ReferenceType::EcmaScriptModules( + EcmaScriptModulesReferenceSubType::Undefined, + )), + ), + }))), + ), + Some(NextRuntime::Edge) => + context.process( + SourceAssetVc::new(next_js_file_path("entry/app-edge-renderer.tsx")).into(), + Value::new(ReferenceType::Internal(InnerAssetsVc::cell(indexmap! { + "INNER_EDGE_CHUNK_GROUP".to_string() => context.with_transition("next-edge-page").process( + asset.into(), + Value::new(ReferenceType::Internal(InnerAssetsVc::cell(inner_assets))), + ), + }))), + ) + }; - let Some(module) = EvaluatableAssetVc::resolve_from(module).await? else { + let Some(module) = EvaluatableAssetVc::resolve_from(renderer_module).await? else { bail!("internal module must be evaluatable"); }; @@ -1096,7 +1216,7 @@ impl AppRouteVc { this.context.compile_time_info().environment(), ) .layer("ssr") - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("app_source")) .build(); let entry_source_asset = SourceAssetVc::new(this.entry_path); @@ -1107,7 +1227,7 @@ impl AppRouteVc { let config = parse_segment_config_from_source(entry_asset); let module = match config.await?.runtime { - NextRuntime::NodeJs => { + Some(NextRuntime::NodeJs) | None => { let bootstrap_asset = next_asset("entry/app/route.ts"); route_bootstrap( @@ -1118,7 +1238,7 @@ impl AppRouteVc { BootstrapConfigVc::empty(), ) } - NextRuntime::Edge => { + Some(NextRuntime::Edge) => { let internal_asset = next_asset("entry/app/edge-route.ts"); let entry = this.context.with_transition("next-edge-route").process( diff --git a/packages/next-swc/crates/next-core/src/bootstrap.rs b/packages/next-swc/crates/next-core/src/bootstrap.rs index 9dc833f153d6..93533b54f788 100644 --- a/packages/next-swc/crates/next-core/src/bootstrap.rs +++ b/packages/next-swc/crates/next-core/src/bootstrap.rs @@ -1,6 +1,6 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use indexmap::{indexmap, IndexMap}; -use turbo_tasks::Value; +use turbo_tasks::{Value, ValueToString}; use turbo_tasks_fs::{File, FileSystemPathVc}; use turbopack_binding::turbopack::{ core::{ @@ -80,10 +80,13 @@ pub async fn bootstrap( config: BootstrapConfigVc, ) -> Result { let path = asset.ident().path().await?; - let path = base_path - .await? - .get_path_to(&path) - .context("asset is not in base_path")?; + let Some(path) = base_path.await?.get_path_to(&path) else { + bail!( + "asset {} is not in base path {}", + asset.ident().to_string().await?, + base_path.to_string().await? + ); + }; let path = if let Some((name, ext)) = path.rsplit_once('.') { if !ext.contains('/') { name diff --git a/packages/next-swc/crates/next-core/src/next_edge/mod.rs b/packages/next-swc/crates/next-core/src/next_edge/mod.rs index 05cf987f8b50..1ee55be5bcbc 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/mod.rs @@ -1,2 +1,3 @@ pub mod context; -pub mod transition; +pub mod page_transition; +pub mod route_transition; diff --git a/packages/next-swc/crates/next-core/src/next_edge/page_transition.rs b/packages/next-swc/crates/next-core/src/next_edge/page_transition.rs new file mode 100644 index 000000000000..277a55de4936 --- /dev/null +++ b/packages/next-swc/crates/next-core/src/next_edge/page_transition.rs @@ -0,0 +1,100 @@ +use anyhow::{bail, Result}; +use indexmap::indexmap; +use turbo_tasks::Value; +use turbopack_binding::{ + turbo::tasks_fs::FileSystemPathVc, + turbopack::{ + core::{ + asset::AssetVc, + chunk::{ChunkableAssetVc, ChunkingContextVc}, + compile_time_info::CompileTimeInfoVc, + context::AssetContext, + reference_type::{EcmaScriptModulesReferenceSubType, InnerAssetsVc, ReferenceType}, + source_asset::SourceAssetVc, + }, + ecmascript::chunk_group_files_asset::ChunkGroupFilesAsset, + turbopack::{ + module_options::ModuleOptionsContextVc, + resolve_options_context::ResolveOptionsContextVc, + transition::{Transition, TransitionVc}, + ModuleAssetContextVc, + }, + }, +}; + +use crate::embed_js::next_js_file_path; + +/// Transition into edge environment to render an app directory page. +/// +/// It changes the environment to the provided edge environment, and wraps the +/// process asset with the provided bootstrap_asset returning the chunks of all +/// that for running them in the edge sandbox. +#[turbo_tasks::value(shared)] +pub struct NextEdgePageTransition { + pub edge_compile_time_info: CompileTimeInfoVc, + pub edge_chunking_context: ChunkingContextVc, + pub edge_module_options_context: Option, + pub edge_resolve_options_context: ResolveOptionsContextVc, + pub output_path: FileSystemPathVc, + pub bootstrap_asset: AssetVc, +} + +#[turbo_tasks::value_impl] +impl Transition for NextEdgePageTransition { + #[turbo_tasks::function] + fn process_compile_time_info( + &self, + _compile_time_info: CompileTimeInfoVc, + ) -> CompileTimeInfoVc { + self.edge_compile_time_info + } + + #[turbo_tasks::function] + fn process_module_options_context( + &self, + context: ModuleOptionsContextVc, + ) -> ModuleOptionsContextVc { + self.edge_module_options_context.unwrap_or(context) + } + + #[turbo_tasks::function] + fn process_resolve_options_context( + &self, + _context: ResolveOptionsContextVc, + ) -> ResolveOptionsContextVc { + self.edge_resolve_options_context + } + + #[turbo_tasks::function] + async fn process_module( + &self, + asset: AssetVc, + context: ModuleAssetContextVc, + ) -> Result { + let asset = context.process( + self.bootstrap_asset, + Value::new(ReferenceType::Internal(InnerAssetsVc::cell(indexmap! { + "APP_ENTRY".to_string() => asset, + "APP_BOOTSTRAP".to_string() => context.with_transition("next-client").process( + SourceAssetVc::new(next_js_file_path("entry/app/hydrate.tsx")).into(), + Value::new(ReferenceType::EcmaScriptModules( + EcmaScriptModulesReferenceSubType::Undefined, + )), + ), + }))), + ); + + let Some(asset) = ChunkableAssetVc::resolve_from(asset).await? else { + bail!("Internal module is not evaluatable"); + }; + + let asset = ChunkGroupFilesAsset { + asset, + client_root: self.output_path, + chunking_context: self.edge_chunking_context, + runtime_entries: None, + }; + + Ok(asset.cell().into()) + } +} diff --git a/packages/next-swc/crates/next-core/src/next_edge/transition.rs b/packages/next-swc/crates/next-core/src/next_edge/route_transition.rs similarity index 96% rename from packages/next-swc/crates/next-core/src/next_edge/transition.rs rename to packages/next-swc/crates/next-core/src/next_edge/route_transition.rs index e64598522e1b..39f0a2672bea 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/transition.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/route_transition.rs @@ -17,7 +17,7 @@ use turbopack_binding::{ use crate::bootstrap::{route_bootstrap, BootstrapConfigVc}; #[turbo_tasks::value(shared)] -pub struct NextEdgeTransition { +pub struct NextEdgeRouteTransition { pub edge_compile_time_info: CompileTimeInfoVc, pub edge_chunking_context: ChunkingContextVc, pub edge_module_options_context: Option, @@ -29,7 +29,7 @@ pub struct NextEdgeTransition { } #[turbo_tasks::value_impl] -impl Transition for NextEdgeTransition { +impl Transition for NextEdgeRouteTransition { #[turbo_tasks::function] fn process_compile_time_info( &self, diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 28fb315ecd46..4fdfffd45fcd 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -209,12 +209,6 @@ pub async fn get_next_server_import_map( ) .await?; - import_map.insert_exact_alias( - "@opentelemetry/api", - // TODO(WEB-625) this actually need to prefer the local version of @opentelemetry/api - ImportMapping::External(Some("next/dist/compiled/@opentelemetry/api".to_string())).into(), - ); - let ty = ty.into_value(); insert_next_server_special_aliases(&mut import_map, ty).await?; @@ -357,6 +351,12 @@ pub async fn insert_next_server_special_aliases( ) -> Result<()> { match ty { ServerContextType::Pages { pages_dir } => { + import_map.insert_exact_alias( + "@opentelemetry/api", + // TODO(WEB-625) this actually need to prefer the local version of + // @opentelemetry/api + external_request_to_import_mapping("next/dist/compiled/@opentelemetry/api"), + ); insert_alias_to_alternatives( import_map, format!("{VIRTUAL_PACKAGE_NAME}/pages/_app"), @@ -386,6 +386,12 @@ pub async fn insert_next_server_special_aliases( ServerContextType::AppSSR { app_dir } | ServerContextType::AppRSC { app_dir } | ServerContextType::AppRoute { app_dir } => { + import_map.insert_exact_alias( + "@opentelemetry/api", + // TODO(WEB-625) this actually need to prefer the local version of + // @opentelemetry/api + request_to_import_mapping(app_dir, "next/dist/compiled/@opentelemetry/api"), + ); import_map.insert_exact_alias( "react", request_to_import_mapping(app_dir, "next/dist/compiled/react"), diff --git a/packages/next-swc/crates/next-core/src/page_source.rs b/packages/next-swc/crates/next-core/src/page_source.rs index 7ac9e53d2c82..95329e2400e7 100644 --- a/packages/next-swc/crates/next-core/src/page_source.rs +++ b/packages/next-swc/crates/next-core/src/page_source.rs @@ -62,7 +62,7 @@ use crate::{ next_config::NextConfigVc, next_edge::{ context::{get_edge_compile_time_info, get_edge_resolve_options_context}, - transition::NextEdgeTransition, + route_transition::NextEdgeRouteTransition, }, next_route_matcher::{ NextExactMatcherVc, NextFallbackMatcherVc, NextParamsMatcherVc, @@ -168,12 +168,12 @@ pub async fn create_page_source( ), edge_compile_time_info.environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("page_source")) .build(); let edge_resolve_options_context = get_edge_resolve_options_context(project_root, server_ty, next_config, execution_context); - let next_edge_transition = NextEdgeTransition { + let next_edge_transition = NextEdgeRouteTransition { edge_compile_time_info, edge_chunking_context, edge_module_options_context: None, @@ -374,7 +374,7 @@ async fn create_page_source_for_file( ), server_context.compile_time_info().environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("page_source")) .build(); let data_node_path = node_path.join("data"); @@ -389,7 +389,7 @@ async fn create_page_source_for_file( ), server_context.compile_time_info().environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("page_source")) .build(); let client_chunking_context = get_client_chunking_context( @@ -534,7 +534,7 @@ async fn create_not_found_page_source( ), server_context.compile_time_info().environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("page_source")) .build(); let client_chunking_context = get_client_chunking_context( diff --git a/packages/next-swc/crates/next-core/src/router.rs b/packages/next-swc/crates/next-core/src/router.rs index a5a58ee23d23..fc56edbb3639 100644 --- a/packages/next-swc/crates/next-core/src/router.rs +++ b/packages/next-swc/crates/next-core/src/router.rs @@ -47,7 +47,7 @@ use crate::{ next_config::NextConfigVc, next_edge::{ context::{get_edge_compile_time_info, get_edge_resolve_options_context}, - transition::NextEdgeTransition, + route_transition::NextEdgeRouteTransition, }, next_import_map::get_next_build_import_map, next_server::context::{get_server_module_options_context, ServerContextType}, @@ -250,7 +250,7 @@ fn edge_transition_map( output_path.join("edge/assets"), edge_compile_time_info.environment(), ) - .reference_chunk_source_maps(false) + .reference_chunk_source_maps(should_debug("router")) .build(); let edge_resolve_options_context = get_edge_resolve_options_context( @@ -268,7 +268,7 @@ fn edge_transition_map( next_config, ); - let next_edge_transition = NextEdgeTransition { + let next_edge_transition = NextEdgeRouteTransition { edge_compile_time_info, edge_chunking_context, edge_module_options_context: Some(server_module_options_context), diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration.rs b/packages/next-swc/crates/next-dev-tests/tests/integration.rs index b26150aa2db1..e2368f4c278e 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration.rs +++ b/packages/next-swc/crates/next-dev-tests/tests/integration.rs @@ -92,6 +92,8 @@ lazy_static! { static ref DEBUG_BROWSER: bool = env::var("TURBOPACK_DEBUG_BROWSER").is_ok(); /// Only starts the dev server on port 3000, but doesn't spawn a browser or run any tests. static ref DEBUG_START: bool = env::var("TURBOPACK_DEBUG_START").is_ok(); + /// When using TURBOPACK_DEBUG_START, this will open the browser to the dev server. + static ref DEBUG_OPEN: bool = env::var("TURBOPACK_DEBUG_OPEN").is_ok(); } fn run_async_test<'a, T>(future: impl Future + Send + 'a) -> T { @@ -137,19 +139,12 @@ fn test(resource: PathBuf) { run_result, } = run_async_test(run_test(resource)); - if !uncaught_exceptions.is_empty() { - panic!( - "Uncaught exception(s) in test:\n{}", - uncaught_exceptions.join("\n") - ) - } + let mut messages = vec![]; - assert!( - !run_result.test_results.is_empty(), - "Expected one or more tests to run." - ); + if run_result.test_results.is_empty() { + messages.push("No tests were run.".to_string()); + } - let mut messages = vec![]; for test_result in run_result.test_results { // It's possible to fail multiple tests across these tests, // so collect them and fail the respective test in Rust with @@ -163,6 +158,10 @@ fn test(resource: PathBuf) { } } + for uncaught_exception in uncaught_exceptions { + messages.push(format!("Uncaught exception: {}", uncaught_exception)); + } + if !messages.is_empty() { panic!( "Failed with error(s) in the following test(s):\n\n{}", @@ -257,7 +256,11 @@ async fn run_test(resource: PathBuf) -> JsResult { let test_dir = resource_temp.to_path_buf(); let workspace_root = cargo_workspace_root.parent().unwrap().parent().unwrap(); let project_dir = test_dir.join("input"); - let requested_addr = get_free_local_addr().unwrap(); + let requested_addr = if *DEBUG_START { + "127.0.0.1:3000".parse().unwrap() + } else { + get_free_local_addr().unwrap() + }; let mock_dir = resource_temp.join("__httpmock__"); let mock_server_future = get_mock_server_future(&mock_dir); @@ -265,89 +268,95 @@ async fn run_test(resource: PathBuf) -> JsResult { let (issue_tx, mut issue_rx) = unbounded_channel(); let issue_tx = TransientInstance::new(issue_tx); - let tt = TurboTasks::new(MemoryBackend::default()); - let server = NextDevServerBuilder::new( - tt.clone(), - project_dir.to_string_lossy().to_string(), - workspace_root.to_string_lossy().to_string(), - ) - .entry_request(EntryRequest::Module( - "@turbo/pack-test-harness".to_string(), - "/harness".to_string(), - )) - .entry_request(EntryRequest::Relative("index.js".to_owned())) - .eager_compile(false) - .hostname(requested_addr.ip()) - .port(requested_addr.port()) - .log_level(turbopack_binding::turbopack::core::issue::IssueSeverity::Warning) - .log_detail(true) - .issue_reporter(Box::new(move || { - TestIssueReporterVc::new(issue_tx.clone()).into() - })) - .show_all(true) - .build() - .await - .unwrap(); + let result; - let local_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), server.addr.port()); + { + let tt = TurboTasks::new(MemoryBackend::default()); + let server = NextDevServerBuilder::new( + tt.clone(), + project_dir.to_string_lossy().to_string(), + workspace_root.to_string_lossy().to_string(), + ) + .entry_request(EntryRequest::Module( + "@turbo/pack-test-harness".to_string(), + "/harness".to_string(), + )) + .entry_request(EntryRequest::Relative("index.js".to_owned())) + .eager_compile(false) + .hostname(requested_addr.ip()) + .port(requested_addr.port()) + .log_level(turbopack_binding::turbopack::core::issue::IssueSeverity::Warning) + .log_detail(true) + .issue_reporter(Box::new(move || { + TestIssueReporterVc::new(issue_tx.clone()).into() + })) + .show_all(true) + .build() + .await + .unwrap(); - println!( - "{event_type} - server started at http://{address}", - event_type = "ready".green(), - address = server.addr - ); + let local_addr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), server.addr.port()); - if *DEBUG_START { - webbrowser::open(&local_addr.to_string()).unwrap(); - tokio::select! { - _ = mock_server_future => {}, - _ = pending() => {}, - _ = server.future => {}, - }; - panic!("Never resolves") - } + println!( + "{event_type} - server started at http://{address}", + event_type = "ready".green(), + address = server.addr + ); - let result = tokio::select! { - // Poll the mock_server first to add the env var - _ = mock_server_future => panic!("Never resolves"), - r = run_browser(local_addr, &project_dir) => r.expect("error while running browser"), - _ = server.future => panic!("Never resolves"), - }; + if *DEBUG_START { + if *DEBUG_OPEN { + webbrowser::open(&format!("http://{}", local_addr)).unwrap(); + } + tokio::select! { + _ = mock_server_future => {}, + _ = pending() => {}, + _ = server.future => {}, + }; + panic!("Never resolves") + } + + result = tokio::select! { + // Poll the mock_server first to add the env var + _ = mock_server_future => panic!("Never resolves"), + r = run_browser(local_addr, &project_dir) => r.expect("error while running browser"), + _ = server.future => panic!("Never resolves"), + }; - env::remove_var("TURBOPACK_TEST_ONLY_MOCK_SERVER"); + env::remove_var("TURBOPACK_TEST_ONLY_MOCK_SERVER"); - let task = tt.spawn_once_task(async move { - let issues_fs = DiskFileSystemVc::new( - "issues".to_string(), - resource.join("issues").to_string_lossy().to_string(), - ) - .as_file_system(); + let task = tt.spawn_once_task(async move { + let issues_fs = DiskFileSystemVc::new( + "issues".to_string(), + resource.join("issues").to_string_lossy().to_string(), + ) + .as_file_system(); - let mut issues = vec![]; - while let Ok(issue) = issue_rx.try_recv() { - issues.push(issue); - } + let mut issues = vec![]; + while let Ok(issue) = issue_rx.try_recv() { + issues.push(issue); + } - snapshot_issues( - issues.iter().cloned(), - issues_fs.root(), - &cargo_workspace_root.to_string_lossy(), - ) - .await?; + snapshot_issues( + issues.iter().cloned(), + issues_fs.root(), + &cargo_workspace_root.to_string_lossy(), + ) + .await?; - Ok(NothingVc::new().into()) - }); - tt.wait_task_completion(task, true).await.unwrap(); + Ok(NothingVc::new().into()) + }); + tt.wait_task_completion(task, true).await.unwrap(); + } - // This sometimes fails for the following test: - // test_tests__integration__next__webpack_loaders__no_options__input - retry( + if let Err(err) = retry( (), |()| std::fs::remove_dir_all(&resource_temp), 3, Duration::from_millis(100), - ) - .expect("failed to remove temporary directory"); + ) { + eprintln!("Failed to remove temporary directory: {}", err); + } result } diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/input/app/test.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/input/app/test.js index 8c388ccc8f51..85fd7d706124 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/input/app/test.js +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/input/app/test.js @@ -10,7 +10,11 @@ async function getJson(url) { const res = await fetch(url) const text = await res.text() const jsonText = /(\{[^}]*\})/.exec(text) - return JSON.parse(jsonText[0].replace(/"/g, '"')) + try { + return JSON.parse(jsonText[0].replace(/"/g, '"')) + } catch (err) { + throw new Error(`Expected JSON but got:\n${text}`) + } } function runTests() { @@ -63,17 +67,10 @@ function runTests() { it('app with edge runtime should import edge conditions', async () => { const json = await getJson('/app-edge') - // TODO We don't currently support edge config in app rendering. - // When we do, this needs to be updated. - expect(json).not.toMatchObject({ + expect(json).toMatchObject({ edgeThenNode: 'edge', nodeThenEdge: 'edge', }) - // TODO: delete this. - expect(json).toMatchObject({ - edgeThenNode: 'node', - nodeThenEdge: 'node', - }) }) it('app route with nodejs runtime should import node conditions', async () => { diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-b2593b.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-b2593b.txt new file mode 100644 index 000000000000..9ca2af924a62 --- /dev/null +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-b2593b.txt @@ -0,0 +1,35 @@ +PlainIssue { + severity: Error, + context: "[project]/packages/next/dist/compiled/nanoid/index.cjs", + category: "resolve", + title: "Error resolving commonjs request", + description: "unable to resolve module \"crypto\"", + detail: "It was not possible to find the requested file.\nParsed request as written in source code: module \"crypto\"\nPath where resolving has started: [project]/packages/next/dist/compiled/nanoid/index.cjs\nType of request: commonjs request\nImport map: No import map entry\n", + documentation_link: "", + source: Some( + PlainIssueSource { + asset: PlainAsset { + ident: "[project]/packages/next/dist/compiled/nanoid/index.cjs", + }, + start: SourcePos { + line: 0, + column: 45, + }, + end: SourcePos { + line: 0, + column: 62, + }, + }, + ), + sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: Some( + "[project]/packages/next-swc/crates/next-dev-tests/tests/temp/next/import/conditions/input/app", + ), + description: "Next.js App Page Route /app-edge", + }, + ], + ), +} \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-dd84e7.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-dd84e7.txt new file mode 100644 index 000000000000..b0da1fb03259 --- /dev/null +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/import/conditions/issues/Error resolving commonjs request-dd84e7.txt @@ -0,0 +1,41 @@ +PlainIssue { + severity: Error, + context: "[project]/packages/next/dist/compiled/nanoid/index.cjs", + category: "resolve", + title: "Error resolving commonjs request", + description: "unable to resolve module \"crypto\"", + detail: "It was not possible to find the requested file.\nParsed request as written in source code: module \"crypto\"\nPath where resolving has started: [project]/packages/next/dist/compiled/nanoid/index.cjs\nType of request: commonjs request\nImport map: No import map entry\n", + documentation_link: "", + source: Some( + PlainIssueSource { + asset: PlainAsset { + ident: "[project]/packages/next/dist/compiled/nanoid/index.cjs", + }, + start: SourcePos { + line: 0, + column: 45, + }, + end: SourcePos { + line: 0, + column: 62, + }, + }, + ), + sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: Some( + "[project]/packages/next-swc/crates/next-dev-tests/tests/temp/next/import/conditions/input/app", + ), + description: "Next.js App Page Route /app-edge", + }, + PlainIssueProcessingPathItem { + context: Some( + "[next]/entry/app-edge-renderer.tsx", + ), + description: "server-side rendering /app-edge", + }, + ], + ), +} \ No newline at end of file diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index 65b1e2f7dee7..b3c7ae8293db 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -623,33 +623,22 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { } } + // Prefer to modify next/src/server/app-render/entry-base.ts since this is shared with Turbopack. + // Any changes to this code should be reflected in Turbopack's app_source.rs and/or app-renderer.tsx as well. const result = ` export ${treeCodeResult.treeCode} export ${treeCodeResult.pages} - - export { default as AppRouter } from 'next/dist/client/components/app-router' - export { default as LayoutRouter } from 'next/dist/client/components/layout-router' - export { default as RenderFromTemplateContext } from 'next/dist/client/components/render-from-template-context' export { default as GlobalError } from ${JSON.stringify( treeCodeResult.globalError || 'next/dist/client/components/error-boundary' )} + export const originalPathname = ${JSON.stringify(page)} + export const __next_app__ = { + require: __webpack_require__, + // all modules are in the entry chunk, so we never actually need to load chunks in webpack + loadChunk: () => Promise.resolve() + } - export { staticGenerationAsyncStorage } from 'next/dist/client/components/static-generation-async-storage' - - export { requestAsyncStorage } from 'next/dist/client/components/request-async-storage' - export { actionAsyncStorage } from 'next/dist/client/components/action-async-storage' - - export { staticGenerationBailout } from 'next/dist/client/components/static-generation-bailout' - export { default as StaticGenerationSearchParamsBailoutProvider } from 'next/dist/client/components/static-generation-searchparams-bailout-provider' - export { createSearchParamsBailoutProxy } from 'next/dist/client/components/searchparams-bailout-proxy' - - export * as serverHooks from 'next/dist/client/components/hooks-server-context' - - export { renderToReadableStream, decodeReply, decodeAction } from 'react-server-dom-webpack/server.edge' - export const __next_app_webpack_require__ = __webpack_require__ - export { preloadStyle, preloadFont, preconnect } from 'next/dist/server/app-render/rsc/preloads' - - export const originalPathname = "${page}" + export * from 'next/dist/server/app-render/entry-base' ` return result diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index 93932b24bf5d..3ca84f962865 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -98,7 +98,7 @@ export function getRender({ getServerSideProps: pageMod.getServerSideProps, getStaticPaths: pageMod.getStaticPaths, ComponentMod: pageMod, - isAppPath: !!pageMod.__next_app_webpack_require__, + isAppPath: !!pageMod.__next_app__, pathname, } } diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index dfa43ba60095..74001c964bba 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -400,7 +400,7 @@ export async function handleAction({ process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node' ][actionId].workers[workerName] const actionHandler = - ComponentMod.__next_app_webpack_require__(actionModId)[actionId] + ComponentMod.__next_app__.require(actionModId)[actionId] const returnVal = await actionHandler.apply(null, bound) diff --git a/packages/next/src/server/app-render/create-server-components-renderer.tsx b/packages/next/src/server/app-render/create-server-components-renderer.tsx index 6275f7cd7610..0d11231ed110 100644 --- a/packages/next/src/server/app-render/create-server-components-renderer.tsx +++ b/packages/next/src/server/app-render/create-server-components-renderer.tsx @@ -13,7 +13,10 @@ export function createServerComponentRenderer( ComponentToRender: (props: Props) => any, ComponentMod: { renderToReadableStream: any - __next_app_webpack_require__?: any + __next_app__?: { + require: any + loadChunk: any + } }, { transformStream, @@ -31,14 +34,14 @@ export function createServerComponentRenderer( serverComponentsErrorHandler: ReturnType, nonce?: string ): (props: Props) => JSX.Element { - // We need to expose the `__webpack_require__` API globally for + // We need to expose the bundled `require` API globally for // react-server-dom-webpack. This is a hack until we find a better way. - if (ComponentMod.__next_app_webpack_require__) { + if (ComponentMod.__next_app__) { // @ts-ignore - globalThis.__next_require__ = ComponentMod.__next_app_webpack_require__ + globalThis.__next_require__ = ComponentMod.__next_app__.require // @ts-ignore - globalThis.__next_chunk_load__ = () => Promise.resolve() + globalThis.__next_chunk_load__ = ComponentMod.__next_app__.loadChunk } let RSCStream: ReadableStream diff --git a/packages/next/src/server/app-render/entry-base.ts b/packages/next/src/server/app-render/entry-base.ts new file mode 100644 index 000000000000..c35c8cb20279 --- /dev/null +++ b/packages/next/src/server/app-render/entry-base.ts @@ -0,0 +1,52 @@ +const { default: AppRouter } = + require('next/dist/client/components/app-router') as typeof import('../../client/components/app-router') +const { default: LayoutRouter } = + require('next/dist/client/components/layout-router') as typeof import('../../client/components/layout-router') +const { default: RenderFromTemplateContext } = + require('next/dist/client/components/render-from-template-context') as typeof import('../../client/components/render-from-template-context') + +const { staticGenerationAsyncStorage } = + require('next/dist/client/components/static-generation-async-storage') as typeof import('../../client/components/static-generation-async-storage') + +const { requestAsyncStorage } = + require('next/dist/client/components/request-async-storage') as typeof import('../../client/components/request-async-storage') +const { actionAsyncStorage } = + require('next/dist/client/components/action-async-storage') as typeof import('../../client/components/action-async-storage') + +const { staticGenerationBailout } = + require('next/dist/client/components/static-generation-bailout') as typeof import('../../client/components/static-generation-bailout') +const { default: StaticGenerationSearchParamsBailoutProvider } = + require('next/dist/client/components/static-generation-searchparams-bailout-provider') as typeof import('../../client/components/static-generation-searchparams-bailout-provider') +const { createSearchParamsBailoutProxy } = + require('next/dist/client/components/searchparams-bailout-proxy') as typeof import('../../client/components/searchparams-bailout-proxy') + +const serverHooks = + require('next/dist/client/components/hooks-server-context') as typeof import('../../client/components/hooks-server-context') + +const { + renderToReadableStream, + decodeReply, + decodeAction, + // eslint-disable-next-line import/no-extraneous-dependencies +} = require('react-server-dom-webpack/server.edge') +const { preloadStyle, preloadFont, preconnect } = + require('next/dist/server/app-render/rsc/preloads') as typeof import('../../server/app-render/rsc/preloads') + +export { + AppRouter, + LayoutRouter, + RenderFromTemplateContext, + staticGenerationAsyncStorage, + requestAsyncStorage, + actionAsyncStorage, + staticGenerationBailout, + createSearchParamsBailoutProxy, + serverHooks, + renderToReadableStream, + decodeReply, + decodeAction, + preloadStyle, + preloadFont, + preconnect, + StaticGenerationSearchParamsBailoutProvider, +} diff --git a/packages/next/src/server/render-result.ts b/packages/next/src/server/render-result.ts index 2d84e9fec292..6975732502bc 100644 --- a/packages/next/src/server/render-result.ts +++ b/packages/next/src/server/render-result.ts @@ -1,6 +1,3 @@ -import type { ServerResponse } from 'http' -import { Writable } from 'stream' - type ContentTypeOption = string | undefined export type RenderResultMetadata = { @@ -14,6 +11,13 @@ export type RenderResultMetadata = { type RenderResultResponse = string | ReadableStream | null +export interface PipeTarget { + write: (chunk: Uint8Array) => unknown + end: () => unknown + flush?: () => unknown + destroy: (err?: Error) => unknown +} + export default class RenderResult { /** * The detected content type for the response. This is used to set the @@ -91,7 +95,7 @@ export default class RenderResult { return this.response } - public async pipe(res: ServerResponse | Writable): Promise { + public async pipe(res: PipeTarget): Promise { if (this.response === null) { throw new Error('Invariant: response is null. This is a bug in Next.js') } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 884fbc203cf6..68ce895b7fc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -984,8 +984,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-230615.1 - '@vercel/turbopack-node': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230615.1 + '@vercel/turbopack-ecmascript-runtime': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2 + '@vercel/turbopack-node': https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230621.2 anser: ^2.1.1 css.escape: ^1.5.1 find-up: ^6.3.0 @@ -997,8 +997,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-230615.1_react-refresh@0.12.0' - '@vercel/turbopack-node': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230615.1' + '@vercel/turbopack-ecmascript-runtime': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2_react-refresh@0.12.0' + '@vercel/turbopack-node': '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230621.2' anser: 2.1.1 css.escape: 1.5.1 next: link:../../../../next @@ -6116,7 +6116,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 @@ -6790,7 +6790,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@swc/core-darwin-x64/1.3.55: @@ -6799,7 +6798,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm-gnueabihf/1.3.55: @@ -6808,7 +6806,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm64-gnu/1.3.55: @@ -6817,7 +6814,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm64-musl/1.3.55: @@ -6826,7 +6822,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-x64-gnu/1.3.55: @@ -6835,7 +6830,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-x64-musl/1.3.55: @@ -6844,7 +6838,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-win32-arm64-msvc/1.3.55: @@ -6853,7 +6846,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core-win32-ia32-msvc/1.3.55: @@ -6862,7 +6854,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core-win32-x64-msvc/1.3.55: @@ -6871,7 +6862,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core/1.3.55_@swc+helpers@0.5.1: @@ -6896,7 +6886,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==} @@ -23799,7 +23788,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==} @@ -25157,7 +25145,6 @@ packages: - '@swc/core' - esbuild - uglify-js - dev: true /websocket-driver/0.7.3: resolution: {integrity: sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==} @@ -25566,9 +25553,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-230615.1_react-refresh@0.12.0': - resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230615.1} - id: '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230615.1' + '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2_react-refresh@0.12.0': + resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2} + id: '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-230621.2' name: '@vercel/turbopack-ecmascript-runtime' version: 0.0.0 dependencies: @@ -25579,8 +25566,8 @@ packages: - webpack dev: false - '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230615.1': - resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230615.1} + '@gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230621.2': + resolution: {tarball: https://gitpkg.vercel.app/vercel/turbo/crates/turbopack-node/js?turbopack-230621.2} name: '@vercel/turbopack-node' version: 0.0.0 dependencies: