UNPKG

next

Version:

The React Framework

177 lines (176 loc) • 8.58 kB
import { addSearchParamsIfPageSegment, PAGE_SEGMENT_KEY } from '../../../shared/lib/segment'; import { createEmptyCacheNode } from '../app-router'; import { applyRouterStatePatchToTree } from './apply-router-state-patch-to-tree'; import { createHrefFromUrl } from './create-href-from-url'; import { createRouterCacheKey } from './create-router-cache-key'; import { fillCacheWithNewSubTreeDataButOnlyLoading } from './fill-cache-with-new-subtree-data'; import { handleMutable } from './handle-mutable'; /** * This is a stop-gap until per-segment caching is implemented. It leverages the `aliased` flag that is added * to prefetch entries when it's determined that the loading state from that entry should be used for this navigation. * This function takes the aliased entry and only applies the loading state to the updated cache node. * We should remove this once per-segment fetching is implemented as ideally the prefetch cache will contain a * more granular segment map and so the router will be able to simply re-use the loading segment for the new navigation. */ export function handleAliasedPrefetchEntry(navigatedAt, state, flightData, url, mutable) { let currentTree = state.tree; let currentCache = state.cache; const href = createHrefFromUrl(url); let applied; if (typeof flightData === 'string') { return false; } for (const normalizedFlightData of flightData){ // If the segment doesn't have a loading component, we don't need to do anything. if (!hasLoadingComponentInSeedData(normalizedFlightData.seedData)) { continue; } let treePatch = normalizedFlightData.tree; // Segments are keyed by searchParams (e.g. __PAGE__?{"foo":"bar"}). We might return a less specific, param-less entry, // so we ensure that the final tree contains the correct searchParams (reflected in the URL) are provided in the updated FlightRouterState tree. // We only do this on the first read, as otherwise we'd be overwriting the searchParams that may have already been set treePatch = addSearchParamsToPageSegments(treePatch, Object.fromEntries(url.searchParams)); const { seedData, isRootRender, pathToSegment } = normalizedFlightData; // TODO-APP: remove '' const flightSegmentPathWithLeadingEmpty = [ '', ...pathToSegment ]; // Segments are keyed by searchParams (e.g. __PAGE__?{"foo":"bar"}). We might return a less specific, param-less entry, // so we ensure that the final tree contains the correct searchParams (reflected in the URL) are provided in the updated FlightRouterState tree. // We only do this on the first read, as otherwise we'd be overwriting the searchParams that may have already been set treePatch = addSearchParamsToPageSegments(treePatch, Object.fromEntries(url.searchParams)); let newTree = applyRouterStatePatchToTree(flightSegmentPathWithLeadingEmpty, currentTree, treePatch, href); const newCache = createEmptyCacheNode(); // The prefetch cache entry was aliased -- this signals that we only fill in the cache with the // loading state and not the actual parallel route seed data. if (isRootRender && seedData) { // Fill in the cache with the new loading / rsc data const rsc = seedData[1]; const loading = seedData[3]; newCache.loading = loading; newCache.rsc = rsc; // Construct a new tree and apply the aliased loading state for each parallel route fillNewTreeWithOnlyLoadingSegments(navigatedAt, newCache, currentCache, treePatch, seedData); } else { // Copy rsc for the root node of the cache. newCache.rsc = currentCache.rsc; newCache.prefetchRsc = currentCache.prefetchRsc; newCache.loading = currentCache.loading; newCache.parallelRoutes = new Map(currentCache.parallelRoutes); // copy the loading state only into the leaf node (the part that changed) fillCacheWithNewSubTreeDataButOnlyLoading(navigatedAt, newCache, currentCache, normalizedFlightData); } // If we don't have an updated tree, there's no reason to update the cache, as the tree // dictates what cache nodes to render. if (newTree) { currentTree = newTree; currentCache = newCache; applied = true; } } if (!applied) { return false; } mutable.patchedTree = currentTree; mutable.cache = currentCache; mutable.canonicalUrl = href; mutable.hashFragment = url.hash; return handleMutable(state, mutable); } function hasLoadingComponentInSeedData(seedData) { if (!seedData) return false; const parallelRoutes = seedData[2]; const loading = seedData[3]; if (loading) { return true; } for(const key in parallelRoutes){ if (hasLoadingComponentInSeedData(parallelRoutes[key])) { return true; } } return false; } function fillNewTreeWithOnlyLoadingSegments(navigatedAt, newCache, existingCache, routerState, cacheNodeSeedData) { const isLastSegment = Object.keys(routerState[1]).length === 0; if (isLastSegment) { return; } for(const key in routerState[1]){ const parallelRouteState = routerState[1][key]; const segmentForParallelRoute = parallelRouteState[0]; const cacheKey = createRouterCacheKey(segmentForParallelRoute); const parallelSeedData = cacheNodeSeedData !== null && cacheNodeSeedData[2][key] !== undefined ? cacheNodeSeedData[2][key] : null; let newCacheNode; if (parallelSeedData !== null) { // New data was sent from the server. const rsc = parallelSeedData[1]; const loading = parallelSeedData[3]; newCacheNode = { lazyData: null, // copy the layout but null the page segment as that's not meant to be used rsc: segmentForParallelRoute.includes(PAGE_SEGMENT_KEY) ? null : rsc, prefetchRsc: null, head: null, prefetchHead: null, parallelRoutes: new Map(), loading, navigatedAt }; } 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(), loading: null, navigatedAt: -1 }; } const existingParallelRoutes = newCache.parallelRoutes.get(key); if (existingParallelRoutes) { existingParallelRoutes.set(cacheKey, newCacheNode); } else { newCache.parallelRoutes.set(key, new Map([ [ cacheKey, newCacheNode ] ])); } fillNewTreeWithOnlyLoadingSegments(navigatedAt, newCacheNode, existingCache, parallelRouteState, parallelSeedData); } } /** * Add search params to the page segments in the flight router state * Page segments that are associated with search params have a page segment key * followed by a query string. This function will add those params to the page segment. * This is useful if we return an aliased prefetch entry (ie, won't have search params) * but the canonical router URL has search params. */ export function addSearchParamsToPageSegments(flightRouterState, searchParams) { const [segment, parallelRoutes, ...rest] = flightRouterState; // If it's a page segment, modify the segment by adding search params if (segment.includes(PAGE_SEGMENT_KEY)) { const newSegment = addSearchParamsIfPageSegment(segment, searchParams); return [ newSegment, parallelRoutes, ...rest ]; } // Otherwise, recurse through the parallel routes and return a new tree const updatedParallelRoutes = {}; for (const [key, parallelRoute] of Object.entries(parallelRoutes)){ updatedParallelRoutes[key] = addSearchParamsToPageSegments(parallelRoute, searchParams); } return [ segment, updatedParallelRoutes, ...rest ]; } //# sourceMappingURL=aliased-prefetch-navigations.js.map