vinext 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/dist/build/client-build-config.d.ts +10 -0
- package/dist/build/client-build-config.js +18 -2
- package/dist/build/pages-client-assets-module.d.ts +8 -0
- package/dist/build/pages-client-assets-module.js +21 -1
- package/dist/build/prerender-paths.js +5 -3
- package/dist/build/run-prerender.js +1 -1
- package/dist/build/ssr-manifest.d.ts +2 -0
- package/dist/build/ssr-manifest.js +6 -2
- package/dist/check.js +7 -6
- package/dist/cli.js +2 -1
- package/dist/client/client-redirect-matcher.d.ts +14 -0
- package/dist/client/client-redirect-matcher.js +37 -0
- package/dist/client/client-redirects.d.ts +27 -0
- package/dist/client/client-redirects.js +28 -0
- package/dist/client/client-rewrites.d.ts +2 -1
- package/dist/client/client-rewrites.js +1 -1
- package/dist/config/config-matchers.d.ts +26 -11
- package/dist/config/config-matchers.js +78 -81
- package/dist/config/destination-params.d.ts +47 -0
- package/dist/config/destination-params.js +116 -0
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +3 -0
- package/dist/entries/app-rsc-entry.d.ts +4 -0
- package/dist/entries/app-rsc-entry.js +20 -6
- package/dist/entries/pages-client-entry.js +3 -1
- package/dist/entries/pages-server-entry.js +13 -37
- package/dist/index.d.ts +3 -1
- package/dist/index.js +134 -43
- package/dist/init-cloudflare.d.ts +9 -0
- package/dist/init-cloudflare.js +107 -31
- package/dist/init-platform.d.ts +4 -0
- package/dist/init-platform.js +27 -2
- package/dist/init.d.ts +1 -0
- package/dist/init.js +67 -3
- package/dist/plugins/ast-scope.d.ts +7 -0
- package/dist/plugins/ast-scope.js +59 -3
- package/dist/plugins/cache-flight-codec.d.ts +1 -1
- package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
- package/dist/plugins/dynamic-preload-metadata.js +9 -8
- package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
- package/dist/plugins/ignore-dynamic-requests.js +3 -4
- package/dist/plugins/middleware-server-only.d.ts +5 -4
- package/dist/plugins/middleware-server-only.js +8 -5
- package/dist/plugins/pages-node-externals.js +1 -1
- package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
- package/dist/plugins/rsc-scan-glob-imports.js +50 -0
- package/dist/plugins/strip-server-exports.js +262 -98
- package/dist/plugins/transform-result.d.ts +9 -5
- package/dist/plugins/transitive-externals.js +1 -1
- package/dist/plugins/typeof-window.d.ts +2 -2
- package/dist/plugins/use-cache-callable.js +8 -2
- package/dist/plugins/worker-server-only-imports.d.ts +20 -0
- package/dist/plugins/worker-server-only-imports.js +31 -0
- package/dist/plugins/worker-use-server.d.ts +25 -0
- package/dist/plugins/worker-use-server.js +129 -0
- package/dist/server/api-handler.d.ts +1 -0
- package/dist/server/api-handler.js +2 -1
- package/dist/server/app-browser-action-result.d.ts +1 -1
- package/dist/server/app-browser-action-result.js +9 -2
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-server-action-client.js +1 -1
- package/dist/server/app-middleware.d.ts +11 -0
- package/dist/server/app-middleware.js +17 -2
- package/dist/server/app-page-cache.d.ts +1 -1
- package/dist/server/app-page-cache.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +3 -1
- package/dist/server/app-page-dispatch.js +4 -3
- package/dist/server/app-page-ppr-runtime.js +1 -1
- package/dist/server/app-page-render-observation.js +1 -1
- package/dist/server/app-page-request.js +53 -15
- package/dist/server/app-page-route-wiring.js +2 -2
- package/dist/server/app-pages-bridge.d.ts +1 -1
- package/dist/server/app-pages-bridge.js +2 -3
- package/dist/server/app-prerender-endpoints.js +9 -6
- package/dist/server/app-request-stage-dispatch.js +6 -2
- package/dist/server/app-request-stage-independent-entry.js +25 -5
- package/dist/server/app-response-stage-entry.js +10 -2
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +5 -2
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +4 -1
- package/dist/server/app-route-handler-runtime.js +45 -24
- package/dist/server/app-router-entry.js +24 -5
- package/dist/server/app-rsc-cache-busting.js +1 -1
- package/dist/server/app-rsc-errors.js +20 -16
- package/dist/server/app-rsc-handler.d.ts +7 -0
- package/dist/server/app-rsc-handler.js +85 -35
- package/dist/server/app-rsc-response-stage.js +4 -2
- package/dist/server/app-segment-config.d.ts +2 -1
- package/dist/server/app-server-action-execution.d.ts +2 -1
- package/dist/server/app-server-action-execution.js +61 -22
- package/dist/server/app-ssr-entry.js +2 -1
- package/dist/server/app-worker-stages.d.ts +5 -0
- package/dist/server/app-worker-stages.js +1 -1
- package/dist/server/cacheability-request.js +2 -2
- package/dist/server/client-build-files.d.ts +16 -0
- package/dist/server/client-build-files.js +36 -0
- package/dist/server/client-reuse-manifest.js +1 -1
- package/dist/server/dev-server.js +38 -27
- package/dist/server/dev-stack-sourcemap.js +18 -23
- package/dist/server/document-initial-head.d.ts +0 -11
- package/dist/server/document-initial-head.js +1 -1
- package/dist/server/forwarded-middleware-context.d.ts +5 -0
- package/dist/server/forwarded-middleware-context.js +22 -0
- package/dist/server/headers.d.ts +4 -1
- package/dist/server/headers.js +4 -1
- package/dist/server/http-conditional.d.ts +11 -0
- package/dist/server/http-conditional.js +10 -1
- package/dist/server/http-error-responses.d.ts +1 -1
- package/dist/server/image-optimization.d.ts +38 -0
- package/dist/server/image-optimization.js +93 -4
- package/dist/server/isr-cache.d.ts +10 -0
- package/dist/server/isr-cache.js +41 -6
- package/dist/server/metadata-route-response.js +9 -8
- package/dist/server/middleware-matcher.d.ts +2 -0
- package/dist/server/middleware-matcher.js +23 -2
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +33 -20
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +3 -1
- package/dist/server/multi-stage.d.ts +11 -0
- package/dist/server/normalize-path.d.ts +0 -13
- package/dist/server/normalize-path.js +1 -34
- package/dist/server/pages-api-route.d.ts +3 -2
- package/dist/server/pages-api-route.js +4 -3
- package/dist/server/pages-cache-tag.d.ts +12 -0
- package/dist/server/pages-cache-tag.js +19 -0
- package/dist/server/pages-client-assets.d.ts +2 -0
- package/dist/server/pages-data-route.d.ts +10 -0
- package/dist/server/pages-data-route.js +19 -2
- package/dist/server/pages-document-asset-props.d.ts +5 -1
- package/dist/server/pages-document-asset-props.js +13 -5
- package/dist/server/pages-document-splice.d.ts +19 -0
- package/dist/server/pages-document-splice.js +55 -0
- package/dist/server/pages-i18n.d.ts +12 -1
- package/dist/server/pages-i18n.js +18 -4
- package/dist/server/pages-node-compat.d.ts +16 -5
- package/dist/server/pages-node-compat.js +20 -12
- package/dist/server/pages-page-data.d.ts +32 -24
- package/dist/server/pages-page-data.js +50 -120
- package/dist/server/pages-page-handler.d.ts +0 -6
- package/dist/server/pages-page-handler.js +188 -126
- package/dist/server/pages-page-response.d.ts +12 -6
- package/dist/server/pages-page-response.js +41 -34
- package/dist/server/pages-request-pipeline.d.ts +10 -2
- package/dist/server/pages-request-pipeline.js +37 -18
- package/dist/server/pages-request-stage-entry.d.ts +6 -0
- package/dist/server/pages-request-stage-entry.js +71 -12
- package/dist/server/pages-response-stage-entry.js +4 -0
- package/dist/server/pages-revalidate.js +1 -1
- package/dist/server/pages-router-entry.js +4 -3
- package/dist/server/prerender-manifest.d.ts +3 -3
- package/dist/server/prerender-manifest.js +4 -3
- package/dist/server/prerender-work-unit-setup.js +4 -0
- package/dist/server/prod-server.d.ts +12 -0
- package/dist/server/prod-server.js +199 -69
- package/dist/server/proper-error.d.ts +4 -0
- package/dist/server/proper-error.js +31 -0
- package/dist/server/request-pipeline.d.ts +4 -1
- package/dist/server/request-pipeline.js +3 -2
- package/dist/server/revalidation-request.d.ts +1 -0
- package/dist/server/revalidation-request.js +1 -1
- package/dist/server/rsc-stream-hints.js +31 -2
- package/dist/server/seed-cache.d.ts +1 -0
- package/dist/server/seed-cache.js +5 -4
- package/dist/server/server-reference-loader.d.ts +4 -0
- package/dist/server/server-reference-loader.js +29 -0
- package/dist/server/socket-error-backstop.d.ts +9 -1
- package/dist/server/socket-error-backstop.js +46 -17
- package/dist/server/static-file-signal.d.ts +15 -4
- package/dist/server/static-file-signal.js +46 -12
- package/dist/server/worker-image-optimization.d.ts +34 -0
- package/dist/server/worker-image-optimization.js +86 -0
- package/dist/server/worker-stages.d.ts +1 -1
- package/dist/server/worker-stages.js +2 -2
- package/dist/server/worker-utils.d.ts +8 -3
- package/dist/server/worker-utils.js +12 -7
- package/dist/shims/app-router-scroll.js +4 -1
- package/dist/shims/app-router-tree-context.d.ts +5 -0
- package/dist/shims/app-router-tree-context.js +27 -0
- package/dist/shims/cache-callable-runtime.d.ts +4 -2
- package/dist/shims/cache-callable-runtime.js +21 -12
- package/dist/shims/cache-handler.js +1 -9
- package/dist/shims/cache-runtime.d.ts +6 -1
- package/dist/shims/cache-runtime.js +2 -1
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +40 -13
- package/dist/shims/cdn-cache.d.ts +8 -0
- package/dist/shims/cdn-cache.js +17 -1
- package/dist/shims/disable-smooth-scroll.d.ts +3 -0
- package/dist/shims/disable-smooth-scroll.js +36 -0
- package/dist/shims/document-placeholders.d.ts +19 -0
- package/dist/shims/document-placeholders.js +45 -0
- package/dist/shims/document.js +21 -5
- package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
- package/dist/shims/dynamic-preload-chunks.js +13 -9
- package/dist/shims/dynamic.d.ts +1 -1
- package/dist/shims/dynamic.js +60 -8
- package/dist/shims/fetch-cache-state.d.ts +66 -0
- package/dist/shims/fetch-cache-state.js +92 -0
- package/dist/shims/fetch-cache.d.ts +3 -67
- package/dist/shims/fetch-cache.js +160 -125
- package/dist/shims/head.d.ts +10 -6
- package/dist/shims/head.js +15 -13
- package/dist/shims/image.js +2 -1
- package/dist/shims/internal/work-store-scope.d.ts +5 -0
- package/dist/shims/internal/work-store-scope.js +24 -0
- package/dist/shims/navigation.js +31 -5
- package/dist/shims/request-context.d.ts +2 -0
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/router.js +12 -20
- package/dist/shims/server.d.ts +16 -0
- package/dist/shims/server.js +24 -4
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/url-utils.d.ts +2 -0
- package/dist/shims/url-utils.js +3 -1
- package/dist/utils/client-runtime-metadata.js +1 -1
- package/dist/utils/hash.d.ts +7 -0
- package/dist/utils/hash.js +11 -1
- package/dist/utils/package-name.d.ts +6 -0
- package/dist/utils/package-name.js +12 -1
- package/dist/utils/plugin-rsc-version.d.ts +4 -0
- package/dist/utils/plugin-rsc-version.js +15 -0
- package/dist/utils/prerender-output-paths.js +15 -4
- package/dist/utils/public-file-path.d.ts +10 -2
- package/dist/utils/public-file-path.js +13 -9
- package/dist/utils/public-routes.js +2 -2
- package/dist/utils/react-version.d.ts +3 -0
- package/dist/utils/react-version.js +1 -1
- package/package.json +7 -3
|
@@ -55,6 +55,15 @@ function indexOfByte(bytes, byte, from) {
|
|
|
55
55
|
for (let index = from; index < bytes.byteLength; index++) if (bytes[index] === byte) return index;
|
|
56
56
|
return -1;
|
|
57
57
|
}
|
|
58
|
+
function utf8CompleteEnd(bytes, from) {
|
|
59
|
+
const length = bytes.byteLength;
|
|
60
|
+
for (let index = length - 1; index >= from && index >= length - 3; index--) {
|
|
61
|
+
const byte = bytes[index];
|
|
62
|
+
if ((byte & 192) === 128) continue;
|
|
63
|
+
return index + (byte >= 240 ? 4 : byte >= 224 ? 3 : byte >= 192 ? 2 : 1) > length ? index : length;
|
|
64
|
+
}
|
|
65
|
+
return length;
|
|
66
|
+
}
|
|
58
67
|
function parseHexBytes(bytes, start, end) {
|
|
59
68
|
if (start === end) return null;
|
|
60
69
|
let value = 0;
|
|
@@ -101,16 +110,30 @@ function normalizeReactFlightPreloadHints(stream) {
|
|
|
101
110
|
let carry = null;
|
|
102
111
|
let rawBytesRemaining = 0;
|
|
103
112
|
let passThrough = false;
|
|
113
|
+
let inPlainRow = false;
|
|
104
114
|
return stream.pipeThrough(new TransformStream({
|
|
105
115
|
transform(chunk, controller) {
|
|
106
116
|
if (passThrough) {
|
|
107
117
|
controller.enqueue(chunk);
|
|
108
118
|
return;
|
|
109
119
|
}
|
|
120
|
+
let start = 0;
|
|
121
|
+
if (inPlainRow) {
|
|
122
|
+
const end = indexOfByte(chunk, NEWLINE_BYTE, 0);
|
|
123
|
+
if (end === -1) {
|
|
124
|
+
const bytes = carry === null ? chunk : concatBytes(carry, chunk);
|
|
125
|
+
const complete = utf8CompleteEnd(bytes, 0);
|
|
126
|
+
carry = complete === bytes.byteLength ? null : bytes.slice(complete);
|
|
127
|
+
if (complete > 0) controller.enqueue(complete === bytes.byteLength ? bytes : bytes.subarray(0, complete));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
inPlainRow = false;
|
|
131
|
+
start = (carry === null ? 0 : carry.byteLength) + end + 1;
|
|
132
|
+
}
|
|
110
133
|
const bytes = carry === null ? chunk : concatBytes(carry, chunk);
|
|
111
134
|
carry = null;
|
|
112
135
|
const byteLength = bytes.byteLength;
|
|
113
|
-
let offset =
|
|
136
|
+
let offset = start;
|
|
114
137
|
let emittedThrough = 0;
|
|
115
138
|
let output = bytes;
|
|
116
139
|
let ownsOutput = bytes !== chunk;
|
|
@@ -146,7 +169,13 @@ function normalizeReactFlightPreloadHints(stream) {
|
|
|
146
169
|
break;
|
|
147
170
|
}
|
|
148
171
|
const newline = indexOfByte(bytes, NEWLINE_BYTE, offset);
|
|
149
|
-
if (newline === -1)
|
|
172
|
+
if (newline === -1) {
|
|
173
|
+
if (!(tagByte === HINT_TAG_BYTE && (colon + 2 === byteLength || bytes[colon + 2] === LINK_HINT_CODE_BYTE))) {
|
|
174
|
+
inPlainRow = true;
|
|
175
|
+
offset = utf8CompleteEnd(bytes, offset);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
150
179
|
if (tagByte === HINT_TAG_BYTE && bytes[colon + 2] === LINK_HINT_CODE_BYTE) {
|
|
151
180
|
const line = bytes.subarray(offset, newline + 1);
|
|
152
181
|
const normalized = normalizeReactFlightHintLine(line);
|
|
@@ -14,6 +14,7 @@ type PrerenderCacheSeedMetadata = {
|
|
|
14
14
|
};
|
|
15
15
|
type PrerenderCacheSeedOptions = {
|
|
16
16
|
buildAppPageHtmlKey?: (pathname: string) => string;
|
|
17
|
+
buildAppPageFallbackShellKey?: (pathname: string, suffix: "html" | "rsc") => string;
|
|
17
18
|
buildAppPageRscKey?: (pathname: string) => string;
|
|
18
19
|
buildAppRouteKey?: (pathname: string) => string;
|
|
19
20
|
writeAppPageEntry?: (key: string, data: CachedAppPageValue, metadata: PrerenderCacheSeedMetadata) => Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
|
|
2
|
-
import { appIsrCacheKey, isrSet, isrSetPrerenderedAppPage } from "./isr-cache.js";
|
|
2
|
+
import { appIsrCacheKey, appIsrFallbackShellKey, isrSet, isrSetPrerenderedAppPage } from "./isr-cache.js";
|
|
3
3
|
import { addPregeneratedConcretePath, addPregeneratedRoute, clearPregeneratedConcretePaths, normalizePregeneratedPathname } from "./pregenerated-concrete-paths.js";
|
|
4
4
|
import { getRenderedAppRoutes, getRenderedMetadataRoutes, isFallbackShellArtifactPath, readPrerenderManifest } from "./prerender-manifest.js";
|
|
5
5
|
import { buildAppPageCacheTags, buildAppRouteCacheTags } from "./app-page-cache.js";
|
|
@@ -61,11 +61,12 @@ async function seedMemoryCacheFromPrerender(serverDir, options) {
|
|
|
61
61
|
const appRoutes = getRenderedAppRoutes(routes);
|
|
62
62
|
for (const route of appRoutes) {
|
|
63
63
|
const concretePathname = route.path ?? route.route;
|
|
64
|
-
|
|
64
|
+
const isFallbackShell = isFallbackShellArtifactPath(concretePathname, route);
|
|
65
|
+
if (!isFallbackShell) addPregeneratedConcretePath(route.route, concretePathname);
|
|
65
66
|
const artifactPathname = route.path ?? route.route;
|
|
66
67
|
const cachePathname = normalizePregeneratedPathname(artifactPathname);
|
|
67
|
-
const htmlKey = options?.buildAppPageHtmlKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "html", buildId);
|
|
68
|
-
const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "rsc", buildId);
|
|
68
|
+
const htmlKey = isFallbackShell ? options?.buildAppPageFallbackShellKey?.(cachePathname, "html") ?? appIsrFallbackShellKey(cachePathname, "html", buildId) : options?.buildAppPageHtmlKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "html", buildId);
|
|
69
|
+
const rscKey = isFallbackShell ? options?.buildAppPageFallbackShellKey?.(cachePathname, "rsc") ?? appIsrFallbackShellKey(cachePathname, "rsc", buildId) : options?.buildAppPageRscKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "rsc", buildId);
|
|
69
70
|
const revalidateSeconds = typeof route.revalidate === "number" ? route.revalidate : void 0;
|
|
70
71
|
const expireSeconds = typeof route.expire === "number" ? route.expire : void 0;
|
|
71
72
|
const staleSeconds = typeof route.stale === "number" && route.stale >= 0 ? route.stale : void 0;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { runOutsideRequestScopes } from "../shims/internal/als-registry.js";
|
|
2
|
+
//#region src/server/server-reference-loader.ts
|
|
3
|
+
/**
|
|
4
|
+
* Keep server-reference module evaluation out of the request that first
|
|
5
|
+
* names the module.
|
|
6
|
+
*
|
|
7
|
+
* React resolves every server reference it decodes through
|
|
8
|
+
* `@vitejs/plugin-rsc`'s `globalThis.__vite_rsc_server_require__`, which
|
|
9
|
+
* imports the referenced module on first use. Some references are resolved
|
|
10
|
+
* only once the action consumes them — promise and async-iterable arguments,
|
|
11
|
+
* and the encrypted closure captures an inline action decrypts in its own
|
|
12
|
+
* body — so they load inside the caller's request, past the loads the action
|
|
13
|
+
* dispatcher already runs outside it. A dynamic `import()` carries
|
|
14
|
+
* AsyncLocalStorage into the module's top-level evaluation, so module-scope
|
|
15
|
+
* `cookies()`/`headers()` would capture that caller's request for every later
|
|
16
|
+
* caller in the isolate. Next.js evaluates action modules outside the request
|
|
17
|
+
* store, where those calls throw.
|
|
18
|
+
*/
|
|
19
|
+
const ISOLATED = Symbol.for("vinext.serverReferenceRequire.isolated");
|
|
20
|
+
/** Wrap plugin-rsc's server-reference loader once; must run after it is installed. */
|
|
21
|
+
function isolateServerReferenceLoads() {
|
|
22
|
+
const load = globalThis.__vite_rsc_server_require__;
|
|
23
|
+
if (typeof load !== "function" || ISOLATED in load) return;
|
|
24
|
+
const isolated = (id) => runOutsideRequestScopes(() => load(id));
|
|
25
|
+
Object.defineProperty(isolated, ISOLATED, { value: true });
|
|
26
|
+
globalThis.__vite_rsc_server_require__ = isolated;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { isolateServerReferenceLoads };
|
|
@@ -43,5 +43,13 @@ export declare function isCancelledRequestBodyEnqueueError(err: unknown): boolea
|
|
|
43
43
|
* Symbol.for guard. Not part of the public API.
|
|
44
44
|
*/
|
|
45
45
|
export declare function isSocketErrorBackstopInstalled(): boolean;
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Installs the backstop once per process. Pass `{ runningServer: true }`
|
|
48
|
+
* from a long-lived production server to log, rather than re-throw,
|
|
49
|
+
* errors outside the absorbed set. The flag is process-global and sticky,
|
|
50
|
+
* so it also applies when `index.ts` installed the listeners first.
|
|
51
|
+
*/
|
|
52
|
+
export declare function installSocketErrorBackstop(options?: {
|
|
53
|
+
runningServer?: boolean;
|
|
54
|
+
}): void;
|
|
47
55
|
//#endregion
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
//#region src/server/socket-error-backstop.ts
|
|
2
2
|
/**
|
|
3
|
-
* Process-level backstop for
|
|
4
|
-
* per-
|
|
3
|
+
* Process-level backstop for errors that escape per-connection /
|
|
4
|
+
* per-request error guards.
|
|
5
5
|
*
|
|
6
|
-
* Three real call sites in vinext that hit this
|
|
6
|
+
* Three real call sites in vinext that hit this with peer-disconnect
|
|
7
|
+
* errors:
|
|
7
8
|
* - `fromWeb(fetch().body).pipe(res)` in proxyExternalRewriteNode.
|
|
8
9
|
* - Streaming surfaces inside `@vitejs/plugin-rsc` with their own
|
|
9
10
|
* pipe topology (destinations aren't inbound connection sockets).
|
|
@@ -14,14 +15,25 @@
|
|
|
14
15
|
* inside a `nextTick` callback. The throw escapes to
|
|
15
16
|
* `uncaughtException`, where this listener filters it.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* race (see
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
18
|
+
* Always absorbs benign static-asset `import()` rejections (see
|
|
19
|
+
* `isBenignAssetImportError`). Outside prerender it also silently
|
|
20
|
+
* absorbs peer-disconnect codes (ECONNRESET / EPIPE / ECONNABORTED) and
|
|
21
|
+
* Node's cancelled-request-body enqueue race (see
|
|
22
|
+
* `isCancelledRequestBodyEnqueueError`). Everything else depends on the
|
|
23
|
+
* phase:
|
|
24
|
+
*
|
|
25
|
+
* - **Running server** (`vite dev`, `vinext start`, standalone
|
|
26
|
+
* `server.js`): logged and absorbed, matching the log-only
|
|
27
|
+
* `uncaughtException` / `unhandledRejection` handlers that Next.js's
|
|
28
|
+
* `router-server.ts` installs for both `next dev` and `next start`.
|
|
29
|
+
* App code may legitimately leave a rejection unhandled for a few
|
|
30
|
+
* ticks (Next.js's documented "Preloading data" pattern,
|
|
31
|
+
* fire-and-forget calls), and request input can often choose whether
|
|
32
|
+
* it rejects, so crashing would let one anonymous request take down
|
|
33
|
+
* the whole process. The dev server's `configureServer` hook and
|
|
34
|
+
* `startProdServer()` opt in via `{ runningServer: true }`.
|
|
35
|
+
* - **Build and prerender**: synchronously re-thrown, preserving
|
|
36
|
+
* Node's default crash semantics.
|
|
25
37
|
*
|
|
26
38
|
* **Installed at module load.** Earlier iterations tried to gate
|
|
27
39
|
* install via Vite's `config()` hook (`command === "serve"`) so it
|
|
@@ -67,11 +79,12 @@
|
|
|
67
79
|
* top of every user's `vite.config.ts`, so vinext's listener registers
|
|
68
80
|
* before most user / tooling listeners (Sentry, OpenTelemetry,
|
|
69
81
|
* structured logging). For peer-disconnect codes the early-return is
|
|
70
|
-
* fine
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
82
|
+
* fine, and on a running production server every other error is
|
|
83
|
+
* logged without throwing, so later listeners still observe it. In
|
|
84
|
+
* the re-throw phases the synchronous re-throw still crashes the
|
|
85
|
+
* process with the original stack, but listeners registered after
|
|
86
|
+
* vinext don't observe the event. Users who need crash-reporter
|
|
87
|
+
* visibility there must register their handler before importing vinext.
|
|
75
88
|
*
|
|
76
89
|
* **Symbol.for caveat.** `Symbol.for("vinext.socketErrorBackstop")`
|
|
77
90
|
* is process-global, so if two different vinext versions are loaded
|
|
@@ -82,6 +95,7 @@
|
|
|
82
95
|
* time the listener absorbs an error.
|
|
83
96
|
*/
|
|
84
97
|
const SOCKET_BACKSTOP_FLAG = Symbol.for("vinext.socketErrorBackstop");
|
|
98
|
+
const RUNNING_SERVER_FLAG = Symbol.for("vinext.socketErrorBackstop.runningServer");
|
|
85
99
|
/**
|
|
86
100
|
* Pure predicate: returns the peer-disconnect code when `err` carries
|
|
87
101
|
* one of `ECONNRESET` / `EPIPE` / `ECONNABORTED`, otherwise `undefined`.
|
|
@@ -179,8 +193,15 @@ function isCancelledRequestBodyEnqueueError(err) {
|
|
|
179
193
|
function isSocketErrorBackstopInstalled() {
|
|
180
194
|
return Boolean(process[SOCKET_BACKSTOP_FLAG]);
|
|
181
195
|
}
|
|
182
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Installs the backstop once per process. Pass `{ runningServer: true }`
|
|
198
|
+
* from a long-lived production server to log, rather than re-throw,
|
|
199
|
+
* errors outside the absorbed set. The flag is process-global and sticky,
|
|
200
|
+
* so it also applies when `index.ts` installed the listeners first.
|
|
201
|
+
*/
|
|
202
|
+
function installSocketErrorBackstop(options = {}) {
|
|
183
203
|
const proc = process;
|
|
204
|
+
if (options.runningServer) proc[RUNNING_SERVER_FLAG] = true;
|
|
184
205
|
if (proc[SOCKET_BACKSTOP_FLAG]) return;
|
|
185
206
|
if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") return;
|
|
186
207
|
proc[SOCKET_BACKSTOP_FLAG] = true;
|
|
@@ -206,6 +227,10 @@ function installSocketErrorBackstop() {
|
|
|
206
227
|
if (debug) console.warn("[vinext] absorbed uncaughtException ERR_INVALID_STATE (cancelled request body)");
|
|
207
228
|
return;
|
|
208
229
|
}
|
|
230
|
+
if (proc[RUNNING_SERVER_FLAG]) {
|
|
231
|
+
console.error("[vinext] uncaughtException:", err);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
209
234
|
throw err;
|
|
210
235
|
});
|
|
211
236
|
process.on("unhandledRejection", (reason) => {
|
|
@@ -216,6 +241,10 @@ function installSocketErrorBackstop() {
|
|
|
216
241
|
if (debug) console.warn(`[vinext] absorbed unhandledRejection ${code}`);
|
|
217
242
|
return;
|
|
218
243
|
}
|
|
244
|
+
if (proc[RUNNING_SERVER_FLAG]) {
|
|
245
|
+
console.error("[vinext] unhandledRejection:", reason);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
219
248
|
throw reason;
|
|
220
249
|
});
|
|
221
250
|
}
|
|
@@ -3,14 +3,25 @@ export type StaticFileSignalContext = {
|
|
|
3
3
|
headers: Headers | null;
|
|
4
4
|
status: number | null;
|
|
5
5
|
};
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Create the only response shape that host runtimes may resolve as an asset.
|
|
8
|
+
* `requestHeaders` are the request headers after middleware's overrides, which
|
|
9
|
+
* the asset fetch uses in place of the original request's headers.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createStaticFileSignal(pathname: string, context: StaticFileSignalContext, requestHeaders?: Headers | null): Response;
|
|
8
12
|
/** Whether this response was created by vinext's public-file router. */
|
|
9
13
|
export declare function isStaticFileSignal(response: Response): boolean;
|
|
10
14
|
/** Return the encoded asset pathname only for a framework-created signal. */
|
|
11
15
|
export declare function readStaticFileSignal(response: Response): string | null;
|
|
12
|
-
/**
|
|
16
|
+
/** Return the post-middleware request headers a framework-created signal carries. */
|
|
17
|
+
export declare function readStaticFileSignalRequestHeaders(response: Response): Headers | null;
|
|
18
|
+
/**
|
|
19
|
+
* Encode a framework-authenticated signal for a standards-only stage transport.
|
|
20
|
+
* Post-middleware request headers travel in the body, which has no platform
|
|
21
|
+
* size limit, rather than in a response header. Their marker header carries
|
|
22
|
+
* the real status, since the body needs a status that allows one.
|
|
23
|
+
*/
|
|
13
24
|
export declare function serializeStaticFileSignalForTransport(response: Response, token: string): Response;
|
|
14
25
|
/** Restore and consume a signal returned by the trusted response-stage wrapper. */
|
|
15
|
-
export declare function restoreStaticFileSignalFromTransport(response: Response, token: string): Response
|
|
26
|
+
export declare function restoreStaticFileSignalFromTransport(response: Response, token: string): Promise<Response>;
|
|
16
27
|
//#endregion
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//#region src/server/static-file-signal.ts
|
|
2
2
|
const STATIC_FILE_SIGNAL = Symbol.for("vinext.static-file-signal");
|
|
3
|
+
const STATIC_FILE_REQUEST_HEADERS = Symbol.for("vinext.static-file-request-headers");
|
|
3
4
|
const STATIC_FILE_SIGNAL_TRANSPORT_HEADER = "x-vinext-stage-static-file";
|
|
5
|
+
const STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER = "x-vinext-stage-static-file-request-headers";
|
|
4
6
|
const STATIC_FILE_REPRESENTATION_HEADERS = [
|
|
5
7
|
"content-encoding",
|
|
6
8
|
"content-length",
|
|
@@ -14,17 +16,19 @@ const STATIC_FILE_REPRESENTATION_HEADERS = [
|
|
|
14
16
|
* before the host runtime can fetch the asset. Application response headers
|
|
15
17
|
* remain ordinary metadata and cannot alter framework control flow.
|
|
16
18
|
*/
|
|
17
|
-
function markStaticFileSignal(response, pathname) {
|
|
18
|
-
return markEncodedStaticFileSignal(response, encodeURIComponent(pathname));
|
|
19
|
+
function markStaticFileSignal(response, pathname, requestHeaders) {
|
|
20
|
+
return markEncodedStaticFileSignal(response, encodeURIComponent(pathname), requestHeaders);
|
|
19
21
|
}
|
|
20
|
-
function markEncodedStaticFileSignal(response, encodedPathname) {
|
|
22
|
+
function markEncodedStaticFileSignal(response, encodedPathname, requestHeaders) {
|
|
21
23
|
Object.defineProperty(response, STATIC_FILE_SIGNAL, { value: encodedPathname });
|
|
24
|
+
if (requestHeaders) Object.defineProperty(response, STATIC_FILE_REQUEST_HEADERS, { value: requestHeaders });
|
|
22
25
|
return response;
|
|
23
26
|
}
|
|
24
27
|
function withoutTransportHeader(response) {
|
|
25
|
-
if (!response.headers.has(STATIC_FILE_SIGNAL_TRANSPORT_HEADER)) return response;
|
|
28
|
+
if (!response.headers.has(STATIC_FILE_SIGNAL_TRANSPORT_HEADER) && !response.headers.has(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER)) return response;
|
|
26
29
|
const headers = new Headers(response.headers);
|
|
27
30
|
headers.delete(STATIC_FILE_SIGNAL_TRANSPORT_HEADER);
|
|
31
|
+
headers.delete(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
|
|
28
32
|
if (response.status < 200 || response.status > 599) return response;
|
|
29
33
|
const body = response.status === 204 || response.status === 205 || response.status === 304 ? null : response.body;
|
|
30
34
|
return new Response(body, {
|
|
@@ -33,14 +37,18 @@ function withoutTransportHeader(response) {
|
|
|
33
37
|
statusText: response.statusText
|
|
34
38
|
});
|
|
35
39
|
}
|
|
36
|
-
/**
|
|
37
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Create the only response shape that host runtimes may resolve as an asset.
|
|
42
|
+
* `requestHeaders` are the request headers after middleware's overrides, which
|
|
43
|
+
* the asset fetch uses in place of the original request's headers.
|
|
44
|
+
*/
|
|
45
|
+
function createStaticFileSignal(pathname, context, requestHeaders = null) {
|
|
38
46
|
const headers = new Headers();
|
|
39
47
|
if (context.headers) for (const [key, value] of context.headers) headers.append(key, value);
|
|
40
48
|
return markStaticFileSignal(new Response(null, {
|
|
41
49
|
status: context.status ?? 200,
|
|
42
50
|
headers
|
|
43
|
-
}), pathname);
|
|
51
|
+
}), pathname, requestHeaders);
|
|
44
52
|
}
|
|
45
53
|
/** Whether this response was created by vinext's public-file router. */
|
|
46
54
|
function isStaticFileSignal(response) {
|
|
@@ -51,22 +59,40 @@ function readStaticFileSignal(response) {
|
|
|
51
59
|
const signal = Reflect.get(response, STATIC_FILE_SIGNAL);
|
|
52
60
|
return typeof signal === "string" ? signal : null;
|
|
53
61
|
}
|
|
54
|
-
/**
|
|
62
|
+
/** Return the post-middleware request headers a framework-created signal carries. */
|
|
63
|
+
function readStaticFileSignalRequestHeaders(response) {
|
|
64
|
+
const headers = Reflect.get(response, STATIC_FILE_REQUEST_HEADERS);
|
|
65
|
+
return headers instanceof Headers ? headers : null;
|
|
66
|
+
}
|
|
67
|
+
function isHeaderEntries(value) {
|
|
68
|
+
return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Encode a framework-authenticated signal for a standards-only stage transport.
|
|
72
|
+
* Post-middleware request headers travel in the body, which has no platform
|
|
73
|
+
* size limit, rather than in a response header. Their marker header carries
|
|
74
|
+
* the real status, since the body needs a status that allows one.
|
|
75
|
+
*/
|
|
55
76
|
function serializeStaticFileSignalForTransport(response, token) {
|
|
56
77
|
const signal = readStaticFileSignal(response);
|
|
57
78
|
if (signal === null) return response;
|
|
58
79
|
const headers = new Headers(response.headers);
|
|
59
80
|
for (const name of STATIC_FILE_REPRESENTATION_HEADERS) headers.delete(name);
|
|
81
|
+
headers.delete(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
|
|
60
82
|
headers.set(STATIC_FILE_SIGNAL_TRANSPORT_HEADER, `${token}:${signal}`);
|
|
61
|
-
|
|
83
|
+
const requestHeaders = readStaticFileSignalRequestHeaders(response);
|
|
84
|
+
if (!requestHeaders) return new Response(null, {
|
|
62
85
|
headers,
|
|
63
86
|
status: response.status,
|
|
64
87
|
statusText: response.statusText
|
|
65
88
|
});
|
|
89
|
+
headers.set(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER, `${token}:${response.status}`);
|
|
90
|
+
return new Response(JSON.stringify([...requestHeaders]), { headers });
|
|
66
91
|
}
|
|
67
92
|
/** Restore and consume a signal returned by the trusted response-stage wrapper. */
|
|
68
|
-
function restoreStaticFileSignalFromTransport(response, token) {
|
|
93
|
+
async function restoreStaticFileSignalFromTransport(response, token) {
|
|
69
94
|
const transported = response.headers.get(STATIC_FILE_SIGNAL_TRANSPORT_HEADER);
|
|
95
|
+
const requestHeadersMarker = response.headers.get(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
|
|
70
96
|
const cleaned = withoutTransportHeader(response);
|
|
71
97
|
const prefix = `${token}:`;
|
|
72
98
|
if (transported === null || !transported.startsWith(prefix)) return cleaned;
|
|
@@ -76,7 +102,15 @@ function restoreStaticFileSignalFromTransport(response, token) {
|
|
|
76
102
|
} catch {
|
|
77
103
|
return cleaned;
|
|
78
104
|
}
|
|
79
|
-
return markEncodedStaticFileSignal(cleaned, encodedPathname);
|
|
105
|
+
if (requestHeadersMarker === null) return markEncodedStaticFileSignal(cleaned, encodedPathname, null);
|
|
106
|
+
const status = Number(requestHeadersMarker.startsWith(prefix) ? requestHeadersMarker.slice(prefix.length) : NaN);
|
|
107
|
+
if (!Number.isInteger(status) || status < 200 || status > 599) return cleaned;
|
|
108
|
+
const entries = JSON.parse(await cleaned.text());
|
|
109
|
+
if (!isHeaderEntries(entries)) throw new Error("Invalid static-file request headers from the response stage");
|
|
110
|
+
return markEncodedStaticFileSignal(new Response(null, {
|
|
111
|
+
headers: cleaned.headers,
|
|
112
|
+
status
|
|
113
|
+
}), encodedPathname, new Headers(entries));
|
|
80
114
|
}
|
|
81
115
|
//#endregion
|
|
82
|
-
export { createStaticFileSignal, isStaticFileSignal, readStaticFileSignal, restoreStaticFileSignalFromTransport, serializeStaticFileSignalForTransport };
|
|
116
|
+
export { createStaticFileSignal, isStaticFileSignal, readStaticFileSignal, readStaticFileSignalRequestHeaders, restoreStaticFileSignalFromTransport, serializeStaticFileSignalForTransport };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { VinextAssetFetcher } from "./multi-stage.js";
|
|
2
|
+
import { ImageConfig, ImageOptimizationSignal } from "./image-optimization.js";
|
|
3
|
+
//#region src/server/worker-image-optimization.d.ts
|
|
4
|
+
export type WorkerImageSourceOptions = {
|
|
5
|
+
assets: VinextAssetFetcher;
|
|
6
|
+
basePath: string;
|
|
7
|
+
assetPathPrefix: string;
|
|
8
|
+
clientBuildFiles: ReadonlySet<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Run `sourceRequest`, a GET without the caller's headers, through the
|
|
11
|
+
* application pipeline and return the public file it selects with a 2xx
|
|
12
|
+
* outcome, or null.
|
|
13
|
+
*/
|
|
14
|
+
fetchPublicFile(sourceRequest: Request): Promise<Response | null>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Fetch a local image source. Build output is fetched from the asset binding,
|
|
18
|
+
* which serves it before the Worker and its middleware. Any other source is
|
|
19
|
+
* served only when the application pipeline selects a public file for it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchWorkerImageSource(imageUrl: string, request: Request, options: WorkerImageSourceOptions): Promise<Response>;
|
|
22
|
+
/**
|
|
23
|
+
* Return the public file a framework static-file signal selects, provided the
|
|
24
|
+
* pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
|
|
25
|
+
* request header overrides, as resolveStaticAssetSignal fetches it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchSignalledPublicFile(response: Response, sourceRequest: Request, assets: VinextAssetFetcher): Promise<Response> | null;
|
|
28
|
+
/**
|
|
29
|
+
* Serve the App Router image endpoint once the request handler admitted it
|
|
30
|
+
* through middleware and rewrites. Middleware's response headers are sent with
|
|
31
|
+
* the image, as Next.js does.
|
|
32
|
+
*/
|
|
33
|
+
export declare function serveWorkerImageOptimization(request: Request, signal: ImageOptimizationSignal, allowedWidths: number[] | undefined, imageConfig: ImageConfig | undefined, options: WorkerImageSourceOptions): Promise<Response>;
|
|
34
|
+
//#endregion
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isNextStaticPath } from "../utils/asset-prefix.js";
|
|
2
|
+
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
3
|
+
import { VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "./headers.js";
|
|
4
|
+
import { normalizePath } from "./normalize-path.js";
|
|
5
|
+
import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
|
|
6
|
+
import { attachRequestCfMetadata, cloneRequestWithUrl } from "./request-pipeline.js";
|
|
7
|
+
import { handleConfiguredImageOptimization, invalidImageSourceResponse, isImageSourceBodyStatus } from "./image-optimization.js";
|
|
8
|
+
import { createStaticAssetRequest, mergeHeaders } from "./worker-utils.js";
|
|
9
|
+
import { resolveClientBuildFile } from "./client-build-files.js";
|
|
10
|
+
//#region src/server/worker-image-optimization.ts
|
|
11
|
+
/**
|
|
12
|
+
* Image optimization for Worker hosts. A local source is fetched as an
|
|
13
|
+
* anonymous direct request would receive it, like Next.js' fetchInternalImage
|
|
14
|
+
* (packages/next/src/server/image-optimizer.ts), so middleware that denies or
|
|
15
|
+
* redirects a direct request also denies the optimizer.
|
|
16
|
+
*/
|
|
17
|
+
const OMITTED_MIDDLEWARE_HEADERS = /* @__PURE__ */ new Set([
|
|
18
|
+
"content-encoding",
|
|
19
|
+
"content-length",
|
|
20
|
+
"content-range",
|
|
21
|
+
"content-type",
|
|
22
|
+
"transfer-encoding",
|
|
23
|
+
VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
|
|
24
|
+
]);
|
|
25
|
+
function cancelResponseBody(response) {
|
|
26
|
+
const body = response.body;
|
|
27
|
+
if (!body || body.locked) return;
|
|
28
|
+
body.cancel().catch(() => {});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fetch a local image source. Build output is fetched from the asset binding,
|
|
32
|
+
* which serves it before the Worker and its middleware. Any other source is
|
|
33
|
+
* served only when the application pipeline selects a public file for it.
|
|
34
|
+
*/
|
|
35
|
+
async function fetchWorkerImageSource(imageUrl, request, options) {
|
|
36
|
+
const sourceUrl = new URL(imageUrl, request.url);
|
|
37
|
+
let sourcePathname;
|
|
38
|
+
try {
|
|
39
|
+
sourcePathname = normalizePath(normalizePathnameForRouteMatchStrict(sourceUrl.pathname));
|
|
40
|
+
} catch {
|
|
41
|
+
return invalidImageSourceResponse();
|
|
42
|
+
}
|
|
43
|
+
const buildAssetPath = isNextStaticPath(sourcePathname, options.basePath, options.assetPathPrefix) ? sourcePathname : resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
|
|
44
|
+
if (buildAssetPath !== null) return options.assets.fetch(new Request(new URL(buildAssetPath, request.url)));
|
|
45
|
+
const sourceResponse = await options.fetchPublicFile(attachRequestCfMetadata(new Request(sourceUrl, { signal: request.signal }), request));
|
|
46
|
+
if (sourceResponse && isImageSourceBodyStatus(sourceResponse.status)) return sourceResponse;
|
|
47
|
+
if (sourceResponse) cancelResponseBody(sourceResponse);
|
|
48
|
+
return invalidImageSourceResponse();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Return the public file a framework static-file signal selects, provided the
|
|
52
|
+
* pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
|
|
53
|
+
* request header overrides, as resolveStaticAssetSignal fetches it.
|
|
54
|
+
*/
|
|
55
|
+
function fetchSignalledPublicFile(response, sourceRequest, assets) {
|
|
56
|
+
const signal = isImageSourceBodyStatus(response.status) ? readStaticFileSignal(response) : null;
|
|
57
|
+
const requestHeaders = readStaticFileSignalRequestHeaders(response);
|
|
58
|
+
cancelResponseBody(response);
|
|
59
|
+
if (signal === null) return null;
|
|
60
|
+
let assetPath = signal;
|
|
61
|
+
try {
|
|
62
|
+
assetPath = decodeURIComponent(signal);
|
|
63
|
+
} catch {}
|
|
64
|
+
return Promise.resolve(assets.fetch(createStaticAssetRequest(assetPath, sourceRequest, requestHeaders)));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Serve the App Router image endpoint once the request handler admitted it
|
|
68
|
+
* through middleware and rewrites. Middleware's response headers are sent with
|
|
69
|
+
* the image, as Next.js does.
|
|
70
|
+
*/
|
|
71
|
+
async function serveWorkerImageOptimization(request, signal, allowedWidths, imageConfig, options) {
|
|
72
|
+
const imageUrl = new URL(request.url);
|
|
73
|
+
imageUrl.search = signal.search;
|
|
74
|
+
const imageRequest = cloneRequestWithUrl(request, imageUrl.href);
|
|
75
|
+
const response = await handleConfiguredImageOptimization(signal.requestHeaders ? new Request(imageRequest, { headers: signal.requestHeaders }) : imageRequest, (sourceUrl) => fetchWorkerImageSource(sourceUrl, imageRequest, options), allowedWidths, imageConfig);
|
|
76
|
+
const middlewareHeaders = {};
|
|
77
|
+
signal.middlewareHeaders.forEach((value, name) => {
|
|
78
|
+
if (name === "set-cookie" || OMITTED_MIDDLEWARE_HEADERS.has(name)) return;
|
|
79
|
+
middlewareHeaders[name] = value;
|
|
80
|
+
});
|
|
81
|
+
const cookies = signal.middlewareHeaders.getSetCookie();
|
|
82
|
+
if (cookies.length > 0) middlewareHeaders["set-cookie"] = cookies;
|
|
83
|
+
return mergeHeaders(response, middlewareHeaders);
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { VinextResponseStageCacheability, VinextResponseStageTransport } from "./multi-stage.js";
|
|
2
2
|
import { PagesRenderOptions } from "./pages-request-pipeline.js";
|
|
3
3
|
//#region src/server/worker-stages.d.ts
|
|
4
|
-
export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION =
|
|
4
|
+
export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
|
|
5
5
|
export declare const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
|
|
6
6
|
export type PagesResponseStagePolicyOwner = "request-time" | "static";
|
|
7
7
|
type PagesResponseStageEnvelope = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/server/worker-stages.ts
|
|
2
|
-
const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION =
|
|
2
|
+
const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
|
|
3
3
|
const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
|
|
4
4
|
function isSerializedHeaders(value) {
|
|
5
5
|
return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
|
|
@@ -12,7 +12,7 @@ function isResponseStageCacheability(value) {
|
|
|
12
12
|
function isPagesResponseStageProps(value) {
|
|
13
13
|
if (!value || typeof value !== "object") return false;
|
|
14
14
|
const props = value;
|
|
15
|
-
if (props.protocolVersion !==
|
|
15
|
+
if (props.protocolVersion !== 5 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || typeof props.requestHost !== "string" || props.requestHost.length === 0 || props.stagedHeaders !== null && !isSerializedHeaders(props.stagedHeaders)) return false;
|
|
16
16
|
if (props.kind === "pages-api") return typeof props.apiUrl === "string";
|
|
17
17
|
if (props.kind === "pages-prerender-discovery") return true;
|
|
18
18
|
if (props.kind !== "pages-page" || typeof props.resolvedUrl !== "string") return false;
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
export declare function finalizeMissingStaticAssetResponse(response: Response, missingBuildAsset: boolean): Response;
|
|
3
3
|
export declare function mergeHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
|
|
4
4
|
export declare function resolveStaticAssetSignal(signalResponse: Response, options: {
|
|
5
|
-
|
|
5
|
+
/** `requestHeaders` are the post-middleware request headers, when middleware set any. */
|
|
6
|
+
fetchAsset(path: string, requestHeaders: Headers | null): Promise<Response>;
|
|
6
7
|
}): Promise<Response | null>;
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Retarget a Worker asset request without dropping its conditional/range
|
|
10
|
+
* fields. `headers` replaces the source request's headers with middleware's
|
|
11
|
+
* overrides, so a removed `Range` or replaced validator reaches the asset fetch.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createStaticAssetRequest(assetPath: string, sourceRequest: Request, headers?: Headers | null): Request;
|
|
9
14
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { notFoundStaticAssetResponse } from "./http-error-responses.js";
|
|
2
|
-
import { readStaticFileSignal } from "./static-file-signal.js";
|
|
2
|
+
import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
|
|
3
3
|
//#region src/server/worker-utils.ts
|
|
4
4
|
/**
|
|
5
5
|
* Shared utilities for Cloudflare Worker entries.
|
|
@@ -47,7 +47,7 @@ function buildHeaderRecord(response, omitNames = []) {
|
|
|
47
47
|
return headers;
|
|
48
48
|
}
|
|
49
49
|
function mergeHeaders(response, extraHeaders, statusOverride) {
|
|
50
|
-
const status = statusOverride ?? response.status;
|
|
50
|
+
const status = response.status === 304 ? 304 : statusOverride ?? response.status;
|
|
51
51
|
const merged = new Headers();
|
|
52
52
|
for (const [k, v] of Object.entries(extraHeaders)) {
|
|
53
53
|
if (isContentLengthHeader(k)) continue;
|
|
@@ -97,17 +97,22 @@ async function resolveStaticAssetSignal(signalResponse, options) {
|
|
|
97
97
|
"content-type",
|
|
98
98
|
"transfer-encoding"
|
|
99
99
|
]);
|
|
100
|
+
const requestHeaders = readStaticFileSignalRequestHeaders(signalResponse);
|
|
100
101
|
cancelResponseBody(signalResponse);
|
|
101
|
-
const assetResponse = await options.fetchAsset(assetPath);
|
|
102
|
+
const assetResponse = await options.fetchAsset(assetPath, requestHeaders);
|
|
102
103
|
return mergeHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
|
|
103
104
|
}
|
|
104
|
-
/**
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Retarget a Worker asset request without dropping its conditional/range
|
|
107
|
+
* fields. `headers` replaces the source request's headers with middleware's
|
|
108
|
+
* overrides, so a removed `Range` or replaced validator reaches the asset fetch.
|
|
109
|
+
*/
|
|
110
|
+
function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
|
|
106
111
|
const assetUrl = new URL(assetPath, sourceRequest.url);
|
|
107
|
-
if (sourceRequest.method === "GET" || sourceRequest.method === "HEAD") return new Request(assetUrl, sourceRequest);
|
|
112
|
+
if (sourceRequest.method === "GET" || sourceRequest.method === "HEAD") return headers ? new Request(new Request(assetUrl, sourceRequest), { headers }) : new Request(assetUrl, sourceRequest);
|
|
108
113
|
return new Request(assetUrl, {
|
|
109
114
|
method: sourceRequest.method,
|
|
110
|
-
headers: sourceRequest.headers
|
|
115
|
+
headers: headers ?? sourceRequest.headers
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { disableSmoothScrollDuringRouteTransition } from "./disable-smooth-scroll.js";
|
|
2
3
|
import { decodeHashFragment } from "./hash-scroll.js";
|
|
3
4
|
import { consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted } from "./app-router-scroll-state.js";
|
|
4
5
|
import * as React$1 from "react";
|
|
@@ -110,7 +111,9 @@ var AppRouterScrollTargetInner = class extends React$1.Component {
|
|
|
110
111
|
const target = next.element;
|
|
111
112
|
const consumed = consumeAppRouterScrollIntent(intent, this.props.commitId);
|
|
112
113
|
if (consumed === null) return;
|
|
113
|
-
|
|
114
|
+
disableSmoothScrollDuringRouteTransition(() => {
|
|
115
|
+
scrollToElement(target, consumed.hash);
|
|
116
|
+
});
|
|
114
117
|
target.focus();
|
|
115
118
|
};
|
|
116
119
|
componentDidMount() {
|