vinext 1.4.0 → 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 +21 -21
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +4 -7
- package/dist/client/dev-error-overlay-store.d.ts +1 -0
- package/dist/client/dev-error-overlay.d.ts +2 -0
- package/dist/client/dev-error-overlay.js +19 -3
- package/dist/config/config-matchers.js +4 -2
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +55 -2
- package/dist/config/next-config.d.ts +22 -1
- package/dist/config/next-config.js +49 -4
- package/dist/entries/app-rsc-entry.js +41 -19
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +5 -1
- package/dist/index.js +44 -32
- 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/plugins/strip-server-exports.js +2 -1
- 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 +5 -6
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +28 -7
- package/dist/server/app-browser-rsc-redirect.js +3 -3
- package/dist/server/app-browser-server-action-client.js +2 -2
- package/dist/server/app-browser-visible-commit.js +2 -2
- 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 +40 -19
- package/dist/server/app-page-boundary-render.js +7 -12
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -2
- package/dist/server/app-page-dispatch.js +73 -31
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +40 -52
- package/dist/server/app-page-execution.js +9 -6
- 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 -3
- package/dist/server/app-page-render.js +31 -17
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +10 -0
- package/dist/server/app-page-route-wiring.js +34 -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-page-stream.d.ts +10 -8
- package/dist/server/app-page-stream.js +2 -1
- package/dist/server/app-request-stage-independent-entry.js +1 -1
- package/dist/server/app-response-stage-entry.js +1 -0
- package/dist/server/app-route-handler-execution.js +3 -2
- package/dist/server/app-route-handler-runtime.d.ts +1 -1
- package/dist/server/app-route-handler-runtime.js +11 -7
- 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 +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +10 -10
- package/dist/server/app-rsc-cache-busting.js +27 -68
- package/dist/server/app-rsc-error-handler.js +1 -1
- package/dist/server/app-rsc-errors.js +4 -1
- package/dist/server/app-rsc-handler.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +126 -103
- package/dist/server/app-rsc-request-normalization.d.ts +10 -12
- package/dist/server/app-rsc-request-normalization.js +11 -14
- 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-response-stage.js +4 -12
- 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 +24 -14
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +13 -8
- package/dist/server/app-worker-stages.d.ts +12 -3
- package/dist/server/app-worker-stages.js +37 -4
- package/dist/server/bfcache-identity.d.ts +7 -0
- package/dist/server/bfcache-identity.js +10 -1
- package/dist/server/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -1
- package/dist/server/cacheability-manifest.js +1 -2
- package/dist/server/dev-origin-check.d.ts +2 -1
- package/dist/server/dev-origin-check.js +2 -1
- package/dist/server/dev-server.js +2 -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 +28 -22
- package/dist/server/image-optimization.js +17 -2
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +25 -7
- package/dist/server/middleware-matcher.js +19 -6
- package/dist/server/middleware-runtime.js +6 -2
- package/dist/server/multi-stage.d.ts +7 -3
- 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-node-compat.d.ts +1 -1
- package/dist/server/pages-node-compat.js +2 -0
- package/dist/server/pages-page-data.js +2 -1
- package/dist/server/pages-page-handler.js +17 -6
- package/dist/server/pages-page-response.d.ts +9 -0
- package/dist/server/pages-page-response.js +18 -4
- package/dist/server/pages-request-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +25 -4
- package/dist/server/pages-router-entry.js +3 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +59 -17
- package/dist/server/request-error-log.d.ts +9 -0
- package/dist/server/request-error-log.js +13 -0
- package/dist/server/request-pipeline.d.ts +14 -4
- package/dist/server/request-pipeline.js +33 -15
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- package/dist/server/rsc-completion-metadata.js +3 -0
- package/dist/server/static-file-cache.d.ts +9 -0
- package/dist/server/static-file-cache.js +13 -2
- 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/cache-request-state.d.ts +6 -6
- package/dist/shims/cache-request-state.js +6 -36
- package/dist/shims/cache-runtime.d.ts +3 -1
- package/dist/shims/cache-runtime.js +77 -47
- package/dist/shims/cache.d.ts +10 -5
- package/dist/shims/cache.js +35 -22
- package/dist/shims/client-hook-error.d.ts +2 -0
- package/dist/shims/client-hook-error.js +16 -1
- package/dist/shims/fetch-cache.js +6 -2
- package/dist/shims/headers.d.ts +2 -1
- package/dist/shims/headers.js +5 -3
- 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/internal/cache-context-accessor.d.ts +1 -1
- package/dist/shims/link.js +5 -10
- package/dist/shims/navigation.js +2 -3
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/slot.d.ts +9 -2
- package/dist/shims/slot.js +76 -11
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- package/dist/utils/cache-life-profiles.d.ts +33 -0
- package/dist/utils/cache-life-profiles.js +111 -0
- package/dist/utils/middleware-request-headers.d.ts +14 -3
- package/dist/utils/middleware-request-headers.js +54 -7
- package/package.json +1 -1
package/dist/shims/link.js
CHANGED
|
@@ -149,7 +149,7 @@ function getLinkPrefetchRouterMode() {
|
|
|
149
149
|
/**
|
|
150
150
|
* Prefetch a URL for faster navigation.
|
|
151
151
|
*
|
|
152
|
-
* For App Router (RSC): fetches the
|
|
152
|
+
* For App Router (RSC): fetches the RSC payload in the background and
|
|
153
153
|
* stores it in an in-memory cache for instant use during navigation.
|
|
154
154
|
* For Pages Router: warms the page chunk, prefetches data only for SSG pages,
|
|
155
155
|
* and falls back to a document prefetch hint when no page loader matches.
|
|
@@ -381,7 +381,7 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale, owner
|
|
|
381
381
|
async function promotePrefetchEntriesForNavigation(href) {
|
|
382
382
|
if (typeof window === "undefined") return;
|
|
383
383
|
if (!hasLinkAppNavigationRuntime()) return;
|
|
384
|
-
const [{ getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, promotePrefetchCacheEntryForNavigation }, { parseRscRequestPath, stripRscCacheBustingSearchParam
|
|
384
|
+
const [{ getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, promotePrefetchCacheEntryForNavigation }, { parseRscRequestPath, stripRscCacheBustingSearchParam }] = await Promise.all([loadNavigationModule(), loadAppRscCacheBustingModule()]);
|
|
385
385
|
let target;
|
|
386
386
|
const fullHref = toCanonicalBrowserNavigationHref(href, window.location.href, __basePath, __trailingSlash);
|
|
387
387
|
try {
|
|
@@ -405,7 +405,7 @@ async function promotePrefetchEntriesForNavigation(href) {
|
|
|
405
405
|
continue;
|
|
406
406
|
}
|
|
407
407
|
stripRscCacheBustingSearchParam(cached);
|
|
408
|
-
if (
|
|
408
|
+
if (cached.pathname === target.pathname && cached.search === target.search) promotePrefetchCacheEntryForNavigation(cacheKey, entry);
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
/**
|
|
@@ -627,16 +627,11 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
627
627
|
})
|
|
628
628
|
});
|
|
629
629
|
const [pending, setPending] = useState(false);
|
|
630
|
-
const mountedRef = useRef(true);
|
|
631
630
|
const pendingPagesIntentPrefetchRef = useRef(null);
|
|
632
|
-
const setPendingRef = useRef(
|
|
633
|
-
if (mountedRef.current) setPending(next);
|
|
634
|
-
});
|
|
631
|
+
const setPendingRef = useRef(setPending);
|
|
635
632
|
useEffect(() => {
|
|
636
|
-
mountedRef.current = true;
|
|
637
633
|
const setter = setPendingRef.current;
|
|
638
634
|
return () => {
|
|
639
|
-
mountedRef.current = false;
|
|
640
635
|
pendingPagesIntentPrefetchRef.current?.();
|
|
641
636
|
pendingPagesIntentPrefetchRef.current = null;
|
|
642
637
|
if (setter) clearLinkForCurrentNavigation(setter);
|
|
@@ -802,7 +797,7 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
802
797
|
setPending(true);
|
|
803
798
|
React.startTransition(() => {
|
|
804
799
|
navigateClientSide(navigateHref, replace ? "replace" : "push", scroll, false).finally(() => {
|
|
805
|
-
|
|
800
|
+
setPending(false);
|
|
806
801
|
if (setter) clearLinkForCurrentNavigation(setter);
|
|
807
802
|
});
|
|
808
803
|
});
|
package/dist/shims/navigation.js
CHANGED
|
@@ -11,7 +11,7 @@ import { resolveDirectHybridClientRouteOwner } from "./internal/hybrid-client-ro
|
|
|
11
11
|
import { isHashOnlyBrowserUrlChange, toCanonicalBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
|
|
12
12
|
import { disableSmoothScrollDuringRouteTransition } from "./disable-smooth-scroll.js";
|
|
13
13
|
import { retryScrollTo, scrollToHashTarget } from "./hash-scroll.js";
|
|
14
|
-
import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, formatRscRequestPath, parseRscRequestPath, stripRscCacheBustingSearchParam
|
|
14
|
+
import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, formatRscRequestPath, parseRscRequestPath, stripRscCacheBustingSearchParam } from "../server/app-rsc-cache-busting.js";
|
|
15
15
|
import { clearAppNavigationFailureTarget, stageAppNavigationFailureTarget } from "../client/app-nav-failure-handler.js";
|
|
16
16
|
import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
|
|
17
17
|
import { getNavigationRuntime, hasAppNavigationRuntime, loadNavigationRuntimeRouteManifest } from "../client/navigation-runtime.js";
|
|
@@ -245,8 +245,7 @@ function normalizeRscCacheLookupUrl(rscUrl) {
|
|
|
245
245
|
}
|
|
246
246
|
function normalizeRscCacheLookupPathname(rscUrl) {
|
|
247
247
|
try {
|
|
248
|
-
|
|
249
|
-
return stripRscSuffix(url.pathname);
|
|
248
|
+
return parseRscRequestPath(rscUrl).pathname;
|
|
250
249
|
} catch {
|
|
251
250
|
return null;
|
|
252
251
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CacheLifeConfig } from "../utils/cache-life-profiles.js";
|
|
1
2
|
import { NavigationContext } from "./navigation-context-state.js";
|
|
2
3
|
import "./navigation.js";
|
|
3
4
|
import { ExecutionContextLike } from "./request-context.js";
|
|
@@ -6,7 +7,7 @@ import { FetchCacheState } from "./fetch-cache-state.js";
|
|
|
6
7
|
import "./fetch-cache.js";
|
|
7
8
|
import { HeadersAccessPhase, VinextHeadersShimState } from "./internal/headers-state.js";
|
|
8
9
|
import { HeadersContext } from "./headers.js";
|
|
9
|
-
import {
|
|
10
|
+
import { CacheState } from "./cache-request-state.js";
|
|
10
11
|
import { RouterState, SSRContext } from "./router-state.js";
|
|
11
12
|
import { PrivateCacheState } from "./cache-runtime.js";
|
|
12
13
|
import { HeadState } from "./head-state.js";
|
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>>;
|
|
@@ -40,6 +43,7 @@ type MergeElementsOptions = {
|
|
|
40
43
|
clearAbsentSlots?: boolean;
|
|
41
44
|
preserveAbsentSlots?: boolean;
|
|
42
45
|
preserveElementIds?: readonly string[];
|
|
46
|
+
preserveElementSeedIds?: readonly string[];
|
|
43
47
|
preservePreviousSlotIds?: readonly string[];
|
|
44
48
|
};
|
|
45
49
|
export declare function updateBfcacheSlotEntryOrder(previousOrder: readonly string[], activeStateKey: string, maxEntries?: number): string[];
|
|
@@ -67,9 +71,12 @@ export declare function BfcacheSegmentBoundary({ children, id, stateKey }: {
|
|
|
67
71
|
* the parent's layout router, above the child's boundaries, so a page left for a
|
|
68
72
|
* sibling with its own layout keeps its state when navigating back.
|
|
69
73
|
*/
|
|
70
|
-
export declare function BfcacheChildrenBoundary({ children, id }: {
|
|
74
|
+
export declare function BfcacheChildrenBoundary({ children, id, stateKey }: {
|
|
71
75
|
children: React$1.ReactNode;
|
|
72
|
-
|
|
76
|
+
/** The child element whose identity keys the entries. */
|
|
77
|
+
id?: string;
|
|
78
|
+
/** A fixed key, for a segment level with no element of its own. */
|
|
79
|
+
stateKey?: string;
|
|
73
80
|
}): React$1.JSX.Element;
|
|
74
81
|
export declare function getNonCacheComponentsSegmentKey(id: string, activeStateKey: string): string | undefined;
|
|
75
82
|
export declare function resolveBfcacheSegmentStateKey(id: string, identityMap: Readonly<Record<string, string>>, bfcacheIdMap: Readonly<Record<string, string>> | null): string | undefined;
|
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)) });
|
|
@@ -213,21 +270,23 @@ function BfcacheSegmentBoundary({ children, id, stateKey }) {
|
|
|
213
270
|
* the parent's layout router, above the child's boundaries, so a page left for a
|
|
214
271
|
* sibling with its own layout keeps its state when navigating back.
|
|
215
272
|
*/
|
|
216
|
-
function BfcacheChildrenBoundary({ children, id }) {
|
|
273
|
+
function BfcacheChildrenBoundary({ children, id, stateKey }) {
|
|
217
274
|
if (!BfcacheIdMapContext || !BfcacheSegmentIdContext || !isCacheComponentsEnabled()) return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
218
275
|
return /* @__PURE__ */ jsx(BfcacheChildrenActivityBoundary, {
|
|
219
276
|
id,
|
|
220
277
|
IdMapContext: BfcacheIdMapContext,
|
|
221
278
|
SegmentContext: BfcacheSegmentIdContext,
|
|
279
|
+
stateKey,
|
|
222
280
|
children
|
|
223
281
|
});
|
|
224
282
|
}
|
|
225
|
-
function BfcacheChildrenActivityBoundary({ children, id, IdMapContext, SegmentContext }) {
|
|
283
|
+
function BfcacheChildrenActivityBoundary({ children, id, IdMapContext, SegmentContext, stateKey }) {
|
|
226
284
|
const elements = React$1.useContext(ElementsContext);
|
|
227
285
|
const identityMap = React$1.useContext(BfcacheIdentityMapContext);
|
|
228
286
|
const bfcacheIdMap = React$1.useContext(IdMapContext);
|
|
229
287
|
const segmentId = React$1.useContext(SegmentContext);
|
|
230
|
-
const
|
|
288
|
+
const rebindStateKey = readOptimisticStateKeyRebinder(elements);
|
|
289
|
+
const activeStateKey = stateKey !== void 0 ? rebindStateKey?.(stateKey) ?? stateKey : id === void 0 ? void 0 : resolveBfcacheSegmentStateKey(id, identityMap, bfcacheIdMap);
|
|
231
290
|
if (segmentId === null || activeStateKey === void 0) return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
232
291
|
return /* @__PURE__ */ jsx(BfcacheActivitySlotBoundary, {
|
|
233
292
|
activeStateKey,
|
|
@@ -272,6 +331,7 @@ function mergeElements(prev, next, options = {}) {
|
|
|
272
331
|
const clearAbsentSlots = typeof options === "boolean" ? options : options.clearAbsentSlots ?? false;
|
|
273
332
|
const preserveAbsentSlots = typeof options === "boolean" ? !options : options.preserveAbsentSlots ?? true;
|
|
274
333
|
const preserveElementIds = typeof options === "boolean" ? [] : options.preserveElementIds ?? [];
|
|
334
|
+
const preserveElementSeedIds = typeof options === "boolean" ? [] : options.preserveElementSeedIds ?? [];
|
|
275
335
|
const preservePreviousSlotIds = typeof options === "boolean" ? [] : options.preservePreviousSlotIds ?? [];
|
|
276
336
|
const merged = { ...next };
|
|
277
337
|
const preservedIdentityIds = /* @__PURE__ */ new Set();
|
|
@@ -282,6 +342,11 @@ function mergeElements(prev, next, options = {}) {
|
|
|
282
342
|
preservedIdentityIds.add(id);
|
|
283
343
|
}
|
|
284
344
|
}
|
|
345
|
+
for (const id of preserveElementSeedIds) {
|
|
346
|
+
if (preservedIdentityIds.has(id) || !Object.hasOwn(prev, id)) continue;
|
|
347
|
+
const value = prev[id];
|
|
348
|
+
if (value !== void 0) merged[id] = value;
|
|
349
|
+
}
|
|
285
350
|
const slotKeys = new Set([...Object.keys(prev), ...Object.keys(next)].filter((key) => AppElementsWire.isSlotId(key) && !isNestedBfcacheSlotSegmentId(key)));
|
|
286
351
|
if (clearAbsentSlots) {
|
|
287
352
|
for (const key of slotKeys) if (!Object.hasOwn(next, key)) delete merged[key];
|
|
@@ -368,4 +433,4 @@ function ParallelSlot({ name }) {
|
|
|
368
433
|
return React$1.useContext(ParallelSlotsContext)?.[name] ?? null;
|
|
369
434
|
}
|
|
370
435
|
//#endregion
|
|
371
|
-
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 };
|
|
@@ -30,6 +30,12 @@ export type UnifiedRequestContext = {
|
|
|
30
30
|
requestCache: WeakMap<(...args: any[]) => any, unknown>;
|
|
31
31
|
/** Shared lifecycle state for work deferred until the response closes. */
|
|
32
32
|
afterContext: AfterRequestContext;
|
|
33
|
+
/**
|
|
34
|
+
* Draft-mode state of the headers context this request last cleared. The
|
|
35
|
+
* render can outlive its headers context (cleared once the response body
|
|
36
|
+
* closes), and so do after() callbacks; cache bypasses keep reading this.
|
|
37
|
+
*/
|
|
38
|
+
draftModeAtHeadersClear: boolean;
|
|
33
39
|
} & VinextHeadersShimState & I18nState & NavigationState & CacheState & PrivateCacheState & FetchCacheState & RouterState & HeadState & RootParamsState;
|
|
34
40
|
export type AfterRequestContext = {
|
|
35
41
|
callbacks: Array<() => unknown>;
|
|
@@ -21,6 +21,13 @@ export declare function stripBasePath(pathname: string, basePath: string): strin
|
|
|
21
21
|
* this pathname-only helper.
|
|
22
22
|
*/
|
|
23
23
|
export declare function addBasePathToPathname(pathname: string, basePath: string | undefined): string;
|
|
24
|
+
/**
|
|
25
|
+
* Re-add a basePath that `stripBasePath` removed. Unlike
|
|
26
|
+
* `addBasePathToPathname`, this always prepends it: a stripped route whose
|
|
27
|
+
* first segment equals the basePath (basePath "/docs", route "/docs/a") still
|
|
28
|
+
* starts with the basePath and must not be mistaken for the original URL.
|
|
29
|
+
*/
|
|
30
|
+
export declare function restoreBasePath(pathname: string, basePath: string | undefined): string;
|
|
24
31
|
/**
|
|
25
32
|
* Remove trailing slashes from a pathname while preserving the root "/".
|
|
26
33
|
* Collapses any number of trailing slashes ("/a//" → "/a"). Used by the
|
package/dist/utils/base-path.js
CHANGED
|
@@ -31,6 +31,16 @@ function addBasePathToPathname(pathname, basePath) {
|
|
|
31
31
|
return pathname === "/" ? basePath : `${basePath}${pathname}`;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
+
* Re-add a basePath that `stripBasePath` removed. Unlike
|
|
35
|
+
* `addBasePathToPathname`, this always prepends it: a stripped route whose
|
|
36
|
+
* first segment equals the basePath (basePath "/docs", route "/docs/a") still
|
|
37
|
+
* starts with the basePath and must not be mistaken for the original URL.
|
|
38
|
+
*/
|
|
39
|
+
function restoreBasePath(pathname, basePath) {
|
|
40
|
+
if (!basePath) return pathname;
|
|
41
|
+
return pathname === "/" ? basePath : `${basePath}${pathname}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
34
44
|
* Remove trailing slashes from a pathname while preserving the root "/".
|
|
35
45
|
* Collapses any number of trailing slashes ("/a//" → "/a"). Used by the
|
|
36
46
|
* trailing-slash redirect path and route pattern normalization.
|
|
@@ -42,4 +52,4 @@ function removeTrailingSlash(pathname) {
|
|
|
42
52
|
return end === 0 ? "/" : pathname.slice(0, end);
|
|
43
53
|
}
|
|
44
54
|
//#endregion
|
|
45
|
-
export { addBasePathToPathname, hasBasePath, removeTrailingSlash, stripBasePath };
|
|
55
|
+
export { addBasePathToPathname, hasBasePath, removeTrailingSlash, restoreBasePath, stripBasePath };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/utils/cache-life-profiles.d.ts
|
|
2
|
+
export type CacheLifeConfig = {
|
|
3
|
+
stale?: number;
|
|
4
|
+
revalidate?: number;
|
|
5
|
+
expire?: number;
|
|
6
|
+
};
|
|
7
|
+
/** Next.js's built-in profiles (`defaultConfig.cacheLife` in config-shared.ts). */
|
|
8
|
+
export declare const BUILT_IN_CACHE_LIFE_PROFILES: Readonly<Record<string, Readonly<CacheLifeConfig>>>;
|
|
9
|
+
/**
|
|
10
|
+
* Merge next.config `cacheLife` profiles over the built-ins, as Next.js does
|
|
11
|
+
* in `assignDefaultsAndValidate` (server/config.ts): a user profile replaces
|
|
12
|
+
* the built-in of the same name whole, and only `default` gets its missing
|
|
13
|
+
* fields filled: `stale` from `staleTimes.static`, `revalidate` from the
|
|
14
|
+
* built-in, `expire` from `expireTime`. That includes the built-in `default`,
|
|
15
|
+
* whose `stale` is unset in Next.js's defaults too. The other profiles keep
|
|
16
|
+
* their gaps, which the "use cache" store resolves from `default`.
|
|
17
|
+
*
|
|
18
|
+
* Next.js only schema-checks these profiles (fields must be numbers) and
|
|
19
|
+
* warns; `revalidate > expire` is checked for `cacheLife({...})` calls, not
|
|
20
|
+
* for config profiles.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveCacheLifeProfiles(userProfiles: unknown, defaults: {
|
|
23
|
+
expireTime: number;
|
|
24
|
+
staticStaleTime: number;
|
|
25
|
+
}, warn: (message: string) => void): Record<string, CacheLifeConfig>;
|
|
26
|
+
/** Serialize resolved profiles for the `__VINEXT_CACHE_LIFE_PROFILES` define. */
|
|
27
|
+
export declare function serializeCacheLifeProfiles(profiles: Record<string, CacheLifeConfig>): string;
|
|
28
|
+
/**
|
|
29
|
+
* Read the `__VINEXT_CACHE_LIFE_PROFILES` define. When absent, the profiles a
|
|
30
|
+
* next.config without `cacheLife` resolves to.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseCacheLifeProfiles(serialized: string | undefined): Record<string, CacheLifeConfig>;
|
|
33
|
+
//#endregion
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isUnknownRecord } from "./record.js";
|
|
2
|
+
//#region src/utils/cache-life-profiles.ts
|
|
3
|
+
/** Next.js's built-in profiles (`defaultConfig.cacheLife` in config-shared.ts). */
|
|
4
|
+
const BUILT_IN_CACHE_LIFE_PROFILES = {
|
|
5
|
+
default: {
|
|
6
|
+
revalidate: 900,
|
|
7
|
+
expire: 4294967294
|
|
8
|
+
},
|
|
9
|
+
seconds: {
|
|
10
|
+
stale: 30,
|
|
11
|
+
revalidate: 1,
|
|
12
|
+
expire: 60
|
|
13
|
+
},
|
|
14
|
+
minutes: {
|
|
15
|
+
stale: 300,
|
|
16
|
+
revalidate: 60,
|
|
17
|
+
expire: 3600
|
|
18
|
+
},
|
|
19
|
+
hours: {
|
|
20
|
+
stale: 300,
|
|
21
|
+
revalidate: 3600,
|
|
22
|
+
expire: 86400
|
|
23
|
+
},
|
|
24
|
+
days: {
|
|
25
|
+
stale: 300,
|
|
26
|
+
revalidate: 86400,
|
|
27
|
+
expire: 604800
|
|
28
|
+
},
|
|
29
|
+
weeks: {
|
|
30
|
+
stale: 300,
|
|
31
|
+
revalidate: 604800,
|
|
32
|
+
expire: 2592e3
|
|
33
|
+
},
|
|
34
|
+
max: {
|
|
35
|
+
stale: 300,
|
|
36
|
+
revalidate: 2592e3,
|
|
37
|
+
expire: 31536e3
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const CACHE_LIFE_FIELDS = [
|
|
41
|
+
"stale",
|
|
42
|
+
"revalidate",
|
|
43
|
+
"expire"
|
|
44
|
+
];
|
|
45
|
+
function copyBuiltInProfiles() {
|
|
46
|
+
const profiles = {};
|
|
47
|
+
for (const [name, profile] of Object.entries(BUILT_IN_CACHE_LIFE_PROFILES)) profiles[name] = { ...profile };
|
|
48
|
+
return profiles;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Merge next.config `cacheLife` profiles over the built-ins, as Next.js does
|
|
52
|
+
* in `assignDefaultsAndValidate` (server/config.ts): a user profile replaces
|
|
53
|
+
* the built-in of the same name whole, and only `default` gets its missing
|
|
54
|
+
* fields filled: `stale` from `staleTimes.static`, `revalidate` from the
|
|
55
|
+
* built-in, `expire` from `expireTime`. That includes the built-in `default`,
|
|
56
|
+
* whose `stale` is unset in Next.js's defaults too. The other profiles keep
|
|
57
|
+
* their gaps, which the "use cache" store resolves from `default`.
|
|
58
|
+
*
|
|
59
|
+
* Next.js only schema-checks these profiles (fields must be numbers) and
|
|
60
|
+
* warns; `revalidate > expire` is checked for `cacheLife({...})` calls, not
|
|
61
|
+
* for config profiles.
|
|
62
|
+
*/
|
|
63
|
+
function resolveCacheLifeProfiles(userProfiles, defaults, warn) {
|
|
64
|
+
const profiles = copyBuiltInProfiles();
|
|
65
|
+
for (const [name, rawProfile] of Object.entries(isUnknownRecord(userProfiles) ? userProfiles : {})) {
|
|
66
|
+
if (!isUnknownRecord(rawProfile)) {
|
|
67
|
+
warn(`[vinext] Invalid next.config option \`cacheLife.${name}\`: expected an object.`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const profile = {};
|
|
71
|
+
for (const field of CACHE_LIFE_FIELDS) {
|
|
72
|
+
const value = rawProfile[field];
|
|
73
|
+
if (value === void 0) continue;
|
|
74
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
75
|
+
warn(`[vinext] Invalid next.config option \`cacheLife.${name}.${field}\`: expected a number of seconds.`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
profile[field] = value;
|
|
79
|
+
}
|
|
80
|
+
profiles[name] = profile;
|
|
81
|
+
}
|
|
82
|
+
const defaultProfile = profiles.default;
|
|
83
|
+
defaultProfile.stale ??= defaults.staticStaleTime;
|
|
84
|
+
defaultProfile.revalidate ??= BUILT_IN_CACHE_LIFE_PROFILES.default.revalidate;
|
|
85
|
+
defaultProfile.expire ??= defaults.expireTime;
|
|
86
|
+
return profiles;
|
|
87
|
+
}
|
|
88
|
+
function encodeNonFiniteNumber(_key, value) {
|
|
89
|
+
return value === Infinity || value === -Infinity ? String(value) : value;
|
|
90
|
+
}
|
|
91
|
+
function decodeNonFiniteNumber(_key, value) {
|
|
92
|
+
return value === "Infinity" || value === "-Infinity" ? Number(value) : value;
|
|
93
|
+
}
|
|
94
|
+
/** Serialize resolved profiles for the `__VINEXT_CACHE_LIFE_PROFILES` define. */
|
|
95
|
+
function serializeCacheLifeProfiles(profiles) {
|
|
96
|
+
return JSON.stringify(profiles, encodeNonFiniteNumber);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read the `__VINEXT_CACHE_LIFE_PROFILES` define. When absent, the profiles a
|
|
100
|
+
* next.config without `cacheLife` resolves to.
|
|
101
|
+
*/
|
|
102
|
+
function parseCacheLifeProfiles(serialized) {
|
|
103
|
+
if (!serialized) {
|
|
104
|
+
const profiles = copyBuiltInProfiles();
|
|
105
|
+
profiles.default.stale = 300;
|
|
106
|
+
return profiles;
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(serialized, decodeNonFiniteNumber);
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { BUILT_IN_CACHE_LIFE_PROFILES, parseCacheLifeProfiles, resolveCacheLifeProfiles, serializeCacheLifeProfiles };
|
|
@@ -7,7 +7,17 @@ type MiddlewareHeaderSource = Headers | Record<string, MiddlewareHeaderValue>;
|
|
|
7
7
|
* under their literal protocol-header names on both the request and response.
|
|
8
8
|
*/
|
|
9
9
|
export declare function getUnconsumedMiddlewareRequestHeaders(source: MiddlewareHeaderSource): Map<string, string>;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* `baseHeaders` with only Next.js's generic merge of ordinary middleware
|
|
12
|
+
* response headers applied, without the request-header protocol.
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyMiddlewareResponseHeadersToRequest(baseHeaders: Headers, middlewareHeaders: MiddlewareHeaderSource): Headers;
|
|
15
|
+
/**
|
|
16
|
+
* Whether middleware changed the headers seen by downstream application code
|
|
17
|
+
* through its request-header protocol. Ordinary response headers are copied to
|
|
18
|
+
* the request as well, but like a client's own headers they do not opt a
|
|
19
|
+
* render out of the shared response stage.
|
|
20
|
+
*/
|
|
11
21
|
export declare function hasMiddlewareRequestHeaderOverrides(source: MiddlewareHeaderSource | null): boolean;
|
|
12
22
|
export declare function encodeMiddlewareRequestHeaders(targetHeaders: Headers, requestHeaders: Headers): void;
|
|
13
23
|
/**
|
|
@@ -16,8 +26,9 @@ export declare function encodeMiddlewareRequestHeaders(targetHeaders: Headers, r
|
|
|
16
26
|
* middleware. Never re-add absent headers from the base request — that would
|
|
17
27
|
* resurrect credentials the app explicitly stripped before an external
|
|
18
28
|
* rewrite. Next.js treats the empty value emitted for `new Headers()` as no
|
|
19
|
-
* override. Any unconsumed `x-middleware-request-*` values
|
|
20
|
-
*
|
|
29
|
+
* override. Any unconsumed `x-middleware-request-*` values and every other
|
|
30
|
+
* truthy middleware response header are subsequently copied to the request
|
|
31
|
+
* under their literal names, replacing values the client sent.
|
|
21
32
|
*/
|
|
22
33
|
export declare function buildRequestHeadersFromMiddlewareResponse(baseHeaders: Headers, middlewareHeaders: MiddlewareHeaderSource): Headers | null;
|
|
23
34
|
export declare function shouldKeepMiddlewareHeader(key: string): boolean;
|
|
@@ -34,7 +34,52 @@ function getUnconsumedMiddlewareRequestHeaders(source) {
|
|
|
34
34
|
for (const [key, value] of getForwardedRequestHeaders(source)) if (value && !overriddenHeaders?.has(key)) unconsumedHeaders.set(`${MIDDLEWARE_REQUEST_HEADER_PREFIX}${key}`, value);
|
|
35
35
|
return unconsumedHeaders;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
const UNMERGED_MIDDLEWARE_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
38
|
+
"accept-encoding",
|
|
39
|
+
"connection",
|
|
40
|
+
"content-encoding",
|
|
41
|
+
"content-length",
|
|
42
|
+
"expect",
|
|
43
|
+
"keep-alive",
|
|
44
|
+
"keepalive",
|
|
45
|
+
"transfer-encoding"
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Return the truthy ordinary middleware response headers. Next.js's generic
|
|
49
|
+
* middleware-header merge writes each of them to the request too, so a value
|
|
50
|
+
* middleware sets replaces one the client sent under the same name. The
|
|
51
|
+
* `x-middleware-*` namespace carries vinext's middleware protocol and is
|
|
52
|
+
* handled separately.
|
|
53
|
+
*/
|
|
54
|
+
function getMergedMiddlewareResponseHeaders(source) {
|
|
55
|
+
const mergedHeaders = new Headers();
|
|
56
|
+
const entries = source instanceof Headers ? source : Object.entries(source).flatMap(([key, value]) => Array.isArray(value) ? value.map((item) => [key, item]) : [[key, value]]);
|
|
57
|
+
for (const [key, value] of entries) {
|
|
58
|
+
const lowerKey = key.toLowerCase();
|
|
59
|
+
if (value && !lowerKey.startsWith("x-middleware-") && !UNMERGED_MIDDLEWARE_RESPONSE_HEADERS.has(lowerKey)) mergedHeaders.append(lowerKey, value);
|
|
60
|
+
}
|
|
61
|
+
return mergedHeaders;
|
|
62
|
+
}
|
|
63
|
+
function applyMergedMiddlewareHeaders(targetHeaders, unconsumedHeaders, mergedHeaders) {
|
|
64
|
+
for (const [key, value] of unconsumedHeaders) targetHeaders.set(key, value);
|
|
65
|
+
for (const key of new Set(mergedHeaders.keys())) targetHeaders.delete(key);
|
|
66
|
+
for (const [key, value] of mergedHeaders) targetHeaders.append(key, value);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `baseHeaders` with only Next.js's generic merge of ordinary middleware
|
|
70
|
+
* response headers applied, without the request-header protocol.
|
|
71
|
+
*/
|
|
72
|
+
function applyMiddlewareResponseHeadersToRequest(baseHeaders, middlewareHeaders) {
|
|
73
|
+
const nextHeaders = new Headers(baseHeaders);
|
|
74
|
+
applyMergedMiddlewareHeaders(nextHeaders, /* @__PURE__ */ new Map(), getMergedMiddlewareResponseHeaders(middlewareHeaders));
|
|
75
|
+
return nextHeaders;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether middleware changed the headers seen by downstream application code
|
|
79
|
+
* through its request-header protocol. Ordinary response headers are copied to
|
|
80
|
+
* the request as well, but like a client's own headers they do not opt a
|
|
81
|
+
* render out of the shared response stage.
|
|
82
|
+
*/
|
|
38
83
|
function hasMiddlewareRequestHeaderOverrides(source) {
|
|
39
84
|
if (!source) return false;
|
|
40
85
|
return Boolean(getMiddlewareHeaderValue(source, "x-middleware-override-headers")) || getUnconsumedMiddlewareRequestHeaders(source).size > 0;
|
|
@@ -50,17 +95,19 @@ function encodeMiddlewareRequestHeaders(targetHeaders, requestHeaders) {
|
|
|
50
95
|
* middleware. Never re-add absent headers from the base request — that would
|
|
51
96
|
* resurrect credentials the app explicitly stripped before an external
|
|
52
97
|
* rewrite. Next.js treats the empty value emitted for `new Headers()` as no
|
|
53
|
-
* override. Any unconsumed `x-middleware-request-*` values
|
|
54
|
-
*
|
|
98
|
+
* override. Any unconsumed `x-middleware-request-*` values and every other
|
|
99
|
+
* truthy middleware response header are subsequently copied to the request
|
|
100
|
+
* under their literal names, replacing values the client sent.
|
|
55
101
|
*/
|
|
56
102
|
function buildRequestHeadersFromMiddlewareResponse(baseHeaders, middlewareHeaders) {
|
|
57
103
|
const forwardedHeaders = getForwardedRequestHeaders(middlewareHeaders);
|
|
58
104
|
const unconsumedHeaders = getUnconsumedMiddlewareRequestHeaders(middlewareHeaders);
|
|
105
|
+
const mergedHeaders = getMergedMiddlewareResponseHeaders(middlewareHeaders);
|
|
59
106
|
const rawOverrideHeader = getMiddlewareHeaderValue(middlewareHeaders, MIDDLEWARE_OVERRIDE_HEADERS);
|
|
60
107
|
if (!rawOverrideHeader) {
|
|
61
|
-
if (unconsumedHeaders.size === 0) return null;
|
|
108
|
+
if (unconsumedHeaders.size === 0 && mergedHeaders.keys().next().done) return null;
|
|
62
109
|
const nextHeaders = new Headers(baseHeaders);
|
|
63
|
-
|
|
110
|
+
applyMergedMiddlewareHeaders(nextHeaders, unconsumedHeaders, mergedHeaders);
|
|
64
111
|
return nextHeaders;
|
|
65
112
|
}
|
|
66
113
|
const overrideHeaderNames = parseOverrideHeaderNames(rawOverrideHeader);
|
|
@@ -69,11 +116,11 @@ function buildRequestHeadersFromMiddlewareResponse(baseHeaders, middlewareHeader
|
|
|
69
116
|
const value = forwardedHeaders.get(key);
|
|
70
117
|
if (value !== void 0) nextHeaders.set(key, value);
|
|
71
118
|
}
|
|
72
|
-
|
|
119
|
+
applyMergedMiddlewareHeaders(nextHeaders, unconsumedHeaders, mergedHeaders);
|
|
73
120
|
return nextHeaders;
|
|
74
121
|
}
|
|
75
122
|
function shouldKeepMiddlewareHeader(key) {
|
|
76
123
|
return key === "x-middleware-override-headers" || key === "x-middleware-cache" || key === "x-middleware-set-cookie" || key.startsWith("x-middleware-request-");
|
|
77
124
|
}
|
|
78
125
|
//#endregion
|
|
79
|
-
export { buildRequestHeadersFromMiddlewareResponse, encodeMiddlewareRequestHeaders, getUnconsumedMiddlewareRequestHeaders, hasMiddlewareRequestHeaderOverrides, shouldKeepMiddlewareHeader };
|
|
126
|
+
export { applyMiddlewareResponseHeadersToRequest, buildRequestHeadersFromMiddlewareResponse, encodeMiddlewareRequestHeaders, getUnconsumedMiddlewareRequestHeaders, hasMiddlewareRequestHeaderOverrides, shouldKeepMiddlewareHeader };
|