Skip to content

Commit dd5202b

Browse files
committed
feat: support server, cache, root params types
1 parent 6e1f178 commit dd5202b

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
@@ -225,6 +225,8 @@ import {
225225
createRouteTypesManifest,
226226
writeRouteTypesManifest,
227227
writeValidatorFile,
228+
writeServerTypesFile,
229+
writeCacheLifeTypesFile,
228230
} from '../server/lib/router-utils/route-types-utils'
229231

230232
type Fallback = null | boolean | string
@@ -1344,6 +1346,34 @@ export default async function build(
13441346
layoutRoutes = processLayoutRoutes(mappedAppLayouts, dir, isSrcDir)
13451347
}
13461348

1349+
// Collect layout parameters for root params extraction
1350+
// Find the root layout (shortest path with dynamic segments)
1351+
const collectedRootParams: Record<string, string[]> = {}
1352+
1353+
// Find layouts that could be root layouts (have dynamic segments)
1354+
const layoutsWithParams = layoutRoutes
1355+
.map(({ route }) => {
1356+
const foundParams = Array.from(
1357+
route.matchAll(/\[(.*?)\]/g),
1358+
(match) => match[1]
1359+
)
1360+
return { route, params: foundParams }
1361+
})
1362+
.filter(({ params }) => params.length > 0)
1363+
1364+
// Sort by path depth (ascending) to find the shallowest layout with params
1365+
layoutsWithParams.sort((a, b) => {
1366+
const aDepth = a.route.split('/').length
1367+
const bDepth = b.route.split('/').length
1368+
return aDepth - bDepth
1369+
})
1370+
1371+
// The root layout is the shallowest layout with dynamic segments
1372+
if (layoutsWithParams.length > 0) {
1373+
const rootLayout = layoutsWithParams[0]
1374+
collectedRootParams[rootLayout.route] = rootLayout.params
1375+
}
1376+
13471377
const routeTypesManifest = await createRouteTypesManifest({
13481378
dir,
13491379
pageRoutes,
@@ -1356,12 +1386,39 @@ export default async function build(
13561386
rewrites: config.rewrites,
13571387
})
13581388

1389+
// Add collected root params and cache life config to manifest
1390+
routeTypesManifest.collectedRootParams = collectedRootParams
1391+
routeTypesManifest.cacheLifeConfig = config.experimental?.cacheLife
1392+
13591393
await writeRouteTypesManifest(
13601394
routeTypesManifest,
13611395
routeTypesFilePath,
13621396
config
13631397
)
13641398
await writeValidatorFile(routeTypesManifest, validatorFilePath)
1399+
1400+
// Generate server types if we have root params
1401+
if (Object.keys(collectedRootParams).length > 0) {
1402+
const serverTypesFilePath = path.join(
1403+
distDir,
1404+
'types',
1405+
'server.d.ts'
1406+
)
1407+
await writeServerTypesFile(routeTypesManifest, serverTypesFilePath)
1408+
}
1409+
1410+
// Generate cache life types if we have cache life config
1411+
if (config.experimental?.cacheLife) {
1412+
const cacheLifeTypesFilePath = path.join(
1413+
distDir,
1414+
'types',
1415+
'cache-life.d.ts'
1416+
)
1417+
await writeCacheLifeTypesFile(
1418+
routeTypesManifest,
1419+
cacheLifeTypesFilePath
1420+
)
1421+
}
13651422
})
13661423

13671424
// 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*`, ...)
@@ -333,3 +340,53 @@ export async function writeValidatorFile(
333340

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

0 commit comments

Comments
 (0)