Skip to content

Commit 2992e6a

Browse files
committed
Merge branch 'ijjk/cache-handler-env' of github.com:ijjk/next.js into ijjk/cache-handler-env
2 parents 1b0ae8f + 8dc6bef commit 2992e6a

24 files changed

+289
-47
lines changed

packages/next/src/build/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,7 @@ export default async function build(
18971897
distDir,
18981898
configFileName,
18991899
runtimeEnvConfig,
1900+
dynamicIO: Boolean(config.experimental.dynamicIO),
19001901
httpAgentOptions: config.httpAgentOptions,
19011902
locales: config.i18n?.locales,
19021903
defaultLocale: config.i18n?.defaultLocale,
@@ -2112,6 +2113,7 @@ export default async function build(
21122113
pageRuntime,
21132114
edgeInfo,
21142115
pageType,
2116+
dynamicIO: Boolean(config.experimental.dynamicIO),
21152117
cacheHandler: config.cacheHandler,
21162118
isrFlushToDisk: ciEnvironment.hasNextSupport
21172119
? false

packages/next/src/build/utils.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,7 @@ export async function buildAppStaticPaths({
13481348
dir,
13491349
page,
13501350
distDir,
1351+
dynamicIO,
13511352
configFileName,
13521353
generateParams,
13531354
isrFlushToDisk,
@@ -1362,6 +1363,7 @@ export async function buildAppStaticPaths({
13621363
}: {
13631364
dir: string
13641365
page: string
1366+
dynamicIO: boolean
13651367
configFileName: string
13661368
generateParams: GenerateParamsResults
13671369
distDir: string
@@ -1390,6 +1392,7 @@ export async function buildAppStaticPaths({
13901392
const incrementalCache = new IncrementalCache({
13911393
fs: nodeFs,
13921394
dev: true,
1395+
dynamicIO,
13931396
flushToDisk: isrFlushToDisk,
13941397
serverDistDir: path.join(distDir, 'server'),
13951398
fetchCacheKeyPrefix,
@@ -1581,6 +1584,7 @@ export async function isPageStatic({
15811584
pageRuntime,
15821585
edgeInfo,
15831586
pageType,
1587+
dynamicIO,
15841588
originalAppPath,
15851589
isrFlushToDisk,
15861590
maxMemoryCacheSize,
@@ -1592,6 +1596,7 @@ export async function isPageStatic({
15921596
dir: string
15931597
page: string
15941598
distDir: string
1599+
dynamicIO: boolean
15951600
configFileName: string
15961601
runtimeEnvConfig: any
15971602
httpAgentOptions: NextConfigComplete['httpAgentOptions']
@@ -1722,6 +1727,7 @@ export async function isPageStatic({
17221727
await buildAppStaticPaths({
17231728
dir,
17241729
page,
1730+
dynamicIO,
17251731
configFileName,
17261732
generateParams,
17271733
distDir,

packages/next/src/client/components/static-generation-async-storage.external.ts

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface StaticGenerationStore {
6363

6464
isDraftMode?: boolean
6565
isUnstableNoStore?: boolean
66+
isPrefetchRequest?: boolean
6667

6768
requestEndedState?: { ended?: boolean }
6869
}

packages/next/src/export/helpers/create-incremental-cache.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
77

88
export async function createIncrementalCache({
99
cacheHandler,
10+
dynamicIO,
1011
cacheMaxMemorySize,
1112
fetchCacheKeyPrefix,
1213
distDir,
1314
dir,
1415
flushToDisk,
1516
}: {
17+
dynamicIO: boolean
1618
cacheHandler?: string
1719
cacheMaxMemorySize?: number
1820
fetchCacheKeyPrefix?: string
@@ -34,6 +36,7 @@ export async function createIncrementalCache({
3436
dev: false,
3537
requestHeaders: {},
3638
flushToDisk,
39+
dynamicIO,
3740
fetchCache: true,
3841
maxMemoryCacheSize: cacheMaxMemorySize,
3942
fetchCacheKeyPrefix,

packages/next/src/export/worker.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
type FallbackRouteParams,
4747
} from '../server/request/fallback-params'
4848
import { needsExperimentalReact } from '../lib/needs-experimental-react'
49+
import { runWithCacheScope } from '../server/async-storage/cache-scope'
4950

5051
const envConfig = require('../shared/lib/runtime-config.external')
5152

@@ -352,6 +353,7 @@ export async function exportPages(
352353
fetchCacheKeyPrefix,
353354
distDir,
354355
dir,
356+
dynamicIO: Boolean(nextConfig.experimental.dynamicIO),
355357
// skip writing to disk in minimal mode for now, pending some
356358
// changes to better support it
357359
flushToDisk: !hasNextSupport,
@@ -459,21 +461,26 @@ export async function exportPages(
459461

460462
return { result, path, pageKey }
461463
}
464+
// for each build worker we share one dynamic IO cache scope
465+
// this is only leveraged if the flag is enabled
466+
const dynamicIOCacheScope = new Map()
462467

463-
for (let i = 0; i < paths.length; i += maxConcurrency) {
464-
const subset = paths.slice(i, i + maxConcurrency)
468+
await runWithCacheScope({ cache: dynamicIOCacheScope }, async () => {
469+
for (let i = 0; i < paths.length; i += maxConcurrency) {
470+
const subset = paths.slice(i, i + maxConcurrency)
465471

466-
const subsetResults = await Promise.all(
467-
subset.map((path) =>
468-
exportPageWithRetry(
469-
path,
470-
nextConfig.experimental.staticGenerationRetryCount ?? 1
472+
const subsetResults = await Promise.all(
473+
subset.map((path) =>
474+
exportPageWithRetry(
475+
path,
476+
nextConfig.experimental.staticGenerationRetryCount ?? 1
477+
)
471478
)
472479
)
473-
)
474480

475-
results.push(...subsetResults)
476-
}
481+
results.push(...subsetResults)
482+
}
483+
})
477484

478485
return results
479486
}

packages/next/src/server/app-render/app-render.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
12841284
fallbackRouteParams,
12851285
renderOpts,
12861286
requestEndedState,
1287+
isPrefetchRequest: Boolean(req.headers[NEXT_ROUTER_PREFETCH_HEADER]),
12871288
},
12881289
(staticGenerationStore) =>
12891290
renderToHTMLOrFlightImpl(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { AsyncLocalStorage } from 'async_hooks'
2+
3+
export interface CacheScopeStore {
4+
cache?: Map<string, any>
5+
}
6+
7+
export const cacheScopeAsyncLocalStorage =
8+
new AsyncLocalStorage<CacheScopeStore>()
9+
10+
/**
11+
* For dynamic IO handling we want to have a scoped memory
12+
* cache which can live either the lifetime of a build worker,
13+
* the lifetime of a specific request, or from a prefetch request
14+
* to the request for non-prefetch version of a page (with
15+
* drop-off after so long to prevent memory inflating)
16+
*/
17+
export function runWithCacheScope(
18+
store: Partial<CacheScopeStore>,
19+
fn: (...args: any[]) => Promise<any>
20+
) {
21+
return cacheScopeAsyncLocalStorage.run(
22+
{
23+
cache: store.cache || new Map(),
24+
},
25+
fn
26+
)
27+
}

packages/next/src/server/async-storage/with-static-generation-store.ts

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type StaticGenerationContext = {
2121
fallbackRouteParams: FallbackRouteParams | null
2222

2323
requestEndedState?: { ended?: boolean }
24+
isPrefetchRequest?: boolean
2425
renderOpts: {
2526
incrementalCache?: IncrementalCache
2627
isOnDemandRevalidate?: boolean
@@ -69,6 +70,7 @@ export const withStaticGenerationStore: WithStore<
6970
fallbackRouteParams,
7071
renderOpts,
7172
requestEndedState,
73+
isPrefetchRequest,
7274
}: StaticGenerationContext,
7375
callback: (store: StaticGenerationStore) => Result
7476
): Result => {
@@ -111,6 +113,7 @@ export const withStaticGenerationStore: WithStore<
111113
isDraftMode: renderOpts.isDraftMode,
112114

113115
requestEndedState,
116+
isPrefetchRequest,
114117
}
115118

116119
// TODO: remove this when we resolve accessing the store outside the execution context

packages/next/src/server/base-server.ts

+71-2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ import type { RouteModule } from './route-modules/route-module'
173173
import { FallbackMode, parseFallbackField } from '../lib/fallback'
174174
import { toResponseCacheEntry } from './response-cache/utils'
175175
import { scheduleOnNextTick } from '../lib/scheduler'
176+
import { PrefetchCacheScopes } from './lib/prefetch-cache-scopes'
177+
import {
178+
runWithCacheScope,
179+
type CacheScopeStore,
180+
} from './async-storage/cache-scope'
176181

177182
export type FindComponentsResult = {
178183
components: LoadComponentsReturnType
@@ -454,6 +459,14 @@ export default abstract class Server<
454459

455460
private readonly isAppPPREnabled: boolean
456461

462+
private readonly prefetchCacheScopesDev = new PrefetchCacheScopes()
463+
464+
/**
465+
* This is used to persist cache scopes across
466+
* prefetch -> full route requests for dynamic IO
467+
* it's only fully used in dev
468+
*/
469+
457470
public constructor(options: ServerOptions) {
458471
const {
459472
dir = '.',
@@ -2082,6 +2095,11 @@ export default abstract class Server<
20822095
typeof query.__nextppronly !== 'undefined' &&
20832096
couldSupportPPR
20842097

2098+
// When enabled, this will allow the use of the `?__nextppronly` query
2099+
// to enable debugging of the fallback shell.
2100+
const hasDebugFallbackShellQuery =
2101+
hasDebugStaticShellQuery && query.__nextppronly === 'fallback'
2102+
20852103
// This page supports PPR if it is marked as being `PARTIALLY_STATIC` in the
20862104
// prerender manifest and this is an app page.
20872105
const isRoutePPREnabled: boolean =
@@ -2106,6 +2124,8 @@ export default abstract class Server<
21062124
const isDebugDynamicAccesses =
21072125
isDebugStaticShell && this.renderOpts.dev === true
21082126

2127+
const isDebugFallbackShell = hasDebugFallbackShellQuery && isRoutePPREnabled
2128+
21092129
// If we're in minimal mode, then try to get the postponed information from
21102130
// the request metadata. If available, use it for resuming the postponed
21112131
// render.
@@ -2741,7 +2761,7 @@ export default abstract class Server<
27412761
}
27422762
}
27432763

2744-
const responseGenerator: ResponseGenerator = async ({
2764+
let responseGenerator: ResponseGenerator = async ({
27452765
hasResolved,
27462766
previousCacheEntry,
27472767
isRevalidating,
@@ -2974,7 +2994,8 @@ export default abstract class Server<
29742994
const fallbackRouteParams =
29752995
isDynamic &&
29762996
isRoutePPREnabled &&
2977-
getRequestMeta(req, 'didSetDefaultRouteMatches')
2997+
(getRequestMeta(req, 'didSetDefaultRouteMatches') ||
2998+
isDebugFallbackShell)
29782999
? getFallbackRouteParams(pathname)
29793000
: null
29803001

@@ -2991,6 +3012,54 @@ export default abstract class Server<
29913012
}
29923013
}
29933014

3015+
if (this.nextConfig.experimental.dynamicIO) {
3016+
const originalResponseGenerator = responseGenerator
3017+
3018+
responseGenerator = async (
3019+
...args: Parameters<typeof responseGenerator>
3020+
): ReturnType<typeof responseGenerator> => {
3021+
let cache: CacheScopeStore['cache'] | undefined
3022+
3023+
if (this.renderOpts.dev) {
3024+
cache = this.prefetchCacheScopesDev.get(urlPathname)
3025+
3026+
// we need to seed the prefetch cache scope in dev
3027+
// since we did not have a prefetch cache available
3028+
// and this is not a prefetch request
3029+
if (
3030+
!cache &&
3031+
!isPrefetchRSCRequest &&
3032+
routeModule?.definition.kind === RouteKind.APP_PAGE
3033+
) {
3034+
req.headers[RSC_HEADER] = '1'
3035+
req.headers[NEXT_ROUTER_PREFETCH_HEADER] = '1'
3036+
3037+
cache = new Map()
3038+
3039+
await runWithCacheScope({ cache }, () =>
3040+
originalResponseGenerator(...args)
3041+
)
3042+
this.prefetchCacheScopesDev.set(urlPathname, cache)
3043+
3044+
delete req.headers[RSC_HEADER]
3045+
delete req.headers[NEXT_ROUTER_PREFETCH_HEADER]
3046+
}
3047+
}
3048+
3049+
return runWithCacheScope({ cache }, () =>
3050+
originalResponseGenerator(...args)
3051+
).finally(() => {
3052+
if (this.renderOpts.dev) {
3053+
if (isPrefetchRSCRequest) {
3054+
this.prefetchCacheScopesDev.set(urlPathname, cache)
3055+
} else {
3056+
this.prefetchCacheScopesDev.del(urlPathname)
3057+
}
3058+
}
3059+
})
3060+
}
3061+
}
3062+
29943063
const cacheEntry = await this.responseCache.get(
29953064
ssgCacheKey,
29963065
responseGenerator,

packages/next/src/server/dev/next-dev-server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ export default class DevServer extends Server {
762762
configFileName,
763763
publicRuntimeConfig,
764764
serverRuntimeConfig,
765+
dynamicIO: Boolean(this.nextConfig.experimental.dynamicIO),
765766
},
766767
httpAgentOptions,
767768
locales,

packages/next/src/server/dev/static-paths-worker.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type RuntimeConfig = {
3030
configFileName: string
3131
publicRuntimeConfig: { [key: string]: any }
3232
serverRuntimeConfig: { [key: string]: any }
33+
dynamicIO: boolean
3334
}
3435

3536
// we call getStaticPaths in a separate process to ensure
@@ -115,6 +116,7 @@ export async function loadStaticPaths({
115116
return await buildAppStaticPaths({
116117
dir,
117118
page: pathname,
119+
dynamicIO: config.dynamicIO,
118120
generateParams,
119121
configFileName: config.configFileName,
120122
distDir,

0 commit comments

Comments
 (0)