Skip to content

Commit acb7423

Browse files
committed
chore: move static paths utils into own folder
1 parent 32957e5 commit acb7423

File tree

6 files changed

+568
-562
lines changed

6 files changed

+568
-562
lines changed

packages/next/src/build/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ import {
133133
collectRoutesUsingEdgeRuntime,
134134
collectMeta,
135135
} from './utils'
136-
import type { PageInfo, PageInfos, PrerenderedRoute } from './utils'
136+
import type { PageInfo, PageInfos } from './utils'
137137
import type { AppSegmentConfig } from './segment-config/app/app-segment-config'
138138
import { writeBuildId } from './write-build-id'
139139
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
@@ -212,6 +212,7 @@ import {
212212
formatNodeOptions,
213213
getParsedNodeOptionsWithoutInspect,
214214
} from '../server/lib/utils'
215+
import type { PrerenderedRoute } from './static-paths/types'
215216

216217
type Fallback = null | boolean | string
217218

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import {
2+
IncrementalCache,
3+
type CacheHandler,
4+
} from '../../server/lib/incremental-cache'
5+
import type { AppPageModule } from '../../server/route-modules/app-page/module.compiled'
6+
import type { AppSegment } from '../segment-config/app/app-segments'
7+
import type { StaticPathsResult } from './types'
8+
import type { Params } from '../../server/request/params'
9+
10+
import path from 'path'
11+
import {
12+
FallbackMode,
13+
fallbackModeToStaticPathsResult,
14+
} from '../../lib/fallback'
15+
import * as ciEnvironment from '../../server/ci-info'
16+
import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
17+
import { interopDefault } from '../../lib/interop-default'
18+
import { AfterRunner } from '../../server/after/run-with-after'
19+
import { createWorkStore } from '../../server/async-storage/work-store'
20+
import { nodeFs } from '../../server/lib/node-fs-methods'
21+
import { getParamKeys } from '../../server/request/fallback-params'
22+
import { buildStaticPaths } from './pages'
23+
24+
export async function buildAppStaticPaths({
25+
dir,
26+
page,
27+
distDir,
28+
dynamicIO,
29+
authInterrupts,
30+
configFileName,
31+
segments,
32+
isrFlushToDisk,
33+
cacheHandler,
34+
cacheLifeProfiles,
35+
requestHeaders,
36+
maxMemoryCacheSize,
37+
fetchCacheKeyPrefix,
38+
nextConfigOutput,
39+
ComponentMod,
40+
isRoutePPREnabled,
41+
buildId,
42+
}: {
43+
dir: string
44+
page: string
45+
dynamicIO: boolean
46+
authInterrupts: boolean
47+
configFileName: string
48+
segments: AppSegment[]
49+
distDir: string
50+
isrFlushToDisk?: boolean
51+
fetchCacheKeyPrefix?: string
52+
cacheHandler?: string
53+
cacheLifeProfiles?: {
54+
[profile: string]: import('../../server/use-cache/cache-life').CacheLife
55+
}
56+
maxMemoryCacheSize?: number
57+
requestHeaders: IncrementalCache['requestHeaders']
58+
nextConfigOutput: 'standalone' | 'export' | undefined
59+
ComponentMod: AppPageModule
60+
isRoutePPREnabled: boolean | undefined
61+
buildId: string
62+
}): Promise<Partial<StaticPathsResult>> {
63+
if (
64+
segments.some((generate) => generate.config?.dynamicParams === true) &&
65+
nextConfigOutput === 'export'
66+
) {
67+
throw new Error(
68+
'"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports'
69+
)
70+
}
71+
72+
ComponentMod.patchFetch()
73+
74+
let CurCacheHandler: typeof CacheHandler | undefined
75+
if (cacheHandler) {
76+
CurCacheHandler = interopDefault(
77+
await import(formatDynamicImportPath(dir, cacheHandler)).then(
78+
(mod) => mod.default || mod
79+
)
80+
)
81+
}
82+
83+
const incrementalCache = new IncrementalCache({
84+
fs: nodeFs,
85+
dev: true,
86+
dynamicIO,
87+
flushToDisk: isrFlushToDisk,
88+
serverDistDir: path.join(distDir, 'server'),
89+
fetchCacheKeyPrefix,
90+
maxMemoryCacheSize,
91+
getPrerenderManifest: () => ({
92+
version: -1 as any, // letting us know this doesn't conform to spec
93+
routes: {},
94+
dynamicRoutes: {},
95+
notFoundRoutes: [],
96+
preview: null as any, // `preview` is special case read in next-dev-server
97+
}),
98+
CurCacheHandler,
99+
requestHeaders,
100+
minimalMode: ciEnvironment.hasNextSupport,
101+
})
102+
103+
const paramKeys = new Set<string>()
104+
105+
const staticParamKeys = new Set<string>()
106+
for (const segment of segments) {
107+
if (segment.param) {
108+
paramKeys.add(segment.param)
109+
110+
if (segment.config?.dynamicParams === false) {
111+
staticParamKeys.add(segment.param)
112+
}
113+
}
114+
}
115+
116+
const afterRunner = new AfterRunner()
117+
118+
const store = createWorkStore({
119+
page,
120+
// We're discovering the parameters here, so we don't have any unknown
121+
// ones.
122+
fallbackRouteParams: null,
123+
renderOpts: {
124+
incrementalCache,
125+
cacheLifeProfiles,
126+
supportsDynamicResponse: true,
127+
isRevalidate: false,
128+
experimental: {
129+
dynamicIO,
130+
authInterrupts,
131+
},
132+
waitUntil: afterRunner.context.waitUntil,
133+
onClose: afterRunner.context.onClose,
134+
onAfterTaskError: afterRunner.context.onTaskError,
135+
buildId,
136+
},
137+
})
138+
139+
const routeParams = await ComponentMod.workAsyncStorage.run(
140+
store,
141+
async () => {
142+
async function builtRouteParams(
143+
parentsParams: Params[] = [],
144+
idx = 0
145+
): Promise<Params[]> {
146+
// If we don't have any more to process, then we're done.
147+
if (idx === segments.length) return parentsParams
148+
149+
const current = segments[idx]
150+
151+
if (
152+
typeof current.generateStaticParams !== 'function' &&
153+
idx < segments.length
154+
) {
155+
return builtRouteParams(parentsParams, idx + 1)
156+
}
157+
158+
const params: Params[] = []
159+
160+
if (current.generateStaticParams) {
161+
// fetchCache can be used to inform the fetch() defaults used inside
162+
// of generateStaticParams. revalidate and dynamic options don't come into
163+
// play within generateStaticParams.
164+
if (typeof current.config?.fetchCache !== 'undefined') {
165+
store.fetchCache = current.config.fetchCache
166+
}
167+
168+
if (parentsParams.length > 0) {
169+
for (const parentParams of parentsParams) {
170+
const result = await current.generateStaticParams({
171+
params: parentParams,
172+
})
173+
174+
for (const item of result) {
175+
params.push({ ...parentParams, ...item })
176+
}
177+
}
178+
} else {
179+
const result = await current.generateStaticParams({ params: {} })
180+
181+
params.push(...result)
182+
}
183+
}
184+
185+
if (idx < segments.length) {
186+
return builtRouteParams(params, idx + 1)
187+
}
188+
189+
return params
190+
}
191+
192+
return builtRouteParams()
193+
}
194+
)
195+
196+
let lastDynamicSegmentHadGenerateStaticParams = false
197+
for (const segment of segments) {
198+
// Check to see if there are any missing params for segments that have
199+
// dynamicParams set to false.
200+
if (
201+
segment.param &&
202+
segment.isDynamicSegment &&
203+
segment.config?.dynamicParams === false
204+
) {
205+
for (const params of routeParams) {
206+
if (segment.param in params) continue
207+
208+
const relative = segment.filePath
209+
? path.relative(dir, segment.filePath)
210+
: undefined
211+
212+
throw new Error(
213+
`Segment "${relative}" exports "dynamicParams: false" but the param "${segment.param}" is missing from the generated route params.`
214+
)
215+
}
216+
}
217+
218+
if (
219+
segment.isDynamicSegment &&
220+
typeof segment.generateStaticParams !== 'function'
221+
) {
222+
lastDynamicSegmentHadGenerateStaticParams = false
223+
} else if (typeof segment.generateStaticParams === 'function') {
224+
lastDynamicSegmentHadGenerateStaticParams = true
225+
}
226+
}
227+
228+
// Determine if all the segments have had their parameters provided. If there
229+
// was no dynamic parameters, then we've collected all the params.
230+
const hadAllParamsGenerated =
231+
paramKeys.size === 0 ||
232+
(routeParams.length > 0 &&
233+
routeParams.every((params) => {
234+
for (const key of paramKeys) {
235+
if (key in params) continue
236+
return false
237+
}
238+
return true
239+
}))
240+
241+
// TODO: dynamic params should be allowed to be granular per segment but
242+
// we need additional information stored/leveraged in the prerender
243+
// manifest to allow this behavior.
244+
const dynamicParams = segments.every(
245+
(segment) => segment.config?.dynamicParams !== false
246+
)
247+
248+
const supportsRoutePreGeneration =
249+
hadAllParamsGenerated || process.env.NODE_ENV === 'production'
250+
251+
const fallbackMode = dynamicParams
252+
? supportsRoutePreGeneration
253+
? isRoutePPREnabled
254+
? FallbackMode.PRERENDER
255+
: FallbackMode.BLOCKING_STATIC_RENDER
256+
: undefined
257+
: FallbackMode.NOT_FOUND
258+
259+
let result: Partial<StaticPathsResult> = {
260+
fallbackMode,
261+
prerenderedRoutes: lastDynamicSegmentHadGenerateStaticParams
262+
? []
263+
: undefined,
264+
}
265+
266+
if (hadAllParamsGenerated && fallbackMode) {
267+
result = await buildStaticPaths({
268+
staticPathsResult: {
269+
fallback: fallbackModeToStaticPathsResult(fallbackMode),
270+
paths: routeParams.map((params) => ({ params })),
271+
},
272+
page,
273+
configFileName,
274+
appDir: true,
275+
})
276+
}
277+
278+
// If the fallback mode is a prerender, we want to include the dynamic
279+
// route in the prerendered routes too.
280+
if (isRoutePPREnabled) {
281+
result.prerenderedRoutes ??= []
282+
result.prerenderedRoutes.unshift({
283+
path: page,
284+
encoded: page,
285+
fallbackRouteParams: getParamKeys(page),
286+
})
287+
}
288+
289+
await afterRunner.executeAfter()
290+
291+
return result
292+
}

0 commit comments

Comments
 (0)