Spaces:
Running
Running
File size: 31,638 Bytes
c592d77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 | "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
completeHardNavigation: null,
completeSoftNavigation: null,
completeTraverseNavigation: null,
convertServerPatchToFullTree: null,
navigate: null,
navigateToKnownRoute: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
completeHardNavigation: function() {
return completeHardNavigation;
},
completeSoftNavigation: function() {
return completeSoftNavigation;
},
completeTraverseNavigation: function() {
return completeTraverseNavigation;
},
convertServerPatchToFullTree: function() {
return convertServerPatchToFullTree;
},
navigate: function() {
return navigate;
},
navigateToKnownRoute: function() {
return navigateToKnownRoute;
}
});
const _fetchserverresponse = require("../router-reducer/fetch-server-response");
const _pprnavigations = require("../router-reducer/ppr-navigations");
const _createhreffromurl = require("../router-reducer/create-href-from-url");
const _constants = require("../../../lib/constants");
const _cache = require("./cache");
const _optimisticroutes = require("./optimistic-routes");
const _cachekey = require("./cache-key");
const _scheduler = require("./scheduler");
const _types = require("./types");
const _links = require("../links");
const _routerreducertypes = require("../router-reducer/router-reducer-types");
const _computechangedpath = require("../router-reducer/compute-changed-path");
const _javascripturl = require("../../lib/javascript-url");
const _bfcache = require("./bfcache");
function navigate(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType) {
// Instant Navigation Testing API: when the lock is active, ensure a
// prefetch task has been initiated before proceeding with the navigation.
// This guarantees that segment data requests are at least pending, even
// for routes that already have a cached route tree. Without this, the
// static shell might be incomplete because some segments were never
// requested.
if (process.env.__NEXT_EXPOSE_TESTING_API) {
const { isNavigationLocked } = require('./navigation-testing-lock');
if (isNavigationLocked()) {
return ensurePrefetchThenNavigate(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType);
}
}
return navigateImpl(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType);
}
function navigateImpl(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType) {
const now = Date.now();
const href = url.href;
const cacheKey = (0, _cachekey.createCacheKey)(href, nextUrl);
const route = (0, _cache.readRouteCacheEntry)(now, cacheKey);
if (route !== null && route.status === _cache.EntryStatus.Fulfilled) {
// We have a matching prefetch.
return navigateUsingPrefetchedRouteTree(now, state, url, currentUrl, currentRenderedSearch, nextUrl, currentCacheNode, currentFlightRouterState, freshnessPolicy, scrollBehavior, navigateType, route);
}
// There was no matching route tree in the cache. Let's see if we can
// construct an "optimistic" route tree using the deprecated search-params
// based matching. This is only used when the new optimisticRouting flag is
// disabled.
//
// Do not construct an optimistic route tree if there was a cache hit, but
// the entry has a rejected status, since it may have been rejected due to a
// rewrite or redirect based on the search params.
//
// TODO: There are multiple reasons a prefetch might be rejected; we should
// track them explicitly and choose what to do here based on that.
if (!process.env.__NEXT_OPTIMISTIC_ROUTING) {
if (route === null || route.status !== _cache.EntryStatus.Rejected) {
const optimisticRoute = (0, _cache.deprecated_requestOptimisticRouteCacheEntry)(now, url, nextUrl);
if (optimisticRoute !== null) {
// We have an optimistic route tree. Proceed with the normal flow.
return navigateUsingPrefetchedRouteTree(now, state, url, currentUrl, currentRenderedSearch, nextUrl, currentCacheNode, currentFlightRouterState, freshnessPolicy, scrollBehavior, navigateType, optimisticRoute);
}
}
}
// There's no matching prefetch for this route in the cache. We must lazily
// fetch it from the server before we can perform the navigation.
//
// TODO: If this is a gesture navigation, instead of performing a
// dynamic request, we should do a runtime prefetch.
return navigateToUnknownRoute(now, state, url, currentUrl, currentRenderedSearch, nextUrl, currentCacheNode, currentFlightRouterState, freshnessPolicy, scrollBehavior, navigateType).catch(()=>{
// If the navigation fails, return the current state
return state;
});
}
function navigateToKnownRoute(now, state, url, canonicalUrl, navigationSeed, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, freshnessPolicy, nextUrl, scrollBehavior, navigateType, debugInfo, // The route cache entry used for this navigation, if it came from route
// prediction. Passed through so it can be marked as having a dynamic rewrite
// if the server returns a different pathname (indicating dynamic rewrite
// behavior).
//
// When null, the navigation did not use route prediction - either because
// the route was already fully cached, or it's a navigation that doesn't
// involve prediction (refresh, history traversal, server action, etc.).
// In these cases, if a mismatch occurs, we still mark the route as having a
// dynamic rewrite by traversing the known route tree (see
// dispatchRetryDueToTreeMismatch).
routeCacheEntry) {
// A version of navigate() that accepts the target route tree as an argument
// rather than reading it from the prefetch cache.
const accumulation = {
separateRefreshUrls: null,
scrollRef: null
};
// We special case navigations to the exact same URL as the current location.
// It's a common UI pattern for apps to refresh when you click a link to the
// current page. So when this happens, we refresh the dynamic data in the page
// segments.
//
// Note that this does not apply if the any part of the hash or search query
// has changed. This might feel a bit weird but it makes more sense when you
// consider that the way to trigger this behavior is to click the same link
// multiple times.
//
// TODO: We should probably refresh the *entire* route when this case occurs,
// not just the page segments. Essentially treating it the same as a refresh()
// triggered by an action, which is the more explicit way of modeling the UI
// pattern described above.
//
// Also note that this only refreshes the dynamic data, not static/ cached
// data. If the page segment is fully static and prefetched, the request is
// skipped. (This is also how refresh() works.)
const isSamePageNavigation = url.href === currentUrl.href;
const task = (0, _pprnavigations.startPPRNavigation)(now, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, navigationSeed.routeTree, navigationSeed.metadataVaryPath, freshnessPolicy, navigationSeed.data, navigationSeed.head, navigationSeed.dynamicStaleAt, isSamePageNavigation, accumulation);
if (task !== null) {
if (freshnessPolicy !== _pprnavigations.FreshnessPolicy.Gesture) {
(0, _pprnavigations.spawnDynamicRequests)(task, url, nextUrl, freshnessPolicy, accumulation, routeCacheEntry, navigateType);
}
return completeSoftNavigation(state, url, nextUrl, task.route, task.node, navigationSeed.renderedSearch, canonicalUrl, navigateType, scrollBehavior, accumulation.scrollRef, debugInfo);
}
// Could not perform a SPA navigation. Revert to a full-page (MPA) navigation.
return completeHardNavigation(state, url, navigateType);
}
function navigateUsingPrefetchedRouteTree(now, state, url, currentUrl, currentRenderedSearch, nextUrl, currentCacheNode, currentFlightRouterState, freshnessPolicy, scrollBehavior, navigateType, route) {
const routeTree = route.tree;
const canonicalUrl = route.canonicalUrl + url.hash;
const renderedSearch = route.renderedSearch;
const prefetchSeed = {
renderedSearch,
routeTree,
metadataVaryPath: route.metadata.varyPath,
data: null,
head: null,
dynamicStaleAt: (0, _bfcache.computeDynamicStaleAt)(now, _bfcache.UnknownDynamicStaleTime)
};
return navigateToKnownRoute(now, state, url, canonicalUrl, prefetchSeed, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, freshnessPolicy, nextUrl, scrollBehavior, navigateType, null, route);
}
// Used to request all the dynamic data for a route, rather than just a subset,
// e.g. during a refresh or a revalidation. Typically this gets constructed
// during the normal flow when diffing the route tree, but for an unprefetched
// navigation, where we don't know the structure of the target route, we use
// this instead.
const DynamicRequestTreeForEntireRoute = [
'',
{},
null,
'refetch'
];
async function navigateToUnknownRoute(now, state, url, currentUrl, currentRenderedSearch, nextUrl, currentCacheNode, currentFlightRouterState, freshnessPolicy, scrollBehavior, navigateType) {
// Runs when a navigation happens but there's no cached prefetch we can use.
// Don't bother to wait for a prefetch response; go straight to a full
// navigation that contains both static and dynamic data in a single stream.
// (This is unlike the old navigation implementation, which instead blocks
// the dynamic request until a prefetch request is received.)
//
// To avoid duplication of logic, we're going to pretend that the tree
// returned by the dynamic request is, in fact, a prefetch tree. Then we can
// use the same server response to write the actual data into the CacheNode
// tree. So it's the same flow as the "happy path" (prefetch, then
// navigation), except we use a single server response for both stages.
let dynamicRequestTree;
switch(freshnessPolicy){
case _pprnavigations.FreshnessPolicy.Default:
case _pprnavigations.FreshnessPolicy.HistoryTraversal:
case _pprnavigations.FreshnessPolicy.Gesture:
dynamicRequestTree = currentFlightRouterState;
break;
case _pprnavigations.FreshnessPolicy.Hydration:
case _pprnavigations.FreshnessPolicy.RefreshAll:
case _pprnavigations.FreshnessPolicy.HMRRefresh:
dynamicRequestTree = DynamicRequestTreeForEntireRoute;
break;
default:
freshnessPolicy;
dynamicRequestTree = currentFlightRouterState;
break;
}
const promiseForDynamicServerResponse = (0, _fetchserverresponse.fetchServerResponse)(url, {
flightRouterState: dynamicRequestTree,
nextUrl
});
const result = await promiseForDynamicServerResponse;
if (typeof result === 'string') {
// This is an MPA navigation.
const redirectUrl = new URL(result, location.origin);
return completeHardNavigation(state, redirectUrl, navigateType);
}
const { flightData, canonicalUrl, renderedSearch, couldBeIntercepted, supportsPerSegmentPrefetching, dynamicStaleTime, staticStageData, runtimePrefetchStream, responseHeaders, debugInfo } = result;
// Since the response format of dynamic requests and prefetches is slightly
// different, we'll need to massage the data a bit. Create FlightRouterState
// tree that simulates what we'd receive as the result of a prefetch.
const navigationSeed = convertServerPatchToFullTree(now, currentFlightRouterState, flightData, renderedSearch, dynamicStaleTime);
// Learn the route pattern so we can predict it for future navigations.
// hasDynamicRewrite is false because this is a fresh navigation to an
// unknown route - any rewrite detection happens during the traversal inside
// discoverKnownRoute. The hasDynamicRewrite param is only set to true when
// retrying after a tree mismatch (see dispatchRetryDueToTreeMismatch).
const metadataVaryPath = navigationSeed.metadataVaryPath;
if (metadataVaryPath !== null) {
(0, _optimisticroutes.discoverKnownRoute)(now, url.pathname, nextUrl, null, navigationSeed.routeTree, metadataVaryPath, couldBeIntercepted, (0, _createhreffromurl.createHrefFromUrl)(canonicalUrl), supportsPerSegmentPrefetching, false // hasDynamicRewrite - not a retry, rewrite detection happens during traversal
);
if (staticStageData !== null) {
const { response: staticStageResponse, isResponsePartial } = staticStageData;
// Write the static stage of the response into the segment cache so that
// subsequent navigations can serve cached static segments instantly.
(0, _cache.getStaleAt)(now, staticStageResponse.s).then((staleAt)=>{
const buildId = responseHeaders.get(_constants.NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? staticStageResponse.b;
(0, _cache.writeStaticStageResponseIntoCache)(now, staticStageResponse.f, buildId, staticStageResponse.h, staleAt, currentFlightRouterState, renderedSearch, isResponsePartial);
}).catch(()=>{
// The static stage processing failed. Not fatal — the navigation
// completed normally, we just won't write into the cache.
});
}
if (runtimePrefetchStream !== null) {
(0, _cache.processRuntimePrefetchStream)(now, runtimePrefetchStream, currentFlightRouterState, renderedSearch).then((processed)=>{
if (processed !== null) {
(0, _cache.writeDynamicRenderResponseIntoCache)(now, _types.FetchStrategy.PPRRuntime, processed.flightDatas, processed.buildId, processed.isResponsePartial, processed.headVaryParams, processed.staleAt, processed.navigationSeed, null);
}
}).catch(()=>{
// The runtime prefetch cache write failed. Not fatal — the
// navigation completed normally, we just won't cache runtime data.
});
}
}
return navigateToKnownRoute(now, state, url, (0, _createhreffromurl.createHrefFromUrl)(canonicalUrl), navigationSeed, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, freshnessPolicy, nextUrl, scrollBehavior, navigateType, debugInfo, // Unknown route navigations don't use route prediction - the route tree
// came directly from the server. If a mismatch occurs during dynamic data
// fetch, the retry handler will traverse the known route tree to mark the
// entry as having a dynamic rewrite.
null);
}
function completeHardNavigation(state, url, navigateType) {
if ((0, _javascripturl.isJavaScriptURLString)(url.href)) {
console.error('Next.js has blocked a javascript: URL as a security precaution.');
return state;
}
const newState = {
canonicalUrl: url.origin === location.origin ? (0, _createhreffromurl.createHrefFromUrl)(url) : url.href,
pushRef: {
pendingPush: navigateType === 'push',
mpaNavigation: true,
preserveCustomHistoryState: false
},
// TODO: None of the rest of these values are consistent with the incoming
// navigation. We rely on the fact that AppRouter will suspend and trigger
// a hard navigation before it accesses any of these values. But instead
// we should trigger the hard navigation and blocking any subsequent
// router updates without updating React.
renderedSearch: state.renderedSearch,
focusAndScrollRef: state.focusAndScrollRef,
cache: state.cache,
tree: state.tree,
nextUrl: state.nextUrl,
previousNextUrl: state.previousNextUrl,
debugInfo: null
};
return newState;
}
function completeSoftNavigation(oldState, url, referringNextUrl, tree, cache, renderedSearch, canonicalUrl, navigateType, scrollBehavior, scrollRef, collectedDebugInfo) {
// The "Next-Url" is a special representation of the URL that Next.js
// uses to implement interception routes.
// TODO: Get rid of this extra traversal by computing this during the
// same traversal that computes the tree itself. We should also figure out
// what is the minimum information needed for the server to correctly
// intercept the route.
const changedPath = (0, _computechangedpath.computeChangedPath)(oldState.tree, tree);
const nextUrlForNewRoute = changedPath ? changedPath : oldState.nextUrl;
// This value is stored on the state as `previousNextUrl`; the naming is
// confusing. What it represents is the "Next-Url" header that was used to
// fetch the incoming route. It's essentially the refererer URL, but in a
// Next.js specific format. During refreshes, this is sent back to the server
// instead of the current route's "Next-Url" so that the same interception
// logic is applied as during the original navigation.
const previousNextUrl = referringNextUrl;
// Check if the only thing that changed was the hash fragment.
const oldUrl = new URL(oldState.canonicalUrl, url);
const onlyHashChange = // We don't need to compare the origins, because client-driven
// navigations are always same-origin.
url.pathname === oldUrl.pathname && url.search === oldUrl.search && url.hash !== oldUrl.hash;
// Determine whether and how the page should scroll after this
// navigation.
//
// By default, we scroll to the segments that were navigated to — i.e.
// segments in the new part of the route, as opposed to shared segments
// that were already part of the previous route. All newly navigated
// segments share a single ScrollRef. When they mount, the first one
// to mount initiates the scroll. They share a ref so that only one
// scroll happens per navigation.
//
// If a subsequent navigation produces new segments, those supersede
// any pending scroll from the previous navigation by invalidating its
// ScrollRef. If a navigation doesn't produce any new segments (e.g.
// a refresh where the route structure didn't change), any pending
// scrolls from previous navigations are unaffected.
//
// The branches below handle special cases layered on top of this
// default model.
let activeScrollRef;
let forceScroll;
if (scrollBehavior === _routerreducertypes.ScrollBehavior.NoScroll) {
// The user explicitly opted out of scrolling (e.g. scroll={false}
// on a Link or router.push).
//
// If this navigation created new scroll targets (scrollRef !== null),
// neutralize them. If it didn't, any prior scroll targets carried
// forward on the cache nodes via reuseSharedCacheNode remain active.
if (scrollRef !== null) {
scrollRef.current = false;
}
activeScrollRef = oldState.focusAndScrollRef.scrollRef;
forceScroll = false;
} else if (onlyHashChange) {
// Hash-only navigations should scroll regardless of per-node state.
// Create a fresh ref so the first segment to scroll consumes it.
//
// Invalidate any scroll ref from a prior navigation that hasn't
// been consumed yet.
const oldScrollRef = oldState.focusAndScrollRef.scrollRef;
if (oldScrollRef !== null) {
oldScrollRef.current = false;
}
// Also invalidate any per-node refs that were accumulated during
// this navigation's tree construction — the hash-only ref
// supersedes them.
if (scrollRef !== null) {
scrollRef.current = false;
}
activeScrollRef = {
current: true
};
forceScroll = true;
} else {
// Default case. Use the accumulated scrollRef (may be null if no
// new segments were created). The handler checks per-node refs, so
// unchanged parallel route slots won't scroll.
activeScrollRef = scrollRef;
// If this navigation created new scroll targets, invalidate any
// pending scroll from a previous navigation.
if (scrollRef !== null) {
const oldScrollRef = oldState.focusAndScrollRef.scrollRef;
if (oldScrollRef !== null) {
oldScrollRef.current = false;
}
}
forceScroll = false;
}
const newState = {
canonicalUrl,
renderedSearch,
pushRef: {
pendingPush: navigateType === 'push',
mpaNavigation: false,
preserveCustomHistoryState: false
},
focusAndScrollRef: {
scrollRef: activeScrollRef,
forceScroll,
onlyHashChange,
hashFragment: // Remove leading # and decode hash to make non-latin hashes work.
//
// Empty hash should trigger default behavior of scrolling layout into
// view. #top is handled in layout-router.
//
// Refer to `ScrollAndFocusHandler` for details on how this is used.
scrollBehavior !== _routerreducertypes.ScrollBehavior.NoScroll && url.hash !== '' ? decodeURIComponent(url.hash.slice(1)) : oldState.focusAndScrollRef.hashFragment
},
cache,
tree,
nextUrl: nextUrlForNewRoute,
previousNextUrl,
debugInfo: collectedDebugInfo
};
return newState;
}
function completeTraverseNavigation(state, url, renderedSearch, cache, tree, nextUrl) {
return {
// Set canonical url
canonicalUrl: (0, _createhreffromurl.createHrefFromUrl)(url),
renderedSearch,
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,
// Restore provided tree
tree,
nextUrl,
// TODO: We need to restore previousNextUrl, too, which represents the
// Next-Url that was used to fetch the data. Anywhere we fetch using the
// canonical URL, there should be a corresponding Next-Url.
previousNextUrl: null,
debugInfo: null
};
}
function convertServerPatchToFullTree(now, currentTree, flightData, renderedSearch, dynamicStaleTimeSeconds) {
// During a client navigation or prefetch, the server sends back only a patch
// for the parts of the tree that have changed.
//
// This applies the patch to the base tree to create a full representation of
// the resulting tree.
//
// The return type includes a full FlightRouterState tree and a full
// CacheNodeSeedData tree. (Conceptually these are the same tree, and should
// eventually be unified, but there's still lots of existing code that
// operates on FlightRouterState trees alone without the CacheNodeSeedData.)
//
// TODO: This similar to what apply-router-state-patch-to-tree does. It
// will eventually fully replace it. We should get rid of all the remaining
// places where we iterate over the server patch format. This should also
// eventually replace normalizeFlightData.
let baseTree = currentTree;
let baseData = null;
let head = null;
if (flightData !== null) {
for (const { segmentPath, tree: treePatch, seedData: dataPatch, head: headPatch } of flightData){
const result = convertServerPatchToFullTreeImpl(baseTree, baseData, treePatch, dataPatch, segmentPath, renderedSearch, 0);
baseTree = result.tree;
baseData = result.data;
// This is the same for all patches per response, so just pick an
// arbitrary one
head = headPatch;
}
}
const finalFlightRouterState = baseTree;
// Convert the final FlightRouterState into a RouteTree type.
//
// TODO: Eventually, FlightRouterState will evolve to being a transport format
// only. The RouteTree type will become the main type used for dealing with
// routes on the client, and we'll store it in the state directly.
const acc = {
metadataVaryPath: null
};
const routeTree = (0, _cache.convertRootFlightRouterStateToRouteTree)(finalFlightRouterState, renderedSearch, acc);
return {
routeTree,
metadataVaryPath: acc.metadataVaryPath,
data: baseData,
renderedSearch,
head,
dynamicStaleAt: (0, _bfcache.computeDynamicStaleAt)(now, dynamicStaleTimeSeconds)
};
}
function convertServerPatchToFullTreeImpl(baseRouterState, baseData, treePatch, dataPatch, segmentPath, renderedSearch, index) {
if (index === segmentPath.length) {
// We reached the part of the tree that we need to patch.
return {
tree: treePatch,
data: dataPatch
};
}
// segmentPath represents the parent path of subtree. It's a repeating
// pattern of parallel route key and segment:
//
// [string, Segment, string, Segment, string, Segment, ...]
//
// This path tells us which part of the base tree to apply the tree patch.
//
// NOTE: We receive the FlightRouterState patch in the same request as the
// seed data patch. Therefore we don't need to worry about diffing the segment
// values; we can assume the server sent us a correct result.
const updatedParallelRouteKey = segmentPath[index];
// const segment: Segment = segmentPath[index + 1] <-- Not used, see note above
const baseTreeChildren = baseRouterState[1];
const baseSeedDataChildren = baseData !== null ? baseData[1] : null;
const newTreeChildren = {};
const newSeedDataChildren = {};
for(const parallelRouteKey in baseTreeChildren){
const childBaseRouterState = baseTreeChildren[parallelRouteKey];
const childBaseSeedData = baseSeedDataChildren !== null ? baseSeedDataChildren[parallelRouteKey] ?? null : null;
if (parallelRouteKey === updatedParallelRouteKey) {
const result = convertServerPatchToFullTreeImpl(childBaseRouterState, childBaseSeedData, treePatch, dataPatch, segmentPath, renderedSearch, // Advance the index by two and keep cloning until we reach
// the end of the segment path.
index + 2);
newTreeChildren[parallelRouteKey] = result.tree;
newSeedDataChildren[parallelRouteKey] = result.data;
} else {
// This child is not being patched. Copy it over as-is.
newTreeChildren[parallelRouteKey] = childBaseRouterState;
newSeedDataChildren[parallelRouteKey] = childBaseSeedData;
}
}
let clonedTree;
let clonedSeedData;
// Clone all the fields except the children.
// Clone the FlightRouterState tree. 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.
clonedTree = [
baseRouterState[0],
newTreeChildren
];
if (2 in baseRouterState) {
const compressedRefreshState = baseRouterState[2];
if (compressedRefreshState !== undefined && compressedRefreshState !== null) {
// Since this part of the tree was patched with new data, any parent
// refresh states should be updated to reflect the new rendered search
// value. (The refresh state acts like a "context provider".) All pages
// within the same server response share the same renderedSearch value,
// but the same RouteTree could be composed from multiple different
// routes, and multiple responses.
clonedTree[2] = [
compressedRefreshState[0],
renderedSearch
];
}
}
if (3 in baseRouterState) {
clonedTree[3] = baseRouterState[3];
}
if (4 in baseRouterState) {
clonedTree[4] = baseRouterState[4];
}
// Clone the CacheNodeSeedData tree.
const isEmptySeedDataPartial = true;
clonedSeedData = [
null,
newSeedDataChildren,
null,
isEmptySeedDataPartial,
null
];
return {
tree: clonedTree,
data: clonedSeedData
};
}
/**
* Instant Navigation Testing API: ensures a prefetch task has been initiated
* and completed before proceeding with the navigation. This guarantees that
* segment data requests are at least pending, even for routes whose route
* tree is already cached.
*
* After the prefetch completes, delegates to the normal navigation flow.
*/ async function ensurePrefetchThenNavigate(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType) {
const link = (0, _links.getLinkForCurrentNavigation)();
const fetchStrategy = link !== null ? link.fetchStrategy : _types.FetchStrategy.PPR;
// Transition the cookie to captured-SPA immediately, before waiting
// for the prefetch. This ensures the devtools panel can update its UI
// right away, even if the prefetch takes time (e.g. dev compilation).
// The "to" tree starts as null and is filled in after the prefetch
// resolves and the navigation produces a new router state.
const { transitionToCapturedSPA, updateCapturedSPAToTree } = require('./navigation-testing-lock');
transitionToCapturedSPA(currentFlightRouterState, null);
const cacheKey = (0, _cachekey.createCacheKey)(url.href, nextUrl);
await new Promise((resolve)=>{
(0, _scheduler.schedulePrefetchTask)(cacheKey, currentFlightRouterState, fetchStrategy, _types.PrefetchPriority.Default, null, resolve // _onComplete callback
);
});
// Prefetch is complete. Proceed with the normal navigation flow, which
// will now find the route in the cache.
const result = await navigateImpl(state, url, currentUrl, currentRenderedSearch, currentCacheNode, currentFlightRouterState, nextUrl, freshnessPolicy, scrollBehavior, navigateType);
// Update the cookie with the resolved "to" tree so the devtools
// panel can display both routes immediately.
updateCapturedSPAToTree(currentFlightRouterState, result.tree);
return result;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=navigation.js.map |