Skip to content

Commit 105a651

Browse files
committed
feat: support server, cache, root params types
1 parent 62c329e commit 105a651

File tree

4 files changed

+324
-17
lines changed

4 files changed

+324
-17
lines changed

packages/next/src/build/index.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ import {
223223
createRouteTypesManifest,
224224
writeRouteTypesManifest,
225225
writeValidatorFile,
226+
writeServerTypesFile,
227+
writeCacheLifeTypesFile,
226228
} from '../server/lib/router-utils/route-types-utils'
227229

228230
type Fallback = null | boolean | string
@@ -1311,6 +1313,34 @@ export default async function build(
13111313
layoutRoutes = processLayoutRoutes(mappedAppLayouts, dir)
13121314
}
13131315

1316+
// Collect layout parameters for root params extraction
1317+
// Find the root layout (shortest path with dynamic segments)
1318+
const collectedRootParams: Record<string, string[]> = {}
1319+
1320+
// Find layouts that could be root layouts (have dynamic segments)
1321+
const layoutsWithParams = layoutRoutes
1322+
.map(({ route }) => {
1323+
const foundParams = Array.from(
1324+
route.matchAll(/\[(.*?)\]/g),
1325+
(match) => match[1]
1326+
)
1327+
return { route, params: foundParams }
1328+
})
1329+
.filter(({ params }) => params.length > 0)
1330+
1331+
// Sort by path depth (ascending) to find the shallowest layout with params
1332+
layoutsWithParams.sort((a, b) => {
1333+
const aDepth = a.route.split('/').length
1334+
const bDepth = b.route.split('/').length
1335+
return aDepth - bDepth
1336+
})
1337+
1338+
// The root layout is the shallowest layout with dynamic segments
1339+
if (layoutsWithParams.length > 0) {
1340+
const rootLayout = layoutsWithParams[0]
1341+
collectedRootParams[rootLayout.route] = rootLayout.params
1342+
}
1343+
13141344
const routeTypesManifest = await createRouteTypesManifest({
13151345
dir,
13161346
pageRoutes,
@@ -1323,12 +1353,39 @@ export default async function build(
13231353
rewrites: config.rewrites,
13241354
})
13251355

1356+
// Add collected root params and cache life config to manifest
1357+
routeTypesManifest.collectedRootParams = collectedRootParams
1358+
routeTypesManifest.cacheLifeConfig = config.experimental?.cacheLife
1359+
13261360
await writeRouteTypesManifest(
13271361
routeTypesManifest,
13281362
routeTypesFilePath,
13291363
config
13301364
)
13311365
await writeValidatorFile(routeTypesManifest, validatorFilePath)
1366+
1367+
// Generate server types if we have root params
1368+
if (Object.keys(collectedRootParams).length > 0) {
1369+
const serverTypesFilePath = path.join(
1370+
distDir,
1371+
'types',
1372+
'server.d.ts'
1373+
)
1374+
await writeServerTypesFile(routeTypesManifest, serverTypesFilePath)
1375+
}
1376+
1377+
// Generate cache life types if we have cache life config
1378+
if (config.experimental?.cacheLife) {
1379+
const cacheLifeTypesFilePath = path.join(
1380+
distDir,
1381+
'types',
1382+
'cache-life.d.ts'
1383+
)
1384+
await writeCacheLifeTypesFile(
1385+
routeTypesManifest,
1386+
cacheLifeTypesFilePath
1387+
)
1388+
}
13321389
})
13331390

13341391
// Turbopack already handles conflicting app and page routes.

packages/next/src/build/webpack-config.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import { CopyFilePlugin } from './webpack/plugins/copy-file-plugin'
5151
import { ClientReferenceManifestPlugin } from './webpack/plugins/flight-manifest-plugin'
5252
import { FlightClientEntryPlugin as NextFlightClientEntryPlugin } from './webpack/plugins/flight-client-entry-plugin'
5353
import { RspackFlightClientEntryPlugin } from './webpack/plugins/rspack-flight-client-entry-plugin'
54-
import { NextTypesPlugin } from './webpack/plugins/next-types-plugin'
5554
import type {
5655
Feature,
5756
SWC_TARGET_TRIPLE,
@@ -319,8 +318,6 @@ export default async function getBaseWebpackConfig(
319318
pagesDir,
320319
reactProductionProfiling = false,
321320
rewrites,
322-
originalRewrites,
323-
originalRedirects,
324321
runWebpackSpan,
325322
appDir,
326323
middlewareMatchers,
@@ -2142,19 +2139,6 @@ export default async function getBaseWebpackConfig(
21422139
isEdgeServer,
21432140
encryptionKey,
21442141
})),
2145-
hasAppDir &&
2146-
!isClient &&
2147-
new NextTypesPlugin({
2148-
dir,
2149-
distDir: config.distDir,
2150-
appDir,
2151-
dev,
2152-
isEdgeServer,
2153-
pageExtensions: config.pageExtensions,
2154-
cacheLifeConfig: config.experimental.cacheLife,
2155-
originalRewrites,
2156-
originalRedirects,
2157-
}),
21582142
!dev &&
21592143
isClient &&
21602144
!!config.experimental.sri?.algorithm &&

packages/next/src/server/lib/router-utils/route-types-utils.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import {
1010
generateRouteTypesFile,
1111
generateLinkTypesFile,
1212
generateValidatorFile,
13+
generateServerTypesFile,
14+
generateCacheLifeTypesFile,
1315
} from './typegen'
1416
import { tryToParsePath } from '../../../lib/try-to-parse-path'
1517
import {
1618
extractInterceptionRouteInformation,
1719
isInterceptionRouteAppPath,
1820
} from '../../../shared/lib/router/utils/interception-routes'
21+
import type { CacheLife } from '../../../server/use-cache/cache-life'
1922

2023
interface RouteInfo {
2124
path: string
@@ -37,8 +40,12 @@ export interface RouteTypesManifest {
3740
layoutPaths: Set<string>
3841
appRouteHandlers: Set<string>
3942
pageApiRoutes: Set<string>
40-
/** Direct mapping from file paths to routes for validation */
43+
/** Direct mapping from file paths to routes for validation (resolves intercepting routes) */
4144
filePathToRoute: Map<string, string>
45+
/** Layout parameters for root params extraction */
46+
collectedRootParams?: Record<string, string[]>
47+
/** Cache life configuration */
48+
cacheLifeConfig?: { [profile: string]: CacheLife }
4249
}
4350

4451
// Convert a custom-route source string (`/blog/:slug`, `/docs/:path*`, ...)
@@ -332,3 +339,53 @@ export async function writeValidatorFile(
332339

333340
await fs.promises.writeFile(filePath, generateValidatorFile(manifest))
334341
}
342+
343+
export async function writeServerTypesFile(
344+
manifest: RouteTypesManifest,
345+
filePath: string
346+
) {
347+
const dirname = path.dirname(filePath)
348+
349+
if (!fs.existsSync(dirname)) {
350+
await fs.promises.mkdir(dirname, { recursive: true })
351+
}
352+
353+
// Extract root params from collected layout params
354+
if (manifest.collectedRootParams) {
355+
// Since we now collect only the actual root layout, we can directly use its params
356+
const allRootParams: { param: string; optional: boolean }[] = []
357+
358+
for (const [, params] of Object.entries(manifest.collectedRootParams)) {
359+
for (const param of params) {
360+
// All root layout params are required (not optional)
361+
// since they define the top-level structure of the app
362+
allRootParams.push({ param, optional: false })
363+
}
364+
}
365+
366+
if (allRootParams.length > 0) {
367+
await fs.promises.writeFile(
368+
filePath,
369+
generateServerTypesFile(allRootParams)
370+
)
371+
}
372+
}
373+
}
374+
375+
export async function writeCacheLifeTypesFile(
376+
manifest: RouteTypesManifest,
377+
filePath: string
378+
) {
379+
const dirname = path.dirname(filePath)
380+
381+
if (!fs.existsSync(dirname)) {
382+
await fs.promises.mkdir(dirname, { recursive: true })
383+
}
384+
385+
if (manifest.cacheLifeConfig) {
386+
await fs.promises.writeFile(
387+
filePath,
388+
generateCacheLifeTypesFile(manifest.cacheLifeConfig)
389+
)
390+
}
391+
}

0 commit comments

Comments
 (0)