Initial boiler plate project

This commit is contained in:
2024-09-24 03:52:46 +00:00
parent 6120b2d6c3
commit 154b93e267
10034 changed files with 2079352 additions and 2 deletions

View File

@ -0,0 +1,37 @@
import { fillLazyItemsTillLeafWithHead } from "./fill-lazy-items-till-leaf-with-head";
import { fillCacheWithNewSubTreeData } from "./fill-cache-with-new-subtree-data";
export function applyFlightData(existingCache, cache, flightDataPath, prefetchEntry) {
// The one before last item is the router state tree patch
const [treePatch, cacheNodeSeedData, head] = flightDataPath.slice(-3);
// Handles case where prefetch only returns the router tree patch without rendered components.
if (cacheNodeSeedData === null) {
return false;
}
if (flightDataPath.length === 3) {
const rsc = cacheNodeSeedData[2];
const loading = cacheNodeSeedData[3];
cache.loading = loading;
cache.rsc = rsc;
// This is a PPR-only field. When PPR is enabled, we shouldn't hit
// this path during a navigation, but until PPR is fully implemented
// yet it's possible the existing node does have a non-null
// `prefetchRsc`. As an incremental step, we'll just de-opt to the
// old behavior — no PPR value.
cache.prefetchRsc = null;
fillLazyItemsTillLeafWithHead(cache, existingCache, treePatch, cacheNodeSeedData, head, prefetchEntry);
} else {
// Copy rsc for the root node of the cache.
cache.rsc = existingCache.rsc;
// This is a PPR-only field. Unlike the previous branch, since we're
// just cloning the existing cache node, we might as well keep the
// PPR value, if it exists.
cache.prefetchRsc = existingCache.prefetchRsc;
cache.parallelRoutes = new Map(existingCache.parallelRoutes);
cache.loading = existingCache.loading;
// Create a copy of the existing cache with the rsc applied.
fillCacheWithNewSubTreeData(cache, existingCache, flightDataPath, prefetchEntry);
}
return true;
}
//# sourceMappingURL=apply-flight-data.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/apply-flight-data.ts"],"names":["fillLazyItemsTillLeafWithHead","fillCacheWithNewSubTreeData","applyFlightData","existingCache","cache","flightDataPath","prefetchEntry","treePatch","cacheNodeSeedData","head","slice","length","rsc","loading","prefetchRsc","parallelRoutes","Map"],"mappings":"AAEA,SAASA,6BAA6B,QAAQ,wCAAuC;AACrF,SAASC,2BAA2B,QAAQ,qCAAoC;AAGhF,OAAO,SAASC,gBACdC,aAAwB,EACxBC,KAAgB,EAChBC,cAA8B,EAC9BC,aAAkC;IAElC,0DAA0D;IAC1D,MAAM,CAACC,WAAWC,mBAAmBC,KAAK,GAAGJ,eAAeK,KAAK,CAAC,CAAC;IAEnE,8FAA8F;IAC9F,IAAIF,sBAAsB,MAAM;QAC9B,OAAO;IACT;IAEA,IAAIH,eAAeM,MAAM,KAAK,GAAG;QAC/B,MAAMC,MAAMJ,iBAAiB,CAAC,EAAE;QAChC,MAAMK,UAAUL,iBAAiB,CAAC,EAAE;QACpCJ,MAAMS,OAAO,GAAGA;QAChBT,MAAMQ,GAAG,GAAGA;QACZ,kEAAkE;QAClE,oEAAoE;QACpE,2DAA2D;QAC3D,kEAAkE;QAClE,+BAA+B;QAC/BR,MAAMU,WAAW,GAAG;QACpBd,8BACEI,OACAD,eACAI,WACAC,mBACAC,MACAH;IAEJ,OAAO;QACL,2CAA2C;QAC3CF,MAAMQ,GAAG,GAAGT,cAAcS,GAAG;QAC7B,oEAAoE;QACpE,kEAAkE;QAClE,2BAA2B;QAC3BR,MAAMU,WAAW,GAAGX,cAAcW,WAAW;QAC7CV,MAAMW,cAAc,GAAG,IAAIC,IAAIb,cAAcY,cAAc;QAC3DX,MAAMS,OAAO,GAAGV,cAAcU,OAAO;QACrC,4DAA4D;QAC5DZ,4BACEG,OACAD,eACAE,gBACAC;IAEJ;IAEA,OAAO;AACT"}

View File

@ -0,0 +1,92 @@
import { DEFAULT_SEGMENT_KEY } from "../../../shared/lib/segment";
import { matchSegment } from "../match-segments";
import { addRefreshMarkerToActiveParallelSegments } from "./refetch-inactive-parallel-segments";
/**
* Deep merge of the two router states. Parallel route keys are preserved if the patch doesn't have them.
*/ function applyPatch(initialTree, patchTree, flightSegmentPath) {
const [initialSegment, initialParallelRoutes] = initialTree;
const [patchSegment, patchParallelRoutes] = patchTree;
// if the applied patch segment is __DEFAULT__ then it can be ignored in favor of the initial tree
// this is because the __DEFAULT__ segment is used as a placeholder on navigation
if (patchSegment === DEFAULT_SEGMENT_KEY && initialSegment !== DEFAULT_SEGMENT_KEY) {
return initialTree;
}
if (matchSegment(initialSegment, patchSegment)) {
const newParallelRoutes = {};
for(const key in initialParallelRoutes){
const isInPatchTreeParallelRoutes = typeof patchParallelRoutes[key] !== "undefined";
if (isInPatchTreeParallelRoutes) {
newParallelRoutes[key] = applyPatch(initialParallelRoutes[key], patchParallelRoutes[key], flightSegmentPath);
} else {
newParallelRoutes[key] = initialParallelRoutes[key];
}
}
for(const key in patchParallelRoutes){
if (newParallelRoutes[key]) {
continue;
}
newParallelRoutes[key] = patchParallelRoutes[key];
}
const tree = [
initialSegment,
newParallelRoutes
];
// Copy over the existing tree
if (initialTree[2]) {
tree[2] = initialTree[2];
}
if (initialTree[3]) {
tree[3] = initialTree[3];
}
if (initialTree[4]) {
tree[4] = initialTree[4];
}
return tree;
}
return patchTree;
}
/**
* Apply the router state from the Flight response, but skip patching default segments.
* Useful for patching the router cache when navigating, where we persist the existing default segment if there isn't a new one.
* Creates a new router state tree.
*/ export function applyRouterStatePatchToTree(flightSegmentPath, flightRouterState, treePatch, path) {
const [segment, parallelRoutes, url, refetch, isRootLayout] = flightRouterState;
// Root refresh
if (flightSegmentPath.length === 1) {
const tree = applyPatch(flightRouterState, treePatch, flightSegmentPath);
addRefreshMarkerToActiveParallelSegments(tree, path);
return tree;
}
const [currentSegment, parallelRouteKey] = flightSegmentPath;
// Tree path returned from the server should always match up with the current tree in the browser
if (!matchSegment(currentSegment, segment)) {
return null;
}
const lastSegment = flightSegmentPath.length === 2;
let parallelRoutePatch;
if (lastSegment) {
parallelRoutePatch = applyPatch(parallelRoutes[parallelRouteKey], treePatch, flightSegmentPath);
} else {
parallelRoutePatch = applyRouterStatePatchToTree(flightSegmentPath.slice(2), parallelRoutes[parallelRouteKey], treePatch, path);
if (parallelRoutePatch === null) {
return null;
}
}
const tree = [
flightSegmentPath[0],
{
...parallelRoutes,
[parallelRouteKey]: parallelRoutePatch
},
url,
refetch
];
// Current segment is the root layout
if (isRootLayout) {
tree[4] = true;
}
addRefreshMarkerToActiveParallelSegments(tree, path);
return tree;
}
//# sourceMappingURL=apply-router-state-patch-to-tree.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/apply-router-state-patch-to-tree.ts"],"names":["DEFAULT_SEGMENT_KEY","matchSegment","addRefreshMarkerToActiveParallelSegments","applyPatch","initialTree","patchTree","flightSegmentPath","initialSegment","initialParallelRoutes","patchSegment","patchParallelRoutes","newParallelRoutes","key","isInPatchTreeParallelRoutes","tree","applyRouterStatePatchToTree","flightRouterState","treePatch","path","segment","parallelRoutes","url","refetch","isRootLayout","length","currentSegment","parallelRouteKey","lastSegment","parallelRoutePatch","slice"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,8BAA6B;AACjE,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SAASC,wCAAwC,QAAQ,uCAAsC;AAE/F;;CAEC,GACD,SAASC,WACPC,WAA8B,EAC9BC,SAA4B,EAC5BC,iBAAoC;IAEpC,MAAM,CAACC,gBAAgBC,sBAAsB,GAAGJ;IAChD,MAAM,CAACK,cAAcC,oBAAoB,GAAGL;IAE5C,kGAAkG;IAClG,iFAAiF;IACjF,IACEI,iBAAiBT,uBACjBO,mBAAmBP,qBACnB;QACA,OAAOI;IACT;IAEA,IAAIH,aAAaM,gBAAgBE,eAAe;QAC9C,MAAME,oBAA0C,CAAC;QACjD,IAAK,MAAMC,OAAOJ,sBAAuB;YACvC,MAAMK,8BACJ,OAAOH,mBAAmB,CAACE,IAAI,KAAK;YACtC,IAAIC,6BAA6B;gBAC/BF,iBAAiB,CAACC,IAAI,GAAGT,WACvBK,qBAAqB,CAACI,IAAI,EAC1BF,mBAAmB,CAACE,IAAI,EACxBN;YAEJ,OAAO;gBACLK,iBAAiB,CAACC,IAAI,GAAGJ,qBAAqB,CAACI,IAAI;YACrD;QACF;QAEA,IAAK,MAAMA,OAAOF,oBAAqB;YACrC,IAAIC,iBAAiB,CAACC,IAAI,EAAE;gBAC1B;YACF;YAEAD,iBAAiB,CAACC,IAAI,GAAGF,mBAAmB,CAACE,IAAI;QACnD;QAEA,MAAME,OAA0B;YAACP;YAAgBI;SAAkB;QAEnE,8BAA8B;QAC9B,IAAIP,WAAW,CAAC,EAAE,EAAE;YAClBU,IAAI,CAAC,EAAE,GAAGV,WAAW,CAAC,EAAE;QAC1B;QAEA,IAAIA,WAAW,CAAC,EAAE,EAAE;YAClBU,IAAI,CAAC,EAAE,GAAGV,WAAW,CAAC,EAAE;QAC1B;QAEA,IAAIA,WAAW,CAAC,EAAE,EAAE;YAClBU,IAAI,CAAC,EAAE,GAAGV,WAAW,CAAC,EAAE;QAC1B;QAEA,OAAOU;IACT;IAEA,OAAOT;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASU,4BACdT,iBAAoC,EACpCU,iBAAoC,EACpCC,SAA4B,EAC5BC,IAAY;IAEZ,MAAM,CAACC,SAASC,gBAAgBC,KAAKC,SAASC,aAAa,GACzDP;IAEF,eAAe;IACf,IAAIV,kBAAkBkB,MAAM,KAAK,GAAG;QAClC,MAAMV,OAA0BX,WAC9Ba,mBACAC,WACAX;QAGFJ,yCAAyCY,MAAMI;QAE/C,OAAOJ;IACT;IAEA,MAAM,CAACW,gBAAgBC,iBAAiB,GAAGpB;IAE3C,iGAAiG;IACjG,IAAI,CAACL,aAAawB,gBAAgBN,UAAU;QAC1C,OAAO;IACT;IAEA,MAAMQ,cAAcrB,kBAAkBkB,MAAM,KAAK;IAEjD,IAAII;IACJ,IAAID,aAAa;QACfC,qBAAqBzB,WACnBiB,cAAc,CAACM,iBAAiB,EAChCT,WACAX;IAEJ,OAAO;QACLsB,qBAAqBb,4BACnBT,kBAAkBuB,KAAK,CAAC,IACxBT,cAAc,CAACM,iBAAiB,EAChCT,WACAC;QAGF,IAAIU,uBAAuB,MAAM;YAC/B,OAAO;QACT;IACF;IAEA,MAAMd,OAA0B;QAC9BR,iBAAiB,CAAC,EAAE;QACpB;YACE,GAAGc,cAAc;YACjB,CAACM,iBAAiB,EAAEE;QACtB;QACAP;QACAC;KACD;IAED,qCAAqC;IACrC,IAAIC,cAAc;QAChBT,IAAI,CAAC,EAAE,GAAG;IACZ;IAEAZ,yCAAyCY,MAAMI;IAE/C,OAAOJ;AACT"}

View File

@ -0,0 +1,64 @@
import { createRouterCacheKey } from "./create-router-cache-key";
/**
* This will clear the CacheNode data for a particular segment path. This will cause a lazy-fetch in layout router to fill in new data.
*/ export function clearCacheNodeDataForSegmentPath(newCache, existingCache, flightSegmentPath) {
const isLastEntry = flightSegmentPath.length <= 2;
const [parallelRouteKey, segment] = flightSegmentPath;
const cacheKey = createRouterCacheKey(segment);
const existingChildSegmentMap = existingCache.parallelRoutes.get(parallelRouteKey);
let childSegmentMap = newCache.parallelRoutes.get(parallelRouteKey);
if (!childSegmentMap || childSegmentMap === existingChildSegmentMap) {
childSegmentMap = new Map(existingChildSegmentMap);
newCache.parallelRoutes.set(parallelRouteKey, childSegmentMap);
}
const existingChildCacheNode = existingChildSegmentMap == null ? void 0 : existingChildSegmentMap.get(cacheKey);
let childCacheNode = childSegmentMap.get(cacheKey);
// In case of last segment start off the fetch at this level and don't copy further down.
if (isLastEntry) {
if (!childCacheNode || !childCacheNode.lazyData || childCacheNode === existingChildCacheNode) {
childSegmentMap.set(cacheKey, {
lazyData: null,
rsc: null,
prefetchRsc: null,
head: null,
prefetchHead: null,
parallelRoutes: new Map(),
lazyDataResolved: false,
loading: null
});
}
return;
}
if (!childCacheNode || !existingChildCacheNode) {
// Start fetch in the place where the existing cache doesn't have the data yet.
if (!childCacheNode) {
childSegmentMap.set(cacheKey, {
lazyData: null,
rsc: null,
prefetchRsc: null,
head: null,
prefetchHead: null,
parallelRoutes: new Map(),
lazyDataResolved: false,
loading: null
});
}
return;
}
if (childCacheNode === existingChildCacheNode) {
childCacheNode = {
lazyData: childCacheNode.lazyData,
rsc: childCacheNode.rsc,
prefetchRsc: childCacheNode.prefetchRsc,
head: childCacheNode.head,
prefetchHead: childCacheNode.prefetchHead,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
lazyDataResolved: childCacheNode.lazyDataResolved,
loading: childCacheNode.loading
};
childSegmentMap.set(cacheKey, childCacheNode);
}
return clearCacheNodeDataForSegmentPath(childCacheNode, existingChildCacheNode, flightSegmentPath.slice(2));
}
//# sourceMappingURL=clear-cache-node-data-for-segment-path.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/clear-cache-node-data-for-segment-path.ts"],"names":["createRouterCacheKey","clearCacheNodeDataForSegmentPath","newCache","existingCache","flightSegmentPath","isLastEntry","length","parallelRouteKey","segment","cacheKey","existingChildSegmentMap","parallelRoutes","get","childSegmentMap","Map","set","existingChildCacheNode","childCacheNode","lazyData","rsc","prefetchRsc","head","prefetchHead","lazyDataResolved","loading","slice"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,4BAA2B;AAEhE;;CAEC,GACD,OAAO,SAASC,iCACdC,QAAmB,EACnBC,aAAwB,EACxBC,iBAAoC;IAEpC,MAAMC,cAAcD,kBAAkBE,MAAM,IAAI;IAEhD,MAAM,CAACC,kBAAkBC,QAAQ,GAAGJ;IACpC,MAAMK,WAAWT,qBAAqBQ;IAEtC,MAAME,0BACJP,cAAcQ,cAAc,CAACC,GAAG,CAACL;IAEnC,IAAIM,kBAAkBX,SAASS,cAAc,CAACC,GAAG,CAACL;IAElD,IAAI,CAACM,mBAAmBA,oBAAoBH,yBAAyB;QACnEG,kBAAkB,IAAIC,IAAIJ;QAC1BR,SAASS,cAAc,CAACI,GAAG,CAACR,kBAAkBM;IAChD;IAEA,MAAMG,yBAAyBN,2CAAAA,wBAAyBE,GAAG,CAACH;IAC5D,IAAIQ,iBAAiBJ,gBAAgBD,GAAG,CAACH;IAEzC,yFAAyF;IACzF,IAAIJ,aAAa;QACf,IACE,CAACY,kBACD,CAACA,eAAeC,QAAQ,IACxBD,mBAAmBD,wBACnB;YACAH,gBAAgBE,GAAG,CAACN,UAAU;gBAC5BS,UAAU;gBACVC,KAAK;gBACLC,aAAa;gBACbC,MAAM;gBACNC,cAAc;gBACdX,gBAAgB,IAAIG;gBACpBS,kBAAkB;gBAClBC,SAAS;YACX;QACF;QACA;IACF;IAEA,IAAI,CAACP,kBAAkB,CAACD,wBAAwB;QAC9C,+EAA+E;QAC/E,IAAI,CAACC,gBAAgB;YACnBJ,gBAAgBE,GAAG,CAACN,UAAU;gBAC5BS,UAAU;gBACVC,KAAK;gBACLC,aAAa;gBACbC,MAAM;gBACNC,cAAc;gBACdX,gBAAgB,IAAIG;gBACpBS,kBAAkB;gBAClBC,SAAS;YACX;QACF;QACA;IACF;IAEA,IAAIP,mBAAmBD,wBAAwB;QAC7CC,iBAAiB;YACfC,UAAUD,eAAeC,QAAQ;YACjCC,KAAKF,eAAeE,GAAG;YACvBC,aAAaH,eAAeG,WAAW;YACvCC,MAAMJ,eAAeI,IAAI;YACzBC,cAAcL,eAAeK,YAAY;YACzCX,gBAAgB,IAAIG,IAAIG,eAAeN,cAAc;YACrDY,kBAAkBN,eAAeM,gBAAgB;YACjDC,SAASP,eAAeO,OAAO;QACjC;QACAX,gBAAgBE,GAAG,CAACN,UAAUQ;IAChC;IAEA,OAAOhB,iCACLgB,gBACAD,wBACAZ,kBAAkBqB,KAAK,CAAC;AAE5B"}

View File

@ -0,0 +1,80 @@
import { INTERCEPTION_ROUTE_MARKERS } from "../../../server/future/helpers/interception-routes";
import { isGroupSegment, DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY } from "../../../shared/lib/segment";
import { matchSegment } from "../match-segments";
const removeLeadingSlash = (segment)=>{
return segment[0] === "/" ? segment.slice(1) : segment;
};
const segmentToPathname = (segment)=>{
if (typeof segment === "string") {
// 'children' is not a valid path -- it's technically a parallel route that corresponds with the current segment's page
// if we don't skip it, then the computed pathname might be something like `/children` which doesn't make sense.
if (segment === "children") return "";
return segment;
}
return segment[1];
};
function normalizeSegments(segments) {
return segments.reduce((acc, segment)=>{
segment = removeLeadingSlash(segment);
if (segment === "" || isGroupSegment(segment)) {
return acc;
}
return acc + "/" + segment;
}, "") || "/";
}
export function extractPathFromFlightRouterState(flightRouterState) {
const segment = Array.isArray(flightRouterState[0]) ? flightRouterState[0][1] : flightRouterState[0];
if (segment === DEFAULT_SEGMENT_KEY || INTERCEPTION_ROUTE_MARKERS.some((m)=>segment.startsWith(m))) return undefined;
if (segment.startsWith(PAGE_SEGMENT_KEY)) return "";
const segments = [
segmentToPathname(segment)
];
var _flightRouterState_;
const parallelRoutes = (_flightRouterState_ = flightRouterState[1]) != null ? _flightRouterState_ : {};
const childrenPath = parallelRoutes.children ? extractPathFromFlightRouterState(parallelRoutes.children) : undefined;
if (childrenPath !== undefined) {
segments.push(childrenPath);
} else {
for (const [key, value] of Object.entries(parallelRoutes)){
if (key === "children") continue;
const childPath = extractPathFromFlightRouterState(value);
if (childPath !== undefined) {
segments.push(childPath);
}
}
}
return normalizeSegments(segments);
}
function computeChangedPathImpl(treeA, treeB) {
const [segmentA, parallelRoutesA] = treeA;
const [segmentB, parallelRoutesB] = treeB;
const normalizedSegmentA = segmentToPathname(segmentA);
const normalizedSegmentB = segmentToPathname(segmentB);
if (INTERCEPTION_ROUTE_MARKERS.some((m)=>normalizedSegmentA.startsWith(m) || normalizedSegmentB.startsWith(m))) {
return "";
}
if (!matchSegment(segmentA, segmentB)) {
var _extractPathFromFlightRouterState;
// once we find where the tree changed, we compute the rest of the path by traversing the tree
return (_extractPathFromFlightRouterState = extractPathFromFlightRouterState(treeB)) != null ? _extractPathFromFlightRouterState : "";
}
for(const parallelRouterKey in parallelRoutesA){
if (parallelRoutesB[parallelRouterKey]) {
const changedPath = computeChangedPathImpl(parallelRoutesA[parallelRouterKey], parallelRoutesB[parallelRouterKey]);
if (changedPath !== null) {
return segmentToPathname(segmentB) + "/" + changedPath;
}
}
}
return null;
}
export function computeChangedPath(treeA, treeB) {
const changedPath = computeChangedPathImpl(treeA, treeB);
if (changedPath == null || changedPath === "/") {
return changedPath;
}
// lightweight normalization to remove route groups
return normalizeSegments(changedPath.split("/"));
}
//# sourceMappingURL=compute-changed-path.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/compute-changed-path.ts"],"names":["INTERCEPTION_ROUTE_MARKERS","isGroupSegment","DEFAULT_SEGMENT_KEY","PAGE_SEGMENT_KEY","matchSegment","removeLeadingSlash","segment","slice","segmentToPathname","normalizeSegments","segments","reduce","acc","extractPathFromFlightRouterState","flightRouterState","Array","isArray","some","m","startsWith","undefined","parallelRoutes","childrenPath","children","push","key","value","Object","entries","childPath","computeChangedPathImpl","treeA","treeB","segmentA","parallelRoutesA","segmentB","parallelRoutesB","normalizedSegmentA","normalizedSegmentB","parallelRouterKey","changedPath","computeChangedPath","split"],"mappings":"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"}

View File

@ -0,0 +1,6 @@
export function createHrefFromUrl(url, includeHash) {
if (includeHash === void 0) includeHash = true;
return url.pathname + url.search + (includeHash ? url.hash : "");
}
//# sourceMappingURL=create-href-from-url.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-href-from-url.ts"],"names":["createHrefFromUrl","url","includeHash","pathname","search","hash"],"mappings":"AAAA,OAAO,SAASA,kBACdC,GAA8C,EAC9CC,WAA2B;IAA3BA,IAAAA,wBAAAA,cAAuB;IAEvB,OAAOD,IAAIE,QAAQ,GAAGF,IAAIG,MAAM,GAAIF,CAAAA,cAAcD,IAAII,IAAI,GAAG,EAAC;AAChE"}

View File

@ -0,0 +1,88 @@
import { createHrefFromUrl } from "./create-href-from-url";
import { fillLazyItemsTillLeafWithHead } from "./fill-lazy-items-till-leaf-with-head";
import { extractPathFromFlightRouterState } from "./compute-changed-path";
import { createPrefetchCacheEntryForInitialLoad } from "./prefetch-cache-utils";
import { PrefetchKind } from "./router-reducer-types";
import { addRefreshMarkerToActiveParallelSegments } from "./refetch-inactive-parallel-segments";
export function createInitialRouterState(param) {
let { buildId, initialTree, initialSeedData, urlParts, initialParallelRoutes, location, initialHead, couldBeIntercepted } = param;
// When initialized on the server, the canonical URL is provided as an array of parts.
// This is to ensure that when the RSC payload streamed to the client, crawlers don't interpret it
// as a URL that should be crawled.
const initialCanonicalUrl = urlParts.join("/");
const isServer = !location;
const rsc = initialSeedData[2];
const cache = {
lazyData: null,
rsc: rsc,
prefetchRsc: null,
head: null,
prefetchHead: null,
// The cache gets seeded during the first render. `initialParallelRoutes` ensures the cache from the first render is there during the second render.
parallelRoutes: isServer ? new Map() : initialParallelRoutes,
lazyDataResolved: false,
loading: initialSeedData[3]
};
const canonicalUrl = // location.href is read as the initial value for canonicalUrl in the browser
// This is safe to do as canonicalUrl can't be rendered, it's only used to control the history updates in the useEffect further down in this file.
location ? createHrefFromUrl(location) : initialCanonicalUrl;
addRefreshMarkerToActiveParallelSegments(initialTree, canonicalUrl);
const prefetchCache = new Map();
// When the cache hasn't been seeded yet we fill the cache with the head.
if (initialParallelRoutes === null || initialParallelRoutes.size === 0) {
fillLazyItemsTillLeafWithHead(cache, undefined, initialTree, initialSeedData, initialHead);
}
var // the || operator is intentional, the pathname can be an empty string
_ref;
const initialState = {
buildId,
tree: initialTree,
cache,
prefetchCache,
pushRef: {
pendingPush: false,
mpaNavigation: false,
// First render needs to preserve the previous window.history.state
// to avoid it being overwritten on navigation back/forward with MPA Navigation.
preserveCustomHistoryState: true
},
focusAndScrollRef: {
apply: false,
onlyHashChange: false,
hashFragment: null,
segmentPaths: []
},
canonicalUrl,
nextUrl: (_ref = extractPathFromFlightRouterState(initialTree) || (location == null ? void 0 : location.pathname)) != null ? _ref : null
};
if (location) {
// Seed the prefetch cache with this page's data.
// This is to prevent needlessly re-prefetching a page that is already reusable,
// and will avoid triggering a loading state/data fetch stall when navigating back to the page.
const url = new URL("" + location.pathname + location.search, location.origin);
const initialFlightData = [
[
"",
initialTree,
null,
null
]
];
createPrefetchCacheEntryForInitialLoad({
url,
kind: PrefetchKind.AUTO,
data: [
initialFlightData,
undefined,
false,
couldBeIntercepted
],
tree: initialState.tree,
prefetchCache: initialState.prefetchCache,
nextUrl: initialState.nextUrl
});
}
return initialState;
}
//# sourceMappingURL=create-initial-router-state.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-initial-router-state.ts"],"names":["createHrefFromUrl","fillLazyItemsTillLeafWithHead","extractPathFromFlightRouterState","createPrefetchCacheEntryForInitialLoad","PrefetchKind","addRefreshMarkerToActiveParallelSegments","createInitialRouterState","buildId","initialTree","initialSeedData","urlParts","initialParallelRoutes","location","initialHead","couldBeIntercepted","initialCanonicalUrl","join","isServer","rsc","cache","lazyData","prefetchRsc","head","prefetchHead","parallelRoutes","Map","lazyDataResolved","loading","canonicalUrl","prefetchCache","size","undefined","initialState","tree","pushRef","pendingPush","mpaNavigation","preserveCustomHistoryState","focusAndScrollRef","apply","onlyHashChange","hashFragment","segmentPaths","nextUrl","pathname","url","URL","search","origin","initialFlightData","kind","AUTO","data"],"mappings":"AAQA,SAASA,iBAAiB,QAAQ,yBAAwB;AAC1D,SAASC,6BAA6B,QAAQ,wCAAuC;AACrF,SAASC,gCAAgC,QAAQ,yBAAwB;AACzE,SAASC,sCAAsC,QAAQ,yBAAwB;AAC/E,SAASC,YAAY,QAAiC,yBAAwB;AAC9E,SAASC,wCAAwC,QAAQ,uCAAsC;AAa/F,OAAO,SAASC,yBAAyB,KASV;IATU,IAAA,EACvCC,OAAO,EACPC,WAAW,EACXC,eAAe,EACfC,QAAQ,EACRC,qBAAqB,EACrBC,QAAQ,EACRC,WAAW,EACXC,kBAAkB,EACW,GATU;IAUvC,sFAAsF;IACtF,kGAAkG;IAClG,mCAAmC;IACnC,MAAMC,sBAAsBL,SAASM,IAAI,CAAC;IAC1C,MAAMC,WAAW,CAACL;IAClB,MAAMM,MAAMT,eAAe,CAAC,EAAE;IAE9B,MAAMU,QAAmB;QACvBC,UAAU;QACVF,KAAKA;QACLG,aAAa;QACbC,MAAM;QACNC,cAAc;QACd,oJAAoJ;QACpJC,gBAAgBP,WAAW,IAAIQ,QAAQd;QACvCe,kBAAkB;QAClBC,SAASlB,eAAe,CAAC,EAAE;IAC7B;IAEA,MAAMmB,eACJ,6EAA6E;IAC7E,kJAAkJ;IAClJhB,WAEIZ,kBAAkBY,YAClBG;IAENV,yCAAyCG,aAAaoB;IAEtD,MAAMC,gBAAgB,IAAIJ;IAE1B,yEAAyE;IACzE,IAAId,0BAA0B,QAAQA,sBAAsBmB,IAAI,KAAK,GAAG;QACtE7B,8BACEkB,OACAY,WACAvB,aACAC,iBACAI;IAEJ;QAsBI,sEAAsE;IACrEX;IArBL,MAAM8B,eAAe;QACnBzB;QACA0B,MAAMzB;QACNW;QACAU;QACAK,SAAS;YACPC,aAAa;YACbC,eAAe;YACf,mEAAmE;YACnE,gFAAgF;YAChFC,4BAA4B;QAC9B;QACAC,mBAAmB;YACjBC,OAAO;YACPC,gBAAgB;YAChBC,cAAc;YACdC,cAAc,EAAE;QAClB;QACAd;QACAe,SAEE,CAACzC,OAAAA,iCAAiCM,iBAAgBI,4BAAAA,SAAUgC,QAAQ,aAAnE1C,OACD;IACJ;IAEA,IAAIU,UAAU;QACZ,iDAAiD;QACjD,gFAAgF;QAChF,+FAA+F;QAC/F,MAAMiC,MAAM,IAAIC,IACd,AAAC,KAAElC,SAASgC,QAAQ,GAAGhC,SAASmC,MAAM,EACtCnC,SAASoC,MAAM;QAGjB,MAAMC,oBAAgC;YAAC;gBAAC;gBAAIzC;gBAAa;gBAAM;aAAK;SAAC;QACrEL,uCAAuC;YACrC0C;YACAK,MAAM9C,aAAa+C,IAAI;YACvBC,MAAM;gBAACH;gBAAmBlB;gBAAW;gBAAOjB;aAAmB;YAC/DmB,MAAMD,aAAaC,IAAI;YACvBJ,eAAeG,aAAaH,aAAa;YACzCc,SAASX,aAAaW,OAAO;QAC/B;IACF;IAEA,OAAOX;AACT"}

View File

@ -0,0 +1,17 @@
import { PAGE_SEGMENT_KEY } from "../../../shared/lib/segment";
export function createRouterCacheKey(segment, withoutSearchParameters) {
if (withoutSearchParameters === void 0) withoutSearchParameters = false;
// if the segment is an array, it means it's a dynamic segment
// for example, ['lang', 'en', 'd']. We need to convert it to a string to store it as a cache node key.
if (Array.isArray(segment)) {
return segment[0] + "|" + segment[1] + "|" + segment[2];
}
// Page segments might have search parameters, ie __PAGE__?foo=bar
// When `withoutSearchParameters` is true, we only want to return the page segment
if (withoutSearchParameters && segment.startsWith(PAGE_SEGMENT_KEY)) {
return PAGE_SEGMENT_KEY;
}
return segment;
}
//# sourceMappingURL=create-router-cache-key.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-router-cache-key.ts"],"names":["PAGE_SEGMENT_KEY","createRouterCacheKey","segment","withoutSearchParameters","Array","isArray","startsWith"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,8BAA6B;AAE9D,OAAO,SAASC,qBACdC,OAAgB,EAChBC,uBAAwC;IAAxCA,IAAAA,oCAAAA,0BAAmC;IAEnC,8DAA8D;IAC9D,uGAAuG;IACvG,IAAIC,MAAMC,OAAO,CAACH,UAAU;QAC1B,OAAO,AAAGA,OAAO,CAAC,EAAE,GAAC,MAAGA,OAAO,CAAC,EAAE,GAAC,MAAGA,OAAO,CAAC,EAAE;IAClD;IAEA,kEAAkE;IAClE,kFAAkF;IAClF,IAAIC,2BAA2BD,QAAQI,UAAU,CAACN,mBAAmB;QACnE,OAAOA;IACT;IAEA,OAAOE;AACT"}

View File

@ -0,0 +1,116 @@
"use client";
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
// import { createFromFetch } from 'react-server-dom-webpack/client'
const { createFromFetch } = !!process.env.NEXT_RUNTIME ? require("react-server-dom-webpack/client.edge") : require("react-server-dom-webpack/client");
import { NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE, NEXT_RSC_UNION_QUERY, NEXT_URL, RSC_HEADER, RSC_CONTENT_TYPE_HEADER, NEXT_DID_POSTPONE_HEADER } from "../app-router-headers";
import { urlToUrlWithoutFlightMarker } from "../app-router";
import { callServer } from "../../app-call-server";
import { PrefetchKind } from "./router-reducer-types";
import { hexHash } from "../../../shared/lib/hash";
function doMpaNavigation(url) {
return [
urlToUrlWithoutFlightMarker(url).toString(),
undefined,
false,
false
];
}
/**
* Fetch the flight data for the provided url. Takes in the current router state to decide what to render server-side.
*/ export async function fetchServerResponse(url, flightRouterState, nextUrl, currentBuildId, prefetchKind) {
const headers = {
// Enable flight response
[RSC_HEADER]: "1",
// Provide the current router state
[NEXT_ROUTER_STATE_TREE]: encodeURIComponent(JSON.stringify(flightRouterState))
};
/**
* Three cases:
* - `prefetchKind` is `undefined`, it means it's a normal navigation, so we want to prefetch the page data fully
* - `prefetchKind` is `full` - we want to prefetch the whole page so same as above
* - `prefetchKind` is `auto` - if the page is dynamic, prefetch the page data partially, if static prefetch the page data fully
*/ if (prefetchKind === PrefetchKind.AUTO) {
headers[NEXT_ROUTER_PREFETCH_HEADER] = "1";
}
if (nextUrl) {
headers[NEXT_URL] = nextUrl;
}
if (process.env.NEXT_DEPLOYMENT_ID) {
headers["x-deployment-id"] = process.env.NEXT_DEPLOYMENT_ID;
}
const uniqueCacheQuery = hexHash([
headers[NEXT_ROUTER_PREFETCH_HEADER] || "0",
headers[NEXT_ROUTER_STATE_TREE],
headers[NEXT_URL]
].join(","));
try {
var _res_headers_get;
let fetchUrl = new URL(url);
if (process.env.NODE_ENV === "production") {
if (process.env.__NEXT_CONFIG_OUTPUT === "export") {
if (fetchUrl.pathname.endsWith("/")) {
fetchUrl.pathname += "index.txt";
} else {
fetchUrl.pathname += ".txt";
}
}
}
// Add unique cache query to avoid caching conflicts on CDN which don't respect to Vary header
fetchUrl.searchParams.set(NEXT_RSC_UNION_QUERY, uniqueCacheQuery);
const res = await fetch(fetchUrl, {
// Backwards compat for older browsers. `same-origin` is the default in modern browsers.
credentials: "same-origin",
headers
});
const responseUrl = urlToUrlWithoutFlightMarker(res.url);
const canonicalUrl = res.redirected ? responseUrl : undefined;
const contentType = res.headers.get("content-type") || "";
const postponed = !!res.headers.get(NEXT_DID_POSTPONE_HEADER);
const interception = !!((_res_headers_get = res.headers.get("vary")) == null ? void 0 : _res_headers_get.includes(NEXT_URL));
let isFlightResponse = contentType === RSC_CONTENT_TYPE_HEADER;
if (process.env.NODE_ENV === "production") {
if (process.env.__NEXT_CONFIG_OUTPUT === "export") {
if (!isFlightResponse) {
isFlightResponse = contentType.startsWith("text/plain");
}
}
}
// If fetch returns something different than flight response handle it like a mpa navigation
// If the fetch was not 200, we also handle it like a mpa navigation
if (!isFlightResponse || !res.ok) {
// in case the original URL came with a hash, preserve it before redirecting to the new URL
if (url.hash) {
responseUrl.hash = url.hash;
}
return doMpaNavigation(responseUrl.toString());
}
// Handle the `fetch` readable stream that can be unwrapped by `React.use`.
const [buildId, flightData] = await createFromFetch(Promise.resolve(res), {
callServer
});
if (currentBuildId !== buildId) {
return doMpaNavigation(res.url);
}
return [
flightData,
canonicalUrl,
postponed,
interception
];
} catch (err) {
console.error("Failed to fetch RSC payload for " + url + ". Falling back to browser navigation.", err);
// If fetch fails handle it like a mpa navigation
// TODO-APP: Add a test for the case where a CORS request fails, e.g. external url redirect coming from the response.
// See https://github.com/vercel/next.js/issues/43605#issuecomment-1451617521 for a reproduction.
return [
url.toString(),
undefined,
false,
false
];
}
}
//# sourceMappingURL=fetch-server-response.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/fetch-server-response.ts"],"names":["createFromFetch","process","env","NEXT_RUNTIME","require","NEXT_ROUTER_PREFETCH_HEADER","NEXT_ROUTER_STATE_TREE","NEXT_RSC_UNION_QUERY","NEXT_URL","RSC_HEADER","RSC_CONTENT_TYPE_HEADER","NEXT_DID_POSTPONE_HEADER","urlToUrlWithoutFlightMarker","callServer","PrefetchKind","hexHash","doMpaNavigation","url","toString","undefined","fetchServerResponse","flightRouterState","nextUrl","currentBuildId","prefetchKind","headers","encodeURIComponent","JSON","stringify","AUTO","NEXT_DEPLOYMENT_ID","uniqueCacheQuery","join","res","fetchUrl","URL","NODE_ENV","__NEXT_CONFIG_OUTPUT","pathname","endsWith","searchParams","set","fetch","credentials","responseUrl","canonicalUrl","redirected","contentType","get","postponed","interception","includes","isFlightResponse","startsWith","ok","hash","buildId","flightData","Promise","resolve","err","console","error"],"mappings":"AAAA;AAEA,aAAa;AACb,6DAA6D;AAC7D,oEAAoE;AACpE,MAAM,EAAEA,eAAe,EAAE,GACvB,CAAC,CAACC,QAAQC,GAAG,CAACC,YAAY,GAEtBC,QAAQ,0CAERA,QAAQ;AAQd,SACEC,2BAA2B,EAC3BC,sBAAsB,EACtBC,oBAAoB,EACpBC,QAAQ,EACRC,UAAU,EACVC,uBAAuB,EACvBC,wBAAwB,QACnB,wBAAuB;AAC9B,SAASC,2BAA2B,QAAQ,gBAAe;AAC3D,SAASC,UAAU,QAAQ,wBAAuB;AAClD,SAASC,YAAY,QAAQ,yBAAwB;AACrD,SAASC,OAAO,QAAQ,2BAA0B;AASlD,SAASC,gBAAgBC,GAAW;IAClC,OAAO;QAACL,4BAA4BK,KAAKC,QAAQ;QAAIC;QAAW;QAAO;KAAM;AAC/E;AAEA;;CAEC,GACD,OAAO,eAAeC,oBACpBH,GAAQ,EACRI,iBAAoC,EACpCC,OAAsB,EACtBC,cAAsB,EACtBC,YAA2B;IAE3B,MAAMC,UAMF;QACF,yBAAyB;QACzB,CAAChB,WAAW,EAAE;QACd,mCAAmC;QACnC,CAACH,uBAAuB,EAAEoB,mBACxBC,KAAKC,SAAS,CAACP;IAEnB;IAEA;;;;;GAKC,GACD,IAAIG,iBAAiBV,aAAae,IAAI,EAAE;QACtCJ,OAAO,CAACpB,4BAA4B,GAAG;IACzC;IAEA,IAAIiB,SAAS;QACXG,OAAO,CAACjB,SAAS,GAAGc;IACtB;IAEA,IAAIrB,QAAQC,GAAG,CAAC4B,kBAAkB,EAAE;QAClCL,OAAO,CAAC,kBAAkB,GAAGxB,QAAQC,GAAG,CAAC4B,kBAAkB;IAC7D;IAEA,MAAMC,mBAAmBhB,QACvB;QACEU,OAAO,CAACpB,4BAA4B,IAAI;QACxCoB,OAAO,CAACnB,uBAAuB;QAC/BmB,OAAO,CAACjB,SAAS;KAClB,CAACwB,IAAI,CAAC;IAGT,IAAI;YA0BqBC;QAzBvB,IAAIC,WAAW,IAAIC,IAAIlB;QACvB,IAAIhB,QAAQC,GAAG,CAACkC,QAAQ,KAAK,cAAc;YACzC,IAAInC,QAAQC,GAAG,CAACmC,oBAAoB,KAAK,UAAU;gBACjD,IAAIH,SAASI,QAAQ,CAACC,QAAQ,CAAC,MAAM;oBACnCL,SAASI,QAAQ,IAAI;gBACvB,OAAO;oBACLJ,SAASI,QAAQ,IAAI;gBACvB;YACF;QACF;QAEA,8FAA8F;QAC9FJ,SAASM,YAAY,CAACC,GAAG,CAAClC,sBAAsBwB;QAEhD,MAAME,MAAM,MAAMS,MAAMR,UAAU;YAChC,wFAAwF;YACxFS,aAAa;YACblB;QACF;QAEA,MAAMmB,cAAchC,4BAA4BqB,IAAIhB,GAAG;QACvD,MAAM4B,eAAeZ,IAAIa,UAAU,GAAGF,cAAczB;QAEpD,MAAM4B,cAAcd,IAAIR,OAAO,CAACuB,GAAG,CAAC,mBAAmB;QACvD,MAAMC,YAAY,CAAC,CAAChB,IAAIR,OAAO,CAACuB,GAAG,CAACrC;QACpC,MAAMuC,eAAe,CAAC,GAACjB,mBAAAA,IAAIR,OAAO,CAACuB,GAAG,CAAC,4BAAhBf,iBAAyBkB,QAAQ,CAAC3C;QACzD,IAAI4C,mBAAmBL,gBAAgBrC;QAEvC,IAAIT,QAAQC,GAAG,CAACkC,QAAQ,KAAK,cAAc;YACzC,IAAInC,QAAQC,GAAG,CAACmC,oBAAoB,KAAK,UAAU;gBACjD,IAAI,CAACe,kBAAkB;oBACrBA,mBAAmBL,YAAYM,UAAU,CAAC;gBAC5C;YACF;QACF;QAEA,4FAA4F;QAC5F,oEAAoE;QACpE,IAAI,CAACD,oBAAoB,CAACnB,IAAIqB,EAAE,EAAE;YAChC,2FAA2F;YAC3F,IAAIrC,IAAIsC,IAAI,EAAE;gBACZX,YAAYW,IAAI,GAAGtC,IAAIsC,IAAI;YAC7B;YAEA,OAAOvC,gBAAgB4B,YAAY1B,QAAQ;QAC7C;QAEA,2EAA2E;QAC3E,MAAM,CAACsC,SAASC,WAAW,GAAuB,MAAMzD,gBACtD0D,QAAQC,OAAO,CAAC1B,MAChB;YACEpB;QACF;QAGF,IAAIU,mBAAmBiC,SAAS;YAC9B,OAAOxC,gBAAgBiB,IAAIhB,GAAG;QAChC;QAEA,OAAO;YAACwC;YAAYZ;YAAcI;YAAWC;SAAa;IAC5D,EAAE,OAAOU,KAAK;QACZC,QAAQC,KAAK,CACX,AAAC,qCAAkC7C,MAAI,yCACvC2C;QAEF,iDAAiD;QACjD,qHAAqH;QACrH,iGAAiG;QACjG,OAAO;YAAC3C,IAAIC,QAAQ;YAAIC;YAAW;YAAO;SAAM;IAClD;AACF"}

View File

@ -0,0 +1,68 @@
import { invalidateCacheByRouterState } from "./invalidate-cache-by-router-state";
import { fillLazyItemsTillLeafWithHead } from "./fill-lazy-items-till-leaf-with-head";
import { createRouterCacheKey } from "./create-router-cache-key";
/**
* Fill cache with rsc based on flightDataPath
*/ export function fillCacheWithNewSubTreeData(newCache, existingCache, flightDataPath, prefetchEntry) {
const isLastEntry = flightDataPath.length <= 5;
const [parallelRouteKey, segment] = flightDataPath;
const cacheKey = createRouterCacheKey(segment);
const existingChildSegmentMap = existingCache.parallelRoutes.get(parallelRouteKey);
if (!existingChildSegmentMap) {
// Bailout because the existing cache does not have the path to the leaf node
// Will trigger lazy fetch in layout-router because of missing segment
return;
}
let childSegmentMap = newCache.parallelRoutes.get(parallelRouteKey);
if (!childSegmentMap || childSegmentMap === existingChildSegmentMap) {
childSegmentMap = new Map(existingChildSegmentMap);
newCache.parallelRoutes.set(parallelRouteKey, childSegmentMap);
}
const existingChildCacheNode = existingChildSegmentMap.get(cacheKey);
let childCacheNode = childSegmentMap.get(cacheKey);
if (isLastEntry) {
if (!childCacheNode || !childCacheNode.lazyData || childCacheNode === existingChildCacheNode) {
const seedData = flightDataPath[3];
const rsc = seedData[2];
const loading = seedData[3];
childCacheNode = {
lazyData: null,
rsc,
prefetchRsc: null,
head: null,
prefetchHead: null,
loading,
// Ensure segments other than the one we got data for are preserved.
parallelRoutes: existingChildCacheNode ? new Map(existingChildCacheNode.parallelRoutes) : new Map(),
lazyDataResolved: false
};
if (existingChildCacheNode) {
invalidateCacheByRouterState(childCacheNode, existingChildCacheNode, flightDataPath[2]);
}
fillLazyItemsTillLeafWithHead(childCacheNode, existingChildCacheNode, flightDataPath[2], seedData, flightDataPath[4], prefetchEntry);
childSegmentMap.set(cacheKey, childCacheNode);
}
return;
}
if (!childCacheNode || !existingChildCacheNode) {
// Bailout because the existing cache does not have the path to the leaf node
// Will trigger lazy fetch in layout-router because of missing segment
return;
}
if (childCacheNode === existingChildCacheNode) {
childCacheNode = {
lazyData: childCacheNode.lazyData,
rsc: childCacheNode.rsc,
prefetchRsc: childCacheNode.prefetchRsc,
head: childCacheNode.head,
prefetchHead: childCacheNode.prefetchHead,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
lazyDataResolved: false,
loading: childCacheNode.loading
};
childSegmentMap.set(cacheKey, childCacheNode);
}
fillCacheWithNewSubTreeData(childCacheNode, existingChildCacheNode, flightDataPath.slice(2), prefetchEntry);
}
//# sourceMappingURL=fill-cache-with-new-subtree-data.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/fill-cache-with-new-subtree-data.ts"],"names":["invalidateCacheByRouterState","fillLazyItemsTillLeafWithHead","createRouterCacheKey","fillCacheWithNewSubTreeData","newCache","existingCache","flightDataPath","prefetchEntry","isLastEntry","length","parallelRouteKey","segment","cacheKey","existingChildSegmentMap","parallelRoutes","get","childSegmentMap","Map","set","existingChildCacheNode","childCacheNode","lazyData","seedData","rsc","loading","prefetchRsc","head","prefetchHead","lazyDataResolved","slice"],"mappings":"AAKA,SAASA,4BAA4B,QAAQ,qCAAoC;AACjF,SAASC,6BAA6B,QAAQ,wCAAuC;AACrF,SAASC,oBAAoB,QAAQ,4BAA2B;AAGhE;;CAEC,GACD,OAAO,SAASC,4BACdC,QAAmB,EACnBC,aAAwB,EACxBC,cAA8B,EAC9BC,aAAkC;IAElC,MAAMC,cAAcF,eAAeG,MAAM,IAAI;IAC7C,MAAM,CAACC,kBAAkBC,QAAQ,GAAGL;IAEpC,MAAMM,WAAWV,qBAAqBS;IAEtC,MAAME,0BACJR,cAAcS,cAAc,CAACC,GAAG,CAACL;IAEnC,IAAI,CAACG,yBAAyB;QAC5B,6EAA6E;QAC7E,sEAAsE;QACtE;IACF;IAEA,IAAIG,kBAAkBZ,SAASU,cAAc,CAACC,GAAG,CAACL;IAClD,IAAI,CAACM,mBAAmBA,oBAAoBH,yBAAyB;QACnEG,kBAAkB,IAAIC,IAAIJ;QAC1BT,SAASU,cAAc,CAACI,GAAG,CAACR,kBAAkBM;IAChD;IAEA,MAAMG,yBAAyBN,wBAAwBE,GAAG,CAACH;IAC3D,IAAIQ,iBAAiBJ,gBAAgBD,GAAG,CAACH;IAEzC,IAAIJ,aAAa;QACf,IACE,CAACY,kBACD,CAACA,eAAeC,QAAQ,IACxBD,mBAAmBD,wBACnB;YACA,MAAMG,WAA8BhB,cAAc,CAAC,EAAE;YACrD,MAAMiB,MAAMD,QAAQ,CAAC,EAAE;YACvB,MAAME,UAAUF,QAAQ,CAAC,EAAE;YAC3BF,iBAAiB;gBACfC,UAAU;gBACVE;gBACAE,aAAa;gBACbC,MAAM;gBACNC,cAAc;gBACdH;gBACA,oEAAoE;gBACpEV,gBAAgBK,yBACZ,IAAIF,IAAIE,uBAAuBL,cAAc,IAC7C,IAAIG;gBACRW,kBAAkB;YACpB;YAEA,IAAIT,wBAAwB;gBAC1BnB,6BACEoB,gBACAD,wBACAb,cAAc,CAAC,EAAE;YAErB;YAEAL,8BACEmB,gBACAD,wBACAb,cAAc,CAAC,EAAE,EACjBgB,UACAhB,cAAc,CAAC,EAAE,EACjBC;YAGFS,gBAAgBE,GAAG,CAACN,UAAUQ;QAChC;QACA;IACF;IAEA,IAAI,CAACA,kBAAkB,CAACD,wBAAwB;QAC9C,6EAA6E;QAC7E,sEAAsE;QACtE;IACF;IAEA,IAAIC,mBAAmBD,wBAAwB;QAC7CC,iBAAiB;YACfC,UAAUD,eAAeC,QAAQ;YACjCE,KAAKH,eAAeG,GAAG;YACvBE,aAAaL,eAAeK,WAAW;YACvCC,MAAMN,eAAeM,IAAI;YACzBC,cAAcP,eAAeO,YAAY;YACzCb,gBAAgB,IAAIG,IAAIG,eAAeN,cAAc;YACrDc,kBAAkB;YAClBJ,SAASJ,eAAeI,OAAO;QACjC;QACAR,gBAAgBE,GAAG,CAACN,UAAUQ;IAChC;IAEAjB,4BACEiB,gBACAD,wBACAb,eAAeuB,KAAK,CAAC,IACrBtB;AAEJ"}

View File

@ -0,0 +1,133 @@
import { createRouterCacheKey } from "./create-router-cache-key";
import { PrefetchCacheEntryStatus } from "./router-reducer-types";
export function fillLazyItemsTillLeafWithHead(newCache, existingCache, routerState, cacheNodeSeedData, head, prefetchEntry) {
const isLastSegment = Object.keys(routerState[1]).length === 0;
if (isLastSegment) {
newCache.head = head;
return;
}
// Remove segment that we got data for so that it is filled in during rendering of rsc.
for(const key in routerState[1]){
const parallelRouteState = routerState[1][key];
const segmentForParallelRoute = parallelRouteState[0];
const cacheKey = createRouterCacheKey(segmentForParallelRoute);
// TODO: We should traverse the cacheNodeSeedData tree instead of the router
// state tree. Ideally, they would always be the same shape, but because of
// the loading.js pattern, cacheNodeSeedData sometimes only represents a
// partial tree. That's why this node is sometimes null. Once PPR lands,
// loading.js will no longer have special behavior and we can traverse the
// data tree instead.
//
// We should also consider merging the router state tree and the data tree
// in the response format, so that we don't have to send the keys twice.
// Then the client can convert them into separate representations.
const parallelSeedData = cacheNodeSeedData !== null && cacheNodeSeedData[1][key] !== undefined ? cacheNodeSeedData[1][key] : null;
if (existingCache) {
const existingParallelRoutesCacheNode = existingCache.parallelRoutes.get(key);
if (existingParallelRoutesCacheNode) {
const hasReusablePrefetch = (prefetchEntry == null ? void 0 : prefetchEntry.kind) === "auto" && prefetchEntry.status === PrefetchCacheEntryStatus.reusable;
let parallelRouteCacheNode = new Map(existingParallelRoutesCacheNode);
const existingCacheNode = parallelRouteCacheNode.get(cacheKey);
let newCacheNode;
if (parallelSeedData !== null) {
// New data was sent from the server.
const seedNode = parallelSeedData[2];
const loading = parallelSeedData[3];
newCacheNode = {
lazyData: null,
rsc: seedNode,
// This is a PPR-only field. When PPR is enabled, we shouldn't hit
// this path during a navigation, but until PPR is fully implemented
// yet it's possible the existing node does have a non-null
// `prefetchRsc`. As an incremental step, we'll just de-opt to the
// old behavior — no PPR value.
prefetchRsc: null,
head: null,
prefetchHead: null,
loading,
parallelRoutes: new Map(existingCacheNode == null ? void 0 : existingCacheNode.parallelRoutes),
lazyDataResolved: false
};
} else if (hasReusablePrefetch && existingCacheNode) {
// No new data was sent from the server, but the existing cache node
// was prefetched, so we should reuse that.
newCacheNode = {
lazyData: existingCacheNode.lazyData,
rsc: existingCacheNode.rsc,
// This is a PPR-only field. Unlike the previous branch, since we're
// just cloning the existing cache node, we might as well keep the
// PPR value, if it exists.
prefetchRsc: existingCacheNode.prefetchRsc,
head: existingCacheNode.head,
prefetchHead: existingCacheNode.prefetchHead,
parallelRoutes: new Map(existingCacheNode.parallelRoutes),
lazyDataResolved: existingCacheNode.lazyDataResolved,
loading: existingCacheNode.loading
};
} else {
// No data available for this node. This will trigger a lazy fetch
// during render.
newCacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
head: null,
prefetchHead: null,
parallelRoutes: new Map(existingCacheNode == null ? void 0 : existingCacheNode.parallelRoutes),
lazyDataResolved: false,
loading: null
};
}
// Overrides the cache key with the new cache node.
parallelRouteCacheNode.set(cacheKey, newCacheNode);
// Traverse deeper to apply the head / fill lazy items till the head.
fillLazyItemsTillLeafWithHead(newCacheNode, existingCacheNode, parallelRouteState, parallelSeedData ? parallelSeedData : null, head, prefetchEntry);
newCache.parallelRoutes.set(key, parallelRouteCacheNode);
continue;
}
}
let newCacheNode;
if (parallelSeedData !== null) {
// New data was sent from the server.
const seedNode = parallelSeedData[2];
const loading = parallelSeedData[3];
newCacheNode = {
lazyData: null,
rsc: seedNode,
prefetchRsc: null,
head: null,
prefetchHead: null,
parallelRoutes: new Map(),
lazyDataResolved: false,
loading
};
} else {
// No data available for this node. This will trigger a lazy fetch
// during render.
newCacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
head: null,
prefetchHead: null,
parallelRoutes: new Map(),
lazyDataResolved: false,
loading: null
};
}
const existingParallelRoutes = newCache.parallelRoutes.get(key);
if (existingParallelRoutes) {
existingParallelRoutes.set(cacheKey, newCacheNode);
} else {
newCache.parallelRoutes.set(key, new Map([
[
cacheKey,
newCacheNode
]
]));
}
fillLazyItemsTillLeafWithHead(newCacheNode, undefined, parallelRouteState, parallelSeedData, head, prefetchEntry);
}
}
//# sourceMappingURL=fill-lazy-items-till-leaf-with-head.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.ts"],"names":["createRouterCacheKey","PrefetchCacheEntryStatus","fillLazyItemsTillLeafWithHead","newCache","existingCache","routerState","cacheNodeSeedData","head","prefetchEntry","isLastSegment","Object","keys","length","key","parallelRouteState","segmentForParallelRoute","cacheKey","parallelSeedData","undefined","existingParallelRoutesCacheNode","parallelRoutes","get","hasReusablePrefetch","kind","status","reusable","parallelRouteCacheNode","Map","existingCacheNode","newCacheNode","seedNode","loading","lazyData","rsc","prefetchRsc","prefetchHead","lazyDataResolved","set","existingParallelRoutes"],"mappings":"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"}

View File

@ -0,0 +1,50 @@
import { computeChangedPath } from "./compute-changed-path";
function isNotUndefined(value) {
return typeof value !== "undefined";
}
export function handleMutable(state, mutable) {
var _mutable_canonicalUrl;
var _mutable_shouldScroll;
// shouldScroll is true by default, can override to false.
const shouldScroll = (_mutable_shouldScroll = mutable.shouldScroll) != null ? _mutable_shouldScroll : true;
let nextUrl = state.nextUrl;
if (isNotUndefined(mutable.patchedTree)) {
// If we received a patched tree, we need to compute the changed path.
const changedPath = computeChangedPath(state.tree, mutable.patchedTree);
if (changedPath) {
// If the tree changed, we need to update the nextUrl
nextUrl = changedPath;
} else if (!nextUrl) {
// if the tree ends up being the same (ie, no changed path), and we don't have a nextUrl, then we should use the canonicalUrl
nextUrl = state.canonicalUrl;
}
// otherwise this will be a no-op and continue to use the existing nextUrl
}
var _mutable_scrollableSegments;
return {
buildId: state.buildId,
// Set href.
canonicalUrl: isNotUndefined(mutable.canonicalUrl) ? mutable.canonicalUrl === state.canonicalUrl ? state.canonicalUrl : mutable.canonicalUrl : state.canonicalUrl,
pushRef: {
pendingPush: isNotUndefined(mutable.pendingPush) ? mutable.pendingPush : state.pushRef.pendingPush,
mpaNavigation: isNotUndefined(mutable.mpaNavigation) ? mutable.mpaNavigation : state.pushRef.mpaNavigation,
preserveCustomHistoryState: isNotUndefined(mutable.preserveCustomHistoryState) ? mutable.preserveCustomHistoryState : state.pushRef.preserveCustomHistoryState
},
// All navigation requires scroll and focus management to trigger.
focusAndScrollRef: {
apply: shouldScroll ? isNotUndefined(mutable == null ? void 0 : mutable.scrollableSegments) ? true : state.focusAndScrollRef.apply : false,
onlyHashChange: !!mutable.hashFragment && state.canonicalUrl.split("#", 1)[0] === ((_mutable_canonicalUrl = mutable.canonicalUrl) == null ? void 0 : _mutable_canonicalUrl.split("#", 1)[0]),
hashFragment: shouldScroll ? // #top is handled in layout-router.
mutable.hashFragment && mutable.hashFragment !== "" ? decodeURIComponent(mutable.hashFragment.slice(1)) : state.focusAndScrollRef.hashFragment : null,
segmentPaths: shouldScroll ? (_mutable_scrollableSegments = mutable == null ? void 0 : mutable.scrollableSegments) != null ? _mutable_scrollableSegments : state.focusAndScrollRef.segmentPaths : []
},
// Apply cache.
cache: mutable.cache ? mutable.cache : state.cache,
prefetchCache: mutable.prefetchCache ? mutable.prefetchCache : state.prefetchCache,
// Apply patched router state.
tree: isNotUndefined(mutable.patchedTree) ? mutable.patchedTree : state.tree,
nextUrl
};
}
//# sourceMappingURL=handle-mutable.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/handle-mutable.ts"],"names":["computeChangedPath","isNotUndefined","value","handleMutable","state","mutable","shouldScroll","nextUrl","patchedTree","changedPath","tree","canonicalUrl","buildId","pushRef","pendingPush","mpaNavigation","preserveCustomHistoryState","focusAndScrollRef","apply","scrollableSegments","onlyHashChange","hashFragment","split","decodeURIComponent","slice","segmentPaths","cache","prefetchCache"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,yBAAwB;AAO3D,SAASC,eAAkBC,KAAQ;IACjC,OAAO,OAAOA,UAAU;AAC1B;AAEA,OAAO,SAASC,cACdC,KAA2B,EAC3BC,OAAgB;QAoDRA;QAjDaA;IADrB,0DAA0D;IAC1D,MAAMC,eAAeD,CAAAA,wBAAAA,QAAQC,YAAY,YAApBD,wBAAwB;IAE7C,IAAIE,UAAUH,MAAMG,OAAO;IAE3B,IAAIN,eAAeI,QAAQG,WAAW,GAAG;QACvC,sEAAsE;QACtE,MAAMC,cAAcT,mBAAmBI,MAAMM,IAAI,EAAEL,QAAQG,WAAW;QACtE,IAAIC,aAAa;YACf,qDAAqD;YACrDF,UAAUE;QACZ,OAAO,IAAI,CAACF,SAAS;YACnB,6HAA6H;YAC7HA,UAAUH,MAAMO,YAAY;QAC9B;IACA,0EAA0E;IAC5E;QA6CQN;IA3CR,OAAO;QACLO,SAASR,MAAMQ,OAAO;QACtB,YAAY;QACZD,cAAcV,eAAeI,QAAQM,YAAY,IAC7CN,QAAQM,YAAY,KAAKP,MAAMO,YAAY,GACzCP,MAAMO,YAAY,GAClBN,QAAQM,YAAY,GACtBP,MAAMO,YAAY;QACtBE,SAAS;YACPC,aAAab,eAAeI,QAAQS,WAAW,IAC3CT,QAAQS,WAAW,GACnBV,MAAMS,OAAO,CAACC,WAAW;YAC7BC,eAAed,eAAeI,QAAQU,aAAa,IAC/CV,QAAQU,aAAa,GACrBX,MAAMS,OAAO,CAACE,aAAa;YAC/BC,4BAA4Bf,eAC1BI,QAAQW,0BAA0B,IAEhCX,QAAQW,0BAA0B,GAClCZ,MAAMS,OAAO,CAACG,0BAA0B;QAC9C;QACA,kEAAkE;QAClEC,mBAAmB;YACjBC,OAAOZ,eACHL,eAAeI,2BAAAA,QAASc,kBAAkB,IACxC,OACAf,MAAMa,iBAAiB,CAACC,KAAK,GAE/B;YACJE,gBACE,CAAC,CAACf,QAAQgB,YAAY,IACtBjB,MAAMO,YAAY,CAACW,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OACjCjB,wBAAAA,QAAQM,YAAY,qBAApBN,sBAAsBiB,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE;YAC1CD,cAAcf,eAEV,oCAAoC;YACpCD,QAAQgB,YAAY,IAAIhB,QAAQgB,YAAY,KAAK,KAE/CE,mBAAmBlB,QAAQgB,YAAY,CAACG,KAAK,CAAC,MAC9CpB,MAAMa,iBAAiB,CAACI,YAAY,GAEtC;YACJI,cAAcnB,eACVD,CAAAA,8BAAAA,2BAAAA,QAASc,kBAAkB,YAA3Bd,8BAA+BD,MAAMa,iBAAiB,CAACQ,YAAY,GAEnE,EAAE;QACR;QACA,eAAe;QACfC,OAAOrB,QAAQqB,KAAK,GAAGrB,QAAQqB,KAAK,GAAGtB,MAAMsB,KAAK;QAClDC,eAAetB,QAAQsB,aAAa,GAChCtB,QAAQsB,aAAa,GACrBvB,MAAMuB,aAAa;QACvB,8BAA8B;QAC9BjB,MAAMT,eAAeI,QAAQG,WAAW,IACpCH,QAAQG,WAAW,GACnBJ,MAAMM,IAAI;QACdH;IACF;AACF"}

View File

@ -0,0 +1,12 @@
import { handleExternalUrl } from "./reducers/navigate-reducer";
/**
* Handles the case where the client router attempted to patch the tree but, due to a mismatch, the patch failed.
* This will perform an MPA navigation to return the router to a valid state.
*/ export function handleSegmentMismatch(state, action, treePatch) {
if (process.env.NODE_ENV === "development") {
console.warn("Performing hard navigation because your application experienced an unrecoverable error. If this keeps occurring, please file a Next.js issue.\n\n" + "Reason: Segment mismatch\n" + ("Last Action: " + action.type + "\n\n") + ("Current Tree: " + JSON.stringify(state.tree) + "\n\n") + ("Tree Patch Payload: " + JSON.stringify(treePatch)));
}
return handleExternalUrl(state, {}, state.canonicalUrl, true);
}
//# sourceMappingURL=handle-segment-mismatch.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/handle-segment-mismatch.ts"],"names":["handleExternalUrl","handleSegmentMismatch","state","action","treePatch","process","env","NODE_ENV","console","warn","type","JSON","stringify","tree","canonicalUrl"],"mappings":"AACA,SAASA,iBAAiB,QAAQ,8BAA6B;AAM/D;;;CAGC,GACD,OAAO,SAASC,sBACdC,KAA2B,EAC3BC,MAAsB,EACtBC,SAA4B;IAE5B,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,eAAe;QAC1CC,QAAQC,IAAI,CACV,sJACE,+BACA,CAAA,AAAC,kBAAeN,OAAOO,IAAI,GAAC,MAAI,IAChC,CAAA,AAAC,mBAAgBC,KAAKC,SAAS,CAACV,MAAMW,IAAI,IAAE,MAAI,IAChD,CAAA,AAAC,yBAAsBF,KAAKC,SAAS,CAACR,UAAW;IAEvD;IAEA,OAAOJ,kBAAkBE,OAAO,CAAC,GAAGA,MAAMY,YAAY,EAAE;AAC1D"}

View File

@ -0,0 +1,46 @@
import { createRouterCacheKey } from "./create-router-cache-key";
/**
* Fill cache up to the end of the flightSegmentPath, invalidating anything below it.
*/ export function invalidateCacheBelowFlightSegmentPath(newCache, existingCache, flightSegmentPath) {
const isLastEntry = flightSegmentPath.length <= 2;
const [parallelRouteKey, segment] = flightSegmentPath;
const cacheKey = createRouterCacheKey(segment);
const existingChildSegmentMap = existingCache.parallelRoutes.get(parallelRouteKey);
if (!existingChildSegmentMap) {
// Bailout because the existing cache does not have the path to the leaf node
// Will trigger lazy fetch in layout-router because of missing segment
return;
}
let childSegmentMap = newCache.parallelRoutes.get(parallelRouteKey);
if (!childSegmentMap || childSegmentMap === existingChildSegmentMap) {
childSegmentMap = new Map(existingChildSegmentMap);
newCache.parallelRoutes.set(parallelRouteKey, childSegmentMap);
}
// In case of last entry don't copy further down.
if (isLastEntry) {
childSegmentMap.delete(cacheKey);
return;
}
const existingChildCacheNode = existingChildSegmentMap.get(cacheKey);
let childCacheNode = childSegmentMap.get(cacheKey);
if (!childCacheNode || !existingChildCacheNode) {
// Bailout because the existing cache does not have the path to the leaf node
// Will trigger lazy fetch in layout-router because of missing segment
return;
}
if (childCacheNode === existingChildCacheNode) {
childCacheNode = {
lazyData: childCacheNode.lazyData,
rsc: childCacheNode.rsc,
prefetchRsc: childCacheNode.prefetchRsc,
head: childCacheNode.head,
prefetchHead: childCacheNode.prefetchHead,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
lazyDataResolved: childCacheNode.lazyDataResolved
};
childSegmentMap.set(cacheKey, childCacheNode);
}
invalidateCacheBelowFlightSegmentPath(childCacheNode, existingChildCacheNode, flightSegmentPath.slice(2));
}
//# sourceMappingURL=invalidate-cache-below-flight-segmentpath.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.ts"],"names":["createRouterCacheKey","invalidateCacheBelowFlightSegmentPath","newCache","existingCache","flightSegmentPath","isLastEntry","length","parallelRouteKey","segment","cacheKey","existingChildSegmentMap","parallelRoutes","get","childSegmentMap","Map","set","delete","existingChildCacheNode","childCacheNode","lazyData","rsc","prefetchRsc","head","prefetchHead","lazyDataResolved","slice"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,4BAA2B;AAEhE;;CAEC,GACD,OAAO,SAASC,sCACdC,QAAmB,EACnBC,aAAwB,EACxBC,iBAAoC;IAEpC,MAAMC,cAAcD,kBAAkBE,MAAM,IAAI;IAChD,MAAM,CAACC,kBAAkBC,QAAQ,GAAGJ;IAEpC,MAAMK,WAAWT,qBAAqBQ;IAEtC,MAAME,0BACJP,cAAcQ,cAAc,CAACC,GAAG,CAACL;IAEnC,IAAI,CAACG,yBAAyB;QAC5B,6EAA6E;QAC7E,sEAAsE;QACtE;IACF;IAEA,IAAIG,kBAAkBX,SAASS,cAAc,CAACC,GAAG,CAACL;IAClD,IAAI,CAACM,mBAAmBA,oBAAoBH,yBAAyB;QACnEG,kBAAkB,IAAIC,IAAIJ;QAC1BR,SAASS,cAAc,CAACI,GAAG,CAACR,kBAAkBM;IAChD;IAEA,iDAAiD;IACjD,IAAIR,aAAa;QACfQ,gBAAgBG,MAAM,CAACP;QACvB;IACF;IAEA,MAAMQ,yBAAyBP,wBAAwBE,GAAG,CAACH;IAC3D,IAAIS,iBAAiBL,gBAAgBD,GAAG,CAACH;IAEzC,IAAI,CAACS,kBAAkB,CAACD,wBAAwB;QAC9C,6EAA6E;QAC7E,sEAAsE;QACtE;IACF;IAEA,IAAIC,mBAAmBD,wBAAwB;QAC7CC,iBAAiB;YACfC,UAAUD,eAAeC,QAAQ;YACjCC,KAAKF,eAAeE,GAAG;YACvBC,aAAaH,eAAeG,WAAW;YACvCC,MAAMJ,eAAeI,IAAI;YACzBC,cAAcL,eAAeK,YAAY;YACzCZ,gBAAgB,IAAIG,IAAII,eAAeP,cAAc;YACrDa,kBAAkBN,eAAeM,gBAAgB;QACnD;QACAX,gBAAgBE,GAAG,CAACN,UAAUS;IAChC;IAEAjB,sCACEiB,gBACAD,wBACAb,kBAAkBqB,KAAK,CAAC;AAE5B"}

View File

@ -0,0 +1,18 @@
import { createRouterCacheKey } from "./create-router-cache-key";
/**
* Invalidate cache one level down from the router state.
*/ export function invalidateCacheByRouterState(newCache, existingCache, routerState) {
// Remove segment that we got data for so that it is filled in during rendering of rsc.
for(const key in routerState[1]){
const segmentForParallelRoute = routerState[1][key][0];
const cacheKey = createRouterCacheKey(segmentForParallelRoute);
const existingParallelRoutesCacheNode = existingCache.parallelRoutes.get(key);
if (existingParallelRoutesCacheNode) {
let parallelRouteCacheNode = new Map(existingParallelRoutesCacheNode);
parallelRouteCacheNode.delete(cacheKey);
newCache.parallelRoutes.set(key, parallelRouteCacheNode);
}
}
}
//# sourceMappingURL=invalidate-cache-by-router-state.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/invalidate-cache-by-router-state.ts"],"names":["createRouterCacheKey","invalidateCacheByRouterState","newCache","existingCache","routerState","key","segmentForParallelRoute","cacheKey","existingParallelRoutesCacheNode","parallelRoutes","get","parallelRouteCacheNode","Map","delete","set"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,4BAA2B;AAEhE;;CAEC,GACD,OAAO,SAASC,6BACdC,QAAmB,EACnBC,aAAwB,EACxBC,WAA8B;IAE9B,uFAAuF;IACvF,IAAK,MAAMC,OAAOD,WAAW,CAAC,EAAE,CAAE;QAChC,MAAME,0BAA0BF,WAAW,CAAC,EAAE,CAACC,IAAI,CAAC,EAAE;QACtD,MAAME,WAAWP,qBAAqBM;QACtC,MAAME,kCACJL,cAAcM,cAAc,CAACC,GAAG,CAACL;QACnC,IAAIG,iCAAiC;YACnC,IAAIG,yBAAyB,IAAIC,IAAIJ;YACrCG,uBAAuBE,MAAM,CAACN;YAC9BL,SAASO,cAAc,CAACK,GAAG,CAACT,KAAKM;QACnC;IACF;AACF"}

View File

@ -0,0 +1,35 @@
export function isNavigatingToNewRootLayout(currentTree, nextTree) {
// Compare segments
const currentTreeSegment = currentTree[0];
const nextTreeSegment = nextTree[0];
// If any segment is different before we find the root layout, the root layout has changed.
// E.g. /same/(group1)/layout.js -> /same/(group2)/layout.js
// First segment is 'same' for both, keep looking. (group1) changed to (group2) before the root layout was found, it must have changed.
if (Array.isArray(currentTreeSegment) && Array.isArray(nextTreeSegment)) {
// Compare dynamic param name and type but ignore the value, different values would not affect the current root layout
// /[name] - /slug1 and /slug2, both values (slug1 & slug2) still has the same layout /[name]/layout.js
if (currentTreeSegment[0] !== nextTreeSegment[0] || currentTreeSegment[2] !== nextTreeSegment[2]) {
return true;
}
} else if (currentTreeSegment !== nextTreeSegment) {
return true;
}
// Current tree root layout found
if (currentTree[4]) {
// If the next tree doesn't have the root layout flag, it must have changed.
return !nextTree[4];
}
// Current tree didn't have its root layout here, must have changed.
if (nextTree[4]) {
return true;
}
// We can't assume it's `parallelRoutes.children` here in case the root layout is `app/@something/layout.js`
// But it's not possible to be more than one parallelRoutes before the root layout is found
// TODO-APP: change to traverse all parallel routes
const currentTreeChild = Object.values(currentTree[1])[0];
const nextTreeChild = Object.values(nextTree[1])[0];
if (!currentTreeChild || !nextTreeChild) return true;
return isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild);
}
//# sourceMappingURL=is-navigating-to-new-root-layout.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/is-navigating-to-new-root-layout.ts"],"names":["isNavigatingToNewRootLayout","currentTree","nextTree","currentTreeSegment","nextTreeSegment","Array","isArray","currentTreeChild","Object","values","nextTreeChild"],"mappings":"AAEA,OAAO,SAASA,4BACdC,WAA8B,EAC9BC,QAA2B;IAE3B,mBAAmB;IACnB,MAAMC,qBAAqBF,WAAW,CAAC,EAAE;IACzC,MAAMG,kBAAkBF,QAAQ,CAAC,EAAE;IAEnC,2FAA2F;IAC3F,4DAA4D;IAC5D,uIAAuI;IACvI,IAAIG,MAAMC,OAAO,CAACH,uBAAuBE,MAAMC,OAAO,CAACF,kBAAkB;QACvE,sHAAsH;QACtH,uGAAuG;QACvG,IACED,kBAAkB,CAAC,EAAE,KAAKC,eAAe,CAAC,EAAE,IAC5CD,kBAAkB,CAAC,EAAE,KAAKC,eAAe,CAAC,EAAE,EAC5C;YACA,OAAO;QACT;IACF,OAAO,IAAID,uBAAuBC,iBAAiB;QACjD,OAAO;IACT;IAEA,iCAAiC;IACjC,IAAIH,WAAW,CAAC,EAAE,EAAE;QAClB,4EAA4E;QAC5E,OAAO,CAACC,QAAQ,CAAC,EAAE;IACrB;IACA,qEAAqE;IACrE,IAAIA,QAAQ,CAAC,EAAE,EAAE;QACf,OAAO;IACT;IACA,4GAA4G;IAC5G,2FAA2F;IAC3F,mDAAmD;IACnD,MAAMK,mBAAmBC,OAAOC,MAAM,CAACR,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE;IACzD,MAAMS,gBAAgBF,OAAOC,MAAM,CAACP,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE;IACnD,IAAI,CAACK,oBAAoB,CAACG,eAAe,OAAO;IAChD,OAAOV,4BAA4BO,kBAAkBG;AACvD"}

View File

@ -0,0 +1,576 @@
import { DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY } from "../../../shared/lib/segment";
import { matchSegment } from "../match-segments";
import { createRouterCacheKey } from "./create-router-cache-key";
// Creates a new Cache Node tree (i.e. copy-on-write) that represents the
// optimistic result of a navigation, using both the current Cache Node tree and
// data that was prefetched prior to navigation.
//
// At the moment we call this function, we haven't yet received the navigation
// response from the server. It could send back something completely different
// from the tree that was prefetched — due to rewrites, default routes, parallel
// routes, etc.
//
// But in most cases, it will return the same tree that we prefetched, just with
// the dynamic holes filled in. So we optimistically assume this will happen,
// and accept that the real result could be arbitrarily different.
//
// We'll reuse anything that was already in the previous tree, since that's what
// the server does.
//
// New segments (ones that don't appear in the old tree) are assigned an
// unresolved promise. The data for these promises will be fulfilled later, when
// the navigation response is received.
//
// The tree can be rendered immediately after it is created (that's why this is
// a synchronous function). Any new trees that do not have prefetch data will
// suspend during rendering, until the dynamic data streams in.
//
// Returns a Task object, which contains both the updated Cache Node and a path
// to the pending subtrees that need to be resolved by the navigation response.
//
// A return value of `null` means there were no changes, and the previous tree
// can be reused without initiating a server request.
export function updateCacheNodeOnNavigation(oldCacheNode, oldRouterState, newRouterState, prefetchData, prefetchHead) {
// Diff the old and new trees to reuse the shared layouts.
const oldRouterStateChildren = oldRouterState[1];
const newRouterStateChildren = newRouterState[1];
const prefetchDataChildren = prefetchData[1];
const oldParallelRoutes = oldCacheNode.parallelRoutes;
// Clone the current set of segment children, even if they aren't active in
// the new tree.
// TODO: We currently retain all the inactive segments indefinitely, until
// there's an explicit refresh, or a parent layout is lazily refreshed. We
// rely on this for popstate navigations, which update the Router State Tree
// but do not eagerly perform a data fetch, because they expect the segment
// data to already be in the Cache Node tree. For highly static sites that
// are mostly read-only, this may happen only rarely, causing memory to
// leak. We should figure out a better model for the lifetime of inactive
// segments, so we can maintain instant back/forward navigations without
// leaking memory indefinitely.
const prefetchParallelRoutes = new Map(oldParallelRoutes);
// As we diff the trees, we may sometimes modify (copy-on-write, not mutate)
// the Route Tree that was returned by the server — for example, in the case
// of default parallel routes, we preserve the currently active segment. To
// avoid mutating the original tree, we clone the router state children along
// the return path.
let patchedRouterStateChildren = {};
let taskChildren = null;
for(let parallelRouteKey in newRouterStateChildren){
const newRouterStateChild = newRouterStateChildren[parallelRouteKey];
const oldRouterStateChild = oldRouterStateChildren[parallelRouteKey];
const oldSegmentMapChild = oldParallelRoutes.get(parallelRouteKey);
const prefetchDataChild = prefetchDataChildren[parallelRouteKey];
const newSegmentChild = newRouterStateChild[0];
const newSegmentKeyChild = createRouterCacheKey(newSegmentChild);
const oldSegmentChild = oldRouterStateChild !== undefined ? oldRouterStateChild[0] : undefined;
const oldCacheNodeChild = oldSegmentMapChild !== undefined ? oldSegmentMapChild.get(newSegmentKeyChild) : undefined;
let taskChild;
if (newSegmentChild === PAGE_SEGMENT_KEY) {
// This is a leaf segment — a page, not a shared layout. We always apply
// its data.
taskChild = spawnPendingTask(newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead);
} else if (newSegmentChild === DEFAULT_SEGMENT_KEY) {
// This is another kind of leaf segment — a default route.
//
// Default routes have special behavior. When there's no matching segment
// for a parallel route, Next.js preserves the currently active segment
// during a client navigation — but not for initial render. The server
// leaves it to the client to account for this. So we need to handle
// it here.
if (oldRouterStateChild !== undefined) {
// Reuse the existing Router State for this segment. We spawn a "task"
// just to keep track of the updated router state; unlike most, it's
// already fulfilled and won't be affected by the dynamic response.
taskChild = spawnReusedTask(oldRouterStateChild);
} else {
// There's no currently active segment. Switch to the "create" path.
taskChild = spawnPendingTask(newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead);
}
} else if (oldSegmentChild !== undefined && matchSegment(newSegmentChild, oldSegmentChild)) {
if (oldCacheNodeChild !== undefined && oldRouterStateChild !== undefined) {
// This segment exists in both the old and new trees.
if (prefetchDataChild !== undefined && prefetchDataChild !== null) {
// Recursively update the children.
taskChild = updateCacheNodeOnNavigation(oldCacheNodeChild, oldRouterStateChild, newRouterStateChild, prefetchDataChild, prefetchHead);
} else {
// The server didn't send any prefetch data for this segment. This
// shouldn't happen because the Route Tree and the Seed Data tree
// should always be the same shape, but until we unify those types
// it's still possible. For now we're going to deopt and trigger a
// lazy fetch during render.
taskChild = spawnTaskForMissingData(newRouterStateChild);
}
} else {
// Either there's no existing Cache Node for this segment, or this
// segment doesn't exist in the old Router State tree. Switch to the
// "create" path.
taskChild = spawnPendingTask(newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead);
}
} else {
// This is a new tree. Switch to the "create" path.
taskChild = spawnPendingTask(newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead);
}
if (taskChild !== null) {
// Something changed in the child tree. Keep track of the child task.
if (taskChildren === null) {
taskChildren = new Map();
}
taskChildren.set(parallelRouteKey, taskChild);
const newCacheNodeChild = taskChild.node;
if (newCacheNodeChild !== null) {
const newSegmentMapChild = new Map(oldSegmentMapChild);
newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
prefetchParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
}
// The child tree's route state may be different from the prefetched
// route sent by the server. We need to clone it as we traverse back up
// the tree.
patchedRouterStateChildren[parallelRouteKey] = taskChild.route;
} else {
// The child didn't change. We can use the prefetched router state.
patchedRouterStateChildren[parallelRouteKey] = newRouterStateChild;
}
}
if (taskChildren === null) {
// No new tasks were spawned.
return null;
}
const newCacheNode = {
lazyData: null,
rsc: oldCacheNode.rsc,
// We intentionally aren't updating the prefetchRsc field, since this node
// is already part of the current tree, because it would be weird for
// prefetch data to be newer than the final data. It probably won't ever be
// observable anyway, but it could happen if the segment is unmounted then
// mounted again, because LayoutRouter will momentarily switch to rendering
// prefetchRsc, via useDeferredValue.
prefetchRsc: oldCacheNode.prefetchRsc,
head: oldCacheNode.head,
prefetchHead: oldCacheNode.prefetchHead,
loading: oldCacheNode.loading,
// Everything is cloned except for the children, which we computed above.
parallelRoutes: prefetchParallelRoutes,
lazyDataResolved: false
};
return {
// Return a cloned copy of the router state with updated children.
route: patchRouterStateWithNewChildren(newRouterState, patchedRouterStateChildren),
node: newCacheNode,
children: taskChildren
};
}
function patchRouterStateWithNewChildren(baseRouterState, newChildren) {
const clone = [
baseRouterState[0],
newChildren
];
// Based on equivalent logic in apply-router-state-patch-to-tree, but should
// confirm whether we need to copy all of these fields. Not sure the server
// ever sends, e.g. the refetch marker.
if (2 in baseRouterState) {
clone[2] = baseRouterState[2];
}
if (3 in baseRouterState) {
clone[3] = baseRouterState[3];
}
if (4 in baseRouterState) {
clone[4] = baseRouterState[4];
}
return clone;
}
function spawnPendingTask(routerState, prefetchData, prefetchHead) {
// Create a task that will later be fulfilled by data from the server.
const pendingCacheNode = createPendingCacheNode(routerState, prefetchData, prefetchHead);
return {
route: routerState,
node: pendingCacheNode,
children: null
};
}
function spawnReusedTask(reusedRouterState) {
// Create a task that reuses an existing segment, e.g. when reusing
// the current active segment in place of a default route.
return {
route: reusedRouterState,
node: null,
children: null
};
}
function spawnTaskForMissingData(routerState) {
// Create a task for a new subtree that wasn't prefetched by the server.
// This shouldn't really ever happen but it's here just in case the Seed Data
// Tree and the Router State Tree disagree unexpectedly.
const pendingCacheNode = createPendingCacheNode(routerState, null, null);
return {
route: routerState,
node: pendingCacheNode,
children: null
};
}
// Writes a dynamic server response into the tree created by
// updateCacheNodeOnNavigation. All pending promises that were spawned by the
// navigation will be resolved, either with dynamic data from the server, or
// `null` to indicate that the data is missing.
//
// A `null` value will trigger a lazy fetch during render, which will then patch
// up the tree using the same mechanism as the non-PPR implementation
// (serverPatchReducer).
//
// Usually, the server will respond with exactly the subset of data that we're
// waiting for — everything below the nearest shared layout. But technically,
// the server can return anything it wants.
//
// This does _not_ create a new tree; it modifies the existing one in place.
// Which means it must follow the Suspense rules of cache safety.
export function listenForDynamicRequest(task, responsePromise) {
responsePromise.then((response)=>{
const flightData = response[0];
for (const flightDataPath of flightData){
const segmentPath = flightDataPath.slice(0, -3);
const serverRouterState = flightDataPath[flightDataPath.length - 3];
const dynamicData = flightDataPath[flightDataPath.length - 2];
const dynamicHead = flightDataPath[flightDataPath.length - 1];
if (typeof segmentPath === "string") {
continue;
}
writeDynamicDataIntoPendingTask(task, segmentPath, serverRouterState, dynamicData, dynamicHead);
}
// Now that we've exhausted all the data we received from the server, if
// there are any remaining pending tasks in the tree, abort them now.
// If there's any missing data, it will trigger a lazy fetch.
abortTask(task, null);
}, (error)=>{
// This will trigger an error during render
abortTask(task, error);
});
}
function writeDynamicDataIntoPendingTask(rootTask, segmentPath, serverRouterState, dynamicData, dynamicHead) {
// The data sent by the server represents only a subtree of the app. We need
// to find the part of the task tree that matches the server response, and
// fulfill it using the dynamic data.
//
// segmentPath represents the parent path of subtree. It's a repeating pattern
// of parallel route key and segment:
//
// [string, Segment, string, Segment, string, Segment, ...]
//
// Iterate through the path and finish any tasks that match this payload.
let task = rootTask;
for(let i = 0; i < segmentPath.length; i += 2){
const parallelRouteKey = segmentPath[i];
const segment = segmentPath[i + 1];
const taskChildren = task.children;
if (taskChildren !== null) {
const taskChild = taskChildren.get(parallelRouteKey);
if (taskChild !== undefined) {
const taskSegment = taskChild.route[0];
if (matchSegment(segment, taskSegment)) {
// Found a match for this task. Keep traversing down the task tree.
task = taskChild;
continue;
}
}
}
// We didn't find a child task that matches the server data. Exit. We won't
// abort the task, though, because a different FlightDataPath may be able to
// fulfill it (see loop in listenForDynamicRequest). We only abort tasks
// once we've run out of data.
return;
}
finishTaskUsingDynamicDataPayload(task, serverRouterState, dynamicData, dynamicHead);
}
function finishTaskUsingDynamicDataPayload(task, serverRouterState, dynamicData, dynamicHead) {
// dynamicData may represent a larger subtree than the task. Before we can
// finish the task, we need to line them up.
const taskChildren = task.children;
const taskNode = task.node;
if (taskChildren === null) {
// We've reached the leaf node of the pending task. The server data tree
// lines up the pending Cache Node tree. We can now switch to the
// normal algorithm.
if (taskNode !== null) {
finishPendingCacheNode(taskNode, task.route, serverRouterState, dynamicData, dynamicHead);
// Null this out to indicate that the task is complete.
task.node = null;
}
return;
}
// The server returned more data than we need to finish the task. Skip over
// the extra segments until we reach the leaf task node.
const serverChildren = serverRouterState[1];
const dynamicDataChildren = dynamicData[1];
for(const parallelRouteKey in serverRouterState){
const serverRouterStateChild = serverChildren[parallelRouteKey];
const dynamicDataChild = dynamicDataChildren[parallelRouteKey];
const taskChild = taskChildren.get(parallelRouteKey);
if (taskChild !== undefined) {
const taskSegment = taskChild.route[0];
if (matchSegment(serverRouterStateChild[0], taskSegment) && dynamicDataChild !== null && dynamicDataChild !== undefined) {
// Found a match for this task. Keep traversing down the task tree.
return finishTaskUsingDynamicDataPayload(taskChild, serverRouterStateChild, dynamicDataChild, dynamicHead);
}
}
// We didn't find a child task that matches the server data. We won't abort
// the task, though, because a different FlightDataPath may be able to
// fulfill it (see loop in listenForDynamicRequest). We only abort tasks
// once we've run out of data.
}
}
function createPendingCacheNode(routerState, prefetchData, prefetchHead) {
const routerStateChildren = routerState[1];
const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
const parallelRoutes = new Map();
for(let parallelRouteKey in routerStateChildren){
const routerStateChild = routerStateChildren[parallelRouteKey];
const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
const segmentChild = routerStateChild[0];
const segmentKeyChild = createRouterCacheKey(segmentChild);
const newCacheNodeChild = createPendingCacheNode(routerStateChild, prefetchDataChild === undefined ? null : prefetchDataChild, prefetchHead);
const newSegmentMapChild = new Map();
newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
parallelRoutes.set(parallelRouteKey, newSegmentMapChild);
}
// The head is assigned to every leaf segment delivered by the server. Based
// on corresponding logic in fill-lazy-items-till-leaf-with-head.ts
const isLeafSegment = parallelRoutes.size === 0;
const maybePrefetchRsc = prefetchData !== null ? prefetchData[2] : null;
const maybePrefetchLoading = prefetchData !== null ? prefetchData[3] : null;
return {
lazyData: null,
parallelRoutes: parallelRoutes,
prefetchRsc: maybePrefetchRsc !== undefined ? maybePrefetchRsc : null,
prefetchHead: isLeafSegment ? prefetchHead : null,
loading: maybePrefetchLoading !== undefined ? maybePrefetchLoading : null,
// Create a deferred promise. This will be fulfilled once the dynamic
// response is received from the server.
rsc: createDeferredRsc(),
head: isLeafSegment ? createDeferredRsc() : null,
lazyDataResolved: false
};
}
function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData, dynamicHead) {
// Writes a dynamic response into an existing Cache Node tree. This does _not_
// create a new tree, it updates the existing tree in-place. So it must follow
// the Suspense rules of cache safety — it can resolve pending promises, but
// it cannot overwrite existing data. It can add segments to the tree (because
// a missing segment will cause the layout router to suspend).
// but it cannot delete them.
//
// We must resolve every promise in the tree, or else it will suspend
// indefinitely. If we did not receive data for a segment, we will resolve its
// data promise to `null` to trigger a lazy fetch during render.
const taskStateChildren = taskState[1];
const serverStateChildren = serverState[1];
const dataChildren = dynamicData[1];
// The router state that we traverse the tree with (taskState) is the same one
// that we used to construct the pending Cache Node tree. That way we're sure
// to resolve all the pending promises.
const parallelRoutes = cacheNode.parallelRoutes;
for(let parallelRouteKey in taskStateChildren){
const taskStateChild = taskStateChildren[parallelRouteKey];
const serverStateChild = serverStateChildren[parallelRouteKey];
const dataChild = dataChildren[parallelRouteKey];
const segmentMapChild = parallelRoutes.get(parallelRouteKey);
const taskSegmentChild = taskStateChild[0];
const taskSegmentKeyChild = createRouterCacheKey(taskSegmentChild);
const cacheNodeChild = segmentMapChild !== undefined ? segmentMapChild.get(taskSegmentKeyChild) : undefined;
if (cacheNodeChild !== undefined) {
if (serverStateChild !== undefined && matchSegment(taskSegmentChild, serverStateChild[0])) {
if (dataChild !== undefined && dataChild !== null) {
// This is the happy path. Recursively update all the children.
finishPendingCacheNode(cacheNodeChild, taskStateChild, serverStateChild, dataChild, dynamicHead);
} else {
// The server never returned data for this segment. Trigger a lazy
// fetch during render. This shouldn't happen because the Route Tree
// and the Seed Data tree sent by the server should always be the same
// shape when part of the same server response.
abortPendingCacheNode(taskStateChild, cacheNodeChild, null);
}
} else {
// The server never returned data for this segment. Trigger a lazy
// fetch during render.
abortPendingCacheNode(taskStateChild, cacheNodeChild, null);
}
} else {
// The server response matches what was expected to receive, but there's
// no matching Cache Node in the task tree. This is a bug in the
// implementation because we should have created a node for every
// segment in the tree that's associated with this task.
}
}
// Use the dynamic data from the server to fulfill the deferred RSC promise
// on the Cache Node.
const rsc = cacheNode.rsc;
const dynamicSegmentData = dynamicData[2];
if (rsc === null) {
// This is a lazy cache node. We can overwrite it. This is only safe
// because we know that the LayoutRouter suspends if `rsc` is `null`.
cacheNode.rsc = dynamicSegmentData;
} else if (isDeferredRsc(rsc)) {
// This is a deferred RSC promise. We can fulfill it with the data we just
// received from the server. If it was already resolved by a different
// navigation, then this does nothing because we can't overwrite data.
rsc.resolve(dynamicSegmentData);
} else {
// This is not a deferred RSC promise, nor is it empty, so it must have
// been populated by a different navigation. We must not overwrite it.
}
// Check if this is a leaf segment. If so, it will have a `head` property with
// a pending promise that needs to be resolved with the dynamic head from
// the server.
const head = cacheNode.head;
if (isDeferredRsc(head)) {
head.resolve(dynamicHead);
}
}
export function abortTask(task, error) {
const cacheNode = task.node;
if (cacheNode === null) {
// This indicates the task is already complete.
return;
}
const taskChildren = task.children;
if (taskChildren === null) {
// Reached the leaf task node. This is the root of a pending cache
// node tree.
abortPendingCacheNode(task.route, cacheNode, error);
} else {
// This is an intermediate task node. Keep traversing until we reach a
// task node with no children. That will be the root of the cache node tree
// that needs to be resolved.
for (const taskChild of taskChildren.values()){
abortTask(taskChild, error);
}
}
// Null this out to indicate that the task is complete.
task.node = null;
}
function abortPendingCacheNode(routerState, cacheNode, error) {
// For every pending segment in the tree, resolve its `rsc` promise to `null`
// to trigger a lazy fetch during render.
//
// Or, if an error object is provided, it will error instead.
const routerStateChildren = routerState[1];
const parallelRoutes = cacheNode.parallelRoutes;
for(let parallelRouteKey in routerStateChildren){
const routerStateChild = routerStateChildren[parallelRouteKey];
const segmentMapChild = parallelRoutes.get(parallelRouteKey);
if (segmentMapChild === undefined) {
continue;
}
const segmentChild = routerStateChild[0];
const segmentKeyChild = createRouterCacheKey(segmentChild);
const cacheNodeChild = segmentMapChild.get(segmentKeyChild);
if (cacheNodeChild !== undefined) {
abortPendingCacheNode(routerStateChild, cacheNodeChild, error);
} else {
// This shouldn't happen because we're traversing the same tree that was
// used to construct the cache nodes in the first place.
}
}
const rsc = cacheNode.rsc;
if (isDeferredRsc(rsc)) {
if (error === null) {
// This will trigger a lazy fetch during render.
rsc.resolve(null);
} else {
// This will trigger an error during rendering.
rsc.reject(error);
}
}
// Check if this is a leaf segment. If so, it will have a `head` property with
// a pending promise that needs to be resolved. If an error was provided, we
// will not resolve it with an error, since this is rendered at the root of
// the app. We want the segment to error, not the entire app.
const head = cacheNode.head;
if (isDeferredRsc(head)) {
head.resolve(null);
}
}
export function updateCacheNodeOnPopstateRestoration(oldCacheNode, routerState) {
// A popstate navigation reads data from the local cache. It does not issue
// new network requests (unless the cache entries have been evicted). So, we
// update the cache to drop the prefetch data for any segment whose dynamic
// data was already received. This prevents an unnecessary flash back to PPR
// state during a back/forward navigation.
//
// This function clones the entire cache node tree and sets the `prefetchRsc`
// field to `null` to prevent it from being rendered. We can't mutate the node
// in place because this is a concurrent data structure.
const routerStateChildren = routerState[1];
const oldParallelRoutes = oldCacheNode.parallelRoutes;
const newParallelRoutes = new Map(oldParallelRoutes);
for(let parallelRouteKey in routerStateChildren){
const routerStateChild = routerStateChildren[parallelRouteKey];
const segmentChild = routerStateChild[0];
const segmentKeyChild = createRouterCacheKey(segmentChild);
const oldSegmentMapChild = oldParallelRoutes.get(parallelRouteKey);
if (oldSegmentMapChild !== undefined) {
const oldCacheNodeChild = oldSegmentMapChild.get(segmentKeyChild);
if (oldCacheNodeChild !== undefined) {
const newCacheNodeChild = updateCacheNodeOnPopstateRestoration(oldCacheNodeChild, routerStateChild);
const newSegmentMapChild = new Map(oldSegmentMapChild);
newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
}
}
}
// Only show prefetched data if the dynamic data is still pending.
//
// Tehnically, what we're actually checking is whether the dynamic network
// response was received. But since it's a streaming response, this does not
// mean that all the dynamic data has fully streamed in. It just means that
// _some_ of the dynamic data was received. But as a heuristic, we assume that
// the rest dynamic data will stream in quickly, so it's still better to skip
// the prefetch state.
const rsc = oldCacheNode.rsc;
const shouldUsePrefetch = isDeferredRsc(rsc) && rsc.status === "pending";
return {
lazyData: null,
rsc,
head: oldCacheNode.head,
prefetchHead: shouldUsePrefetch ? oldCacheNode.prefetchHead : null,
prefetchRsc: shouldUsePrefetch ? oldCacheNode.prefetchRsc : null,
loading: shouldUsePrefetch ? oldCacheNode.loading : null,
// These are the cloned children we computed above
parallelRoutes: newParallelRoutes,
lazyDataResolved: false
};
}
const DEFERRED = Symbol();
// This type exists to distinguish a DeferredRsc from a Flight promise. It's a
// compromise to avoid adding an extra field on every Cache Node, which would be
// awkward because the pre-PPR parts of codebase would need to account for it,
// too. We can remove it once type Cache Node type is more settled.
function isDeferredRsc(value) {
return value && value.tag === DEFERRED;
}
function createDeferredRsc() {
let resolve;
let reject;
const pendingRsc = new Promise((res, rej)=>{
resolve = res;
reject = rej;
});
pendingRsc.status = "pending";
pendingRsc.resolve = (value)=>{
if (pendingRsc.status === "pending") {
const fulfilledRsc = pendingRsc;
fulfilledRsc.status = "fulfilled";
fulfilledRsc.value = value;
resolve(value);
}
};
pendingRsc.reject = (error)=>{
if (pendingRsc.status === "pending") {
const rejectedRsc = pendingRsc;
rejectedRsc.status = "rejected";
rejectedRsc.reason = error;
reject(error);
}
};
pendingRsc.tag = DEFERRED;
return pendingRsc;
}
//# sourceMappingURL=ppr-navigations.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,180 @@
import { createHrefFromUrl } from "./create-href-from-url";
import { fetchServerResponse } from "./fetch-server-response";
import { PrefetchCacheEntryStatus, PrefetchKind } from "./router-reducer-types";
import { prefetchQueue } from "./reducers/prefetch-reducer";
/**
* Creates a cache key for the router prefetch cache
*
* @param url - The URL being navigated to
* @param nextUrl - an internal URL, primarily used for handling rewrites. Defaults to '/'.
* @return The generated prefetch cache key.
*/ function createPrefetchCacheKey(url, nextUrl) {
const pathnameFromUrl = createHrefFromUrl(url, // Ensures the hash is not part of the cache key as it does not impact the server fetch
false);
// nextUrl is used as a cache key delimiter since entries can vary based on the Next-URL header
if (nextUrl) {
return nextUrl + "%" + pathnameFromUrl;
}
return pathnameFromUrl;
}
/**
* Returns a prefetch cache entry if one exists. Otherwise creates a new one and enqueues a fetch request
* to retrieve the prefetch data from the server.
*/ export function getOrCreatePrefetchCacheEntry(param) {
let { url, nextUrl, tree, buildId, prefetchCache, kind } = param;
let existingCacheEntry = undefined;
// We first check if there's a more specific interception route prefetch entry
// This is because when we detect a prefetch that corresponds with an interception route, we prefix it with nextUrl (see `createPrefetchCacheKey`)
// to avoid conflicts with other pages that may have the same URL but render different things depending on the `Next-URL` header.
const interceptionCacheKey = createPrefetchCacheKey(url, nextUrl);
const interceptionData = prefetchCache.get(interceptionCacheKey);
if (interceptionData) {
existingCacheEntry = interceptionData;
} else {
// If we dont find a more specific interception route prefetch entry, we check for a regular prefetch entry
const prefetchCacheKey = createPrefetchCacheKey(url);
const prefetchData = prefetchCache.get(prefetchCacheKey);
if (prefetchData) {
existingCacheEntry = prefetchData;
}
}
if (existingCacheEntry) {
// Grab the latest status of the cache entry and update it
existingCacheEntry.status = getPrefetchEntryCacheStatus(existingCacheEntry);
// when `kind` is provided, an explicit prefetch was requested.
// if the requested prefetch is "full" and the current cache entry wasn't, we want to re-prefetch with the new intent
const switchedToFullPrefetch = existingCacheEntry.kind !== PrefetchKind.FULL && kind === PrefetchKind.FULL;
if (switchedToFullPrefetch) {
return createLazyPrefetchEntry({
tree,
url,
buildId,
nextUrl,
prefetchCache,
// If we didn't get an explicit prefetch kind, we want to set a temporary kind
// rather than assuming the same intent as the previous entry, to be consistent with how we
// lazily create prefetch entries when intent is left unspecified.
kind: kind != null ? kind : PrefetchKind.TEMPORARY
});
}
// If the existing cache entry was marked as temporary, it means it was lazily created when attempting to get an entry,
// where we didn't have the prefetch intent. Now that we have the intent (in `kind`), we want to update the entry to the more accurate kind.
if (kind && existingCacheEntry.kind === PrefetchKind.TEMPORARY) {
existingCacheEntry.kind = kind;
}
// We've determined that the existing entry we found is still valid, so we return it.
return existingCacheEntry;
}
// If we didn't return an entry, create a new one.
return createLazyPrefetchEntry({
tree,
url,
buildId,
nextUrl,
prefetchCache,
kind: kind || // in dev, there's never gonna be a prefetch entry so we want to prefetch here
(process.env.NODE_ENV === "development" ? PrefetchKind.AUTO : PrefetchKind.TEMPORARY)
});
}
/*
* Used to take an existing cache entry and prefix it with the nextUrl, if it exists.
* This ensures that we don't have conflicting cache entries for the same URL (as is the case with route interception).
*/ function prefixExistingPrefetchCacheEntry(param) {
let { url, nextUrl, prefetchCache } = param;
const existingCacheKey = createPrefetchCacheKey(url);
const existingCacheEntry = prefetchCache.get(existingCacheKey);
if (!existingCacheEntry) {
// no-op -- there wasn't an entry to move
return;
}
const newCacheKey = createPrefetchCacheKey(url, nextUrl);
prefetchCache.set(newCacheKey, existingCacheEntry);
prefetchCache.delete(existingCacheKey);
}
/**
* Use to seed the prefetch cache with data that has already been fetched.
*/ export function createPrefetchCacheEntryForInitialLoad(param) {
let { nextUrl, tree, prefetchCache, url, kind, data } = param;
const [, , , intercept] = data;
// if the prefetch corresponds with an interception route, we use the nextUrl to prefix the cache key
const prefetchCacheKey = intercept ? createPrefetchCacheKey(url, nextUrl) : createPrefetchCacheKey(url);
const prefetchEntry = {
treeAtTimeOfPrefetch: tree,
data: Promise.resolve(data),
kind,
prefetchTime: Date.now(),
lastUsedTime: Date.now(),
key: prefetchCacheKey,
status: PrefetchCacheEntryStatus.fresh
};
prefetchCache.set(prefetchCacheKey, prefetchEntry);
return prefetchEntry;
}
/**
* Creates a prefetch entry entry and enqueues a fetch request to retrieve the data.
*/ function createLazyPrefetchEntry(param) {
let { url, kind, tree, nextUrl, buildId, prefetchCache } = param;
const prefetchCacheKey = createPrefetchCacheKey(url);
// initiates the fetch request for the prefetch and attaches a listener
// to the promise to update the prefetch cache entry when the promise resolves (if necessary)
const data = prefetchQueue.enqueue(()=>fetchServerResponse(url, tree, nextUrl, buildId, kind).then((prefetchResponse)=>{
// TODO: `fetchServerResponse` should be more tighly coupled to these prefetch cache operations
// to avoid drift between this cache key prefixing logic
// (which is currently directly influenced by the server response)
const [, , , intercepted] = prefetchResponse;
if (intercepted) {
prefixExistingPrefetchCacheEntry({
url,
nextUrl,
prefetchCache
});
}
return prefetchResponse;
}));
const prefetchEntry = {
treeAtTimeOfPrefetch: tree,
data,
kind,
prefetchTime: Date.now(),
lastUsedTime: null,
key: prefetchCacheKey,
status: PrefetchCacheEntryStatus.fresh
};
prefetchCache.set(prefetchCacheKey, prefetchEntry);
return prefetchEntry;
}
export function prunePrefetchCache(prefetchCache) {
for (const [href, prefetchCacheEntry] of prefetchCache){
if (getPrefetchEntryCacheStatus(prefetchCacheEntry) === PrefetchCacheEntryStatus.expired) {
prefetchCache.delete(href);
}
}
}
// These values are set by `define-env-plugin` (based on `nextConfig.experimental.staleTimes`)
// and default to 5 minutes (static) / 30 seconds (dynamic)
const DYNAMIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000;
const STATIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME) * 1000;
function getPrefetchEntryCacheStatus(param) {
let { kind, prefetchTime, lastUsedTime } = param;
// We will re-use the cache entry data for up to the `dynamic` staletime window.
if (Date.now() < (lastUsedTime != null ? lastUsedTime : prefetchTime) + DYNAMIC_STALETIME_MS) {
return lastUsedTime ? PrefetchCacheEntryStatus.reusable : PrefetchCacheEntryStatus.fresh;
}
// For "auto" prefetching, we'll re-use only the loading boundary for up to `static` staletime window.
// A stale entry will only re-use the `loading` boundary, not the full data.
// This will trigger a "lazy fetch" for the full data.
if (kind === "auto") {
if (Date.now() < prefetchTime + STATIC_STALETIME_MS) {
return PrefetchCacheEntryStatus.stale;
}
}
// for "full" prefetching, we'll re-use the cache entry data for up to `static` staletime window.
if (kind === "full") {
if (Date.now() < prefetchTime + STATIC_STALETIME_MS) {
return PrefetchCacheEntryStatus.reusable;
}
}
return PrefetchCacheEntryStatus.expired;
}
//# sourceMappingURL=prefetch-cache-utils.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/prefetch-cache-utils.ts"],"names":["createHrefFromUrl","fetchServerResponse","PrefetchCacheEntryStatus","PrefetchKind","prefetchQueue","createPrefetchCacheKey","url","nextUrl","pathnameFromUrl","getOrCreatePrefetchCacheEntry","tree","buildId","prefetchCache","kind","existingCacheEntry","undefined","interceptionCacheKey","interceptionData","get","prefetchCacheKey","prefetchData","status","getPrefetchEntryCacheStatus","switchedToFullPrefetch","FULL","createLazyPrefetchEntry","TEMPORARY","process","env","NODE_ENV","AUTO","prefixExistingPrefetchCacheEntry","existingCacheKey","newCacheKey","set","delete","createPrefetchCacheEntryForInitialLoad","data","intercept","prefetchEntry","treeAtTimeOfPrefetch","Promise","resolve","prefetchTime","Date","now","lastUsedTime","key","fresh","enqueue","then","prefetchResponse","intercepted","prunePrefetchCache","href","prefetchCacheEntry","expired","DYNAMIC_STALETIME_MS","Number","__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME","STATIC_STALETIME_MS","__NEXT_CLIENT_ROUTER_STATIC_STALETIME","reusable","stale"],"mappings":"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"}

View File

@ -0,0 +1,79 @@
import { fetchServerResponse } from "../fetch-server-response";
import { createHrefFromUrl } from "../create-href-from-url";
import { applyRouterStatePatchToTree } from "../apply-router-state-patch-to-tree";
import { isNavigatingToNewRootLayout } from "../is-navigating-to-new-root-layout";
import { handleExternalUrl } from "./navigate-reducer";
import { handleMutable } from "../handle-mutable";
import { applyFlightData } from "../apply-flight-data";
import { createEmptyCacheNode } from "../../app-router";
import { handleSegmentMismatch } from "../handle-segment-mismatch";
import { hasInterceptionRouteInCurrentTree } from "./has-interception-route-in-current-tree";
// A version of refresh reducer that keeps the cache around instead of wiping all of it.
function fastRefreshReducerImpl(state, action) {
const { origin } = action;
const mutable = {};
const href = state.canonicalUrl;
mutable.preserveCustomHistoryState = false;
const cache = createEmptyCacheNode();
// If the current tree was intercepted, the nextUrl should be included in the request.
// This is to ensure that the refresh request doesn't get intercepted, accidentally triggering the interception route.
const includeNextUrl = hasInterceptionRouteInCurrentTree(state.tree);
// TODO-APP: verify that `href` is not an external url.
// Fetch data from the root of the tree.
cache.lazyData = fetchServerResponse(new URL(href, origin), [
state.tree[0],
state.tree[1],
state.tree[2],
"refetch"
], includeNextUrl ? state.nextUrl : null, state.buildId);
return cache.lazyData.then((param)=>{
let [flightData, canonicalUrlOverride] = param;
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === "string") {
return handleExternalUrl(state, mutable, flightData, state.pushRef.pendingPush);
}
// Remove cache.lazyData as it has been resolved at this point.
cache.lazyData = null;
let currentTree = state.tree;
let currentCache = state.cache;
for (const flightDataPath of flightData){
// FlightDataPath with more than two items means unexpected Flight data was returned
if (flightDataPath.length !== 3) {
// TODO-APP: handle this case better
console.log("REFRESH FAILED");
return state;
}
// Given the path can only have two items the items are only the router state and rsc for the root.
const [treePatch] = flightDataPath;
const newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
[
""
], currentTree, treePatch, state.canonicalUrl);
if (newTree === null) {
return handleSegmentMismatch(state, action, treePatch);
}
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, href, state.pushRef.pendingPush);
}
const canonicalUrlOverrideHref = canonicalUrlOverride ? createHrefFromUrl(canonicalUrlOverride) : undefined;
if (canonicalUrlOverride) {
mutable.canonicalUrl = canonicalUrlOverrideHref;
}
const applied = applyFlightData(currentCache, cache, flightDataPath);
if (applied) {
mutable.cache = cache;
currentCache = cache;
}
mutable.patchedTree = newTree;
mutable.canonicalUrl = href;
currentTree = newTree;
}
return handleMutable(state, mutable);
}, ()=>state);
}
function fastRefreshReducerNoop(state, _action) {
return state;
}
export const fastRefreshReducer = process.env.NODE_ENV === "production" ? fastRefreshReducerNoop : fastRefreshReducerImpl;
//# sourceMappingURL=fast-refresh-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/fast-refresh-reducer.ts"],"names":["fetchServerResponse","createHrefFromUrl","applyRouterStatePatchToTree","isNavigatingToNewRootLayout","handleExternalUrl","handleMutable","applyFlightData","createEmptyCacheNode","handleSegmentMismatch","hasInterceptionRouteInCurrentTree","fastRefreshReducerImpl","state","action","origin","mutable","href","canonicalUrl","preserveCustomHistoryState","cache","includeNextUrl","tree","lazyData","URL","nextUrl","buildId","then","flightData","canonicalUrlOverride","pushRef","pendingPush","currentTree","currentCache","flightDataPath","length","console","log","treePatch","newTree","canonicalUrlOverrideHref","undefined","applied","patchedTree","fastRefreshReducerNoop","_action","fastRefreshReducer","process","env","NODE_ENV"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,2BAA0B;AAC9D,SAASC,iBAAiB,QAAQ,0BAAyB;AAC3D,SAASC,2BAA2B,QAAQ,sCAAqC;AACjF,SAASC,2BAA2B,QAAQ,sCAAqC;AAOjF,SAASC,iBAAiB,QAAQ,qBAAoB;AACtD,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,eAAe,QAAQ,uBAAsB;AAEtD,SAASC,oBAAoB,QAAQ,mBAAkB;AACvD,SAASC,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,iCAAiC,QAAQ,2CAA0C;AAE5F,wFAAwF;AACxF,SAASC,uBACPC,KAA2B,EAC3BC,MAAyB;IAEzB,MAAM,EAAEC,MAAM,EAAE,GAAGD;IACnB,MAAME,UAAmB,CAAC;IAC1B,MAAMC,OAAOJ,MAAMK,YAAY;IAE/BF,QAAQG,0BAA0B,GAAG;IAErC,MAAMC,QAAmBX;IACzB,sFAAsF;IACtF,sHAAsH;IACtH,MAAMY,iBAAiBV,kCAAkCE,MAAMS,IAAI;IAEnE,uDAAuD;IACvD,wCAAwC;IACxCF,MAAMG,QAAQ,GAAGrB,oBACf,IAAIsB,IAAIP,MAAMF,SACd;QAACF,MAAMS,IAAI,CAAC,EAAE;QAAET,MAAMS,IAAI,CAAC,EAAE;QAAET,MAAMS,IAAI,CAAC,EAAE;QAAE;KAAU,EACxDD,iBAAiBR,MAAMY,OAAO,GAAG,MACjCZ,MAAMa,OAAO;IAGf,OAAON,MAAMG,QAAQ,CAACI,IAAI,CACxB;YAAC,CAACC,YAAYC,qBAAqB;QACjC,4DAA4D;QAC5D,IAAI,OAAOD,eAAe,UAAU;YAClC,OAAOtB,kBACLO,OACAG,SACAY,YACAf,MAAMiB,OAAO,CAACC,WAAW;QAE7B;QAEA,+DAA+D;QAC/DX,MAAMG,QAAQ,GAAG;QAEjB,IAAIS,cAAcnB,MAAMS,IAAI;QAC5B,IAAIW,eAAepB,MAAMO,KAAK;QAE9B,KAAK,MAAMc,kBAAkBN,WAAY;YACvC,oFAAoF;YACpF,IAAIM,eAAeC,MAAM,KAAK,GAAG;gBAC/B,oCAAoC;gBACpCC,QAAQC,GAAG,CAAC;gBACZ,OAAOxB;YACT;YAEA,mGAAmG;YACnG,MAAM,CAACyB,UAAU,GAAGJ;YACpB,MAAMK,UAAUnC,4BACd,sBAAsB;YACtB;gBAAC;aAAG,EACJ4B,aACAM,WACAzB,MAAMK,YAAY;YAGpB,IAAIqB,YAAY,MAAM;gBACpB,OAAO7B,sBAAsBG,OAAOC,QAAQwB;YAC9C;YAEA,IAAIjC,4BAA4B2B,aAAaO,UAAU;gBACrD,OAAOjC,kBACLO,OACAG,SACAC,MACAJ,MAAMiB,OAAO,CAACC,WAAW;YAE7B;YAEA,MAAMS,2BAA2BX,uBAC7B1B,kBAAkB0B,wBAClBY;YAEJ,IAAIZ,sBAAsB;gBACxBb,QAAQE,YAAY,GAAGsB;YACzB;YACA,MAAME,UAAUlC,gBAAgByB,cAAcb,OAAOc;YAErD,IAAIQ,SAAS;gBACX1B,QAAQI,KAAK,GAAGA;gBAChBa,eAAeb;YACjB;YAEAJ,QAAQ2B,WAAW,GAAGJ;YACtBvB,QAAQE,YAAY,GAAGD;YAEvBe,cAAcO;QAChB;QACA,OAAOhC,cAAcM,OAAOG;IAC9B,GACA,IAAMH;AAEV;AAEA,SAAS+B,uBACP/B,KAA2B,EAC3BgC,OAA0B;IAE1B,OAAOhC;AACT;AAEA,OAAO,MAAMiC,qBACXC,QAAQC,GAAG,CAACC,QAAQ,KAAK,eACrBL,yBACAhC,uBAAsB"}

View File

@ -0,0 +1,33 @@
import { createRouterCacheKey } from "../create-router-cache-key";
export function findHeadInCache(cache, parallelRoutes) {
return findHeadInCacheImpl(cache, parallelRoutes, "");
}
function findHeadInCacheImpl(cache, parallelRoutes, keyPrefix) {
const isLastItem = Object.keys(parallelRoutes).length === 0;
if (isLastItem) {
// Returns the entire Cache Node of the segment whose head we will render.
return [
cache,
keyPrefix
];
}
for(const key in parallelRoutes){
const [segment, childParallelRoutes] = parallelRoutes[key];
const childSegmentMap = cache.parallelRoutes.get(key);
if (!childSegmentMap) {
continue;
}
const cacheKey = createRouterCacheKey(segment);
const cacheNode = childSegmentMap.get(cacheKey);
if (!cacheNode) {
continue;
}
const item = findHeadInCacheImpl(cacheNode, childParallelRoutes, keyPrefix + "/" + cacheKey);
if (item) {
return item;
}
}
return null;
}
//# sourceMappingURL=find-head-in-cache.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/find-head-in-cache.ts"],"names":["createRouterCacheKey","findHeadInCache","cache","parallelRoutes","findHeadInCacheImpl","keyPrefix","isLastItem","Object","keys","length","key","segment","childParallelRoutes","childSegmentMap","get","cacheKey","cacheNode","item"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,6BAA4B;AAEjE,OAAO,SAASC,gBACdC,KAAgB,EAChBC,cAAoC;IAEpC,OAAOC,oBAAoBF,OAAOC,gBAAgB;AACpD;AAEA,SAASC,oBACPF,KAAgB,EAChBC,cAAoC,EACpCE,SAAiB;IAEjB,MAAMC,aAAaC,OAAOC,IAAI,CAACL,gBAAgBM,MAAM,KAAK;IAC1D,IAAIH,YAAY;QACd,0EAA0E;QAC1E,OAAO;YAACJ;YAAOG;SAAU;IAC3B;IACA,IAAK,MAAMK,OAAOP,eAAgB;QAChC,MAAM,CAACQ,SAASC,oBAAoB,GAAGT,cAAc,CAACO,IAAI;QAC1D,MAAMG,kBAAkBX,MAAMC,cAAc,CAACW,GAAG,CAACJ;QACjD,IAAI,CAACG,iBAAiB;YACpB;QACF;QAEA,MAAME,WAAWf,qBAAqBW;QAEtC,MAAMK,YAAYH,gBAAgBC,GAAG,CAACC;QACtC,IAAI,CAACC,WAAW;YACd;QACF;QAEA,MAAMC,OAAOb,oBACXY,WACAJ,qBACAP,YAAY,MAAMU;QAEpB,IAAIE,MAAM;YACR,OAAOA;QACT;IACF;IAEA,OAAO;AACT"}

View File

@ -0,0 +1,5 @@
export function getSegmentValue(segment) {
return Array.isArray(segment) ? segment[1] : segment;
}
//# sourceMappingURL=get-segment-value.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/get-segment-value.ts"],"names":["getSegmentValue","segment","Array","isArray"],"mappings":"AAEA,OAAO,SAASA,gBAAgBC,OAAgB;IAC9C,OAAOC,MAAMC,OAAO,CAACF,WAAWA,OAAO,CAAC,EAAE,GAAGA;AAC/C"}

View File

@ -0,0 +1,23 @@
import { isInterceptionRouteAppPath } from "../../../../server/future/helpers/interception-routes";
export function hasInterceptionRouteInCurrentTree(param) {
let [segment, parallelRoutes] = param;
// If we have a dynamic segment, it's marked as an interception route by the presence of the `i` suffix.
if (Array.isArray(segment) && (segment[2] === "di" || segment[2] === "ci")) {
return true;
}
// If segment is not an array, apply the existing string-based check
if (typeof segment === "string" && isInterceptionRouteAppPath(segment)) {
return true;
}
// Iterate through parallelRoutes if they exist
if (parallelRoutes) {
for(const key in parallelRoutes){
if (hasInterceptionRouteInCurrentTree(parallelRoutes[key])) {
return true;
}
}
}
return false;
}
//# sourceMappingURL=has-interception-route-in-current-tree.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/has-interception-route-in-current-tree.ts"],"names":["isInterceptionRouteAppPath","hasInterceptionRouteInCurrentTree","segment","parallelRoutes","Array","isArray","key"],"mappings":"AACA,SAASA,0BAA0B,QAAQ,wDAAuD;AAElG,OAAO,SAASC,kCAAkC,KAG9B;IAH8B,IAAA,CAChDC,SACAC,eACkB,GAH8B;IAIhD,wGAAwG;IACxG,IAAIC,MAAMC,OAAO,CAACH,YAAaA,CAAAA,OAAO,CAAC,EAAE,KAAK,QAAQA,OAAO,CAAC,EAAE,KAAK,IAAG,GAAI;QAC1E,OAAO;IACT;IAEA,oEAAoE;IACpE,IAAI,OAAOA,YAAY,YAAYF,2BAA2BE,UAAU;QACtE,OAAO;IACT;IAEA,+CAA+C;IAC/C,IAAIC,gBAAgB;QAClB,IAAK,MAAMG,OAAOH,eAAgB;YAChC,IAAIF,kCAAkCE,cAAc,CAACG,IAAI,GAAG;gBAC1D,OAAO;YACT;QACF;IACF;IAEA,OAAO;AACT"}

View File

@ -0,0 +1,365 @@
import { fetchServerResponse } from "../fetch-server-response";
import { createHrefFromUrl } from "../create-href-from-url";
import { invalidateCacheBelowFlightSegmentPath } from "../invalidate-cache-below-flight-segmentpath";
import { applyRouterStatePatchToTree } from "../apply-router-state-patch-to-tree";
import { shouldHardNavigate } from "../should-hard-navigate";
import { isNavigatingToNewRootLayout } from "../is-navigating-to-new-root-layout";
import { PrefetchCacheEntryStatus } from "../router-reducer-types";
import { handleMutable } from "../handle-mutable";
import { applyFlightData } from "../apply-flight-data";
import { prefetchQueue } from "./prefetch-reducer";
import { createEmptyCacheNode } from "../../app-router";
import { DEFAULT_SEGMENT_KEY } from "../../../../shared/lib/segment";
import { listenForDynamicRequest, updateCacheNodeOnNavigation } from "../ppr-navigations";
import { getOrCreatePrefetchCacheEntry, prunePrefetchCache } from "../prefetch-cache-utils";
import { clearCacheNodeDataForSegmentPath } from "../clear-cache-node-data-for-segment-path";
export function handleExternalUrl(state, mutable, url, pendingPush) {
mutable.mpaNavigation = true;
mutable.canonicalUrl = url;
mutable.pendingPush = pendingPush;
mutable.scrollableSegments = undefined;
return handleMutable(state, mutable);
}
function generateSegmentsFromPatch(flightRouterPatch) {
const segments = [];
const [segment, parallelRoutes] = flightRouterPatch;
if (Object.keys(parallelRoutes).length === 0) {
return [
[
segment
]
];
}
for (const [parallelRouteKey, parallelRoute] of Object.entries(parallelRoutes)){
for (const childSegment of generateSegmentsFromPatch(parallelRoute)){
// If the segment is empty, it means we are at the root of the tree
if (segment === "") {
segments.push([
parallelRouteKey,
...childSegment
]);
} else {
segments.push([
segment,
parallelRouteKey,
...childSegment
]);
}
}
}
return segments;
}
function triggerLazyFetchForLeafSegments(newCache, currentCache, flightSegmentPath, treePatch) {
let appliedPatch = false;
newCache.rsc = currentCache.rsc;
newCache.prefetchRsc = currentCache.prefetchRsc;
newCache.loading = currentCache.loading;
newCache.parallelRoutes = new Map(currentCache.parallelRoutes);
const segmentPathsToFill = generateSegmentsFromPatch(treePatch).map((segment)=>[
...flightSegmentPath,
...segment
]);
for (const segmentPaths of segmentPathsToFill){
clearCacheNodeDataForSegmentPath(newCache, currentCache, segmentPaths);
appliedPatch = true;
}
return appliedPatch;
}
// These implementations are expected to diverge significantly, so I've forked
// the entire function. The one that's disabled should be dead code eliminated
// because the check here is statically inlined at build time.
export const navigateReducer = process.env.__NEXT_PPR ? navigateReducer_PPR : navigateReducer_noPPR;
// This is the implementation when PPR is disabled. We can assume its behavior
// is relatively stable because it's been running in production for a while.
function navigateReducer_noPPR(state, action) {
const { url, isExternalUrl, navigateType, shouldScroll } = action;
const mutable = {};
const { hash } = url;
const href = createHrefFromUrl(url);
const pendingPush = navigateType === "push";
// we want to prune the prefetch cache on every navigation to avoid it growing too large
prunePrefetchCache(state.prefetchCache);
mutable.preserveCustomHistoryState = false;
if (isExternalUrl) {
return handleExternalUrl(state, mutable, url.toString(), pendingPush);
}
const prefetchValues = getOrCreatePrefetchCacheEntry({
url,
nextUrl: state.nextUrl,
tree: state.tree,
buildId: state.buildId,
prefetchCache: state.prefetchCache
});
const { treeAtTimeOfPrefetch, data } = prefetchValues;
prefetchQueue.bump(data);
return data.then((param)=>{
let [flightData, canonicalUrlOverride] = param;
let isFirstRead = false;
// we only want to mark this once
if (!prefetchValues.lastUsedTime) {
// important: we should only mark the cache node as dirty after we unsuspend from the call above
prefetchValues.lastUsedTime = Date.now();
isFirstRead = true;
}
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === "string") {
return handleExternalUrl(state, mutable, flightData, pendingPush);
}
// Handles case where `<meta http-equiv="refresh">` tag is present,
// which will trigger an MPA navigation.
if (document.getElementById("__next-page-redirect")) {
return handleExternalUrl(state, mutable, href, pendingPush);
}
let currentTree = state.tree;
let currentCache = state.cache;
let scrollableSegments = [];
for (const flightDataPath of flightData){
const flightSegmentPath = flightDataPath.slice(0, -4);
// The one before last item is the router state tree patch
const treePatch = flightDataPath.slice(-3)[0];
// TODO-APP: remove ''
const flightSegmentPathWithLeadingEmpty = [
"",
...flightSegmentPath
];
// Create new tree based on the flightSegmentPath and router state patch
let newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, currentTree, treePatch, href);
// If the tree patch can't be applied to the current tree then we use the tree at time of prefetch
// TODO-APP: This should instead fill in the missing pieces in `currentTree` with the data from `treeAtTimeOfPrefetch`, then apply the patch.
if (newTree === null) {
newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, treeAtTimeOfPrefetch, treePatch, href);
}
if (newTree !== null) {
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, href, pendingPush);
}
const cache = createEmptyCacheNode();
let applied = false;
if (prefetchValues.status === PrefetchCacheEntryStatus.stale && !isFirstRead) {
// When we have a stale prefetch entry, we only want to re-use the loading state of the route we're navigating to, to support instant loading navigations
// this will trigger a lazy fetch for the actual page data by nulling the `rsc` and `prefetchRsc` values for page data,
// while copying over the `loading` for the segment that contains the page data.
// We only do this on subsequent reads, as otherwise there'd be no loading data to re-use.
applied = triggerLazyFetchForLeafSegments(cache, currentCache, flightSegmentPath, treePatch);
// since we re-used the stale cache's loading state & refreshed the data,
// update the `lastUsedTime` so that it can continue to be re-used for the next 30s
prefetchValues.lastUsedTime = Date.now();
} else {
applied = applyFlightData(currentCache, cache, flightDataPath, prefetchValues);
}
const hardNavigate = shouldHardNavigate(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, currentTree);
if (hardNavigate) {
// Copy rsc for the root node of the cache.
cache.rsc = currentCache.rsc;
cache.prefetchRsc = currentCache.prefetchRsc;
invalidateCacheBelowFlightSegmentPath(cache, currentCache, flightSegmentPath);
// Ensure the existing cache value is used when the cache was not invalidated.
mutable.cache = cache;
} else if (applied) {
mutable.cache = cache;
// If we applied the cache, we update the "current cache" value so any other
// segments in the FlightDataPath will be able to reference the updated cache.
currentCache = cache;
}
currentTree = newTree;
for (const subSegment of generateSegmentsFromPatch(treePatch)){
const scrollableSegmentPath = [
...flightSegmentPath,
...subSegment
];
// Filter out the __DEFAULT__ paths as they shouldn't be scrolled to in this case.
if (scrollableSegmentPath[scrollableSegmentPath.length - 1] !== DEFAULT_SEGMENT_KEY) {
scrollableSegments.push(scrollableSegmentPath);
}
}
}
}
mutable.patchedTree = currentTree;
mutable.canonicalUrl = canonicalUrlOverride ? createHrefFromUrl(canonicalUrlOverride) : href;
mutable.pendingPush = pendingPush;
mutable.scrollableSegments = scrollableSegments;
mutable.hashFragment = hash;
mutable.shouldScroll = shouldScroll;
return handleMutable(state, mutable);
}, ()=>state);
}
// This is the experimental PPR implementation. It's closer to the behavior we
// want, but is likelier to include accidental regressions because it rewrites
// existing functionality.
function navigateReducer_PPR(state, action) {
const { url, isExternalUrl, navigateType, shouldScroll } = action;
const mutable = {};
const { hash } = url;
const href = createHrefFromUrl(url);
const pendingPush = navigateType === "push";
// we want to prune the prefetch cache on every navigation to avoid it growing too large
prunePrefetchCache(state.prefetchCache);
mutable.preserveCustomHistoryState = false;
if (isExternalUrl) {
return handleExternalUrl(state, mutable, url.toString(), pendingPush);
}
const prefetchValues = getOrCreatePrefetchCacheEntry({
url,
nextUrl: state.nextUrl,
tree: state.tree,
buildId: state.buildId,
prefetchCache: state.prefetchCache
});
const { treeAtTimeOfPrefetch, data } = prefetchValues;
prefetchQueue.bump(data);
return data.then((param)=>{
let [flightData, canonicalUrlOverride, _postponed] = param;
let isFirstRead = false;
// we only want to mark this once
if (!prefetchValues.lastUsedTime) {
// important: we should only mark the cache node as dirty after we unsuspend from the call above
prefetchValues.lastUsedTime = Date.now();
isFirstRead = true;
}
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === "string") {
return handleExternalUrl(state, mutable, flightData, pendingPush);
}
// Handles case where `<meta http-equiv="refresh">` tag is present,
// which will trigger an MPA navigation.
if (document.getElementById("__next-page-redirect")) {
return handleExternalUrl(state, mutable, href, pendingPush);
}
let currentTree = state.tree;
let currentCache = state.cache;
let scrollableSegments = [];
// TODO: In practice, this is always a single item array. We probably
// aren't going to every send multiple segments, at least not in this
// format. So we could remove the extra wrapper for now until
// that settles.
for (const flightDataPath of flightData){
const flightSegmentPath = flightDataPath.slice(0, -4);
// The one before last item is the router state tree patch
const treePatch = flightDataPath.slice(-3)[0];
// TODO-APP: remove ''
const flightSegmentPathWithLeadingEmpty = [
"",
...flightSegmentPath
];
// Create new tree based on the flightSegmentPath and router state patch
let newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, currentTree, treePatch, href);
// If the tree patch can't be applied to the current tree then we use the tree at time of prefetch
// TODO-APP: This should instead fill in the missing pieces in `currentTree` with the data from `treeAtTimeOfPrefetch`, then apply the patch.
if (newTree === null) {
newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, treeAtTimeOfPrefetch, treePatch, href);
}
if (newTree !== null) {
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, href, pendingPush);
}
if (// This is just a paranoid check. When PPR is enabled, the server
// will always send back a static response that's rendered from
// the root. If for some reason it doesn't, we fall back to the
// non-PPR implementation.
// TODO: We should get rid of the else branch and do all navigations
// via updateCacheNodeOnNavigation. The current structure is just
// an incremental step.
flightDataPath.length === 3) {
const prefetchedTree = flightDataPath[0];
const seedData = flightDataPath[1];
const head = flightDataPath[2];
const task = updateCacheNodeOnNavigation(currentCache, currentTree, prefetchedTree, seedData, head);
if (task !== null && task.node !== null) {
// We've created a new Cache Node tree that contains a prefetched
// version of the next page. This can be rendered instantly.
// Use the tree computed by updateCacheNodeOnNavigation instead
// of the one computed by applyRouterStatePatchToTree.
// TODO: We should remove applyRouterStatePatchToTree
// from the PPR path entirely.
const patchedRouterState = task.route;
newTree = patchedRouterState;
const newCache = task.node;
// The prefetched tree has dynamic holes in it. We initiate a
// dynamic request to fill them in.
//
// Do not block on the result. We'll immediately render the Cache
// Node tree and suspend on the dynamic parts. When the request
// comes in, we'll fill in missing data and ping React to
// re-render. Unlike the lazy fetching model in the non-PPR
// implementation, this is modeled as a single React update +
// streaming, rather than multiple top-level updates. (However,
// even in the new model, we'll still need to sometimes update the
// root multiple times per navigation, like if the server sends us
// a different response than we expected. For now, we revert back
// to the lazy fetching mechanism in that case.)
listenForDynamicRequest(task, fetchServerResponse(url, currentTree, state.nextUrl, state.buildId));
mutable.cache = newCache;
} else {
// Nothing changed, so reuse the old cache.
// TODO: What if the head changed but not any of the segment data?
// Is that possible? If so, we should clone the whole tree and
// update the head.
newTree = prefetchedTree;
}
} else {
// The static response does not include any dynamic holes, so
// there's no need to do a second request.
// TODO: As an incremental step this just reverts back to the
// non-PPR implementation. We can simplify this branch further,
// given that PPR prefetches are always static and return the whole
// tree. Or in the meantime we could factor it out into a
// separate function.
const cache = createEmptyCacheNode();
let applied = false;
if (prefetchValues.status === PrefetchCacheEntryStatus.stale && !isFirstRead) {
// When we have a stale prefetch entry, we only want to re-use the loading state of the route we're navigating to, to support instant loading navigations
// this will trigger a lazy fetch for the actual page data by nulling the `rsc` and `prefetchRsc` values for page data,
// while copying over the `loading` for the segment that contains the page data.
// We only do this on subsequent reads, as otherwise there'd be no loading data to re-use.
applied = triggerLazyFetchForLeafSegments(cache, currentCache, flightSegmentPath, treePatch);
// since we re-used the stale cache's loading state & refreshed the data,
// update the `lastUsedTime` so that it can continue to be re-used for the next 30s
prefetchValues.lastUsedTime = Date.now();
} else {
applied = applyFlightData(currentCache, cache, flightDataPath, prefetchValues);
}
const hardNavigate = shouldHardNavigate(// TODO-APP: remove ''
flightSegmentPathWithLeadingEmpty, currentTree);
if (hardNavigate) {
// Copy rsc for the root node of the cache.
cache.rsc = currentCache.rsc;
cache.prefetchRsc = currentCache.prefetchRsc;
invalidateCacheBelowFlightSegmentPath(cache, currentCache, flightSegmentPath);
// Ensure the existing cache value is used when the cache was not invalidated.
mutable.cache = cache;
} else if (applied) {
mutable.cache = cache;
// If we applied the cache, we update the "current cache" value so any other
// segments in the FlightDataPath will be able to reference the updated cache.
currentCache = cache;
}
}
currentTree = newTree;
for (const subSegment of generateSegmentsFromPatch(treePatch)){
const scrollableSegmentPath = [
...flightSegmentPath,
...subSegment
];
// Filter out the __DEFAULT__ paths as they shouldn't be scrolled to in this case.
if (scrollableSegmentPath[scrollableSegmentPath.length - 1] !== DEFAULT_SEGMENT_KEY) {
scrollableSegments.push(scrollableSegmentPath);
}
}
}
}
mutable.patchedTree = currentTree;
mutable.canonicalUrl = canonicalUrlOverride ? createHrefFromUrl(canonicalUrlOverride) : href;
mutable.pendingPush = pendingPush;
mutable.scrollableSegments = scrollableSegments;
mutable.hashFragment = hash;
mutable.shouldScroll = shouldScroll;
return handleMutable(state, mutable);
}, ()=>state);
}
//# sourceMappingURL=navigate-reducer.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
import { NEXT_RSC_UNION_QUERY } from "../../app-router-headers";
import { PromiseQueue } from "../../promise-queue";
import { getOrCreatePrefetchCacheEntry, prunePrefetchCache } from "../prefetch-cache-utils";
export const prefetchQueue = new PromiseQueue(5);
export function prefetchReducer(state, action) {
// let's prune the prefetch cache before we do anything else
prunePrefetchCache(state.prefetchCache);
const { url } = action;
url.searchParams.delete(NEXT_RSC_UNION_QUERY);
getOrCreatePrefetchCacheEntry({
url,
nextUrl: state.nextUrl,
prefetchCache: state.prefetchCache,
kind: action.kind,
tree: state.tree,
buildId: state.buildId
});
return state;
}
//# sourceMappingURL=prefetch-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/prefetch-reducer.ts"],"names":["NEXT_RSC_UNION_QUERY","PromiseQueue","getOrCreatePrefetchCacheEntry","prunePrefetchCache","prefetchQueue","prefetchReducer","state","action","prefetchCache","url","searchParams","delete","nextUrl","kind","tree","buildId"],"mappings":"AAKA,SAASA,oBAAoB,QAAQ,2BAA0B;AAC/D,SAASC,YAAY,QAAQ,sBAAqB;AAClD,SACEC,6BAA6B,EAC7BC,kBAAkB,QACb,0BAAyB;AAEhC,OAAO,MAAMC,gBAAgB,IAAIH,aAAa,GAAE;AAEhD,OAAO,SAASI,gBACdC,KAA2B,EAC3BC,MAAsB;IAEtB,4DAA4D;IAC5DJ,mBAAmBG,MAAME,aAAa;IAEtC,MAAM,EAAEC,GAAG,EAAE,GAAGF;IAChBE,IAAIC,YAAY,CAACC,MAAM,CAACX;IAExBE,8BAA8B;QAC5BO;QACAG,SAASN,MAAMM,OAAO;QACtBJ,eAAeF,MAAME,aAAa;QAClCK,MAAMN,OAAOM,IAAI;QACjBC,MAAMR,MAAMQ,IAAI;QAChBC,SAAST,MAAMS,OAAO;IACxB;IAEA,OAAOT;AACT"}

View File

@ -0,0 +1,88 @@
import { fetchServerResponse } from "../fetch-server-response";
import { createHrefFromUrl } from "../create-href-from-url";
import { applyRouterStatePatchToTree } from "../apply-router-state-patch-to-tree";
import { isNavigatingToNewRootLayout } from "../is-navigating-to-new-root-layout";
import { handleExternalUrl } from "./navigate-reducer";
import { handleMutable } from "../handle-mutable";
import { fillLazyItemsTillLeafWithHead } from "../fill-lazy-items-till-leaf-with-head";
import { createEmptyCacheNode } from "../../app-router";
import { handleSegmentMismatch } from "../handle-segment-mismatch";
import { hasInterceptionRouteInCurrentTree } from "./has-interception-route-in-current-tree";
import { refreshInactiveParallelSegments } from "../refetch-inactive-parallel-segments";
export function refreshReducer(state, action) {
const { origin } = action;
const mutable = {};
const href = state.canonicalUrl;
let currentTree = state.tree;
mutable.preserveCustomHistoryState = false;
const cache = createEmptyCacheNode();
// If the current tree was intercepted, the nextUrl should be included in the request.
// This is to ensure that the refresh request doesn't get intercepted, accidentally triggering the interception route.
const includeNextUrl = hasInterceptionRouteInCurrentTree(state.tree);
// TODO-APP: verify that `href` is not an external url.
// Fetch data from the root of the tree.
cache.lazyData = fetchServerResponse(new URL(href, origin), [
currentTree[0],
currentTree[1],
currentTree[2],
"refetch"
], includeNextUrl ? state.nextUrl : null, state.buildId);
return cache.lazyData.then(async (param)=>{
let [flightData, canonicalUrlOverride] = param;
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === "string") {
return handleExternalUrl(state, mutable, flightData, state.pushRef.pendingPush);
}
// Remove cache.lazyData as it has been resolved at this point.
cache.lazyData = null;
for (const flightDataPath of flightData){
// FlightDataPath with more than two items means unexpected Flight data was returned
if (flightDataPath.length !== 3) {
// TODO-APP: handle this case better
console.log("REFRESH FAILED");
return state;
}
// Given the path can only have two items the items are only the router state and rsc for the root.
const [treePatch] = flightDataPath;
const newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
[
""
], currentTree, treePatch, state.canonicalUrl);
if (newTree === null) {
return handleSegmentMismatch(state, action, treePatch);
}
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, href, state.pushRef.pendingPush);
}
const canonicalUrlOverrideHref = canonicalUrlOverride ? createHrefFromUrl(canonicalUrlOverride) : undefined;
if (canonicalUrlOverride) {
mutable.canonicalUrl = canonicalUrlOverrideHref;
}
// The one before last item is the router state tree patch
const [cacheNodeSeedData, head] = flightDataPath.slice(-2);
// Handles case where prefetch only returns the router tree patch without rendered components.
if (cacheNodeSeedData !== null) {
const rsc = cacheNodeSeedData[2];
cache.rsc = rsc;
cache.prefetchRsc = null;
fillLazyItemsTillLeafWithHead(cache, // Existing cache is not passed in as `router.refresh()` has to invalidate the entire cache.
undefined, treePatch, cacheNodeSeedData, head);
mutable.prefetchCache = new Map();
}
await refreshInactiveParallelSegments({
state,
updatedTree: newTree,
updatedCache: cache,
includeNextUrl,
canonicalUrl: mutable.canonicalUrl || state.canonicalUrl
});
mutable.cache = cache;
mutable.patchedTree = newTree;
mutable.canonicalUrl = href;
currentTree = newTree;
}
return handleMutable(state, mutable);
}, ()=>state);
}
//# sourceMappingURL=refresh-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/refresh-reducer.ts"],"names":["fetchServerResponse","createHrefFromUrl","applyRouterStatePatchToTree","isNavigatingToNewRootLayout","handleExternalUrl","handleMutable","fillLazyItemsTillLeafWithHead","createEmptyCacheNode","handleSegmentMismatch","hasInterceptionRouteInCurrentTree","refreshInactiveParallelSegments","refreshReducer","state","action","origin","mutable","href","canonicalUrl","currentTree","tree","preserveCustomHistoryState","cache","includeNextUrl","lazyData","URL","nextUrl","buildId","then","flightData","canonicalUrlOverride","pushRef","pendingPush","flightDataPath","length","console","log","treePatch","newTree","canonicalUrlOverrideHref","undefined","cacheNodeSeedData","head","slice","rsc","prefetchRsc","prefetchCache","Map","updatedTree","updatedCache","patchedTree"],"mappings":"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"}

View File

@ -0,0 +1,39 @@
import { createHrefFromUrl } from "../create-href-from-url";
import { extractPathFromFlightRouterState } from "../compute-changed-path";
import { updateCacheNodeOnPopstateRestoration } from "../ppr-navigations";
export function restoreReducer(state, action) {
const { url, tree } = action;
const href = createHrefFromUrl(url);
// This action is used to restore the router state from the history state.
// However, it's possible that the history state no longer contains the `FlightRouterState`.
// We will copy over the internal state on pushState/replaceState events, but if a history entry
// occurred before hydration, or if the user navigated to a hash using a regular anchor link,
// the history state will not contain the `FlightRouterState`.
// In this case, we'll continue to use the existing tree so the router doesn't get into an invalid state.
const treeToRestore = tree || state.tree;
const oldCache = state.cache;
const newCache = process.env.__NEXT_PPR ? // data for any segment whose dynamic data was already received. This
// prevents an unnecessary flash back to PPR state during a
// back/forward navigation.
updateCacheNodeOnPopstateRestoration(oldCache, treeToRestore) : oldCache;
var _extractPathFromFlightRouterState;
return {
buildId: state.buildId,
// Set canonical url
canonicalUrl: href,
pushRef: {
pendingPush: false,
mpaNavigation: false,
// Ensures that the custom history state that was set is preserved when applying this update.
preserveCustomHistoryState: true
},
focusAndScrollRef: state.focusAndScrollRef,
cache: newCache,
prefetchCache: state.prefetchCache,
// Restore provided tree
tree: treeToRestore,
nextUrl: (_extractPathFromFlightRouterState = extractPathFromFlightRouterState(treeToRestore)) != null ? _extractPathFromFlightRouterState : url.pathname
};
}
//# sourceMappingURL=restore-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/restore-reducer.ts"],"names":["createHrefFromUrl","extractPathFromFlightRouterState","updateCacheNodeOnPopstateRestoration","restoreReducer","state","action","url","tree","href","treeToRestore","oldCache","cache","newCache","process","env","__NEXT_PPR","buildId","canonicalUrl","pushRef","pendingPush","mpaNavigation","preserveCustomHistoryState","focusAndScrollRef","prefetchCache","nextUrl","pathname"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0BAAyB;AAM3D,SAASC,gCAAgC,QAAQ,0BAAyB;AAC1E,SAASC,oCAAoC,QAAQ,qBAAoB;AAEzE,OAAO,SAASC,eACdC,KAA2B,EAC3BC,MAAqB;IAErB,MAAM,EAAEC,GAAG,EAAEC,IAAI,EAAE,GAAGF;IACtB,MAAMG,OAAOR,kBAAkBM;IAC/B,0EAA0E;IAC1E,4FAA4F;IAC5F,gGAAgG;IAChG,6FAA6F;IAC7F,8DAA8D;IAC9D,yGAAyG;IACzG,MAAMG,gBAAgBF,QAAQH,MAAMG,IAAI;IAExC,MAAMG,WAAWN,MAAMO,KAAK;IAC5B,MAAMC,WAAWC,QAAQC,GAAG,CAACC,UAAU,GAEnC,qEAAqE;IACrE,2DAA2D;IAC3D,2BAA2B;IAC3Bb,qCAAqCQ,UAAUD,iBAC/CC;QAiBOT;IAfX,OAAO;QACLe,SAASZ,MAAMY,OAAO;QACtB,oBAAoB;QACpBC,cAAcT;QACdU,SAAS;YACPC,aAAa;YACbC,eAAe;YACf,6FAA6F;YAC7FC,4BAA4B;QAC9B;QACAC,mBAAmBlB,MAAMkB,iBAAiB;QAC1CX,OAAOC;QACPW,eAAenB,MAAMmB,aAAa;QAClC,wBAAwB;QACxBhB,MAAME;QACNe,SAASvB,CAAAA,oCAAAA,iCAAiCQ,0BAAjCR,oCAAmDK,IAAImB,QAAQ;IAC1E;AACF"}

View File

@ -0,0 +1,175 @@
import { callServer } from "../../../app-call-server";
import { ACTION, NEXT_ROUTER_STATE_TREE, NEXT_URL, RSC_CONTENT_TYPE_HEADER } from "../../app-router-headers";
// // eslint-disable-next-line import/no-extraneous-dependencies
// import { createFromFetch } from 'react-server-dom-webpack/client'
// // eslint-disable-next-line import/no-extraneous-dependencies
// import { encodeReply } from 'react-server-dom-webpack/client'
const { createFromFetch, encodeReply } = !!process.env.NEXT_RUNTIME ? require("react-server-dom-webpack/client.edge") : require("react-server-dom-webpack/client");
import { addBasePath } from "../../../add-base-path";
import { createHrefFromUrl } from "../create-href-from-url";
import { handleExternalUrl } from "./navigate-reducer";
import { applyRouterStatePatchToTree } from "../apply-router-state-patch-to-tree";
import { isNavigatingToNewRootLayout } from "../is-navigating-to-new-root-layout";
import { handleMutable } from "../handle-mutable";
import { fillLazyItemsTillLeafWithHead } from "../fill-lazy-items-till-leaf-with-head";
import { createEmptyCacheNode } from "../../app-router";
import { hasInterceptionRouteInCurrentTree } from "./has-interception-route-in-current-tree";
import { handleSegmentMismatch } from "../handle-segment-mismatch";
import { refreshInactiveParallelSegments } from "../refetch-inactive-parallel-segments";
async function fetchServerAction(state, nextUrl, param) {
let { actionId, actionArgs } = param;
const body = await encodeReply(actionArgs);
const res = await fetch("", {
method: "POST",
headers: {
Accept: RSC_CONTENT_TYPE_HEADER,
[ACTION]: actionId,
[NEXT_ROUTER_STATE_TREE]: encodeURIComponent(JSON.stringify(state.tree)),
...process.env.NEXT_DEPLOYMENT_ID ? {
"x-deployment-id": process.env.NEXT_DEPLOYMENT_ID
} : {},
...nextUrl ? {
[NEXT_URL]: nextUrl
} : {}
},
body
});
const location = res.headers.get("x-action-redirect");
let revalidatedParts;
try {
const revalidatedHeader = JSON.parse(res.headers.get("x-action-revalidated") || "[[],0,0]");
revalidatedParts = {
paths: revalidatedHeader[0] || [],
tag: !!revalidatedHeader[1],
cookie: revalidatedHeader[2]
};
} catch (e) {
revalidatedParts = {
paths: [],
tag: false,
cookie: false
};
}
const redirectLocation = location ? new URL(addBasePath(location), // Ensure relative redirects in Server Actions work, e.g. redirect('./somewhere-else')
new URL(state.canonicalUrl, window.location.href)) : undefined;
let isFlightResponse = res.headers.get("content-type") === RSC_CONTENT_TYPE_HEADER;
if (isFlightResponse) {
const response = await createFromFetch(Promise.resolve(res), {
callServer
});
if (location) {
// if it was a redirection, then result is just a regular RSC payload
const [, actionFlightData] = response != null ? response : [];
return {
actionFlightData: actionFlightData,
redirectLocation,
revalidatedParts
};
}
// otherwise it's a tuple of [actionResult, actionFlightData]
const [actionResult, [, actionFlightData]] = response != null ? response : [];
return {
actionResult,
actionFlightData,
redirectLocation,
revalidatedParts
};
}
return {
redirectLocation,
revalidatedParts
};
}
/*
* This reducer is responsible for calling the server action and processing any side-effects from the server action.
* It does not mutate the state by itself but rather delegates to other reducers to do the actual mutation.
*/ export function serverActionReducer(state, action) {
const { resolve, reject } = action;
const mutable = {};
const href = state.canonicalUrl;
let currentTree = state.tree;
mutable.preserveCustomHistoryState = false;
// only pass along the `nextUrl` param (used for interception routes) if the current route was intercepted.
// If the route has been intercepted, the action should be as well.
// Otherwise the server action might be intercepted with the wrong action id
// (ie, one that corresponds with the intercepted route)
const nextUrl = state.nextUrl && hasInterceptionRouteInCurrentTree(state.tree) ? state.nextUrl : null;
mutable.inFlightServerAction = fetchServerAction(state, nextUrl, action);
return mutable.inFlightServerAction.then(async (param)=>{
let { actionResult, actionFlightData: flightData, redirectLocation } = param;
// Make sure the redirection is a push instead of a replace.
// Issue: https://github.com/vercel/next.js/issues/53911
if (redirectLocation) {
state.pushRef.pendingPush = true;
mutable.pendingPush = true;
}
if (!flightData) {
resolve(actionResult);
// If there is a redirect but no flight data we need to do a mpaNavigation.
if (redirectLocation) {
return handleExternalUrl(state, mutable, redirectLocation.href, state.pushRef.pendingPush);
}
return state;
}
if (typeof flightData === "string") {
// Handle case when navigating to page in `pages` from `app`
return handleExternalUrl(state, mutable, flightData, state.pushRef.pendingPush);
}
// Remove cache.data as it has been resolved at this point.
mutable.inFlightServerAction = null;
if (redirectLocation) {
const newHref = createHrefFromUrl(redirectLocation, false);
mutable.canonicalUrl = newHref;
}
for (const flightDataPath of flightData){
// FlightDataPath with more than two items means unexpected Flight data was returned
if (flightDataPath.length !== 3) {
// TODO-APP: handle this case better
console.log("SERVER ACTION APPLY FAILED");
return state;
}
// Given the path can only have two items the items are only the router state and rsc for the root.
const [treePatch] = flightDataPath;
const newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
[
""
], currentTree, treePatch, redirectLocation ? createHrefFromUrl(redirectLocation) : state.canonicalUrl);
if (newTree === null) {
return handleSegmentMismatch(state, action, treePatch);
}
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, href, state.pushRef.pendingPush);
}
// The one before last item is the router state tree patch
const [cacheNodeSeedData, head] = flightDataPath.slice(-2);
const rsc = cacheNodeSeedData !== null ? cacheNodeSeedData[2] : null;
// Handles case where prefetch only returns the router tree patch without rendered components.
if (rsc !== null) {
const cache = createEmptyCacheNode();
cache.rsc = rsc;
cache.prefetchRsc = null;
fillLazyItemsTillLeafWithHead(cache, // Existing cache is not passed in as `router.refresh()` has to invalidate the entire cache.
undefined, treePatch, cacheNodeSeedData, head);
await refreshInactiveParallelSegments({
state,
updatedTree: newTree,
updatedCache: cache,
includeNextUrl: Boolean(nextUrl),
canonicalUrl: mutable.canonicalUrl || state.canonicalUrl
});
mutable.cache = cache;
mutable.prefetchCache = new Map();
}
mutable.patchedTree = newTree;
currentTree = newTree;
}
resolve(actionResult);
return handleMutable(state, mutable);
}, (e)=>{
// When the server action is rejected we don't update the state and instead call the reject handler of the promise.
reject(e);
return state;
});
}
//# sourceMappingURL=server-action-reducer.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,49 @@
import { createHrefFromUrl } from "../create-href-from-url";
import { applyRouterStatePatchToTree } from "../apply-router-state-patch-to-tree";
import { isNavigatingToNewRootLayout } from "../is-navigating-to-new-root-layout";
import { handleExternalUrl } from "./navigate-reducer";
import { applyFlightData } from "../apply-flight-data";
import { handleMutable } from "../handle-mutable";
import { createEmptyCacheNode } from "../../app-router";
import { handleSegmentMismatch } from "../handle-segment-mismatch";
export function serverPatchReducer(state, action) {
const { serverResponse } = action;
const [flightData, overrideCanonicalUrl] = serverResponse;
const mutable = {};
mutable.preserveCustomHistoryState = false;
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === "string") {
return handleExternalUrl(state, mutable, flightData, state.pushRef.pendingPush);
}
let currentTree = state.tree;
let currentCache = state.cache;
for (const flightDataPath of flightData){
// Slices off the last segment (which is at -4) as it doesn't exist in the tree yet
const flightSegmentPath = flightDataPath.slice(0, -4);
const [treePatch] = flightDataPath.slice(-3, -2);
const newTree = applyRouterStatePatchToTree(// TODO-APP: remove ''
[
"",
...flightSegmentPath
], currentTree, treePatch, state.canonicalUrl);
if (newTree === null) {
return handleSegmentMismatch(state, action, treePatch);
}
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
return handleExternalUrl(state, mutable, state.canonicalUrl, state.pushRef.pendingPush);
}
const canonicalUrlOverrideHref = overrideCanonicalUrl ? createHrefFromUrl(overrideCanonicalUrl) : undefined;
if (canonicalUrlOverrideHref) {
mutable.canonicalUrl = canonicalUrlOverrideHref;
}
const cache = createEmptyCacheNode();
applyFlightData(currentCache, cache, flightDataPath);
mutable.patchedTree = newTree;
mutable.cache = cache;
currentCache = cache;
currentTree = newTree;
}
return handleMutable(state, mutable);
}
//# sourceMappingURL=server-patch-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/server-patch-reducer.ts"],"names":["createHrefFromUrl","applyRouterStatePatchToTree","isNavigatingToNewRootLayout","handleExternalUrl","applyFlightData","handleMutable","createEmptyCacheNode","handleSegmentMismatch","serverPatchReducer","state","action","serverResponse","flightData","overrideCanonicalUrl","mutable","preserveCustomHistoryState","pushRef","pendingPush","currentTree","tree","currentCache","cache","flightDataPath","flightSegmentPath","slice","treePatch","newTree","canonicalUrl","canonicalUrlOverrideHref","undefined","patchedTree"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0BAAyB;AAC3D,SAASC,2BAA2B,QAAQ,sCAAqC;AACjF,SAASC,2BAA2B,QAAQ,sCAAqC;AAOjF,SAASC,iBAAiB,QAAQ,qBAAoB;AACtD,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,aAAa,QAAQ,oBAAmB;AAEjD,SAASC,oBAAoB,QAAQ,mBAAkB;AACvD,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,OAAO,SAASC,mBACdC,KAA2B,EAC3BC,MAAyB;IAEzB,MAAM,EAAEC,cAAc,EAAE,GAAGD;IAC3B,MAAM,CAACE,YAAYC,qBAAqB,GAAGF;IAE3C,MAAMG,UAAmB,CAAC;IAE1BA,QAAQC,0BAA0B,GAAG;IAErC,4DAA4D;IAC5D,IAAI,OAAOH,eAAe,UAAU;QAClC,OAAOT,kBACLM,OACAK,SACAF,YACAH,MAAMO,OAAO,CAACC,WAAW;IAE7B;IAEA,IAAIC,cAAcT,MAAMU,IAAI;IAC5B,IAAIC,eAAeX,MAAMY,KAAK;IAE9B,KAAK,MAAMC,kBAAkBV,WAAY;QACvC,mFAAmF;QACnF,MAAMW,oBAAoBD,eAAeE,KAAK,CAAC,GAAG,CAAC;QAEnD,MAAM,CAACC,UAAU,GAAGH,eAAeE,KAAK,CAAC,CAAC,GAAG,CAAC;QAC9C,MAAME,UAAUzB,4BACd,sBAAsB;QACtB;YAAC;eAAOsB;SAAkB,EAC1BL,aACAO,WACAhB,MAAMkB,YAAY;QAGpB,IAAID,YAAY,MAAM;YACpB,OAAOnB,sBAAsBE,OAAOC,QAAQe;QAC9C;QAEA,IAAIvB,4BAA4BgB,aAAaQ,UAAU;YACrD,OAAOvB,kBACLM,OACAK,SACAL,MAAMkB,YAAY,EAClBlB,MAAMO,OAAO,CAACC,WAAW;QAE7B;QAEA,MAAMW,2BAA2Bf,uBAC7Bb,kBAAkBa,wBAClBgB;QAEJ,IAAID,0BAA0B;YAC5Bd,QAAQa,YAAY,GAAGC;QACzB;QAEA,MAAMP,QAAmBf;QACzBF,gBAAgBgB,cAAcC,OAAOC;QAErCR,QAAQgB,WAAW,GAAGJ;QACtBZ,QAAQO,KAAK,GAAGA;QAEhBD,eAAeC;QACfH,cAAcQ;IAChB;IAEA,OAAOrB,cAAcI,OAAOK;AAC9B"}

View File

@ -0,0 +1,88 @@
import { applyFlightData } from "./apply-flight-data";
import { fetchServerResponse } from "./fetch-server-response";
import { PAGE_SEGMENT_KEY } from "../../../shared/lib/segment";
/**
* Refreshes inactive segments that are still in the current FlightRouterState.
* A segment is considered "inactive" when the server response indicates it didn't match to a page component.
* This happens during a soft-navigation, where the server will want to patch in the segment
* with the "default" component, but we explicitly ignore the server in this case
* and keep the existing state for that segment. New data for inactive segments are inherently
* not part of the server response when we patch the tree, because they were associated with a response
* from an earlier navigation/request. For each segment, once it becomes "active", we encode the URL that provided
* the data for it. This function traverses parallel routes looking for these markers so that it can re-fetch
* and patch the new data into the tree.
*/ export async function refreshInactiveParallelSegments(options) {
const fetchedSegments = new Set();
await refreshInactiveParallelSegmentsImpl({
...options,
rootTree: options.updatedTree,
fetchedSegments
});
}
async function refreshInactiveParallelSegmentsImpl(param) {
let { state, updatedTree, updatedCache, includeNextUrl, fetchedSegments, rootTree = updatedTree, canonicalUrl } = param;
const [, parallelRoutes, refetchPath, refetchMarker] = updatedTree;
const fetchPromises = [];
if (refetchPath && refetchPath !== canonicalUrl && refetchMarker === "refresh" && // it's possible for the tree to contain multiple segments that contain data at the same URL
// we keep track of them so we can dedupe the requests
!fetchedSegments.has(refetchPath)) {
fetchedSegments.add(refetchPath) // Mark this URL as fetched
;
// Eagerly kick off the fetch for the refetch path & the parallel routes. This should be fine to do as they each operate
// independently on their own cache nodes, and `applyFlightData` will copy anything it doesn't care about from the existing cache.
const fetchPromise = fetchServerResponse(new URL(refetchPath, location.origin), // refetch from the root of the updated tree, otherwise it will be scoped to the current segment
// and might not contain the data we need to patch in interception route data (such as dynamic params from a previous segment)
[
rootTree[0],
rootTree[1],
rootTree[2],
"refetch"
], includeNextUrl ? state.nextUrl : null, state.buildId).then((fetchResponse)=>{
const flightData = fetchResponse[0];
if (typeof flightData !== "string") {
for (const flightDataPath of flightData){
// we only pass the new cache as this function is called after clearing the router cache
// and filling in the new page data from the server. Meaning the existing cache is actually the cache that's
// just been created & has been written to, but hasn't been "committed" yet.
applyFlightData(updatedCache, updatedCache, flightDataPath);
}
} else {
// When flightData is a string, it suggests that the server response should have triggered an MPA navigation
// I'm not 100% sure of this decision, but it seems unlikely that we'd want to introduce a redirect side effect
// when refreshing on-screen data, so handling this has been ommitted.
}
});
fetchPromises.push(fetchPromise);
}
for(const key in parallelRoutes){
const parallelFetchPromise = refreshInactiveParallelSegmentsImpl({
state,
updatedTree: parallelRoutes[key],
updatedCache,
includeNextUrl,
fetchedSegments,
rootTree,
canonicalUrl
});
fetchPromises.push(parallelFetchPromise);
}
await Promise.all(fetchPromises);
}
/**
* Walks the current parallel segments to determine if they are "active".
* An active parallel route will have a `__PAGE__` segment in the FlightRouterState.
* As opposed to a `__DEFAULT__` segment, which means there was no match for that parallel route.
* We add a special marker here so that we know how to refresh its data when the router is revalidated.
*/ export function addRefreshMarkerToActiveParallelSegments(tree, path) {
const [segment, parallelRoutes, , refetchMarker] = tree;
// a page segment might also contain concatenated search params, so we do a partial match on the key
if (segment.includes(PAGE_SEGMENT_KEY) && refetchMarker !== "refresh") {
tree[2] = path;
tree[3] = "refresh";
}
for(const key in parallelRoutes){
addRefreshMarkerToActiveParallelSegments(parallelRoutes[key], path);
}
}
//# sourceMappingURL=refetch-inactive-parallel-segments.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/refetch-inactive-parallel-segments.ts"],"names":["applyFlightData","fetchServerResponse","PAGE_SEGMENT_KEY","refreshInactiveParallelSegments","options","fetchedSegments","Set","refreshInactiveParallelSegmentsImpl","rootTree","updatedTree","state","updatedCache","includeNextUrl","canonicalUrl","parallelRoutes","refetchPath","refetchMarker","fetchPromises","has","add","fetchPromise","URL","location","origin","nextUrl","buildId","then","fetchResponse","flightData","flightDataPath","push","key","parallelFetchPromise","Promise","all","addRefreshMarkerToActiveParallelSegments","tree","path","segment","includes"],"mappings":"AAGA,SAASA,eAAe,QAAQ,sBAAqB;AACrD,SAASC,mBAAmB,QAAQ,0BAAyB;AAC7D,SAASC,gBAAgB,QAAQ,8BAA6B;AAU9D;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,gCACpBC,OAAwC;IAExC,MAAMC,kBAAkB,IAAIC;IAC5B,MAAMC,oCAAoC;QACxC,GAAGH,OAAO;QACVI,UAAUJ,QAAQK,WAAW;QAC7BJ;IACF;AACF;AAEA,eAAeE,oCAAoC,KAWlD;IAXkD,IAAA,EACjDG,KAAK,EACLD,WAAW,EACXE,YAAY,EACZC,cAAc,EACdP,eAAe,EACfG,WAAWC,WAAW,EACtBI,YAAY,EAIb,GAXkD;IAYjD,MAAM,GAAGC,gBAAgBC,aAAaC,cAAc,GAAGP;IACvD,MAAMQ,gBAAgB,EAAE;IAExB,IACEF,eACAA,gBAAgBF,gBAChBG,kBAAkB,aAClB,4FAA4F;IAC5F,sDAAsD;IACtD,CAACX,gBAAgBa,GAAG,CAACH,cACrB;QACAV,gBAAgBc,GAAG,CAACJ,aAAa,2BAA2B;;QAE5D,wHAAwH;QACxH,kIAAkI;QAClI,MAAMK,eAAenB,oBACnB,IAAIoB,IAAIN,aAAaO,SAASC,MAAM,GACpC,gGAAgG;QAChG,8HAA8H;QAC9H;YAACf,QAAQ,CAAC,EAAE;YAAEA,QAAQ,CAAC,EAAE;YAAEA,QAAQ,CAAC,EAAE;YAAE;SAAU,EAClDI,iBAAiBF,MAAMc,OAAO,GAAG,MACjCd,MAAMe,OAAO,EACbC,IAAI,CAAC,CAACC;YACN,MAAMC,aAAaD,aAAa,CAAC,EAAE;YACnC,IAAI,OAAOC,eAAe,UAAU;gBAClC,KAAK,MAAMC,kBAAkBD,WAAY;oBACvC,wFAAwF;oBACxF,4GAA4G;oBAC5G,4EAA4E;oBAC5E5B,gBAAgBW,cAAcA,cAAckB;gBAC9C;YACF,OAAO;YACL,4GAA4G;YAC5G,+GAA+G;YAC/G,sEAAsE;YACxE;QACF;QAEAZ,cAAca,IAAI,CAACV;IACrB;IAEA,IAAK,MAAMW,OAAOjB,eAAgB;QAChC,MAAMkB,uBAAuBzB,oCAAoC;YAC/DG;YACAD,aAAaK,cAAc,CAACiB,IAAI;YAChCpB;YACAC;YACAP;YACAG;YACAK;QACF;QAEAI,cAAca,IAAI,CAACE;IACrB;IAEA,MAAMC,QAAQC,GAAG,CAACjB;AACpB;AAEA;;;;;CAKC,GACD,OAAO,SAASkB,yCACdC,IAAuB,EACvBC,IAAY;IAEZ,MAAM,CAACC,SAASxB,kBAAkBE,cAAc,GAAGoB;IACnD,oGAAoG;IACpG,IAAIE,QAAQC,QAAQ,CAACrC,qBAAqBc,kBAAkB,WAAW;QACrEoB,IAAI,CAAC,EAAE,GAAGC;QACVD,IAAI,CAAC,EAAE,GAAG;IACZ;IAEA,IAAK,MAAML,OAAOjB,eAAgB;QAChCqB,yCAAyCrB,cAAc,CAACiB,IAAI,EAAEM;IAChE;AACF"}

View File

@ -0,0 +1,29 @@
export const ACTION_REFRESH = "refresh";
export const ACTION_NAVIGATE = "navigate";
export const ACTION_RESTORE = "restore";
export const ACTION_SERVER_PATCH = "server-patch";
export const ACTION_PREFETCH = "prefetch";
export const ACTION_FAST_REFRESH = "fast-refresh";
export const ACTION_SERVER_ACTION = "server-action";
export var PrefetchKind;
(function(PrefetchKind) {
PrefetchKind["AUTO"] = "auto";
PrefetchKind["FULL"] = "full";
PrefetchKind["TEMPORARY"] = "temporary";
})(PrefetchKind || (PrefetchKind = {}));
export var PrefetchCacheEntryStatus;
(function(PrefetchCacheEntryStatus) {
PrefetchCacheEntryStatus["fresh"] = "fresh";
PrefetchCacheEntryStatus["reusable"] = "reusable";
PrefetchCacheEntryStatus["expired"] = "expired";
PrefetchCacheEntryStatus["stale"] = "stale";
})(PrefetchCacheEntryStatus || (PrefetchCacheEntryStatus = {}));
export function isThenable(value) {
// TODO: We don't gain anything from this abstraction. It's unsound, and only
// makes sense in the specific places where we use it. So it's better to keep
// the type coercion inline, instead of leaking this to other places in
// the codebase.
return value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
}
//# sourceMappingURL=router-reducer-types.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/router-reducer-types.ts"],"names":["ACTION_REFRESH","ACTION_NAVIGATE","ACTION_RESTORE","ACTION_SERVER_PATCH","ACTION_PREFETCH","ACTION_FAST_REFRESH","ACTION_SERVER_ACTION","PrefetchKind","PrefetchCacheEntryStatus","isThenable","value","then"],"mappings":"AAOA,OAAO,MAAMA,iBAAiB,UAAS;AACvC,OAAO,MAAMC,kBAAkB,WAAU;AACzC,OAAO,MAAMC,iBAAiB,UAAS;AACvC,OAAO,MAAMC,sBAAsB,eAAc;AACjD,OAAO,MAAMC,kBAAkB,WAAU;AACzC,OAAO,MAAMC,sBAAsB,eAAc;AACjD,OAAO,MAAMC,uBAAuB,gBAAe;;UAuIvCC;;;;GAAAA,iBAAAA;;UA8DAC;;;;;GAAAA,6BAAAA;AA+DZ,OAAO,SAASC,WAAWC,KAAU;IACnC,6EAA6E;IAC7E,6EAA6E;IAC7E,uEAAuE;IACvE,gBAAgB;IAChB,OACEA,SACC,CAAA,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAS,KACxD,OAAOA,MAAMC,IAAI,KAAK;AAE1B"}

View File

@ -0,0 +1,52 @@
import { ACTION_NAVIGATE, ACTION_SERVER_PATCH, ACTION_RESTORE, ACTION_REFRESH, ACTION_PREFETCH, ACTION_FAST_REFRESH, ACTION_SERVER_ACTION } from "./router-reducer-types";
import { navigateReducer } from "./reducers/navigate-reducer";
import { serverPatchReducer } from "./reducers/server-patch-reducer";
import { restoreReducer } from "./reducers/restore-reducer";
import { refreshReducer } from "./reducers/refresh-reducer";
import { prefetchReducer } from "./reducers/prefetch-reducer";
import { fastRefreshReducer } from "./reducers/fast-refresh-reducer";
import { serverActionReducer } from "./reducers/server-action-reducer";
/**
* Reducer that handles the app-router state updates.
*/ function clientReducer(state, action) {
switch(action.type){
case ACTION_NAVIGATE:
{
return navigateReducer(state, action);
}
case ACTION_SERVER_PATCH:
{
return serverPatchReducer(state, action);
}
case ACTION_RESTORE:
{
return restoreReducer(state, action);
}
case ACTION_REFRESH:
{
return refreshReducer(state, action);
}
case ACTION_FAST_REFRESH:
{
return fastRefreshReducer(state, action);
}
case ACTION_PREFETCH:
{
return prefetchReducer(state, action);
}
case ACTION_SERVER_ACTION:
{
return serverActionReducer(state, action);
}
// This case should never be hit as dispatch is strongly typed.
default:
throw new Error("Unknown action");
}
}
function serverReducer(state, _action) {
return state;
}
// we don't run the client reducer on the server, so we use a noop function for better tree shaking
export const reducer = typeof window === "undefined" ? serverReducer : clientReducer;
//# sourceMappingURL=router-reducer.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/router-reducer.ts"],"names":["ACTION_NAVIGATE","ACTION_SERVER_PATCH","ACTION_RESTORE","ACTION_REFRESH","ACTION_PREFETCH","ACTION_FAST_REFRESH","ACTION_SERVER_ACTION","navigateReducer","serverPatchReducer","restoreReducer","refreshReducer","prefetchReducer","fastRefreshReducer","serverActionReducer","clientReducer","state","action","type","Error","serverReducer","_action","reducer","window"],"mappings":"AAAA,SACEA,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,cAAc,EACdC,eAAe,EACfC,mBAAmB,EACnBC,oBAAoB,QACf,yBAAwB;AAM/B,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,kBAAkB,QAAQ,kCAAiC;AACpE,SAASC,cAAc,QAAQ,6BAA4B;AAC3D,SAASC,cAAc,QAAQ,6BAA4B;AAC3D,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,kBAAkB,QAAQ,kCAAiC;AACpE,SAASC,mBAAmB,QAAQ,mCAAkC;AAEtE;;CAEC,GACD,SAASC,cACPC,KAA2B,EAC3BC,MAAsB;IAEtB,OAAQA,OAAOC,IAAI;QACjB,KAAKjB;YAAiB;gBACpB,OAAOO,gBAAgBQ,OAAOC;YAChC;QACA,KAAKf;YAAqB;gBACxB,OAAOO,mBAAmBO,OAAOC;YACnC;QACA,KAAKd;YAAgB;gBACnB,OAAOO,eAAeM,OAAOC;YAC/B;QACA,KAAKb;YAAgB;gBACnB,OAAOO,eAAeK,OAAOC;YAC/B;QACA,KAAKX;YAAqB;gBACxB,OAAOO,mBAAmBG,OAAOC;YACnC;QACA,KAAKZ;YAAiB;gBACpB,OAAOO,gBAAgBI,OAAOC;YAChC;QACA,KAAKV;YAAsB;gBACzB,OAAOO,oBAAoBE,OAAOC;YACpC;QACA,+DAA+D;QAC/D;YACE,MAAM,IAAIE,MAAM;IACpB;AACF;AAEA,SAASC,cACPJ,KAA2B,EAC3BK,OAAuB;IAEvB,OAAOL;AACT;AAEA,mGAAmG;AACnG,OAAO,MAAMM,UACX,OAAOC,WAAW,cAAcH,gBAAgBL,cAAa"}

View File

@ -0,0 +1,23 @@
import { matchSegment } from "../match-segments";
// TODO-APP: flightSegmentPath will be empty in case of static response, needs to be handled.
export function shouldHardNavigate(flightSegmentPath, flightRouterState) {
const [segment, parallelRoutes] = flightRouterState;
// TODO-APP: Check if `as` can be replaced.
const [currentSegment, parallelRouteKey] = flightSegmentPath;
// Check if current segment matches the existing segment.
if (!matchSegment(currentSegment, segment)) {
// If dynamic parameter in tree doesn't match up with segment path a hard navigation is triggered.
if (Array.isArray(currentSegment)) {
return true;
}
// If the existing segment did not match soft navigation is triggered.
return false;
}
const lastSegment = flightSegmentPath.length <= 2;
if (lastSegment) {
return false;
}
return shouldHardNavigate(flightSegmentPath.slice(2), parallelRoutes[parallelRouteKey]);
}
//# sourceMappingURL=should-hard-navigate.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/should-hard-navigate.ts"],"names":["matchSegment","shouldHardNavigate","flightSegmentPath","flightRouterState","segment","parallelRoutes","currentSegment","parallelRouteKey","Array","isArray","lastSegment","length","slice"],"mappings":"AAKA,SAASA,YAAY,QAAQ,oBAAmB;AAEhD,6FAA6F;AAC7F,OAAO,SAASC,mBACdC,iBAAiC,EACjCC,iBAAoC;IAEpC,MAAM,CAACC,SAASC,eAAe,GAAGF;IAClC,2CAA2C;IAC3C,MAAM,CAACG,gBAAgBC,iBAAiB,GAAGL;IAK3C,yDAAyD;IACzD,IAAI,CAACF,aAAaM,gBAAgBF,UAAU;QAC1C,kGAAkG;QAClG,IAAII,MAAMC,OAAO,CAACH,iBAAiB;YACjC,OAAO;QACT;QAEA,sEAAsE;QACtE,OAAO;IACT;IACA,MAAMI,cAAcR,kBAAkBS,MAAM,IAAI;IAEhD,IAAID,aAAa;QACf,OAAO;IACT;IAEA,OAAOT,mBACLC,kBAAkBU,KAAK,CAAC,IACxBP,cAAc,CAACE,iBAAiB;AAEpC"}