UNPKG

next

Version:

The React Framework

597 lines (595 loc) 31.6 kB
"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