vinext 1.4.1 → 1.4.3
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 +5 -2
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/build/prerender-paths.d.ts +11 -1
- package/dist/build/prerender-paths.js +36 -10
- package/dist/check.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/dev-error-overlay.d.ts +2 -1
- package/dist/client/dev-error-overlay.js +8 -3
- package/dist/config/config-matchers.js +8 -4
- package/dist/config/destination-params.js +5 -2
- package/dist/config/next-config.d.ts +25 -1
- package/dist/config/next-config.js +31 -1
- package/dist/entries/app-rsc-entry.js +65 -30
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +19 -4
- package/dist/index.js +37 -37
- package/dist/init-cloudflare.js +18 -2
- package/dist/init-platform.js +2 -2
- package/dist/init.js +42 -5
- package/dist/plugins/css-target.d.ts +18 -0
- package/dist/plugins/css-target.js +93 -0
- package/dist/routing/app-route-graph.d.ts +10 -0
- package/dist/routing/app-route-graph.js +15 -1
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +10 -8
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +37 -41
- package/dist/server/app-browser-error.d.ts +1 -1
- package/dist/server/app-browser-error.js +13 -4
- package/dist/server/app-browser-supplemental-refresh.d.ts +13 -0
- package/dist/server/app-browser-supplemental-refresh.js +36 -4
- package/dist/server/app-elements.d.ts +3 -0
- package/dist/server/app-elements.js +14 -1
- package/dist/server/app-fallback-renderer.js +2 -2
- package/dist/server/app-middleware.d.ts +2 -0
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.d.ts +12 -1
- package/dist/server/app-optimistic-routing.js +38 -16
- package/dist/server/app-page-boundary-render.js +4 -2
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +11 -1
- package/dist/server/app-page-dispatch.js +78 -31
- package/dist/server/app-page-element-builder.d.ts +11 -20
- package/dist/server/app-page-element-builder.js +39 -50
- package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
- package/dist/server/app-page-intercept-segment-config.js +90 -0
- package/dist/server/app-page-navigation-params.d.ts +23 -0
- package/dist/server/app-page-navigation-params.js +49 -0
- package/dist/server/app-page-render.d.ts +5 -2
- package/dist/server/app-page-render.js +17 -3
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +19 -13
- package/dist/server/app-page-segment-plan.js +9 -1
- package/dist/server/app-page-segment-state.d.ts +9 -0
- package/dist/server/app-page-segment-state.js +54 -1
- package/dist/server/app-pages-bridge.d.ts +6 -0
- package/dist/server/app-pages-bridge.js +8 -3
- package/dist/server/app-request-stage-independent-entry.js +2 -3
- package/dist/server/app-response-stage-entry.js +1 -0
- package/dist/server/app-route-module-loader.d.ts +2 -0
- package/dist/server/app-route-module-loader.js +1 -0
- package/dist/server/app-router-entry.js +2 -3
- package/dist/server/app-rsc-cache-busting.d.ts +8 -0
- package/dist/server/app-rsc-cache-busting.js +13 -1
- package/dist/server/app-rsc-combined-handler.js +2 -1
- package/dist/server/app-rsc-handler.d.ts +14 -1
- package/dist/server/app-rsc-handler.js +111 -62
- package/dist/server/app-rsc-response-finalizer.d.ts +18 -3
- package/dist/server/app-rsc-response-finalizer.js +61 -7
- package/dist/server/app-rsc-route-matching.d.ts +14 -0
- package/dist/server/app-rsc-route-matching.js +42 -18
- package/dist/server/app-server-action-execution.js +16 -10
- package/dist/server/app-ssr-entry.js +8 -0
- package/dist/server/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -1
- package/dist/server/config-headers.d.ts +7 -0
- package/dist/server/config-headers.js +20 -1
- package/dist/server/image-animation.d.ts +9 -0
- package/dist/server/image-animation.js +118 -0
- package/dist/server/image-local-patterns.d.ts +21 -0
- package/dist/server/image-local-patterns.js +59 -0
- package/dist/server/image-optimization.d.ts +48 -27
- package/dist/server/image-optimization.js +316 -22
- package/dist/server/image-source-reader.d.ts +34 -0
- package/dist/server/image-source-reader.js +126 -0
- package/dist/server/metadata-route-response.d.ts +6 -1
- package/dist/server/metadata-route-response.js +46 -10
- package/dist/server/middleware-matcher.d.ts +1 -0
- package/dist/server/middleware-matcher.js +1 -1
- package/dist/server/middleware-runtime.d.ts +5 -0
- package/dist/server/middleware-runtime.js +48 -15
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +2 -1
- package/dist/server/nitro-public-files-plugin.js +20 -8
- package/dist/server/nitro-request-target-plugin.d.ts +17 -0
- package/dist/server/nitro-request-target-plugin.js +22 -0
- package/dist/server/open-redirect.d.ts +2 -1
- package/dist/server/open-redirect.js +2 -1
- package/dist/server/pages-client-assets.d.ts +1 -0
- package/dist/server/pages-data-route.d.ts +5 -1
- package/dist/server/pages-data-route.js +14 -6
- package/dist/server/pages-i18n.d.ts +7 -0
- package/dist/server/pages-i18n.js +14 -1
- package/dist/server/pages-node-compat.js +7 -3
- package/dist/server/pages-page-handler.js +4 -4
- package/dist/server/pages-request-pipeline.d.ts +9 -0
- package/dist/server/pages-request-pipeline.js +35 -15
- package/dist/server/pages-request-stage-entry.js +8 -2
- package/dist/server/pages-router-entry.js +3 -0
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +64 -62
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +11 -1
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- package/dist/server/srvx-middleware-origin.d.ts +13 -0
- package/dist/server/srvx-middleware-origin.js +19 -0
- package/dist/server/srvx-request-target.d.ts +12 -0
- package/dist/server/srvx-request-target.js +20 -0
- package/dist/server/static-file-cache.js +10 -22
- package/dist/server/worker-image-optimization.d.ts +10 -3
- package/dist/server/worker-image-optimization.js +43 -9
- package/dist/server/worker-revalidation-context.js +1 -0
- package/dist/server/worker-utils.d.ts +8 -0
- package/dist/server/worker-utils.js +19 -2
- package/dist/shims/error-boundary.d.ts +6 -2
- package/dist/shims/error-boundary.js +4 -2
- package/dist/shims/image-props.d.ts +6 -0
- package/dist/shims/image-props.js +39 -3
- package/dist/shims/image.js +3 -3
- package/dist/shims/request-context.d.ts +3 -1
- package/dist/shims/slot.d.ts +3 -0
- package/dist/shims/slot.js +67 -9
- package/dist/shims/url-utils.js +6 -1
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- package/dist/utils/client-entry-manifest.d.ts +5 -0
- package/dist/utils/client-entry-manifest.js +1 -0
- package/dist/utils/client-runtime-metadata.d.ts +5 -0
- package/dist/utils/client-runtime-metadata.js +33 -7
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { preserveFullyBufferedBodyMetadata } from "./fully-buffered-response.js";
|
|
1
2
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
2
3
|
import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
|
|
3
4
|
import { sanitizeMethodNotAllowedHeaders } from "./http-error-responses.js";
|
|
@@ -6,12 +7,14 @@ import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
|
6
7
|
import { isNonCacheableCacheControl } from "../shims/cdn-cache.js";
|
|
7
8
|
import { captureRouteCacheabilityResponsePolicy } from "../shims/cacheability-classification.js";
|
|
8
9
|
import { NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, captureCdnResponsePolicyHeaders, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader } from "./cache-control.js";
|
|
9
|
-
import { hasPostConfigLinkHeaders } from "./app-response-header-provenance.js";
|
|
10
|
+
import { copyLinkHeaderProvenance, hasPostConfigLinkHeaders } from "./app-response-header-provenance.js";
|
|
10
11
|
import { mergeVaryHeader } from "./middleware-response-headers.js";
|
|
11
12
|
import "./app-rsc-cache-busting.js";
|
|
12
13
|
//#region src/server/app-rsc-response-finalizer.ts
|
|
13
14
|
const HAS_CONFIG_HEADERS = process.env.__VINEXT_HAS_CONFIG_HEADERS !== "false";
|
|
14
15
|
const configHeadersAlreadyApplied = /* @__PURE__ */ new WeakSet();
|
|
16
|
+
const applicationResponses = /* @__PURE__ */ new WeakSet();
|
|
17
|
+
const routeHandlerResponses = /* @__PURE__ */ new WeakMap();
|
|
15
18
|
function normalizeExplicitNonCacheablePolicy(headers) {
|
|
16
19
|
if (!hasExplicitNonCacheableResponsePolicy(headers)) return;
|
|
17
20
|
const cacheControl = headers.get("Cache-Control");
|
|
@@ -25,6 +28,48 @@ function markAppRscResponseConfigHeadersApplied(response) {
|
|
|
25
28
|
configHeadersAlreadyApplied.add(response);
|
|
26
29
|
return response;
|
|
27
30
|
}
|
|
31
|
+
const streamedActionRedirects = /* @__PURE__ */ new WeakSet();
|
|
32
|
+
/** Mark a Server Action redirect that streams its target's render, and so keeps its policy. */
|
|
33
|
+
function markStreamedActionRedirectResponse(response) {
|
|
34
|
+
streamedActionRedirects.add(response);
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
function isStreamedActionRedirectResponse(response) {
|
|
38
|
+
return streamedActionRedirects.has(response);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mark a response that a page, Route Handler, Server Action or Pages route
|
|
42
|
+
* produced. Next.js stages next.config headers before application code runs,
|
|
43
|
+
* so the redirects these return carry them. Redirects that routing answers
|
|
44
|
+
* itself (next.config redirects, trailing slash, middleware) go out without.
|
|
45
|
+
*/
|
|
46
|
+
function markAppRscApplicationResponse(response) {
|
|
47
|
+
applicationResponses.add(response);
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
/** Mark a response a Route Handler produced, so its own headers meet config as in Next.js. */
|
|
51
|
+
function markAppRscRouteHandlerResponse(response, runtime) {
|
|
52
|
+
routeHandlerResponses.set(response, runtime);
|
|
53
|
+
return markAppRscApplicationResponse(response);
|
|
54
|
+
}
|
|
55
|
+
/** Response.redirect() creates immutable headers; copy them only when needed. */
|
|
56
|
+
function withMutableHeaders(response) {
|
|
57
|
+
const probe = "x-vinext-mutable-headers-probe";
|
|
58
|
+
const probeValue = response.headers.get(probe);
|
|
59
|
+
try {
|
|
60
|
+
if (probeValue === null) response.headers.delete(probe);
|
|
61
|
+
else response.headers.set(probe, probeValue);
|
|
62
|
+
return response;
|
|
63
|
+
} catch {
|
|
64
|
+
const headers = new Headers(response.headers);
|
|
65
|
+
copyLinkHeaderProvenance(response.headers, headers);
|
|
66
|
+
return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
|
|
67
|
+
headers,
|
|
68
|
+
status: response.status,
|
|
69
|
+
statusText: response.statusText
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
28
73
|
/** Apply only the matching next.config headers for an App Router request. */
|
|
29
74
|
async function applyAppRscConfigHeaders(headers, request, options) {
|
|
30
75
|
if (!HAS_CONFIG_HEADERS || !options.configHeaders.length) return;
|
|
@@ -48,6 +93,7 @@ async function applyAppRscConfigHeaders(headers, request, options) {
|
|
|
48
93
|
appendToPostConfigLink: hasPostConfigLinkHeaders(headers),
|
|
49
94
|
middlewareHeaders: options.middlewareHeaders,
|
|
50
95
|
recordCacheability: options.recordCacheability,
|
|
96
|
+
routeHandlerRuntime: options.routeHandlerRuntime,
|
|
51
97
|
overwriteExisting: options.overwriteExisting ?? isCdnResponsePolicyHeader
|
|
52
98
|
});
|
|
53
99
|
}
|
|
@@ -59,12 +105,16 @@ async function applyAppRscConfigHeaders(headers, request, options) {
|
|
|
59
105
|
* handling, so that every response path (page, route handler, server action,
|
|
60
106
|
* metadata, not-found) gets headers applied consistently.
|
|
61
107
|
*
|
|
62
|
-
* Skips 3xx
|
|
63
|
-
*
|
|
64
|
-
*
|
|
108
|
+
* Skips 3xx responses that routing answered itself, as Next.js does not
|
|
109
|
+
* apply config headers to those. A 3xx that application code returned is
|
|
110
|
+
* finalized like any other response.
|
|
65
111
|
*/
|
|
66
112
|
async function finalizeAppRscResponse(response, request, options) {
|
|
67
|
-
|
|
113
|
+
const routeHandlerRuntime = routeHandlerResponses.get(response);
|
|
114
|
+
if (response.status >= 300 && response.status < 400) {
|
|
115
|
+
if (!applicationResponses.has(response) || configHeadersAlreadyApplied.has(response)) return response;
|
|
116
|
+
response = withMutableHeaders(response);
|
|
117
|
+
}
|
|
68
118
|
if (!isStaticFileSignal(response)) {
|
|
69
119
|
const varyHeader = response.headers.get("Vary");
|
|
70
120
|
if (varyHeader === null) response.headers.set("Vary", VINEXT_RSC_VARY_HEADER);
|
|
@@ -73,15 +123,19 @@ async function finalizeAppRscResponse(response, request, options) {
|
|
|
73
123
|
if (!response.headers.has("Cache-Control")) {
|
|
74
124
|
applyCdnResponseHeaders(response.headers, { cacheControl: "" });
|
|
75
125
|
captureRouteCacheabilityResponsePolicy(captureCdnResponsePolicyHeaders(response.headers));
|
|
126
|
+
if (isStaticFileSignal(response)) response.headers.delete("Cache-Control");
|
|
76
127
|
}
|
|
77
128
|
if (configHeadersAlreadyApplied.has(response)) {
|
|
78
129
|
normalizeExplicitNonCacheablePolicy(response.headers);
|
|
79
130
|
return response;
|
|
80
131
|
}
|
|
81
|
-
await applyAppRscConfigHeaders(response.headers, request,
|
|
132
|
+
await applyAppRscConfigHeaders(response.headers, request, {
|
|
133
|
+
...options,
|
|
134
|
+
routeHandlerRuntime
|
|
135
|
+
});
|
|
82
136
|
normalizeExplicitNonCacheablePolicy(response.headers);
|
|
83
137
|
if (response.status === 405 && response.headers.get("Allow") === "GET, HEAD") sanitizeMethodNotAllowedHeaders(response.headers, "GET, HEAD");
|
|
84
138
|
return response;
|
|
85
139
|
}
|
|
86
140
|
//#endregion
|
|
87
|
-
export { applyAppRscConfigHeaders, finalizeAppRscResponse, markAppRscResponseConfigHeadersApplied };
|
|
141
|
+
export { applyAppRscConfigHeaders, finalizeAppRscResponse, isStreamedActionRedirectResponse, markAppRscApplicationResponse, markAppRscResponseConfigHeadersApplied, markAppRscRouteHandlerResponse, markStreamedActionRedirectResponse };
|
|
@@ -101,6 +101,7 @@ type AppRscRouteForMatching = {
|
|
|
101
101
|
pattern: string;
|
|
102
102
|
patternParts: string[];
|
|
103
103
|
routeHandler?: unknown;
|
|
104
|
+
routeSegments?: readonly string[] | null;
|
|
104
105
|
slots?: Record<string, AppRscSlotForMatching>;
|
|
105
106
|
siblingIntercepts?: AppRscSiblingInterceptForMatching[];
|
|
106
107
|
};
|
|
@@ -108,6 +109,14 @@ type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
|
|
|
108
109
|
matchedParams: AppRscRouteParams;
|
|
109
110
|
sourceRouteIsConcrete: boolean;
|
|
110
111
|
sourceMatchedParams: AppRscRouteParams;
|
|
112
|
+
/**
|
|
113
|
+
* The source route and params the request's interception context names,
|
|
114
|
+
* which the request handler authorizes. They differ from the rendered
|
|
115
|
+
* `sourceRouteIndex` only for a sibling intercept opened from a deeper
|
|
116
|
+
* source, which renders the intercepting route itself.
|
|
117
|
+
*/
|
|
118
|
+
authorizationSourceRouteIndex: number;
|
|
119
|
+
authorizationSourceParams: AppRscRouteParams;
|
|
111
120
|
};
|
|
112
121
|
type AppRscInterceptLoadState = {
|
|
113
122
|
page: unknown;
|
|
@@ -120,6 +129,11 @@ type AppRscInterceptLookupEntry = {
|
|
|
120
129
|
interceptionId: string | null;
|
|
121
130
|
interceptionGraphId: string | null;
|
|
122
131
|
sourceRouteIndex: number;
|
|
132
|
+
/**
|
|
133
|
+
* A sibling intercept whose owner route's page sits in the directory of the
|
|
134
|
+
* interception marker, so the owner is the intercepting route itself.
|
|
135
|
+
*/
|
|
136
|
+
ownerIsInterceptingRoute: boolean;
|
|
123
137
|
slotKey: string;
|
|
124
138
|
targetPattern: string;
|
|
125
139
|
targetPatternParts: string[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments
|
|
1
|
+
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments } from "../routing/utils.js";
|
|
2
2
|
import { createAppRouteGraphInterceptionId } from "../routing/app-route-ids.js";
|
|
3
3
|
import { buildRouteTrie, trieMatchRaw } from "../routing/route-trie.js";
|
|
4
4
|
import { extractRawRoutePatternParams, matchRoutePatternPrefix, matchRoutePatternRaw } from "../routing/route-pattern.js";
|
|
5
|
+
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
5
6
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
6
7
|
//#region src/server/app-rsc-route-matching.ts
|
|
7
8
|
/**
|
|
@@ -13,10 +14,19 @@ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
|
|
|
13
14
|
function createRouteParams() {
|
|
14
15
|
return Object.create(null);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Whether each encoded raw segment is exactly the spelling the URL parser, as
|
|
19
|
+
* a browser runs it, gives its decoded form: `caf%C3%A9` for `café`, but `q&a`
|
|
20
|
+
* for `q&a`. `%61dmin`, `caf%c3%a9`, `%63af%C3%A9` and `q%26a` are other
|
|
21
|
+
* spellings of a page, which middleware never compared as its pathname.
|
|
22
|
+
*/
|
|
23
|
+
function isCanonicalPathEncoding(rawParts, decodedParts) {
|
|
24
|
+
return rawParts.every((rawPart, index) => !rawPart.includes("%") || rawPart === new URL(`/${decodedParts[index]}`, "http://n").pathname.slice(1));
|
|
25
|
+
}
|
|
26
|
+
function appRscPathnameParts(pathname) {
|
|
17
27
|
const pathOnly = pathname.split("?")[0];
|
|
18
28
|
const normalizedPathname = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
|
|
19
|
-
return
|
|
29
|
+
return splitPathSegments(normalizedPathname);
|
|
20
30
|
}
|
|
21
31
|
function isAppRouteHandlerRoute(route) {
|
|
22
32
|
return route.routeHandler != null || typeof route.__loadRouteHandler === "function";
|
|
@@ -49,8 +59,10 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
49
59
|
const result = matchRawRequestParts(rawParts);
|
|
50
60
|
if (!result) return null;
|
|
51
61
|
if (decodedPathnameRoutes.size > 0 && result.route.patternParts.some((part) => part.startsWith(":")) && !isAppRouteHandlerRoute(result.route) && rawParts.some((part) => part.includes("%"))) {
|
|
52
|
-
const
|
|
53
|
-
|
|
62
|
+
const decodedParts = rawParts.map(decodeRouteSegment);
|
|
63
|
+
const decodedPathname = decodedParts.join("/");
|
|
64
|
+
const route = decodedPathnameRoutes.get(decodedPathname);
|
|
65
|
+
if (route && route !== result.route && (isCanonicalPathEncoding(rawParts, decodedParts) || getRenderedConcreteUrlPathsForRoute(route.pattern)?.has(`/${decodedPathname}`) === true)) return {
|
|
54
66
|
route,
|
|
55
67
|
params: createRouteParams()
|
|
56
68
|
};
|
|
@@ -62,23 +74,18 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
62
74
|
return interceptionIds.has(interceptionId);
|
|
63
75
|
},
|
|
64
76
|
matchRoute(url) {
|
|
65
|
-
|
|
66
|
-
const result = trieMatchRaw(routeTrie, appRscPathnameParts(url, false));
|
|
67
|
-
if (!result) return null;
|
|
68
|
-
result.params = extractRawRoutePatternParams(result.route.patternParts, rawParts);
|
|
69
|
-
normalizeMatchedParamsForRoute(result);
|
|
70
|
-
return result;
|
|
77
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
71
78
|
},
|
|
72
79
|
matchRequestRoute(url) {
|
|
73
|
-
return matchRequestParts(appRscPathnameParts(url
|
|
80
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
74
81
|
},
|
|
75
82
|
matchRawRequestRoute(url) {
|
|
76
|
-
return matchRawRequestParts(appRscPathnameParts(url
|
|
83
|
+
return matchRawRequestParts(appRscPathnameParts(url));
|
|
77
84
|
},
|
|
78
85
|
findIntercept(pathname, sourcePathname = null, interceptionId = null) {
|
|
79
86
|
if (sourcePathname === null) return null;
|
|
80
|
-
const urlParts = appRscPathnameParts(pathname
|
|
81
|
-
const sourceParts = appRscPathnameParts(sourcePathname
|
|
87
|
+
const urlParts = appRscPathnameParts(pathname);
|
|
88
|
+
const sourceParts = appRscPathnameParts(sourcePathname);
|
|
82
89
|
const matchedSourceRoute = matchRawRequestParts(sourceParts);
|
|
83
90
|
for (const entry of interceptLookup) {
|
|
84
91
|
if (interceptionId !== null && entry.interceptionId !== interceptionId) continue;
|
|
@@ -92,15 +99,21 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
92
99
|
if (sourceRoute && isAppRouteHandlerRoute(sourceRoute)) continue;
|
|
93
100
|
const matchedSourceParams = concreteSourceRoute ? concreteSourceRoute.params : sourceRoute ? matchSlotOwnerSourceParams(sourceParts, sourceRoute.patternParts, entry.sourceMatchPatternParts !== null) : null;
|
|
94
101
|
if (!concreteSourceRoute && matchedSourceParams) canonicalizeAppPageParams(matchedSourceParams);
|
|
102
|
+
const ownerRoute = routes[entry.sourceRouteIndex];
|
|
103
|
+
const rendersOwnerRoute = entry.ownerIsInterceptingRoute && concreteSourceRouteIndex !== entry.sourceRouteIndex && ownerRoute !== void 0;
|
|
104
|
+
const renderedSourceParams = rendersOwnerRoute ? matchSlotOwnerSourceParams(sourceParts, ownerRoute.patternParts, true) : matchedSourceParams;
|
|
105
|
+
if (rendersOwnerRoute && renderedSourceParams) canonicalizeAppPageParams(renderedSourceParams);
|
|
95
106
|
if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
|
|
96
|
-
const sourceParams =
|
|
107
|
+
const sourceParams = renderedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(renderedSourceParams, entry.sourceMatchPatternParts) : renderedSourceParams ?? createRouteParams();
|
|
97
108
|
return {
|
|
98
109
|
...entry,
|
|
99
110
|
page: entry.__loadState.page,
|
|
100
111
|
sourceRouteIsConcrete: concreteSourceRoute !== null,
|
|
101
|
-
sourceRouteIndex: concreteSourceRouteIndex,
|
|
112
|
+
sourceRouteIndex: rendersOwnerRoute ? entry.sourceRouteIndex : concreteSourceRouteIndex,
|
|
102
113
|
matchedParams: mergeMatchedParams(sourceParams, params),
|
|
103
|
-
sourceMatchedParams:
|
|
114
|
+
sourceMatchedParams: renderedSourceParams ?? createRouteParams(),
|
|
115
|
+
authorizationSourceRouteIndex: concreteSourceRouteIndex,
|
|
116
|
+
authorizationSourceParams: matchedSourceParams ?? createRouteParams()
|
|
104
117
|
};
|
|
105
118
|
}
|
|
106
119
|
return null;
|
|
@@ -108,6 +121,15 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
124
|
+
* Whether a sibling intercept's owner route is the intercepting route: its
|
|
125
|
+
* page lives in the directory that holds the interception marker. An owner
|
|
126
|
+
* found elsewhere stands in for a marker directory without a page.
|
|
127
|
+
*/
|
|
128
|
+
function isInterceptingRoute(routeSegments, sourcePageSegments) {
|
|
129
|
+
if (!routeSegments || !sourcePageSegments) return false;
|
|
130
|
+
return sourcePageSegments.findIndex((segment) => /^\(\.+\)/.test(segment)) === routeSegments.length && routeSegments.every((segment, index) => segment === sourcePageSegments[index]);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
111
133
|
* Params for the slot owner when interception falls back to it instead of a
|
|
112
134
|
* concrete descendant source route. The owner is what renders, and
|
|
113
135
|
* `matchInterceptRoute` reads the promoted route's params solely from these,
|
|
@@ -195,6 +217,7 @@ function createInterceptLookup(routes) {
|
|
|
195
217
|
interceptionId: typeof slotModule.id === "string" && sourceMatchPattern !== null ? createAppRouteGraphInterceptionId(slotModule.id, sourceMatchPattern, intercept.targetPattern) : null,
|
|
196
218
|
interceptionGraphId: null,
|
|
197
219
|
sourceRouteIndex: ownerRouteIndex,
|
|
220
|
+
ownerIsInterceptingRoute: false,
|
|
198
221
|
slotKey,
|
|
199
222
|
slotId: typeof slotModule.id === "string" ? slotModule.id : null,
|
|
200
223
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -246,6 +269,7 @@ function createInterceptLookup(routes) {
|
|
|
246
269
|
interceptionId: typeof intercept.id === "string" ? intercept.id : null,
|
|
247
270
|
interceptionGraphId: typeof intercept.id === "string" ? intercept.id : null,
|
|
248
271
|
sourceRouteIndex: routeIndex,
|
|
272
|
+
ownerIsInterceptingRoute: !isAppRouteHandlerRoute(route) && isInterceptingRoute(route.routeSegments, intercept.sourcePageSegments),
|
|
249
273
|
slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
|
|
250
274
|
slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
|
|
251
275
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -12,6 +12,7 @@ import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayloa
|
|
|
12
12
|
import { splitPathSegments } from "../routing/utils.js";
|
|
13
13
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
14
14
|
import { logRequestError } from "./request-error-log.js";
|
|
15
|
+
import { SERVER_ACTION_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
|
|
15
16
|
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
16
17
|
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
17
18
|
import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
|
|
@@ -21,17 +22,18 @@ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
|
21
22
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
22
23
|
import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, createRscRequestUrl, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
23
24
|
import { getSetCookieName } from "./cookie-utils.js";
|
|
25
|
+
import { markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
|
|
24
26
|
import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
|
|
25
27
|
import { applyEdgeRuntimeHeader } from "./app-page-response.js";
|
|
26
28
|
import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
|
|
27
|
-
import {
|
|
28
|
-
import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
|
|
29
|
+
import { resolveAppPageNavigationParams } from "./app-page-navigation-params.js";
|
|
29
30
|
import "./app-page-stream.js";
|
|
31
|
+
import { hasDigest } from "./app-rsc-errors.js";
|
|
30
32
|
import { buildAppPageTags } from "./implicit-tags.js";
|
|
33
|
+
import { resolveAppPageInterceptSegmentConfig } from "./app-page-intercept-segment-config.js";
|
|
31
34
|
import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
|
|
32
35
|
import { createRootParamsUsageController, runWithRootParamsUsage } from "../shims/root-params.js";
|
|
33
36
|
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
34
|
-
import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-finalizer.js";
|
|
35
37
|
//#region src/server/app-server-action-execution.ts
|
|
36
38
|
/**
|
|
37
39
|
* Run a server-reference load or decode outside every request scope.
|
|
@@ -659,7 +661,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
659
661
|
options.clearRequestContext();
|
|
660
662
|
const headers = new Headers();
|
|
661
663
|
mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
|
|
662
|
-
headers.set("Cache-Control",
|
|
664
|
+
headers.set("Cache-Control", SERVER_ACTION_CACHE_CONTROL);
|
|
663
665
|
for (const cookie of actionPendingCookies) headers.append("Set-Cookie", cookie);
|
|
664
666
|
if (actionDraftCookie) headers.append("Set-Cookie", actionDraftCookie);
|
|
665
667
|
return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action failed: " + getServerActionFailureMessage(actionError), { headers });
|
|
@@ -879,6 +881,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
879
881
|
applyEdgeRuntimeHeader(redirectHeaders, options.isEdgeRuntime);
|
|
880
882
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.sourceConfigHeaders ?? null);
|
|
881
883
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.middlewareHeaders);
|
|
884
|
+
applyCdnResponseHeaders(redirectHeaders, { cacheControl: SERVER_ACTION_CACHE_CONTROL });
|
|
882
885
|
applyRscCompatibilityIdHeader(redirectHeaders);
|
|
883
886
|
const actionRedirectUrl = applyActionRedirectBasePath(actionRedirect.url, options.basePath ?? "");
|
|
884
887
|
redirectHeaders.set(ACTION_REDIRECT_HEADER, actionRedirectUrl);
|
|
@@ -938,10 +941,10 @@ async function handleServerActionRscRequest(options) {
|
|
|
938
941
|
targetPathname,
|
|
939
942
|
targetRoute: targetMatch?.route ?? null
|
|
940
943
|
}) ? 200 : 303;
|
|
941
|
-
return markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
944
|
+
return markStreamedActionRedirectResponse(markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
942
945
|
status: redirectResponseStatus,
|
|
943
946
|
headers: redirectHeaders
|
|
944
|
-
}, options.clearRequestContext));
|
|
947
|
+
}, options.clearRequestContext)));
|
|
945
948
|
}
|
|
946
949
|
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
947
950
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
@@ -976,6 +979,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
976
979
|
let element;
|
|
977
980
|
let errorPattern = match ? match.route.pattern : options.cleanPathname;
|
|
978
981
|
let renderedPathAndSearch = null;
|
|
982
|
+
let actionRerenderIsEdgeRuntime = options.isEdgeRuntime;
|
|
979
983
|
const actionRerenderIsRscRequest = true;
|
|
980
984
|
if (match) {
|
|
981
985
|
const { route: actionRoute, params: actionParams } = match;
|
|
@@ -991,7 +995,9 @@ async function handleServerActionRscRequest(options) {
|
|
|
991
995
|
});
|
|
992
996
|
const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
|
|
993
997
|
await options.ensureRouteLoaded?.(actionRerenderTarget.route);
|
|
994
|
-
const
|
|
998
|
+
const actionInterceptConfig = actionRerenderTarget.interceptOpts ? resolveAppPageInterceptSegmentConfig(actionRerenderTarget.route, actionRerenderTarget.interceptOpts) : null;
|
|
999
|
+
if (actionInterceptConfig) actionRerenderIsEdgeRuntime = actionInterceptConfig.isEdgeRuntime;
|
|
1000
|
+
const actionRerenderDynamicConfig = actionInterceptConfig ? actionInterceptConfig.dynamicConfig : options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
|
|
995
1001
|
const actionRerenderSearchParams = prepareActionPageRerenderContext({
|
|
996
1002
|
draftModeCookie: actionDraftCookie,
|
|
997
1003
|
draftModeSecret: options.draftModeSecret,
|
|
@@ -1007,8 +1013,8 @@ async function handleServerActionRscRequest(options) {
|
|
|
1007
1013
|
});
|
|
1008
1014
|
const renderedSearch = options.searchParams.toString();
|
|
1009
1015
|
renderedPathAndSearch = renderedSearch ? `${options.cleanPathname}?${renderedSearch}` : options.cleanPathname;
|
|
1010
|
-
setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1011
|
-
setCurrentFetchRevalidate(options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1016
|
+
setCurrentFetchCacheMode(actionInterceptConfig ? actionInterceptConfig.fetchCacheMode : options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1017
|
+
setCurrentFetchRevalidate(actionInterceptConfig ? actionInterceptConfig.revalidateSeconds : options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1012
1018
|
setCurrentForceDynamicFetchDefault(actionRerenderDynamicConfig === "force-dynamic");
|
|
1013
1019
|
setCurrentFetchSoftTags(buildServerActionPageTags(actionRerenderTarget.route, options.cleanPathname));
|
|
1014
1020
|
const buildActionRerenderElement = () => options.buildPageElement({
|
|
@@ -1047,7 +1053,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
1047
1053
|
"Content-Type": VINEXT_RSC_CONTENT_TYPE,
|
|
1048
1054
|
Vary: VINEXT_RSC_VARY_HEADER
|
|
1049
1055
|
});
|
|
1050
|
-
applyEdgeRuntimeHeader(actionHeaders,
|
|
1056
|
+
applyEdgeRuntimeHeader(actionHeaders, actionRerenderIsEdgeRuntime);
|
|
1051
1057
|
mergeMiddlewareResponseHeaders(actionHeaders, options.middlewareHeaders);
|
|
1052
1058
|
applyRscCompatibilityIdHeader(actionHeaders);
|
|
1053
1059
|
setActionRevalidatedHeader(actionHeaders, actionRevalidationKind);
|
|
@@ -32,6 +32,7 @@ import { createRenderStreamCancellation, isAppRenderAbortError } from "./app-ren
|
|
|
32
32
|
import { runWithRootParamsScope } from "../shims/root-params.js";
|
|
33
33
|
import { runWithNavigationContext } from "../shims/navigation-state.js";
|
|
34
34
|
import { startCandidateSearchParamsGate } from "./app-ssr-search-params-gate.js";
|
|
35
|
+
import { isSrvxAbsoluteFormTarget } from "./srvx-request-target.js";
|
|
35
36
|
import { withAppRouterTree } from "../shims/app-router-tree-context.js";
|
|
36
37
|
import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
|
|
37
38
|
import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
|
|
@@ -230,6 +231,12 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
230
231
|
fetchPriority: "low",
|
|
231
232
|
nonce: options?.scriptNonce
|
|
232
233
|
});
|
|
234
|
+
for (const moduleUrl of pagesClientAssets.appBootstrapPreloadModules ?? []) preloadModule(moduleUrl, {
|
|
235
|
+
as: "script",
|
|
236
|
+
crossOrigin: assetCrossOrigin,
|
|
237
|
+
fetchPriority: "low",
|
|
238
|
+
nonce: options?.scriptNonce
|
|
239
|
+
});
|
|
233
240
|
if (pagesClientAssets.appRouteManifestModule) preloadModule(pagesClientAssets.appRouteManifestModule, {
|
|
234
241
|
as: "script",
|
|
235
242
|
crossOrigin: assetCrossOrigin,
|
|
@@ -420,6 +427,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
420
427
|
});
|
|
421
428
|
}
|
|
422
429
|
var app_ssr_entry_default = { async fetch(request) {
|
|
430
|
+
if (isSrvxAbsoluteFormTarget(request)) return notFoundResponse();
|
|
423
431
|
const url = new URL(request.url);
|
|
424
432
|
const slashRedirect = repeatedSlashRedirectResponse(url.pathname + url.search);
|
|
425
433
|
if (slashRedirect) return slashRedirect;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CdnCacheableHeaderInput, isNonCacheableCacheControl } from "../shims/cdn-cache.js";
|
|
2
2
|
//#region src/server/cache-control.d.ts
|
|
3
3
|
export declare const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
4
|
+
/** The policy Next.js sends on every Server Action response. */
|
|
5
|
+
export declare const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
|
|
4
6
|
export declare const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
|
|
5
7
|
export declare const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
|
|
6
8
|
export declare const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
|
|
@@ -2,6 +2,8 @@ import { getCdnCacheAdapter, isNonCacheableCacheControl } from "../shims/cdn-cac
|
|
|
2
2
|
import { recordRouteCacheabilityCdnTags } from "../shims/cacheability-classification.js";
|
|
3
3
|
//#region src/server/cache-control.ts
|
|
4
4
|
const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
5
|
+
/** The policy Next.js sends on every Server Action response. */
|
|
6
|
+
const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
|
|
5
7
|
const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
|
|
6
8
|
const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
|
|
7
9
|
const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
|
|
@@ -167,4 +169,4 @@ function buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expire
|
|
|
167
169
|
return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
|
|
168
170
|
}
|
|
169
171
|
//#endregion
|
|
170
|
-
export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
|
|
172
|
+
export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, SERVER_ACTION_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
|
|
@@ -3,6 +3,7 @@ import { RequestContext } from "../config/request-context.js";
|
|
|
3
3
|
import { BasePathMatchState } from "../config/config-matchers.js";
|
|
4
4
|
import { HeaderRecord } from "./request-pipeline.js";
|
|
5
5
|
//#region src/server/config-headers.d.ts
|
|
6
|
+
export type RouteHandlerRuntime = "node" | "edge";
|
|
6
7
|
export type ResponseStageCachePolicyOptions = {
|
|
7
8
|
basePathState?: BasePathMatchState;
|
|
8
9
|
configHeaders: NextHeader[];
|
|
@@ -34,6 +35,12 @@ type ApplyConfigHeadersOptions = {
|
|
|
34
35
|
middlewareHeaders?: Headers | null;
|
|
35
36
|
/** Whether this composition participates in response-cache admission. */
|
|
36
37
|
recordCacheability?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Set when a Route Handler produced the response. Next.js copies a Node
|
|
40
|
+
* handler's headers only where config left none, and appends an edge
|
|
41
|
+
* handler's after config. Cache policy headers keep their own rules.
|
|
42
|
+
*/
|
|
43
|
+
routeHandlerRuntime?: RouteHandlerRuntime;
|
|
37
44
|
};
|
|
38
45
|
/** Apply matched next.config.js headers to a Web Headers object. */
|
|
39
46
|
export declare function applyConfigHeadersToResponse(responseHeaders: Headers, options: ApplyConfigHeadersOptions): void;
|
|
@@ -5,6 +5,15 @@ import { isCdnResponsePolicyHeader, isNonCacheableCdnResponsePolicy } from "./ca
|
|
|
5
5
|
import { mergeVaryHeader } from "./middleware-response-headers.js";
|
|
6
6
|
//#region src/server/config-headers.ts
|
|
7
7
|
const ADDITIVE_CONFIG_HEADER_NAMES = /* @__PURE__ */ new Set(["set-cookie", "vary"]);
|
|
8
|
+
const ROUTE_HANDLER_APPENDED_HEADER_NAMES = /* @__PURE__ */ new Set(["www-authenticate", "proxy-authenticate"]);
|
|
9
|
+
/**
|
|
10
|
+
* Next.js sets x-nextjs-cache over config only on the responses its cache
|
|
11
|
+
* manages, which vinext marks with x-vinext-cache too. A Route Handler's own
|
|
12
|
+
* x-nextjs-cache meets config like any other header.
|
|
13
|
+
*/
|
|
14
|
+
function isFrameworkCacheStateHeader(headers, lowerName) {
|
|
15
|
+
return lowerName === "X-Vinext-Cache".toLowerCase() || lowerName === "x-nextjs-cache" && headers.has("X-Vinext-Cache");
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* Resolve positive config cache policy that must accompany an inner artifact.
|
|
10
19
|
* The request stage evaluates every condition. Shared stage transports must key
|
|
@@ -77,6 +86,7 @@ function applyConfigHeadersToResponse(responseHeaders, options) {
|
|
|
77
86
|
const matched = retainLastSingularConfigValues(matchHeaders(options.pathname, options.configHeaders, options.requestContext, options.basePathState, options.recordCacheability === false ? void 0 : markConditionalConfigHeaderCacheability));
|
|
78
87
|
if (options.recordCacheability !== false) markExplicitConfigResponseVeto(matched);
|
|
79
88
|
const configCookies = [];
|
|
89
|
+
const configVary = [];
|
|
80
90
|
for (const header of matched) {
|
|
81
91
|
const lowerName = header.key.toLowerCase();
|
|
82
92
|
if (lowerName === "x-vinext-special-error-status") continue;
|
|
@@ -86,9 +96,18 @@ function applyConfigHeadersToResponse(responseHeaders, options) {
|
|
|
86
96
|
const postConfigLink = options.appendToPostConfigLink ? responseHeaders.get(lowerName) : null;
|
|
87
97
|
responseHeaders.set(header.key, postConfigLink ? `${header.value}, ${postConfigLink}` : header.value);
|
|
88
98
|
} else if (!ADDITIVE_CONFIG_HEADER_NAMES.has(lowerName) && options.middlewareHeaders?.has(lowerName)) continue;
|
|
89
|
-
else if (lowerName === "vary")
|
|
99
|
+
else if (lowerName === "vary") configVary.push(header.value);
|
|
90
100
|
else if (ADDITIVE_CONFIG_HEADER_NAMES.has(lowerName)) responseHeaders.append(header.key, header.value);
|
|
91
101
|
else if ((typeof options.overwriteExisting === "function" ? options.overwriteExisting(lowerName) : options.overwriteExisting?.has(lowerName)) || !responseHeaders.has(lowerName)) responseHeaders.set(header.key, header.value);
|
|
102
|
+
else if (options.routeHandlerRuntime && !isFrameworkCacheStateHeader(responseHeaders, lowerName) && !isCdnResponsePolicyHeader(lowerName)) {
|
|
103
|
+
const handlerValue = responseHeaders.get(lowerName);
|
|
104
|
+
responseHeaders.set(header.key, handlerValue !== header.value && (options.routeHandlerRuntime === "edge" || ROUTE_HANDLER_APPENDED_HEADER_NAMES.has(lowerName)) ? `${header.value}, ${handlerValue}` : header.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (configVary.length > 0) {
|
|
108
|
+
const routeHandlerVary = options.routeHandlerRuntime ? responseHeaders.get("vary") : null;
|
|
109
|
+
if (routeHandlerVary) responseHeaders.delete("vary");
|
|
110
|
+
for (const value of [...configVary, ...routeHandlerVary ? [routeHandlerVary] : []]) mergeVaryHeader(responseHeaders, value);
|
|
92
111
|
}
|
|
93
112
|
if (configCookies.length > 0 && !options.middlewareHeaders?.get("set-cookie")) {
|
|
94
113
|
const responseCookies = responseHeaders.getSetCookie();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ImageSourceReader } from "./image-source-reader.js";
|
|
2
|
+
//#region src/server/image-animation.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether a source is an animated GIF, PNG or WebP, judged by its signature.
|
|
5
|
+
* The source is parsed as it streams in, so only the bytes a format's check
|
|
6
|
+
* needs are read, and none are kept beyond what the reader holds to replay.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isAnimatedImage(source: ImageSourceReader): Promise<boolean>;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/server/image-animation.ts
|
|
2
|
+
const PNG_SIGNATURE = [
|
|
3
|
+
137,
|
|
4
|
+
80,
|
|
5
|
+
78,
|
|
6
|
+
71,
|
|
7
|
+
13,
|
|
8
|
+
10,
|
|
9
|
+
26,
|
|
10
|
+
10
|
|
11
|
+
];
|
|
12
|
+
const WEBP_FOURCC = [
|
|
13
|
+
87,
|
|
14
|
+
69,
|
|
15
|
+
66,
|
|
16
|
+
80
|
|
17
|
+
];
|
|
18
|
+
const WEBP_ANIM_CHUNK = [
|
|
19
|
+
65,
|
|
20
|
+
78,
|
|
21
|
+
73,
|
|
22
|
+
77
|
|
23
|
+
];
|
|
24
|
+
function bytesAt(bytes, offset, expected) {
|
|
25
|
+
return expected.every((byte, index) => bytes[offset + index] === byte);
|
|
26
|
+
}
|
|
27
|
+
function ascii(bytes, start, end) {
|
|
28
|
+
return String.fromCharCode(...bytes.subarray(start, end));
|
|
29
|
+
}
|
|
30
|
+
/** Length of a run of GIF data sub-blocks, including its zero terminator. */
|
|
31
|
+
async function gifDataBlocksLength(source, offset) {
|
|
32
|
+
let length = 0;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const size = await source.byte(offset + length) ?? 0;
|
|
35
|
+
if (size === 0) return length + 1;
|
|
36
|
+
length += size + 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function isAnimatedGif(source, head) {
|
|
40
|
+
let hasColorTable = head[10] & 128;
|
|
41
|
+
let colorTableSize = head[10] & 7;
|
|
42
|
+
let offset = 13 + (hasColorTable ? 3 * 2 ** (colorTableSize + 1) : 0);
|
|
43
|
+
let frames = 0;
|
|
44
|
+
while (frames < 2) switch (await source.byte(offset) ?? 59) {
|
|
45
|
+
case 44: {
|
|
46
|
+
frames += 1;
|
|
47
|
+
const flags = await source.byte(offset + 9) ?? 0;
|
|
48
|
+
hasColorTable = flags & 128;
|
|
49
|
+
colorTableSize = flags & 7;
|
|
50
|
+
offset += 10 + (hasColorTable ? 3 * 2 ** (colorTableSize + 1) : 0);
|
|
51
|
+
offset += await gifDataBlocksLength(source, offset + 1) + 1;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 33:
|
|
55
|
+
offset += 2;
|
|
56
|
+
offset += await gifDataBlocksLength(source, offset);
|
|
57
|
+
break;
|
|
58
|
+
default: return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/** An APNG has an `acTL` chunk before its first `IDAT`, and `fdAT` frames after. */
|
|
63
|
+
async function isAnimatedPng(source) {
|
|
64
|
+
let hasAnimationControl = false;
|
|
65
|
+
let hasImageData = false;
|
|
66
|
+
let hasFrameData = false;
|
|
67
|
+
let previousChunk = null;
|
|
68
|
+
let offset = 8;
|
|
69
|
+
for (;;) {
|
|
70
|
+
const header = await source.bytes(offset, 8);
|
|
71
|
+
if (header.byteLength < 8) break;
|
|
72
|
+
const length = new DataView(header.buffer).getUint32(0);
|
|
73
|
+
const chunk = ascii(header, 4, 8);
|
|
74
|
+
switch (chunk) {
|
|
75
|
+
case "acTL":
|
|
76
|
+
hasAnimationControl = true;
|
|
77
|
+
break;
|
|
78
|
+
case "IDAT":
|
|
79
|
+
if (!hasAnimationControl) return false;
|
|
80
|
+
if (previousChunk !== "fcTL" && previousChunk !== "IDAT") return false;
|
|
81
|
+
hasImageData = true;
|
|
82
|
+
break;
|
|
83
|
+
case "fdAT":
|
|
84
|
+
if (!hasImageData) return false;
|
|
85
|
+
if (previousChunk !== "fcTL" && previousChunk !== "fdAT") return false;
|
|
86
|
+
hasFrameData = true;
|
|
87
|
+
}
|
|
88
|
+
previousChunk = chunk;
|
|
89
|
+
offset += 12 + length;
|
|
90
|
+
}
|
|
91
|
+
return hasAnimationControl && hasImageData && hasFrameData;
|
|
92
|
+
}
|
|
93
|
+
/** Bytes of a WebP source searched per read. */
|
|
94
|
+
const WEBP_SCAN_STEP = 65536;
|
|
95
|
+
/** Like is-animated, any `ANIM` in a WebP source marks it animated. */
|
|
96
|
+
async function isAnimatedWebp(source) {
|
|
97
|
+
const first = WEBP_ANIM_CHUNK[0];
|
|
98
|
+
const readLength = WEBP_SCAN_STEP + WEBP_ANIM_CHUNK.length - 1;
|
|
99
|
+
for (let start = 0;; start += WEBP_SCAN_STEP) {
|
|
100
|
+
const bytes = await source.bytes(start, readLength);
|
|
101
|
+
for (let offset = 0; offset < bytes.length; offset++) if (bytes[offset] === first && bytesAt(bytes, offset, WEBP_ANIM_CHUNK)) return true;
|
|
102
|
+
if (bytes.length < readLength) return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a source is an animated GIF, PNG or WebP, judged by its signature.
|
|
107
|
+
* The source is parsed as it streams in, so only the bytes a format's check
|
|
108
|
+
* needs are read, and none are kept beyond what the reader holds to replay.
|
|
109
|
+
*/
|
|
110
|
+
async function isAnimatedImage(source) {
|
|
111
|
+
const head = await source.bytes(0, 12);
|
|
112
|
+
if (ascii(head, 0, 3) === "GIF") return isAnimatedGif(source, head);
|
|
113
|
+
if (bytesAt(head, 0, PNG_SIGNATURE)) return isAnimatedPng(source);
|
|
114
|
+
if (bytesAt(head, 8, WEBP_FOURCC)) return isAnimatedWebp(source);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { isAnimatedImage };
|