From e64729521d2a30be8b93e7f81489e52936cd70f0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 27 Jul 2022 14:43:43 +0200 Subject: [PATCH] Add additional comments for reducer/cachenode (#39065) - Add additional comments for reducer functions. - Add comments for CacheNode ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --- packages/next/client/components/reducer.ts | 87 ++++++++++++++----- packages/next/server/app-render.tsx | 38 ++++++-- .../next/shared/lib/app-router-context.ts | 14 ++- 3 files changed, 105 insertions(+), 34 deletions(-) diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 5d011d17c19..2e5a19daba6 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -7,6 +7,9 @@ import type { import { matchSegment } from './match-segments' import { fetchServerResponse } from './app-router.client' +/** + * Fill cache with subTreeData based on flightDataPath + */ function fillCacheWithNewSubTreeData( newCache: CacheNode, existingCache: CacheNode, @@ -73,6 +76,9 @@ function fillCacheWithNewSubTreeData( ) } +/** + * Kick off fetch based on the common layout between two routes. Fill cache with data property holding the in-progress fetch. + */ function fillCacheWithDataProperty( newCache: CacheNode, existingCache: CacheNode, @@ -148,6 +154,10 @@ function fillCacheWithDataProperty( ) } +/** + * Decide if the segments can be optimistically rendered, kicking off the fetch in layout-router. + * - When somewhere in the path to the segment there is a loading.js this becomes true + */ function canOptimisticallyRender( segments: string[], flightRouterState: FlightRouterState @@ -186,6 +196,10 @@ function canOptimisticallyRender( ) } +/** + * Create optimistic version of router state based on the existing router state and segments. + * This is used to allow rendering layout-routers up till the point where data is missing. + */ function createOptimisticTree( segments: string[], flightRouterState: FlightRouterState | null, @@ -246,7 +260,10 @@ function createOptimisticTree( return result } -function walkTreeWithFlightDataPath( +/** + * Apply the router state from the Flight response. Creates a new router state tree. + */ +function applyRouterStatePatchToTree( flightSegmentPath: FlightData[0], flightRouterState: FlightRouterState, treePatch: FlightRouterState @@ -279,7 +296,7 @@ function walkTreeWithFlightDataPath( ...parallelRoutes, [parallelRouteKey]: lastSegment ? treePatch - : walkTreeWithFlightDataPath( + : applyRouterStatePatchToTree( flightSegmentPath.slice(2), parallelRoutes[parallelRouteKey], treePatch @@ -299,17 +316,6 @@ function walkTreeWithFlightDataPath( return tree } -type PushRef = { - /** - * If the app-router should push a new history entry in app-router's useEffect() - */ - pendingPush: boolean - /** - * Multi-page navigation through location.href. - */ - mpaNavigation: boolean -} - export type FocusAndScrollRef = { /** * If focus and scroll should be set in the layout-router's useEffect() @@ -317,14 +323,6 @@ export type FocusAndScrollRef = { apply: boolean } -type AppRouterState = { - tree: FlightRouterState - cache: CacheNode - pushRef: PushRef - focusAndScrollRef: FocusAndScrollRef - canonicalUrl: string -} - export const ACTION_RELOAD = 'reload' export const ACTION_NAVIGATE = 'navigate' export const ACTION_RESTORE = 'restore' @@ -407,6 +405,47 @@ interface ServerPatchAction { cache: CacheNode } +interface PushRef { + /** + * If the app-router should push a new history entry in app-router's useEffect() + */ + pendingPush: boolean + /** + * Multi-page navigation through location.href. + */ + mpaNavigation: boolean +} + +/** + * Handles keeping the state of app-router. + */ +type AppRouterState = { + /** + * The router state, this is written into the history state in app-router using replaceState/pushState. + * - Has to be serializable as it is written into the history state. + * - Holds which segments are shown on the screen. + * - Holds where loading states (loading.js) exists. + */ + tree: FlightRouterState + /** + * The cache holds React nodes for every segment that is shown on screen as well as previously shown segments and prefetched segments. + * It also holds in-progress data requests. + */ + cache: CacheNode + /** + * Decides if the update should create a new history entry and if the navigation can't be handled by app-router. + */ + pushRef: PushRef + /** + * Decides if the update should apply scroll and focus management. + */ + focusAndScrollRef: FocusAndScrollRef + /** + * The canonical url that is pushed/replaced + */ + canonicalUrl: string +} + /** * Reducer that handles the app-router state updates. */ @@ -575,7 +614,7 @@ export function reducer( const flightSegmentPath = flightDataPath.slice(0, -3) // Create new tree based on the flightSegmentPath and router state patch - const newTree = walkTreeWithFlightDataPath( + const newTree = applyRouterStatePatchToTree( // TODO-APP: remove '' ['', ...flightSegmentPath], state.tree, @@ -641,7 +680,7 @@ export function reducer( const treePath = flightDataPath.slice(0, -3) const [treePatch] = flightDataPath.slice(-2) - const newTree = walkTreeWithFlightDataPath( + const newTree = applyRouterStatePatchToTree( // TODO-APP: remove '' ['', ...treePath], state.tree, @@ -726,7 +765,7 @@ export function reducer( // Given the path can only have two items the items are only the router state and subTreeData for the root. const [treePatch, subTreeData] = flightDataPath - const newTree = walkTreeWithFlightDataPath( + const newTree = applyRouterStatePatchToTree( // TODO-APP: remove '' [''], state.tree, diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index e6221207665..d3e2d98ed09 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -246,6 +246,9 @@ type DynamicParamTypes = 'catchall' | 'optional-catchall' | 'dynamic' // d = dynamic export type DynamicParamTypesShort = 'c' | 'oc' | 'd' +/** + * Shorten the dynamic param in order to make it smaller when transmitted to the browser. + */ function getShortDynamicParamType( type: DynamicParamTypes ): DynamicParamTypesShort { @@ -261,10 +264,16 @@ function getShortDynamicParamType( } } +/** + * Segment in the router state. + */ export type Segment = | string | [param: string, value: string, type: DynamicParamTypesShort] +/** + * LoaderTree is generated in next-app-loader. + */ type LoaderTree = [ segment: string, parallelRoutes: { [parallelRouterKey: string]: LoaderTree }, @@ -275,6 +284,9 @@ type LoaderTree = [ } ] +/** + * Router state + */ export type FlightRouterState = [ segment: Segment, parallelRoutes: { [parallelRouterKey: string]: FlightRouterState }, @@ -283,7 +295,11 @@ export type FlightRouterState = [ loading?: 'loading' ] +/** + * Individual Flight response path + */ export type FlightSegmentPath = + // Uses `any` as repeating pattern can't be typed. | any[] // Looks somewhat like this | [ @@ -296,21 +312,25 @@ export type FlightSegmentPath = ] export type FlightDataPath = + // Uses `any` as repeating pattern can't be typed. | any[] // Looks somewhat like this | [ - segment: Segment, - parallelRoute: string, - segment: Segment, - parallelRoute: string, - segment: Segment, - parallelRoute: string, - currentSegment: Segment, - tree: FlightRouterState, - subTreeData: React.ReactNode + // Holds full path to the segment. + ...FlightSegmentPath, + /* segment of the rendered slice: */ Segment, + /* treePatch */ FlightRouterState, + /* subTreeData: */ React.ReactNode ] +/** + * The Flight response data + */ export type FlightData = Array | string + +/** + * Property holding the current subTreeData. + */ export type ChildProp = { current: React.ReactNode segment: Segment diff --git a/packages/next/shared/lib/app-router-context.ts b/packages/next/shared/lib/app-router-context.ts index 7abceb4c978..9483ebb6362 100644 --- a/packages/next/shared/lib/app-router-context.ts +++ b/packages/next/shared/lib/app-router-context.ts @@ -4,11 +4,23 @@ import type { FlightRouterState, FlightData } from '../../server/app-render' export type ChildSegmentMap = Map +/** + * Cache node used in app-router / layout-router. + */ export type CacheNode = { + /** + * In-flight request for this node. + */ data: ReturnType< typeof import('../../client/components/app-router.client').fetchServerResponse > | null - subTreeData: null | React.ReactNode + /** + * React Component for this node. + */ + subTreeData: React.ReactNode | null + /** + * Child parallel routes. + */ parallelRoutes: Map }