Skip to content

Commit

Permalink
consolidate prefetch utils
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Feb 7, 2024
1 parent 77ade43 commit b770e4e
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 145 deletions.
50 changes: 1 addition & 49 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { defaultConfig } from '../server/config-shared'
import devalue from 'next/dist/compiled/devalue'
import findUp from 'next/dist/compiled/find-up'
import { nanoid } from 'next/dist/compiled/nanoid/index.cjs'
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
import path from 'path'
import {
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR,
Expand All @@ -41,9 +40,7 @@ import type {
Redirect,
Rewrite,
RouteHas,
RouteType,
} from '../lib/load-custom-routes'
import { getRedirectStatus, modifyRouteRegex } from '../lib/redirect-status'
import { nonNullable } from '../lib/non-nullable'
import { recursiveDelete } from '../lib/recursive-delete'
import { verifyPartytownSetup } from '../lib/verify-partytown-setup'
Expand Down Expand Up @@ -167,6 +164,7 @@ import { hasCustomExportOutput } from '../export/utils'
import { interopDefault } from '../lib/interop-default'
import { formatDynamicImportPath } from '../lib/format-dynamic-import-path'
import { isInterceptionRouteAppPath } from '../server/future/helpers/interception-routes'
import { buildCustomRoute } from '../lib/build-custom-route'

interface ExperimentalBypassForInfo {
experimentalBypassFor?: RouteHas[]
Expand Down Expand Up @@ -272,52 +270,6 @@ export type RoutesManifest = {
caseSensitive?: boolean
}

export function buildCustomRoute(
type: 'header',
route: Header
): ManifestHeaderRoute
export function buildCustomRoute(
type: 'rewrite',
route: Rewrite
): ManifestRewriteRoute
export function buildCustomRoute(
type: 'redirect',
route: Redirect,
restrictedRedirectPaths: string[]
): ManifestRedirectRoute
export function buildCustomRoute(
type: RouteType,
route: Redirect | Rewrite | Header,
restrictedRedirectPaths?: string[]
): ManifestHeaderRoute | ManifestRewriteRoute | ManifestRedirectRoute {
const compiled = pathToRegexp(route.source, [], {
strict: true,
sensitive: false,
delimiter: '/', // default is `/#?`, but Next does not pass query info
})

let source = compiled.source
if (!route.internal) {
source = modifyRouteRegex(
source,
type === 'redirect' ? restrictedRedirectPaths : undefined
)
}

const regex = normalizeRouteRegex(source)

if (type !== 'redirect') {
return { ...route, regex }
}

return {
...route,
statusCode: getRedirectStatus(route as Redirect),
permanent: undefined,
regex,
}
}

function pageToRoute(page: string) {
const routeRegex = getNamedRouteRegex(page, true)
return {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
PrefetchCacheEntryStatus,
type AppRouterState,
type PrefetchCacheEntry,
} from './router-reducer-types'
import { addPathPrefix } from '../../../shared/lib/router/utils/add-path-prefix'
import { pathHasPrefix } from '../../../shared/lib/router/utils/path-has-prefix'
import { createHrefFromUrl } from './create-href-from-url'

/**
* Creates a cache key for the router prefetch cache
*
* @param url - The URL being navigated to
* @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) {
const pathnameFromUrl = createHrefFromUrl(
url,
// Ensures the hash is not part of the cache key as it does not impact the server fetch
false
)

// delimit the prefix so we don't conflict with other pages
const nextUrlPrefix = `${nextUrl}%`

// Route interception depends on `nextUrl` values which aren't a 1:1 mapping to a URL
// The cache key that we store needs to use `nextUrl` to properly distinguish cache entries
if (nextUrl && !pathHasPrefix(pathnameFromUrl, nextUrl)) {
return addPathPrefix(pathnameFromUrl, nextUrlPrefix)
}

return pathnameFromUrl
}

export function prunePrefetchCache(
prefetchCache: AppRouterState['prefetchCache']
) {
for (const [href, prefetchCacheEntry] of prefetchCache) {
if (
getPrefetchEntryCacheStatus(prefetchCacheEntry) ===
PrefetchCacheEntryStatus.expired
) {
prefetchCache.delete(href)
}
}
}

const FIVE_MINUTES = 5 * 60 * 1000
const THIRTY_SECONDS = 30 * 1000

export function getPrefetchEntryCacheStatus({
kind,
prefetchTime,
lastUsedTime,
}: PrefetchCacheEntry): PrefetchCacheEntryStatus {
// if the cache entry was prefetched or read less than 30s ago, then we want to re-use it
if (Date.now() < (lastUsedTime ?? prefetchTime) + THIRTY_SECONDS) {
return lastUsedTime
? PrefetchCacheEntryStatus.reusable
: PrefetchCacheEntryStatus.fresh
}

// if the cache entry was prefetched less than 5 mins ago, then we want to re-use only the loading state
if (kind === 'auto') {
if (Date.now() < prefetchTime + FIVE_MINUTES) {
return PrefetchCacheEntryStatus.stale
}
}

// if the cache entry was prefetched less than 5 mins ago and was a "full" prefetch, then we want to re-use it "full
if (kind === 'full') {
if (Date.now() < prefetchTime + FIVE_MINUTES) {
return PrefetchCacheEntryStatus.reusable
}
}

return PrefetchCacheEntryStatus.expired
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@ import type {
ReadonlyReducerState,
ReducerState,
} from '../router-reducer-types'
import { PrefetchKind } from '../router-reducer-types'
import { PrefetchKind, PrefetchCacheEntryStatus } from '../router-reducer-types'
import { handleMutable } from '../handle-mutable'
import { applyFlightData } from '../apply-flight-data'
import {
PrefetchCacheEntryStatus,
getPrefetchEntryCacheStatus,
} from '../get-prefetch-cache-entry-status'
import { prunePrefetchCache } from './prune-prefetch-cache'
import { prefetchQueue } from './prefetch-reducer'
import { createEmptyCacheNode } from '../../app-router'
import { DEFAULT_SEGMENT_KEY } from '../../../../shared/lib/segment'
import {
listenForDynamicRequest,
updateCacheNodeOnNavigation,
} from '../ppr-navigations'
import { createPrefetchCacheKey } from './create-prefetch-cache-key'
import {
createPrefetchCacheKey,
prunePrefetchCache,
getPrefetchEntryCacheStatus,
} from '../prefetch-cache-utils'

export function handleExternalUrl(
state: ReadonlyReducerState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import type {
ReadonlyReducerState,
} from '../router-reducer-types'
import { PrefetchKind } from '../router-reducer-types'
import { prunePrefetchCache } from './prune-prefetch-cache'
import { NEXT_RSC_UNION_QUERY } from '../../app-router-headers'
import { PromiseQueue } from '../../promise-queue'
import { createPrefetchCacheKey } from './create-prefetch-cache-key'
import {
createPrefetchCacheKey,
prunePrefetchCache,
} from '../prefetch-cache-utils'

export const prefetchQueue = new PromiseQueue(5)

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ export type PrefetchCacheEntry = {
lastUsedTime: number | null
}

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

/**
* Handles keeping the state of app-router.
*/
Expand Down
60 changes: 60 additions & 0 deletions packages/next/src/lib/build-custom-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
import type {
ManifestHeaderRoute,
ManifestRedirectRoute,
ManifestRewriteRoute,
} from '../build'
import {
normalizeRouteRegex,
type Header,
type Redirect,
type Rewrite,
type RouteType,
} from './load-custom-routes'
import { getRedirectStatus, modifyRouteRegex } from './redirect-status'

export function buildCustomRoute(
type: 'header',
route: Header
): ManifestHeaderRoute
export function buildCustomRoute(
type: 'rewrite',
route: Rewrite
): ManifestRewriteRoute
export function buildCustomRoute(
type: 'redirect',
route: Redirect,
restrictedRedirectPaths: string[]
): ManifestRedirectRoute
export function buildCustomRoute(
type: RouteType,
route: Redirect | Rewrite | Header,
restrictedRedirectPaths?: string[]
): ManifestHeaderRoute | ManifestRewriteRoute | ManifestRedirectRoute {
const compiled = pathToRegexp(route.source, [], {
strict: true,
sensitive: false,
delimiter: '/', // default is `/#?`, but Next does not pass query info
})

let source = compiled.source
if (!route.internal) {
source = modifyRouteRegex(
source,
type === 'redirect' ? restrictedRedirectPaths : undefined
)
}

const regex = normalizeRouteRegex(source)

if (type !== 'redirect') {
return { ...route, regex }
}

return {
...route,
statusCode: getRedirectStatus(route as Redirect),
permanent: undefined,
regex,
}
}

0 comments on commit b770e4e

Please sign in to comment.