Skip to content

Commit

Permalink
Add CacheNode.prefetchRsc field (#59537)
Browse files Browse the repository at this point in the history
Adds a new field `prefetchRsc` to CacheNode that will be used by the PPR
implementation. It represents a static version of the segment that can
be showed immediately, and may or may not contain dynamic holes. It's
prefetched before a navigation occurs. During rendering, we will choose
whether to render `rsc` or `prefetchRsc` with `useDeferredValue`.

As with the `rsc` field, a value of `null` means no value was provided.
In this case, the LayoutRouter will go straight to rendering the `rsc`
value; if that one is also missing, it will suspend and trigger a lazy
fetch.

The non-PPR implementation will never set this value.

This PR adds the field to the CacheNode type but doesn't implement any
of the behavior yet. Mostly this involves updating the router reducer
unit tests.

Closes NEXT-1855
  • Loading branch information
acdlite authored Dec 12, 2023
1 parent b441cff commit 5adacb6
Show file tree
Hide file tree
Showing 24 changed files with 230 additions and 9 deletions.
13 changes: 8 additions & 5 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,14 @@ function HistoryUpdater({
return null
}

export const createEmptyCacheNode = () => ({
lazyData: null,
rsc: null,
parallelRoutes: new Map(),
})
export function createEmptyCacheNode(): CacheNode {
return {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
}
}

function useServerActionDispatcher(dispatch: React.Dispatch<ReducerActions>) {
const serverActionDispatcher: ServerActionDispatcher = useCallback(
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ function InnerLayoutRouter({
// TODO-APP: remove ''
const refetchTree = walkAddRefetch(['', ...segmentPath], fullTree)

// TODO: Since this case always suspends indefinitely, and the only thing
// we're doing here is setting `lazyData`, it would be fine to mutate the
// current cache node (if it exists) rather than cloning it.
childNode = {
lazyData: fetchServerResponse(
new URL(url, location.origin),
Expand All @@ -361,6 +364,7 @@ function InnerLayoutRouter({
buildId
),
rsc: null,
prefetchRsc: childNode ? childNode.prefetchRsc : null,
head: childNode ? childNode.head : undefined,
parallelRoutes: childNode ? childNode.parallelRoutes : new Map(),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export function applyFlightData(
if (flightDataPath.length === 3) {
const rsc = cacheNodeSeedData[2]
cache.rsc = rsc
// This is a PPR-only field. When PPR is enabled, we shouldn't hit
// this path during a navigation, but until PPR is fully implemented
// yet it's possible the existing node does have a non-null
// `prefetchRsc`. As an incremental step, we'll just de-opt to the
// old behavior — no PPR value.
cache.prefetchRsc = null
fillLazyItemsTillLeafWithHead(
cache,
existingCache,
Expand All @@ -31,6 +37,10 @@ export function applyFlightData(
} else {
// Copy rsc for the root node of the cache.
cache.rsc = existingCache.rsc
// This is a PPR-only field. Unlike the previous branch, since we're
// just cloning the existing cache node, we might as well keep the
// PPR value, if it exists.
cache.prefetchRsc = existingCache.prefetchRsc
cache.parallelRoutes = new Map(existingCache.parallelRoutes)
// Create a copy of the existing cache with the rsc applied.
fillCacheWithNewSubTreeData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('createInitialRouterState', () => {
const expectedCache: CacheNode = {
lazyData: null,
rsc: children,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -73,6 +74,7 @@ describe('createInitialRouterState', () => {
{
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
head: <title>Test</title>,
},
Expand All @@ -82,6 +84,7 @@ describe('createInitialRouterState', () => {
]),
lazyData: null,
rsc: null,
prefetchRsc: null,
},
],
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function createInitialRouterState({
const cache: CacheNode = {
lazyData: null,
rsc: rsc,
prefetchRsc: null,
// The cache gets seeded during the first render. `initialParallelRoutes` ensures the cache from the first render is there during the second render.
parallelRoutes: isServer ? new Map() : initialParallelRoutes,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ describe('fillCacheWithDataProperty', () => {
const cache: CacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
}
const existingCache: CacheNode = {
lazyData: null,
rsc: <>Root layout</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -38,6 +40,7 @@ describe('fillCacheWithDataProperty', () => {
{
lazyData: null,
rsc: <>Linking</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -47,6 +50,7 @@ describe('fillCacheWithDataProperty', () => {
{
lazyData: null,
rsc: <>Page</>,
prefetchRsc: null,
parallelRoutes: new Map(),
},
],
Expand Down Expand Up @@ -76,23 +80,27 @@ describe('fillCacheWithDataProperty', () => {
"" => {
"lazyData": null,
"parallelRoutes": Map {},
"prefetchRsc": null,
"rsc": <React.Fragment>
Page
</React.Fragment>,
},
},
},
"prefetchRsc": null,
"rsc": <React.Fragment>
Linking
</React.Fragment>,
},
"dashboard" => {
"lazyData": Promise {},
"parallelRoutes": Map {},
"prefetchRsc": null,
"rsc": null,
},
},
},
"prefetchRsc": null,
"rsc": null,
}
`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function fillCacheWithDataProperty(
childSegmentMap.set(cacheKey, {
lazyData: fetchResponse(),
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
})
}
Expand All @@ -52,6 +53,7 @@ export function fillCacheWithDataProperty(
childSegmentMap.set(cacheKey, {
lazyData: fetchResponse(),
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
})
}
Expand All @@ -62,6 +64,7 @@ export function fillCacheWithDataProperty(
childCacheNode = {
lazyData: childCacheNode.lazyData,
rsc: childCacheNode.rsc,
prefetchRsc: childCacheNode.prefetchRsc,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
} as CacheNode
childSegmentMap.set(cacheKey, childCacheNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ describe('fillCacheWithNewSubtreeData', () => {
const cache: CacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
}
const existingCache: CacheNode = {
lazyData: null,
rsc: <>Root layout</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -43,6 +45,7 @@ describe('fillCacheWithNewSubtreeData', () => {
{
lazyData: null,
rsc: <>Linking</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -52,6 +55,7 @@ describe('fillCacheWithNewSubtreeData', () => {
{
lazyData: null,
rsc: <>Page</>,
prefetchRsc: null,
parallelRoutes: new Map(),
},
],
Expand Down Expand Up @@ -79,6 +83,7 @@ describe('fillCacheWithNewSubtreeData', () => {
const expectedCache: CacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -88,6 +93,7 @@ describe('fillCacheWithNewSubtreeData', () => {
{
lazyData: null,
rsc: <>Linking</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -98,6 +104,7 @@ describe('fillCacheWithNewSubtreeData', () => {
{
lazyData: null,
rsc: <>Page</>,
prefetchRsc: null,
parallelRoutes: new Map(),
},
],
Expand All @@ -114,6 +121,7 @@ describe('fillCacheWithNewSubtreeData', () => {
{
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
head: (
<>
Expand All @@ -126,6 +134,7 @@ describe('fillCacheWithNewSubtreeData', () => {
],
]),
rsc: <h1>SubTreeData Injected!</h1>,
prefetchRsc: null,
},
],
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function fillCacheWithNewSubTreeData(
childCacheNode = {
lazyData: null,
rsc,
prefetchRsc: null,
// Ensure segments other than the one we got data for are preserved.
parallelRoutes: existingChildCacheNode
? new Map(existingChildCacheNode.parallelRoutes)
Expand Down Expand Up @@ -88,6 +89,7 @@ export function fillCacheWithNewSubTreeData(
childCacheNode = {
lazyData: childCacheNode.lazyData,
rsc: childCacheNode.rsc,
prefetchRsc: childCacheNode.prefetchRsc,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
} as CacheNode
childSegmentMap.set(cacheKey, childCacheNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ describe('fillLazyItemsTillLeafWithHead', () => {
const cache: CacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
}
const existingCache: CacheNode = {
lazyData: null,
rsc: <>Root layout</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -52,6 +54,7 @@ describe('fillLazyItemsTillLeafWithHead', () => {
{
lazyData: null,
rsc: <>Linking</>,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -61,6 +64,7 @@ describe('fillLazyItemsTillLeafWithHead', () => {
{
lazyData: null,
rsc: <>Page</>,
prefetchRsc: null,
parallelRoutes: new Map(),
},
],
Expand Down Expand Up @@ -94,6 +98,7 @@ describe('fillLazyItemsTillLeafWithHead', () => {
const expectedCache: CacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -103,6 +108,7 @@ describe('fillLazyItemsTillLeafWithHead', () => {
{
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map([
[
'children',
Expand All @@ -120,6 +126,7 @@ describe('fillLazyItemsTillLeafWithHead', () => {
{
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
head: (
<>
Expand All @@ -132,13 +139,15 @@ describe('fillLazyItemsTillLeafWithHead', () => {
],
]),
rsc: null,
prefetchRsc: null,
},
],
[
'',
{
lazyData: null,
rsc: <>Page</>,
prefetchRsc: null,
parallelRoutes: new Map(),
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export function fillLazyItemsTillLeafWithHead(
newCacheNode = {
lazyData: null,
rsc: seedNode,
// This is a PPR-only field. When PPR is enabled, we shouldn't hit
// this path during a navigation, but until PPR is fully implemented
// yet it's possible the existing node does have a non-null
// `prefetchRsc`. As an incremental step, we'll just de-opt to the
// old behavior — no PPR value.
prefetchRsc: null,
parallelRoutes: new Map(existingCacheNode?.parallelRoutes),
}
} else if (wasPrefetched && existingCacheNode) {
Expand All @@ -61,6 +67,10 @@ export function fillLazyItemsTillLeafWithHead(
newCacheNode = {
lazyData: existingCacheNode.lazyData,
rsc: existingCacheNode.rsc,
// This is a PPR-only field. Unlike the previous branch, since we're
// just cloning the existing cache node, we might as well keep the
// PPR value, if it exists.
prefetchRsc: existingCacheNode.prefetchRsc,
parallelRoutes: new Map(existingCacheNode.parallelRoutes),
} as CacheNode
} else {
Expand All @@ -69,6 +79,7 @@ export function fillLazyItemsTillLeafWithHead(
newCacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(existingCacheNode?.parallelRoutes),
}
}
Expand Down Expand Up @@ -97,6 +108,7 @@ export function fillLazyItemsTillLeafWithHead(
newCacheNode = {
lazyData: null,
rsc: seedNode,
prefetchRsc: null,
parallelRoutes: new Map(),
}
} else {
Expand All @@ -105,6 +117,7 @@ export function fillLazyItemsTillLeafWithHead(
newCacheNode = {
lazyData: null,
rsc: null,
prefetchRsc: null,
parallelRoutes: new Map(),
}
}
Expand Down
Loading

0 comments on commit 5adacb6

Please sign in to comment.