Skip to content

Commit

Permalink
move code subtleties closer together
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Feb 6, 2024
1 parent 5fd7d5a commit 843f681
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ describe('navigateReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down Expand Up @@ -455,6 +456,7 @@ describe('navigateReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down Expand Up @@ -893,6 +895,7 @@ describe('navigateReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down Expand Up @@ -1120,6 +1123,7 @@ describe('navigateReducer', () => {
"kind": "auto",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down Expand Up @@ -1375,6 +1379,7 @@ describe('navigateReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down Expand Up @@ -1719,6 +1724,7 @@ describe('navigateReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { fillCacheWithDataProperty } from '../fill-cache-with-data-property'
import { applyRouterStatePatchToTreeSkipDefault } from '../apply-router-state-patch-to-tree'
import { shouldHardNavigate } from '../should-hard-navigate'
import { isNavigatingToNewRootLayout } from '../is-navigating-to-new-root-layout'
import type {
Mutable,
NavigateAction,
ReadonlyReducerState,
ReducerState,
import {
PrefetchCacheEntryStatus,
type Mutable,
type NavigateAction,
type ReadonlyReducerState,
type ReducerState,
} from '../router-reducer-types'
import { PrefetchKind } from '../router-reducer-types'
import { handleMutable } from '../handle-mutable'
import { applyFlightData } from '../apply-flight-data'
import { prefetchQueue } from './prefetch-reducer'
Expand All @@ -28,12 +28,8 @@ import {
updateCacheNodeOnNavigation,
} from '../ppr-navigations'
import {
createPrefetchCacheKey,
getPrefetchCacheEntry,
PrefetchCacheEntryStatus,
getPrefetchEntryCacheStatus,
prunePrefetchCache,
createPrefetchCacheEntry,
} from './prefetch-cache-utils'

export function handleExternalUrl(
Expand Down Expand Up @@ -129,27 +125,17 @@ function navigateReducer_noPPR(
return handleExternalUrl(state, mutable, url.toString(), pendingPush)
}

let prefetchValues = getPrefetchCacheEntry(url, state)
// If we don't have a prefetch value, we need to create one
if (!prefetchValues) {
const cacheKey = createPrefetchCacheKey(url)
prefetchValues = createPrefetchCacheEntry({
state,
url,
// in dev, there's never gonna be a prefetch entry so we want to prefetch here
kind:
process.env.NODE_ENV === 'development'
? PrefetchKind.AUTO
: PrefetchKind.TEMPORARY,
prefetchCacheKey: cacheKey,
})

state.prefetchCache.set(cacheKey, prefetchValues)
}

const prefetchEntryCacheStatus = getPrefetchEntryCacheStatus(prefetchValues)
const prefetchValues = getPrefetchCacheEntry({
url,
state,
createIfNotFound: true,
})
const {
treeAtTimeOfPrefetch,
data,
status: prefetchEntryCacheStatus,
} = prefetchValues

const { treeAtTimeOfPrefetch, data } = prefetchValues
prefetchQueue.bump(data)

return data.then(
Expand Down Expand Up @@ -306,27 +292,17 @@ function navigateReducer_PPR(
return handleExternalUrl(state, mutable, url.toString(), pendingPush)
}

let prefetchValues = getPrefetchCacheEntry(url, state)
// If we don't have a prefetch value, we need to create one
if (!prefetchValues) {
const cacheKey = createPrefetchCacheKey(url)
prefetchValues = createPrefetchCacheEntry({
state,
url,
// in dev, there's never gonna be a prefetch entry so we want to prefetch here
kind:
process.env.NODE_ENV === 'development'
? PrefetchKind.AUTO
: PrefetchKind.TEMPORARY,
prefetchCacheKey: cacheKey,
})

state.prefetchCache.set(cacheKey, prefetchValues)
}

const prefetchEntryCacheStatus = getPrefetchEntryCacheStatus(prefetchValues)
const prefetchValues = getPrefetchCacheEntry({
url,
state,
createIfNotFound: true,
})
const {
treeAtTimeOfPrefetch,
data,
status: prefetchEntryCacheStatus,
} = prefetchValues

const { treeAtTimeOfPrefetch, data } = prefetchValues
prefetchQueue.bump(data)

return data.then(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { createHrefFromUrl } from '../create-href-from-url'
import { fetchServerResponse } from '../fetch-server-response'
import type {
PrefetchCacheEntry,
import {
PrefetchCacheEntryStatus,
type PrefetchCacheEntry,
PrefetchKind,
ReadonlyReducerState,
type ReadonlyReducerState,
} from '../router-reducer-types'
import { prefetchQueue } from './prefetch-reducer'

Expand All @@ -14,7 +15,7 @@ import { prefetchQueue } from './prefetch-reducer'
* @param nextUrl - an internal URL, primarily used for handling rewrites. Defaults to '/'.
* @return The generated prefetch cache key.
*/
export function createPrefetchCacheKey(url: URL, nextUrl?: string | null) {
function createPrefetchCacheKey(url: URL, nextUrl?: string | null) {
const pathnameFromUrl = createHrefFromUrl(
url,
// Ensures the hash is not part of the cache key as it does not impact the server fetch
Expand All @@ -32,38 +33,88 @@ export function createPrefetchCacheKey(url: URL, nextUrl?: string | null) {
return pathnameFromUrl
}

export function getPrefetchCacheEntry(
url: URL,
/**
* Returns a prefetch cache entry if one exists. Otherwise creates a new one.
*/
export function getPrefetchCacheEntry({
url,
state,
createIfNotFound,
}: {
url: URL
state: ReadonlyReducerState
createIfNotFound: true
}): PrefetchCacheEntry
export function getPrefetchCacheEntry({
url,
state,
createIfNotFound,
}: {
url: URL
state: ReadonlyReducerState
createIfNotFound: false
}): PrefetchCacheEntry | undefined
export function getPrefetchCacheEntry({
url,
state,
createIfNotFound,
}: {
url: URL
state: ReadonlyReducerState
): PrefetchCacheEntry | undefined {
createIfNotFound: boolean
}): PrefetchCacheEntry | undefined {
let existingCacheEntry: PrefetchCacheEntry | undefined = undefined
// We first check if there's a more specific interception route prefetch entry
// This is because when we detect a prefetch that corresponds with an interception route, we prefix it with nextUrl (see `createPrefetchCacheKey`)
// to avoid conflicts with other pages that may have the same URL but render different things depending on the `Next-URL` header.
const interceptionCacheKey = createPrefetchCacheKey(url, state.nextUrl)
const interceptionData = state.prefetchCache.get(interceptionCacheKey)

if (interceptionData) {
return interceptionData
existingCacheEntry = interceptionData
} else {
// If we dont find a more specific interception route prefetch entry, we check for a regular prefetch entry
const prefetchCacheKey = createPrefetchCacheKey(url)
const prefetchData = state.prefetchCache.get(prefetchCacheKey)
if (prefetchData) {
existingCacheEntry = prefetchData
}
}

// If we dont find a more specific interception route prefetch entry, we check for a regular prefetch entry
const prefetchCacheKey = createPrefetchCacheKey(url)
return state.prefetchCache.get(prefetchCacheKey)
if (existingCacheEntry) {
// Grab the latest status of the cache entry and update it
existingCacheEntry.status = getPrefetchEntryCacheStatus(existingCacheEntry)
return existingCacheEntry
}

// When retrieving a prefetch entry, we usually want to create one if it doesn't exist
// This let's us create a new one if it doesn't exist to avoid needing typeguards in the calling code
if (createIfNotFound) {
// If we don't have a prefetch value, we need to create one
return createPrefetchCacheEntry({
state,
url,
// in dev, there's never gonna be a prefetch entry so we want to prefetch here
kind:
process.env.NODE_ENV === 'development'
? PrefetchKind.AUTO
: PrefetchKind.TEMPORARY,
})
}
}

export function createPrefetchCacheEntry({
state,
url,
kind,
prefetchCacheKey,
}: {
state: ReadonlyReducerState
url: URL
kind: PrefetchKind
prefetchCacheKey: string
}): PrefetchCacheEntry {
// initiates the fetch request for the prefetch and attaches a listener
// to the promise to update the prefetch cache entry when the promise resolves (if necessary)
const prefetchCacheKey = createPrefetchCacheKey(url)
const getPrefetchData = () =>
fetchServerResponse(
url,
Expand All @@ -88,14 +139,19 @@ export function createPrefetchCacheEntry({

const data = prefetchQueue.enqueue(getPrefetchData)

return {
const prefetchEntry = {
treeAtTimeOfPrefetch: state.tree,
data,
kind,
prefetchTime: Date.now(),
lastUsedTime: null,
key: prefetchCacheKey,
status: PrefetchCacheEntryStatus.fresh,
}

state.prefetchCache.set(prefetchCacheKey, prefetchEntry)

return prefetchEntry
}

export function prunePrefetchCache(
Expand All @@ -114,13 +170,6 @@ export function prunePrefetchCache(
const FIVE_MINUTES = 5 * 60 * 1000
const THIRTY_SECONDS = 30 * 1000

export enum PrefetchCacheEntryStatus {
fresh = 'fresh',
reusable = 'reusable',
expired = 'expired',
stale = 'stale',
}

export function getPrefetchEntryCacheStatus({
kind,
prefetchTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import type { FlightData } from '../../../../server/app-render/types'
import type { FlightRouterState } from '../../../../server/app-render/types'
import type { CacheNode } from '../../../../shared/lib/app-router-context.shared-runtime'
import { createInitialRouterState } from '../create-initial-router-state'
import { ACTION_PREFETCH, PrefetchKind } from '../router-reducer-types'
import {
ACTION_PREFETCH,
PrefetchCacheEntryStatus,
PrefetchKind,
} from '../router-reducer-types'
import type { PrefetchAction } from '../router-reducer-types'
import { prefetchReducer } from './prefetch-reducer'
import { fetchServerResponse } from '../fetch-server-response'
Expand Down Expand Up @@ -153,6 +157,7 @@ describe('prefetchReducer', () => {
kind: PrefetchKind.AUTO,
lastUsedTime: null,
prefetchTime: expect.any(Number),
status: PrefetchCacheEntryStatus.fresh,
treeAtTimeOfPrefetch: [
'',
{
Expand Down Expand Up @@ -310,6 +315,7 @@ describe('prefetchReducer', () => {
prefetchTime: expect.any(Number),
kind: PrefetchKind.AUTO,
lastUsedTime: null,
status: PrefetchCacheEntryStatus.fresh,
treeAtTimeOfPrefetch: [
'',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { PrefetchKind } from '../router-reducer-types'
import { NEXT_RSC_UNION_QUERY } from '../../app-router-headers'
import { PromiseQueue } from '../../promise-queue'
import {
createPrefetchCacheKey,
createPrefetchCacheEntry,
getPrefetchCacheEntry,
prunePrefetchCache,
Expand All @@ -25,7 +24,11 @@ export function prefetchReducer(
const { url } = action
url.searchParams.delete(NEXT_RSC_UNION_QUERY)

const cacheEntry = getPrefetchCacheEntry(url, state)
const cacheEntry = getPrefetchCacheEntry({
url,
state,
createIfNotFound: false,
})

if (cacheEntry) {
/**
Expand Down Expand Up @@ -53,15 +56,7 @@ export function prefetchReducer(
}
}

const prefetchCacheKey = createPrefetchCacheKey(url)
const newEntry = createPrefetchCacheEntry({
state,
url,
kind: action.kind,
prefetchCacheKey,
})

state.prefetchCache.set(prefetchCacheKey, newEntry)
createPrefetchCacheEntry({ url, state, kind: action.kind })

return state
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ describe('serverPatchReducer', () => {
"kind": "temporary",
"lastUsedTime": 1690329600000,
"prefetchTime": 1690329600000,
"status": "fresh",
"treeAtTimeOfPrefetch": [
"",
{
Expand Down
Loading

0 comments on commit 843f681

Please sign in to comment.