vinext 1.4.1 → 1.4.2
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 +1 -1
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/check.js +1 -1
- package/dist/config/config-matchers.js +2 -2
- package/dist/config/destination-params.js +5 -2
- package/dist/config/next-config.d.ts +16 -1
- package/dist/config/next-config.js +31 -1
- package/dist/entries/app-rsc-entry.js +41 -18
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +2 -0
- package/dist/index.js +15 -12
- package/dist/init-cloudflare.js +18 -2
- 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 +3 -5
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +21 -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-optimistic-routing.d.ts +12 -1
- package/dist/server/app-optimistic-routing.js +38 -16
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -1
- package/dist/server/app-page-dispatch.js +73 -30
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +37 -49
- 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 +10 -0
- package/dist/server/app-page-route-wiring.js +16 -12
- 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-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-rsc-handler.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +75 -43
- package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
- package/dist/server/app-rsc-response-finalizer.js +11 -1
- 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/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -1
- 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 +23 -22
- package/dist/server/image-optimization.js +12 -2
- package/dist/server/metadata-route-response.js +24 -6
- package/dist/server/nitro-public-files-plugin.js +20 -8
- package/dist/server/pages-data-route.d.ts +5 -1
- package/dist/server/pages-data-route.js +14 -6
- package/dist/server/pages-request-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +1 -0
- package/dist/server/prod-server.js +17 -9
- 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/worker-image-optimization.d.ts +2 -2
- package/dist/server/worker-image-optimization.js +18 -3
- package/dist/server/worker-utils.d.ts +8 -0
- package/dist/server/worker-utils.js +19 -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/slot.d.ts +3 -0
- package/dist/shims/slot.js +67 -9
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MIDDLEWARE_SKIP_HEADER } from "../utils/protocol-headers.js";
|
|
2
|
-
import {
|
|
2
|
+
import { hasBasePath, restoreBasePath, stripBasePath } from "../utils/base-path.js";
|
|
3
3
|
import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
|
|
4
|
-
import { cloneRequestWithUrl } from "./request-pipeline.js";
|
|
4
|
+
import { cloneRequestWithUrl, hasUrlParserDotSegment } from "./request-pipeline.js";
|
|
5
5
|
//#region src/server/pages-data-route.ts
|
|
6
6
|
/**
|
|
7
7
|
* Helpers for the Pages Router `/_next/data/{buildId}/{...page}.json` endpoint.
|
|
@@ -62,13 +62,19 @@ function encodeUrlParserIgnoredCharacters(pathname) {
|
|
|
62
62
|
* `/_next/data/<buildId>/en.json` → `/en`
|
|
63
63
|
* `/_next/data/<wrong-id>/about.json` → null
|
|
64
64
|
* `/_next/data/<buildId>/about` → null (missing .json suffix)
|
|
65
|
+
* `/_next/data/<buildId>/blog/...json` → null (strips to a dot segment)
|
|
66
|
+
*
|
|
67
|
+
* `decoded` marks a percent-decoded copy of a pathname whose raw form already
|
|
68
|
+
* parsed: a `%2e` left in it was `%252e` on the wire, an ordinary character.
|
|
65
69
|
*/
|
|
66
|
-
function parseNextDataPathname(pathname, buildId) {
|
|
70
|
+
function parseNextDataPathname(pathname, buildId, decoded = false) {
|
|
67
71
|
if (!buildId) return null;
|
|
68
72
|
if (!isNextDataPathname(pathname)) return null;
|
|
69
73
|
const expectedPrefix = `${NEXT_DATA_PREFIX}${buildId}/`;
|
|
70
74
|
if (!pathname.startsWith(expectedPrefix)) return null;
|
|
71
|
-
|
|
75
|
+
const rest = pathname.slice(expectedPrefix.length, -5);
|
|
76
|
+
if (!decoded && hasUrlParserDotSegment(rest)) return null;
|
|
77
|
+
return pageFromNextDataRest(rest);
|
|
72
78
|
}
|
|
73
79
|
/**
|
|
74
80
|
* The page pathname a `_next/data` URL addresses, whatever its buildId. Next.js
|
|
@@ -83,7 +89,9 @@ function nextDataMiddlewareMatchPathname(pathname) {
|
|
|
83
89
|
if (!isNextDataPathname(pathname)) return null;
|
|
84
90
|
const buildIdEnd = pathname.indexOf("/", 12);
|
|
85
91
|
if (buildIdEnd <= 12) return null;
|
|
86
|
-
|
|
92
|
+
const rest = pathname.slice(buildIdEnd + 1, -5);
|
|
93
|
+
if (hasUrlParserDotSegment(rest)) return null;
|
|
94
|
+
return pageFromNextDataRest(rest)?.pagePathname ?? null;
|
|
87
95
|
}
|
|
88
96
|
function pageFromNextDataRest(rest) {
|
|
89
97
|
if (rest.length === 0) return null;
|
|
@@ -189,7 +197,7 @@ function normalizePagesDataRequest(request, buildId, basePath = "", trailingSlas
|
|
|
189
197
|
};
|
|
190
198
|
const pagePathname = normalizeNextDataPagePathname(dataMatch.pagePathname, trailingSlash);
|
|
191
199
|
const normalizedUrl = new URL(reqUrl);
|
|
192
|
-
normalizedUrl.pathname = hadBasePath ?
|
|
200
|
+
normalizedUrl.pathname = hadBasePath ? restoreBasePath(pagePathname, basePath) : pagePathname;
|
|
193
201
|
return {
|
|
194
202
|
isDataReq: true,
|
|
195
203
|
request: cloneRequestWithUrl(request, normalizedUrl.href),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
|
|
2
2
|
import { requestContextFromRequest } from "../config/request-context.js";
|
|
3
3
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
4
|
-
import {
|
|
4
|
+
import { restoreBasePath } from "../utils/base-path.js";
|
|
5
5
|
import { VINEXT_PRERENDER_REWRITTEN_HEADER } from "./headers.js";
|
|
6
6
|
import { methodNotAllowedResponse, notFoundStaticAssetResponse, sanitizeMethodNotAllowedHeaders } from "./http-error-responses.js";
|
|
7
7
|
import { cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
|
|
@@ -14,10 +14,11 @@ import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
|
|
|
14
14
|
import { markRouteCacheabilityDynamic } from "../shims/cacheability-classification.js";
|
|
15
15
|
import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
|
|
16
16
|
import { mergeRewriteQuery } from "../utils/query.js";
|
|
17
|
+
import { isImageOptimizationPath } from "./image-optimization.js";
|
|
17
18
|
import { setFrameworkRequestRoute } from "./request-tracing.js";
|
|
18
19
|
import { withExternalRewriteResponseHeaders } from "./middleware-response-headers.js";
|
|
19
20
|
import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
|
|
20
|
-
import { finalizeMissingStaticAssetResponse, mergeHeaders } from "./worker-utils.js";
|
|
21
|
+
import { finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders } from "./worker-utils.js";
|
|
21
22
|
//#region src/server/pages-request-pipeline.ts
|
|
22
23
|
function ruleUsesUnkeyedRequestCondition(rule) {
|
|
23
24
|
return [...rule.has ?? [], ...rule.missing ?? []].some((condition) => condition.type === "header" || condition.type === "cookie" || condition.type === "host");
|
|
@@ -80,7 +81,7 @@ function wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) {
|
|
|
80
81
|
if (!hadBasePath || !basePath) return runMiddleware;
|
|
81
82
|
return (request, ctx, opts) => {
|
|
82
83
|
const mwUrl = new URL(request.url);
|
|
83
|
-
mwUrl.pathname =
|
|
84
|
+
mwUrl.pathname = restoreBasePath(mwUrl.pathname, basePath);
|
|
84
85
|
return runMiddleware(cloneRequestWithUrl(request, mwUrl.toString()), ctx, opts);
|
|
85
86
|
};
|
|
86
87
|
}
|
|
@@ -166,7 +167,7 @@ async function runPagesRequest(request, deps) {
|
|
|
166
167
|
const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase, resolvedUrl, request, resolvedPathnameHasBasePath);
|
|
167
168
|
if (served instanceof Response) {
|
|
168
169
|
const isStaticMethodNotAllowed = served.status === 405 && served.headers.get("allow") === "GET, HEAD";
|
|
169
|
-
const response = mergeHeaders(served, middlewareHeaders, isStaticMethodNotAllowed || !served.ok || served.status === 206 ? void 0 : middlewareStatus);
|
|
170
|
+
const response = (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname) ? mergeHeaders : mergeStaticFileHeaders)(served, middlewareHeaders, isStaticMethodNotAllowed || !served.ok || served.status === 206 ? void 0 : middlewareStatus);
|
|
170
171
|
if (isStaticMethodNotAllowed) sanitizeMethodNotAllowedHeaders(response.headers, "GET, HEAD");
|
|
171
172
|
return {
|
|
172
173
|
type: "response",
|
|
@@ -306,7 +307,7 @@ async function runPagesRequest(request, deps) {
|
|
|
306
307
|
let apiRequest = request;
|
|
307
308
|
if (basePath && hadBasePath) {
|
|
308
309
|
const apiRequestUrl = new URL(request.url);
|
|
309
|
-
apiRequestUrl.pathname =
|
|
310
|
+
apiRequestUrl.pathname = restoreBasePath(apiRequestUrl.pathname, basePath);
|
|
310
311
|
apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
|
|
311
312
|
}
|
|
312
313
|
const response = await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null, headersFromRecord(middlewareHeaders));
|
|
@@ -69,6 +69,7 @@ const configRewrites = vinextConfig?.rewrites ?? {
|
|
|
69
69
|
const configHeaders = vinextConfig?.headers ?? [];
|
|
70
70
|
const imageConfig = vinextConfig?.images ? {
|
|
71
71
|
qualities: vinextConfig.images.qualities,
|
|
72
|
+
localPatterns: vinextConfig.images.localPatterns,
|
|
72
73
|
dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
|
|
73
74
|
dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
|
|
74
75
|
contentDispositionType: vinextConfig.images.contentDispositionType,
|
|
@@ -270,6 +270,7 @@ const OMIT_STATIC_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
|
270
270
|
"content-type",
|
|
271
271
|
VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
|
|
272
272
|
]);
|
|
273
|
+
const IMAGE_ERROR_SIGNAL_HEADERS = ["content-type", "content-encoding"];
|
|
273
274
|
function omitHeadersCaseInsensitive(headersRecord, targets) {
|
|
274
275
|
const filtered = {};
|
|
275
276
|
for (const [key, value] of Object.entries(headersRecord)) {
|
|
@@ -1195,18 +1196,25 @@ async function startAppRouterServer(options) {
|
|
|
1195
1196
|
if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
|
|
1196
1197
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
1197
1198
|
const serveImageOptimization = async (req, res, request, signal) => {
|
|
1198
|
-
const
|
|
1199
|
+
const signalHeaders = mergeResponseHeaders({}, new Response(null, { headers: signal.middlewareHeaders }));
|
|
1200
|
+
const stagedHeaders = omitHeadersCaseInsensitive(signalHeaders, OMIT_STATIC_RESPONSE_HEADERS);
|
|
1199
1201
|
for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
|
|
1202
|
+
const sendBadRequest = (body) => {
|
|
1203
|
+
for (const name of IMAGE_ERROR_SIGNAL_HEADERS) {
|
|
1204
|
+
const value = signalHeaders[name];
|
|
1205
|
+
if (value !== void 0) res.setHeader(name, value);
|
|
1206
|
+
}
|
|
1207
|
+
res.writeHead(400);
|
|
1208
|
+
res.end(body);
|
|
1209
|
+
};
|
|
1200
1210
|
const parsedUrl = new URL(`/${signal.search}`, "http://localhost");
|
|
1201
|
-
const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities);
|
|
1211
|
+
const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
|
|
1202
1212
|
if (!params) {
|
|
1203
|
-
|
|
1204
|
-
res.end("Bad Request");
|
|
1213
|
+
sendBadRequest("Bad Request");
|
|
1205
1214
|
return;
|
|
1206
1215
|
}
|
|
1207
1216
|
const sendUnsafeContentType = () => {
|
|
1208
|
-
|
|
1209
|
-
res.end("The requested resource is not an allowed image type");
|
|
1217
|
+
sendBadRequest("The requested resource is not an allowed image type");
|
|
1210
1218
|
};
|
|
1211
1219
|
const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), imageConfig?.dangerouslyAllowSVG);
|
|
1212
1220
|
const imageSecurityHeaders = {
|
|
@@ -1243,8 +1251,7 @@ async function startAppRouterServer(options) {
|
|
|
1243
1251
|
if (await serveSource(sourcePath)) return;
|
|
1244
1252
|
}
|
|
1245
1253
|
}
|
|
1246
|
-
|
|
1247
|
-
res.end(INVALID_IMAGE_SOURCE_MESSAGE);
|
|
1254
|
+
sendBadRequest(INVALID_IMAGE_SOURCE_MESSAGE);
|
|
1248
1255
|
};
|
|
1249
1256
|
const clientBuildFiles = new Set(Array.isArray(rscModule.__clientBuildFiles) ? rscModule.__clientBuildFiles : []);
|
|
1250
1257
|
const handleRequestImpl = async (req, res) => {
|
|
@@ -1455,6 +1462,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1455
1462
|
dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
|
|
1456
1463
|
dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
|
|
1457
1464
|
qualities: vinextConfig.images.qualities,
|
|
1465
|
+
localPatterns: vinextConfig.images.localPatterns,
|
|
1458
1466
|
contentDispositionType: vinextConfig.images.contentDispositionType,
|
|
1459
1467
|
contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
|
|
1460
1468
|
} : void 0;
|
|
@@ -1689,7 +1697,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1689
1697
|
if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
|
|
1690
1698
|
for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
|
|
1691
1699
|
const parsedUrl = new URL(resolvedUrl, "http://localhost");
|
|
1692
|
-
const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities);
|
|
1700
|
+
const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities, { localPatterns: pagesImageConfig?.localPatterns });
|
|
1693
1701
|
if (!params) {
|
|
1694
1702
|
res.writeHead(400);
|
|
1695
1703
|
res.end("Bad Request");
|
|
@@ -16,6 +16,11 @@ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedire
|
|
|
16
16
|
export declare function canonicalizeRequestPathname(pathname: string): string;
|
|
17
17
|
/** Canonicalize only the pathname portion while preserving the raw query. */
|
|
18
18
|
export declare function canonicalizeRequestUrlPathname(url: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the pathname has a segment the URL parser resolves as `.` or `..`,
|
|
21
|
+
* literally or percent-encoded. Such a pathname changes when it becomes a URL.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasUrlParserDotSegment(pathname: string): boolean;
|
|
19
24
|
/**
|
|
20
25
|
* Shared request pipeline utilities.
|
|
21
26
|
*
|
|
@@ -30,6 +30,16 @@ function canonicalizeRequestUrlPathname(url) {
|
|
|
30
30
|
return canonicalizeRequestPathname(pathname) + search;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
+
* Whether the pathname has a segment the URL parser resolves as `.` or `..`,
|
|
34
|
+
* literally or percent-encoded. Such a pathname changes when it becomes a URL.
|
|
35
|
+
*/
|
|
36
|
+
function hasUrlParserDotSegment(pathname) {
|
|
37
|
+
return pathname.split("/").some((segment) => {
|
|
38
|
+
const decodedDots = segment.replaceAll(/%2e/gi, ".");
|
|
39
|
+
return decodedDots === "." || decodedDots === "..";
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
33
43
|
* Shared request pipeline utilities.
|
|
34
44
|
*
|
|
35
45
|
* Extracted from generated entries and server hot paths to keep codegen focused
|
|
@@ -472,4 +482,4 @@ function stripRequestUrlFragment(request) {
|
|
|
472
482
|
return attachRequestCfMetadata(new Request(stripped, { signal }), request);
|
|
473
483
|
}
|
|
474
484
|
//#endregion
|
|
475
|
-
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
|
|
485
|
+
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, hasUrlParserDotSegment, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
|
|
@@ -23,6 +23,13 @@ export type ResponseStageCacheabilityOptions = {
|
|
|
23
23
|
* shared response drops them before admission and storage.
|
|
24
24
|
*/
|
|
25
25
|
recomposesRequestScopedHeaders?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Keeps `X-Vinext-Params` when the request scoped headers are dropped. An
|
|
28
|
+
* intercepted response's params include its target's, which the request
|
|
29
|
+
* stage doesn't match. Its identity holds the interception context, so the
|
|
30
|
+
* stored params are its own.
|
|
31
|
+
*/
|
|
32
|
+
keepsParamsHeader?: boolean;
|
|
26
33
|
/** Generated adapter registration, deferred until the response stage executes. */
|
|
27
34
|
registerCacheAdapters(): void;
|
|
28
35
|
request: Request;
|
|
@@ -24,13 +24,13 @@ async function withResponseStageCacheability(options, render) {
|
|
|
24
24
|
const stripsRequestScopedHeaders = options.recomposesRequestScopedHeaders === true && options.cache === "shared" && !options.probeMode;
|
|
25
25
|
if (!cacheability) {
|
|
26
26
|
const rendered = await render(context);
|
|
27
|
-
return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered) : rendered;
|
|
27
|
+
return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered, options.keepsParamsHeader === true) : rendered;
|
|
28
28
|
}
|
|
29
29
|
if (options.policyHeadersAppliedBeforeRender) cacheability.recordResponseStageCachePolicy(context, options.policyHeaders);
|
|
30
30
|
const state = Reflect.get(context, CACHEABILITY_REQUEST_STATE);
|
|
31
31
|
if (options.probeMode && options.forceDynamic && !options.policyHeaders?.length && state) state.patternDynamicReason = "dynamic = \"force-dynamic\"";
|
|
32
32
|
const renderedResponse = await render(context);
|
|
33
|
-
const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse) : renderedResponse;
|
|
33
|
+
const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse, options.keepsParamsHeader === true) : renderedResponse;
|
|
34
34
|
const response = options.policyHeadersAppliedBeforeRender ? rendered : cacheability.applyResponseStageCachePolicy(rendered, context, options.policyHeaders);
|
|
35
35
|
const complete = (candidate) => cacheability.finalizeWorkerCacheabilityResponse(candidate, context);
|
|
36
36
|
const route = state?.route;
|
|
@@ -40,10 +40,11 @@ async function withResponseStageCacheability(options, render) {
|
|
|
40
40
|
}
|
|
41
41
|
return complete(response);
|
|
42
42
|
}
|
|
43
|
-
function withoutRequestScopedHeaders(response) {
|
|
44
|
-
|
|
43
|
+
function withoutRequestScopedHeaders(response, keepsParams) {
|
|
44
|
+
const dropsParams = !keepsParams && response.headers.has("X-Vinext-Params");
|
|
45
|
+
if (!dropsParams && !response.headers.has("X-Vinext-Rendered-Path-And-Search")) return response;
|
|
45
46
|
const headers = new Headers(response.headers);
|
|
46
|
-
headers.delete(VINEXT_PARAMS_HEADER);
|
|
47
|
+
if (dropsParams) headers.delete(VINEXT_PARAMS_HEADER);
|
|
47
48
|
headers.delete(VINEXT_RENDERED_PATH_AND_SEARCH_HEADER);
|
|
48
49
|
return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
|
|
49
50
|
headers,
|
|
@@ -27,8 +27,8 @@ export declare function fetchWorkerImageSource(imageUrl: string, request: Reques
|
|
|
27
27
|
export declare function fetchSignalledPublicFile(response: Response, sourceRequest: Request, assets: VinextAssetFetcher): Promise<Response> | null;
|
|
28
28
|
/**
|
|
29
29
|
* Serve the App Router image endpoint once the request handler admitted it
|
|
30
|
-
* through middleware and rewrites.
|
|
31
|
-
* the image, as Next.js does.
|
|
30
|
+
* through middleware and rewrites. Config and middleware response headers are
|
|
31
|
+
* sent with the image, as Next.js does.
|
|
32
32
|
*/
|
|
33
33
|
export declare function serveWorkerImageOptimization(request: Request, signal: ImageOptimizationSignal, allowedWidths: number[] | undefined, imageConfig: ImageConfig | undefined, options: WorkerImageSourceOptions): Promise<Response>;
|
|
34
34
|
//#endregion
|
|
@@ -78,8 +78,8 @@ function fetchSignalledPublicFile(response, sourceRequest, assets) {
|
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Serve the App Router image endpoint once the request handler admitted it
|
|
81
|
-
* through middleware and rewrites.
|
|
82
|
-
* the image, as Next.js does.
|
|
81
|
+
* through middleware and rewrites. Config and middleware response headers are
|
|
82
|
+
* sent with the image, as Next.js does.
|
|
83
83
|
*/
|
|
84
84
|
async function serveWorkerImageOptimization(request, signal, allowedWidths, imageConfig, options) {
|
|
85
85
|
const imageUrl = new URL(request.url);
|
|
@@ -93,7 +93,22 @@ async function serveWorkerImageOptimization(request, signal, allowedWidths, imag
|
|
|
93
93
|
});
|
|
94
94
|
const cookies = signal.middlewareHeaders.getSetCookie();
|
|
95
95
|
if (cookies.length > 0) middlewareHeaders["set-cookie"] = cookies;
|
|
96
|
-
|
|
96
|
+
const merged = mergeHeaders(response, middlewareHeaders);
|
|
97
|
+
if (response.status < 400) return merged;
|
|
98
|
+
const errorHeaders = new Headers(merged.headers);
|
|
99
|
+
let replaced = false;
|
|
100
|
+
for (const name of ["content-type", "content-encoding"]) {
|
|
101
|
+
const value = signal.middlewareHeaders.get(name);
|
|
102
|
+
if (value === null) continue;
|
|
103
|
+
errorHeaders.set(name, value);
|
|
104
|
+
replaced = true;
|
|
105
|
+
}
|
|
106
|
+
if (!replaced) return merged;
|
|
107
|
+
return new Response(merged.body, {
|
|
108
|
+
headers: errorHeaders,
|
|
109
|
+
status: merged.status,
|
|
110
|
+
statusText: merged.statusText
|
|
111
|
+
});
|
|
97
112
|
}
|
|
98
113
|
//#endregion
|
|
99
114
|
export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
//#region src/server/worker-utils.d.ts
|
|
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
|
+
/**
|
|
5
|
+
* Merge middleware/config headers into a file served from the asset layer.
|
|
6
|
+
* Its Cache-Control is only the layer's default (Workers Static Assets sends
|
|
7
|
+
* `public, max-age=0, must-revalidate` with every file), so one set by
|
|
8
|
+
* middleware or `headers()` wins, as Next.js's static file server sets its
|
|
9
|
+
* own only when the response has none.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeStaticFileHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
|
|
4
12
|
export declare function resolveStaticAssetSignal(signalResponse: Response, options: {
|
|
5
13
|
/** `requestHeaders` are the post-middleware request headers, when middleware set any. */
|
|
6
14
|
fetchAsset(path: string, requestHeaders: Headers | null): Promise<Response>;
|
|
@@ -82,6 +82,23 @@ function mergeHeaders(response, extraHeaders, statusOverride) {
|
|
|
82
82
|
headers: merged
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Merge middleware/config headers into a file served from the asset layer.
|
|
87
|
+
* Its Cache-Control is only the layer's default (Workers Static Assets sends
|
|
88
|
+
* `public, max-age=0, must-revalidate` with every file), so one set by
|
|
89
|
+
* middleware or `headers()` wins, as Next.js's static file server sets its
|
|
90
|
+
* own only when the response has none.
|
|
91
|
+
*/
|
|
92
|
+
function mergeStaticFileHeaders(response, extraHeaders, statusOverride) {
|
|
93
|
+
const merged = mergeHeaders(response, extraHeaders, statusOverride);
|
|
94
|
+
for (const [name, value] of Object.entries(extraHeaders)) {
|
|
95
|
+
if (name.toLowerCase() !== "cache-control") continue;
|
|
96
|
+
const cacheControl = (Array.isArray(value) ? value.join(", ") : value).trim();
|
|
97
|
+
if (!cacheControl) continue;
|
|
98
|
+
merged.headers.set(name, cacheControl);
|
|
99
|
+
}
|
|
100
|
+
return merged;
|
|
101
|
+
}
|
|
85
102
|
async function resolveStaticAssetSignal(signalResponse, options) {
|
|
86
103
|
const signal = readStaticFileSignal(signalResponse);
|
|
87
104
|
if (!signal) return null;
|
|
@@ -100,7 +117,7 @@ async function resolveStaticAssetSignal(signalResponse, options) {
|
|
|
100
117
|
const requestHeaders = readStaticFileSignalRequestHeaders(signalResponse);
|
|
101
118
|
cancelResponseBody(signalResponse);
|
|
102
119
|
const assetResponse = await options.fetchAsset(assetPath, requestHeaders);
|
|
103
|
-
return
|
|
120
|
+
return mergeStaticFileHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
|
|
104
121
|
}
|
|
105
122
|
/**
|
|
106
123
|
* Retarget a Worker asset request without dropping its conditional/range
|
|
@@ -116,4 +133,4 @@ function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
|
|
|
116
133
|
});
|
|
117
134
|
}
|
|
118
135
|
//#endregion
|
|
119
|
-
export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, resolveStaticAssetSignal };
|
|
136
|
+
export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders, resolveStaticAssetSignal };
|
|
@@ -71,6 +71,12 @@ export declare function resolveImageSource(v: {
|
|
|
71
71
|
* Configurable via `images.deviceSizes` in next.config.js.
|
|
72
72
|
*/
|
|
73
73
|
export declare const RESPONSIVE_WIDTHS: number[];
|
|
74
|
+
/**
|
|
75
|
+
* Parse a `quality` prop like Next.js's `getInt` (shared/lib/get-img-props.ts):
|
|
76
|
+
* finite numbers and digit-only strings pass, anything else becomes NaN, which
|
|
77
|
+
* the default loader treats as 75.
|
|
78
|
+
*/
|
|
79
|
+
export declare function parseImageQuality(quality: unknown): number | undefined;
|
|
74
80
|
/**
|
|
75
81
|
* Build a `/_next/image` optimization URL.
|
|
76
82
|
*
|
|
@@ -52,6 +52,14 @@ const __imageSizes = (() => {
|
|
|
52
52
|
];
|
|
53
53
|
}
|
|
54
54
|
})();
|
|
55
|
+
const __imageQualities = (() => {
|
|
56
|
+
try {
|
|
57
|
+
const qualities = JSON.parse(process.env.__VINEXT_IMAGE_QUALITIES ?? "null");
|
|
58
|
+
return Array.isArray(qualities) ? qualities.sort((a, b) => a - b) : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
55
63
|
/**
|
|
56
64
|
* Whether dangerouslyAllowSVG is enabled in next.config.js.
|
|
57
65
|
* When false (default), .svg sources auto-skip the optimization endpoint
|
|
@@ -195,6 +203,33 @@ function extractLocalDeploymentId(src) {
|
|
|
195
203
|
}
|
|
196
204
|
const __imageEndpoint = `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/_next/image${process.env.__VINEXT_TRAILING_SLASH === "true" ? "/" : ""}`;
|
|
197
205
|
/**
|
|
206
|
+
* Parse a `quality` prop like Next.js's `getInt` (shared/lib/get-img-props.ts):
|
|
207
|
+
* finite numbers and digit-only strings pass, anything else becomes NaN, which
|
|
208
|
+
* the default loader treats as 75.
|
|
209
|
+
*/
|
|
210
|
+
function parseImageQuality(quality) {
|
|
211
|
+
if (quality === void 0) return void 0;
|
|
212
|
+
if (typeof quality === "number") return Number.isFinite(quality) ? quality : NaN;
|
|
213
|
+
if (typeof quality === "string" && /^[0-9]+$/.test(quality)) return parseInt(quality, 10);
|
|
214
|
+
return NaN;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Round a `quality` prop to the closest entry of `images.qualities`, as the
|
|
218
|
+
* Next.js default loader does (shared/lib/find-closest-quality.ts), so the
|
|
219
|
+
* URL is one the optimization endpoint accepts.
|
|
220
|
+
*
|
|
221
|
+
* With no list configured the prop is used as written (75 when absent). This
|
|
222
|
+
* is deliberate: Next.js 16 defaults the list to `[75]` and would round every
|
|
223
|
+
* quality to 75, but vinext keeps the Next.js 14/15 behaviour for an unset
|
|
224
|
+
* list so migrating apps keep the qualities they ask for (see
|
|
225
|
+
* ImageConfig.qualities in server/image-optimization.ts).
|
|
226
|
+
*/
|
|
227
|
+
function findClosestQuality(quality) {
|
|
228
|
+
const q = quality || 75;
|
|
229
|
+
if (!__imageQualities?.length) return q;
|
|
230
|
+
return __imageQualities.reduce((prev, cur) => Math.abs(cur - q) < Math.abs(prev - q) ? cur : prev, __imageQualities[0]);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
198
233
|
* Build a `/_next/image` optimization URL.
|
|
199
234
|
*
|
|
200
235
|
* In production (Cloudflare Workers), the worker intercepts this path and uses
|
|
@@ -202,9 +237,10 @@ const __imageEndpoint = `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/_next/image
|
|
|
202
237
|
* server handles it as a passthrough (serves the original file).
|
|
203
238
|
*/
|
|
204
239
|
function imageOptimizationUrl(src, width, quality = 75) {
|
|
240
|
+
const q = findClosestQuality(quality);
|
|
205
241
|
const source = extractLocalDeploymentId(src);
|
|
206
242
|
const deploymentQuery = source.src.startsWith("/") && source.deploymentId ? `&dpl=${source.deploymentId}` : "";
|
|
207
|
-
return `${__imageEndpoint}?url=${encodeURIComponent(source.src)}&w=${width}&q=${
|
|
243
|
+
return `${__imageEndpoint}?url=${encodeURIComponent(source.src)}&w=${width}&q=${q}${deploymentQuery}`;
|
|
208
244
|
}
|
|
209
245
|
/**
|
|
210
246
|
* Generate a srcSet string for responsive images.
|
|
@@ -283,7 +319,7 @@ function getImageProps(props) {
|
|
|
283
319
|
}
|
|
284
320
|
}
|
|
285
321
|
}
|
|
286
|
-
const imgQuality =
|
|
322
|
+
const imgQuality = parseImageQuality(_quality) ?? 75;
|
|
287
323
|
const resolvedSrc = blockedInProd ? "" : loader ? loader({
|
|
288
324
|
src,
|
|
289
325
|
width: imgWidth ?? 0,
|
|
@@ -320,4 +356,4 @@ function getImageProps(props) {
|
|
|
320
356
|
return { props: Object.assign(imageProps, { "data-nimg": fill ? "fill" : "1" }) };
|
|
321
357
|
}
|
|
322
358
|
//#endregion
|
|
323
|
-
export { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl };
|
|
359
|
+
export { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, parseImageQuality, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl };
|
package/dist/shims/image.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useMergedRef } from "./use-merged-ref.js";
|
|
3
|
-
import { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl } from "./image-props.js";
|
|
3
|
+
import { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, parseImageQuality, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl } from "./image-props.js";
|
|
4
4
|
import { forwardRef, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import * as ReactDOM from "react-dom";
|
|
@@ -197,7 +197,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
|
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
if (loader) {
|
|
200
|
-
const resolvedQuality =
|
|
200
|
+
const resolvedQuality = parseImageQuality(quality) ?? 75;
|
|
201
201
|
const resolvedSrc = loader({
|
|
202
202
|
src,
|
|
203
203
|
width: imgWidth ?? 0,
|
|
@@ -293,7 +293,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
|
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
|
-
const imgQuality =
|
|
296
|
+
const imgQuality = parseImageQuality(quality) ?? 75;
|
|
297
297
|
const skipOptimization = isSvgUrl(src) && !__dangerouslyAllowSVG;
|
|
298
298
|
const optimizedAttributes = imgWidth && !fill && !skipOptimization ? generateImageAttributes(src, imgWidth, imgQuality, sizes) : void 0;
|
|
299
299
|
const srcSet = optimizedAttributes ? optimizedAttributes.srcSet : imgWidth && !fill ? RESPONSIVE_WIDTHS.filter((w) => w <= imgWidth * 2).map((w) => `${src} ${w}w`).join(", ") || `${src} ${imgWidth}w` : void 0;
|
package/dist/shims/slot.d.ts
CHANGED
|
@@ -28,10 +28,13 @@ export declare function setAppElementsRenderedSearch(elements: AppElements, sear
|
|
|
28
28
|
* that arrives after the head (initial hydration) is still seen.
|
|
29
29
|
*/
|
|
30
30
|
export declare function bindAppElementsRenderedSearch(elements: AppElements, snapshot: Pick<ClientNavigationRenderSnapshot, "renderedSearch" | "search">): void;
|
|
31
|
+
export declare function isBfcacheIdentityRetained(identity: string): boolean;
|
|
31
32
|
export declare const BfcacheIdentityMapContext: React$1.Context<Readonly<Record<string, string>>>;
|
|
32
33
|
export type BfcacheSlotEntry = {
|
|
33
34
|
content: React$1.ReactNode;
|
|
34
35
|
elements?: AppElements;
|
|
36
|
+
/** Every entry key inside a hidden entry, its own included. */
|
|
37
|
+
retainedKeys?: readonly string[];
|
|
35
38
|
segmentId?: string;
|
|
36
39
|
stateKey: string;
|
|
37
40
|
stateKeyMap?: Readonly<Record<string, string>>;
|
package/dist/shims/slot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { APP_BFCACHE_SEGMENT_IDENTITIES_KEY, AppElementsWire, UNMATCHED_SLOT } from "../server/app-elements-wire.js";
|
|
3
|
-
import "../server/app-elements.js";
|
|
3
|
+
import { readOptimisticStateKeyRebinder } from "../server/app-elements.js";
|
|
4
4
|
import { isNestedBfcacheSlotSegmentId, isNestedBfcacheSlotSegmentIdFor } from "../server/bfcache-identity.js";
|
|
5
5
|
import { notFound } from "./navigation-errors.js";
|
|
6
6
|
import { getBfcacheIdMapContext, getBfcacheSegmentIdContext } from "./navigation-context-state.js";
|
|
@@ -51,6 +51,25 @@ const BfcacheSegmentIdContext = getBfcacheSegmentIdContext();
|
|
|
51
51
|
const EMPTY_BFCACHE_STATE_KEYS = Object.freeze({});
|
|
52
52
|
const MAX_BFCACHE_SLOT_ENTRIES_WITH_CACHE_COMPONENTS = 3;
|
|
53
53
|
const MAX_BFCACHE_SLOT_ENTRIES_WITHOUT_CACHE_COMPONENTS = 1;
|
|
54
|
+
const rootBfcacheRetainedKeysReports = /* @__PURE__ */ new Map();
|
|
55
|
+
function isBfcacheIdentityRetained(identity) {
|
|
56
|
+
return hasBfcacheRetainedKey(rootBfcacheRetainedKeysReports, identity);
|
|
57
|
+
}
|
|
58
|
+
function hasBfcacheRetainedKey(reports, identity) {
|
|
59
|
+
for (const report of reports.values()) {
|
|
60
|
+
if (report.keys.includes(identity) || report.hiddenKeys.includes(identity)) return true;
|
|
61
|
+
if (hasBfcacheRetainedKey(report.activeReports, identity)) return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const BfcacheRetainedKeysContext = React$1.createContext(rootBfcacheRetainedKeysReports);
|
|
66
|
+
function collectBfcacheRetainedKeys(reports, keys) {
|
|
67
|
+
for (const report of reports.values()) {
|
|
68
|
+
for (const key of report.keys) keys.add(key);
|
|
69
|
+
for (const key of report.hiddenKeys) keys.add(key);
|
|
70
|
+
collectBfcacheRetainedKeys(report.activeReports, keys);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
54
73
|
const BfcacheIdentityMapContext = React$1.createContext(EMPTY_BFCACHE_STATE_KEYS);
|
|
55
74
|
function isCacheComponentsEnabled() {
|
|
56
75
|
return String(process.env.__NEXT_CACHE_COMPONENTS) === "true";
|
|
@@ -136,34 +155,71 @@ function warnTransportMetadataEntry(id) {
|
|
|
136
155
|
* falling back to the live boundary values for entries that predate per-entry
|
|
137
156
|
* capture.
|
|
138
157
|
*/
|
|
139
|
-
function BfcacheEntryProviders({ entry, fallbackElements, fallbackSegmentId, fallbackStateKeyMap, SegmentContext }) {
|
|
158
|
+
function BfcacheEntryProviders({ entry, fallbackElements, fallbackSegmentId, fallbackStateKeyMap, retainedKeysReports, SegmentContext }) {
|
|
140
159
|
return /* @__PURE__ */ jsx(BfcacheIdentityMapContext.Provider, {
|
|
141
160
|
value: entry.stateKeyMap ?? fallbackStateKeyMap,
|
|
142
161
|
children: /* @__PURE__ */ jsx(ElementsContext.Provider, {
|
|
143
162
|
value: entry.elements ?? fallbackElements,
|
|
144
163
|
children: /* @__PURE__ */ jsx(SegmentContext.Provider, {
|
|
145
164
|
value: entry.segmentId ?? fallbackSegmentId,
|
|
146
|
-
children:
|
|
165
|
+
children: /* @__PURE__ */ jsx(BfcacheRetainedKeysContext.Provider, {
|
|
166
|
+
value: retainedKeysReports,
|
|
167
|
+
children: entry.content
|
|
168
|
+
})
|
|
147
169
|
})
|
|
148
170
|
})
|
|
149
171
|
});
|
|
150
172
|
}
|
|
151
|
-
function useBfcacheSlotEntries(activeEntry) {
|
|
173
|
+
function useBfcacheSlotEntries(listId, activeEntry) {
|
|
152
174
|
const snapshotsByStateKey = React$1.useRef(/* @__PURE__ */ new Map());
|
|
153
175
|
const [entryOrder, setEntryOrder] = React$1.useState(() => [activeEntry.stateKey]);
|
|
176
|
+
const [retainedKeysReportsByStateKey] = React$1.useState(() => /* @__PURE__ */ new Map());
|
|
177
|
+
const getRetainedKeysReports = (stateKey) => {
|
|
178
|
+
let reports = retainedKeysReportsByStateKey.get(stateKey);
|
|
179
|
+
if (!reports) {
|
|
180
|
+
reports = /* @__PURE__ */ new Map();
|
|
181
|
+
retainedKeysReportsByStateKey.set(stateKey, reports);
|
|
182
|
+
}
|
|
183
|
+
return reports;
|
|
184
|
+
};
|
|
154
185
|
const committedSnapshots = snapshotsByStateKey.current;
|
|
155
186
|
const staged = stageBfcacheSlotEntryForRender(committedSnapshots, entryOrder, activeEntry);
|
|
187
|
+
for (const [index, entry] of staged.entries.entries()) {
|
|
188
|
+
if (entry.stateKey === activeEntry.stateKey || entry.retainedKeys) continue;
|
|
189
|
+
const retainedKeys = /* @__PURE__ */ new Set([entry.stateKey]);
|
|
190
|
+
collectBfcacheRetainedKeys(getRetainedKeysReports(entry.stateKey), retainedKeys);
|
|
191
|
+
const hiddenEntry = {
|
|
192
|
+
...entry,
|
|
193
|
+
retainedKeys: [...retainedKeys]
|
|
194
|
+
};
|
|
195
|
+
staged.entries[index] = hiddenEntry;
|
|
196
|
+
staged.snapshots.set(entry.stateKey, hiddenEntry);
|
|
197
|
+
}
|
|
156
198
|
const nextOrder = staged.order;
|
|
157
199
|
const orderChanged = !haveSameBfcacheSlotEntryOrder(entryOrder, nextOrder);
|
|
158
200
|
React$1.useLayoutEffect(() => {
|
|
159
201
|
snapshotsByStateKey.current = staged.snapshots;
|
|
160
|
-
|
|
202
|
+
for (const stateKey of retainedKeysReportsByStateKey.keys()) if (!staged.snapshots.has(stateKey)) retainedKeysReportsByStateKey.delete(stateKey);
|
|
203
|
+
}, [retainedKeysReportsByStateKey, staged.snapshots]);
|
|
204
|
+
const hiddenKeys = [...new Set(staged.entries.flatMap((entry) => entry.retainedKeys ?? []))];
|
|
205
|
+
const enclosingReports = React$1.useContext(BfcacheRetainedKeysContext);
|
|
206
|
+
const report = {
|
|
207
|
+
activeReports: getRetainedKeysReports(activeEntry.stateKey),
|
|
208
|
+
hiddenKeys,
|
|
209
|
+
keys: staged.entries.map((entry) => entry.stateKey)
|
|
210
|
+
};
|
|
211
|
+
React$1.useLayoutEffect(() => {
|
|
212
|
+
enclosingReports.set(listId, report);
|
|
213
|
+
});
|
|
161
214
|
if (process.env.NODE_ENV !== "production" && !staged.snapshots.has(activeEntry.stateKey)) throw new Error("BFCache Activity slot is missing the active entry snapshot");
|
|
162
215
|
if (orderChanged) setEntryOrder(nextOrder);
|
|
163
|
-
return
|
|
216
|
+
return {
|
|
217
|
+
entries: staged.entries,
|
|
218
|
+
getRetainedKeysReports
|
|
219
|
+
};
|
|
164
220
|
}
|
|
165
221
|
function BfcacheActivitySlotBoundary({ activeStateKey, content, elements, id, SegmentContext, stateKeyMap }) {
|
|
166
|
-
const renderEntries = useBfcacheSlotEntries({
|
|
222
|
+
const { entries: renderEntries, getRetainedKeysReports } = useBfcacheSlotEntries(id, {
|
|
167
223
|
content,
|
|
168
224
|
elements,
|
|
169
225
|
segmentId: id,
|
|
@@ -177,6 +233,7 @@ function BfcacheActivitySlotBoundary({ activeStateKey, content, elements, id, Se
|
|
|
177
233
|
fallbackElements: elements,
|
|
178
234
|
fallbackSegmentId: id,
|
|
179
235
|
fallbackStateKeyMap: stateKeyMap,
|
|
236
|
+
retainedKeysReports: getRetainedKeysReports(entry.stateKey),
|
|
180
237
|
SegmentContext
|
|
181
238
|
})
|
|
182
239
|
}, entry.stateKey)) });
|
|
@@ -228,7 +285,8 @@ function BfcacheChildrenActivityBoundary({ children, id, IdMapContext, SegmentCo
|
|
|
228
285
|
const identityMap = React$1.useContext(BfcacheIdentityMapContext);
|
|
229
286
|
const bfcacheIdMap = React$1.useContext(IdMapContext);
|
|
230
287
|
const segmentId = React$1.useContext(SegmentContext);
|
|
231
|
-
const
|
|
288
|
+
const rebindStateKey = readOptimisticStateKeyRebinder(elements);
|
|
289
|
+
const activeStateKey = stateKey !== void 0 ? rebindStateKey?.(stateKey) ?? stateKey : id === void 0 ? void 0 : resolveBfcacheSegmentStateKey(id, identityMap, bfcacheIdMap);
|
|
232
290
|
if (segmentId === null || activeStateKey === void 0) return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
233
291
|
return /* @__PURE__ */ jsx(BfcacheActivitySlotBoundary, {
|
|
234
292
|
activeStateKey,
|
|
@@ -375,4 +433,4 @@ function ParallelSlot({ name }) {
|
|
|
375
433
|
return React$1.useContext(ParallelSlotsContext)?.[name] ?? null;
|
|
376
434
|
}
|
|
377
435
|
//#endregion
|
|
378
|
-
export { BfcacheChildrenBoundary, BfcacheIdentityMapContext, BfcacheSegmentBoundary, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, RenderedSearchContext, Slot, UNMATCHED_SLOT, bindAppElementsRenderedSearch, getNonCacheComponentsSegmentKey, mergeElements, resolveBfcacheSegmentStateKey, setAppElementsRenderedSearch, stageBfcacheSlotEntryForRender, updateBfcacheSlotEntryOrder };
|
|
436
|
+
export { BfcacheChildrenBoundary, BfcacheIdentityMapContext, BfcacheSegmentBoundary, Children, ChildrenContext, ElementsContext, ParallelSlot, ParallelSlotsContext, RenderedSearchContext, Slot, UNMATCHED_SLOT, bindAppElementsRenderedSearch, getNonCacheComponentsSegmentKey, isBfcacheIdentityRetained, mergeElements, resolveBfcacheSegmentStateKey, setAppElementsRenderedSearch, stageBfcacheSlotEntryForRender, updateBfcacheSlotEntryOrder };
|