| import type { |
| CacheNodeSeedData, |
| FlightData, |
| FlightDataPath, |
| FlightRouterState, |
| FlightSegmentPath, |
| Segment, |
| HeadData, |
| InitialRSCPayload, |
| } from '../shared/lib/app-router-types' |
| import { PAGE_SEGMENT_KEY } from '../shared/lib/segment' |
| import type { NormalizedSearch } from './components/segment-cache/cache-key' |
| import { |
| getCacheKeyForDynamicParam, |
| parseDynamicParamFromURLPart, |
| doesStaticSegmentAppearInURL, |
| getRenderedPathname, |
| getRenderedSearch, |
| } from './route-params' |
| import { createHrefFromUrl } from './components/router-reducer/create-href-from-url' |
|
|
| export type NormalizedFlightData = { |
| |
| |
| |
| segmentPath: FlightSegmentPath |
| |
| |
| |
| pathToSegment: FlightSegmentPath |
| segment: Segment |
| tree: FlightRouterState |
| seedData: CacheNodeSeedData | null |
| head: HeadData |
| isHeadPartial: boolean |
| isRootRender: boolean |
| } |
|
|
| |
| |
| |
| |
| export function getFlightDataPartsFromPath( |
| flightDataPath: FlightDataPath |
| ): NormalizedFlightData { |
| |
| const flightDataPathLength = 4 |
| |
| const [tree, seedData, head, isHeadPartial] = |
| flightDataPath.slice(-flightDataPathLength) |
| |
| const segmentPath = flightDataPath.slice(0, -flightDataPathLength) |
|
|
| return { |
| |
| |
| |
| pathToSegment: segmentPath.slice(0, -1), |
| segmentPath, |
| |
| |
| segment: segmentPath[segmentPath.length - 1] ?? '', |
| tree, |
| seedData, |
| head, |
| isHeadPartial, |
| isRootRender: flightDataPath.length === flightDataPathLength, |
| } |
| } |
|
|
| export function createInitialRSCPayloadFromFallbackPrerender( |
| response: Response, |
| fallbackInitialRSCPayload: InitialRSCPayload |
| ): InitialRSCPayload { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| const renderedPathname = getRenderedPathname(response) |
| const renderedSearch = getRenderedSearch(response) |
| const canonicalUrl = createHrefFromUrl(new URL(location.href)) |
| const originalFlightDataPath = fallbackInitialRSCPayload.f[0] |
| const originalFlightRouterState = originalFlightDataPath[0] |
| return { |
| b: fallbackInitialRSCPayload.b, |
| c: canonicalUrl.split('/'), |
| q: renderedSearch, |
| i: fallbackInitialRSCPayload.i, |
| f: [ |
| [ |
| fillInFallbackFlightRouterState( |
| originalFlightRouterState, |
| renderedPathname, |
| renderedSearch as NormalizedSearch |
| ), |
| originalFlightDataPath[1], |
| originalFlightDataPath[2], |
| originalFlightDataPath[2], |
| ], |
| ], |
| m: fallbackInitialRSCPayload.m, |
| G: fallbackInitialRSCPayload.G, |
| S: fallbackInitialRSCPayload.S, |
| } |
| } |
|
|
| function fillInFallbackFlightRouterState( |
| flightRouterState: FlightRouterState, |
| renderedPathname: string, |
| renderedSearch: NormalizedSearch |
| ): FlightRouterState { |
| const pathnameParts = renderedPathname.split('/').filter((p) => p !== '') |
| const index = 0 |
| return fillInFallbackFlightRouterStateImpl( |
| flightRouterState, |
| renderedSearch, |
| pathnameParts, |
| index |
| ) |
| } |
|
|
| function fillInFallbackFlightRouterStateImpl( |
| flightRouterState: FlightRouterState, |
| renderedSearch: NormalizedSearch, |
| pathnameParts: Array<string>, |
| pathnamePartsIndex: number |
| ): FlightRouterState { |
| const originalSegment = flightRouterState[0] |
| let newSegment: Segment |
| let doesAppearInURL: boolean |
| if (typeof originalSegment === 'string') { |
| newSegment = originalSegment |
| doesAppearInURL = doesStaticSegmentAppearInURL(originalSegment) |
| } else { |
| const paramName = originalSegment[0] |
| const paramType = originalSegment[2] |
| const paramValue = parseDynamicParamFromURLPart( |
| paramType, |
| pathnameParts, |
| pathnamePartsIndex |
| ) |
| const cacheKey = getCacheKeyForDynamicParam(paramValue, renderedSearch) |
| newSegment = [paramName, cacheKey, paramType] |
| doesAppearInURL = true |
| } |
|
|
| |
| |
| const childPathnamePartsIndex = doesAppearInURL |
| ? pathnamePartsIndex + 1 |
| : pathnamePartsIndex |
|
|
| const children = flightRouterState[1] |
| const newChildren: { [key: string]: FlightRouterState } = {} |
| for (let key in children) { |
| const childFlightRouterState = children[key] |
| newChildren[key] = fillInFallbackFlightRouterStateImpl( |
| childFlightRouterState, |
| renderedSearch, |
| pathnameParts, |
| childPathnamePartsIndex |
| ) |
| } |
|
|
| const newState: FlightRouterState = [ |
| newSegment, |
| newChildren, |
| null, |
| flightRouterState[3], |
| flightRouterState[4], |
| ] |
| return newState |
| } |
|
|
| export function getNextFlightSegmentPath( |
| flightSegmentPath: FlightSegmentPath |
| ): FlightSegmentPath { |
| |
| |
| return flightSegmentPath.slice(2) |
| } |
|
|
| export function normalizeFlightData( |
| flightData: FlightData |
| ): NormalizedFlightData[] | string { |
| |
| |
| if (typeof flightData === 'string') { |
| return flightData |
| } |
|
|
| return flightData.map((flightDataPath) => |
| getFlightDataPartsFromPath(flightDataPath) |
| ) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function prepareFlightRouterStateForRequest( |
| flightRouterState: FlightRouterState, |
| isHmrRefresh?: boolean |
| ): string { |
| |
| if (isHmrRefresh) { |
| return encodeURIComponent(JSON.stringify(flightRouterState)) |
| } |
|
|
| return encodeURIComponent( |
| JSON.stringify(stripClientOnlyDataFromFlightRouterState(flightRouterState)) |
| ) |
| } |
|
|
| |
| |
| |
| |
| function stripClientOnlyDataFromFlightRouterState( |
| flightRouterState: FlightRouterState |
| ): FlightRouterState { |
| const [ |
| segment, |
| parallelRoutes, |
| _url, |
| refreshMarker, |
| isRootLayout, |
| hasLoadingBoundary, |
| ] = flightRouterState |
|
|
| |
| |
| const cleanedSegment = stripSearchParamsFromPageSegment(segment) |
|
|
| |
| const cleanedParallelRoutes: { [key: string]: FlightRouterState } = {} |
| for (const [key, childState] of Object.entries(parallelRoutes)) { |
| cleanedParallelRoutes[key] = |
| stripClientOnlyDataFromFlightRouterState(childState) |
| } |
|
|
| const result: FlightRouterState = [ |
| cleanedSegment, |
| cleanedParallelRoutes, |
| null, |
| shouldPreserveRefreshMarker(refreshMarker) ? refreshMarker : null, |
| ] |
|
|
| |
| if (isRootLayout !== undefined) { |
| result[4] = isRootLayout |
| } |
| if (hasLoadingBoundary !== undefined) { |
| result[5] = hasLoadingBoundary |
| } |
|
|
| return result |
| } |
|
|
| |
| |
| |
| |
| function stripSearchParamsFromPageSegment(segment: Segment): Segment { |
| if ( |
| typeof segment === 'string' && |
| segment.startsWith(PAGE_SEGMENT_KEY + '?') |
| ) { |
| return PAGE_SEGMENT_KEY |
| } |
| return segment |
| } |
|
|
| |
| |
| |
| |
| |
| function shouldPreserveRefreshMarker( |
| refreshMarker: FlightRouterState[3] |
| ): boolean { |
| return Boolean(refreshMarker && refreshMarker !== 'refresh') |
| } |
|
|