Skip to content

Commit 347fa9e

Browse files
committed
feat: support server, cache, root params types
1 parent 5155e1a commit 347fa9e

File tree

5 files changed

+328
-19
lines changed

5 files changed

+328
-19
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,8 @@
118118
"${workspaceRoot}/.git-blame-ignore-revs"
119119
],
120120
"astGrep.serverPath": "node_modules/@ast-grep/cli/ast-grep",
121-
"rust-analyzer.imports.prefix": "crate"
121+
"rust-analyzer.imports.prefix": "crate",
122+
"[typescript]": {
123+
"editor.defaultFormatter": "esbenp.prettier-vscode"
124+
}
122125
}

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
@@ -1340,6 +1342,34 @@ export default async function build(
13401342
layoutRoutes = processLayoutRoutes(mappedAppLayouts, dir)
13411343
}
13421344

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

1385+
// Add collected root params and cache life config to manifest
1386+
routeTypesManifest.collectedRootParams = collectedRootParams
1387+
routeTypesManifest.cacheLifeConfig = config.experimental?.cacheLife
1388+
13551389
await writeRouteTypesManifest(
13561390
routeTypesManifest,
13571391
routeTypesFilePath,
13581392
config
13591393
)
13601394
await writeValidatorFile(routeTypesManifest, validatorFilePath)
1395+
1396+
// Generate server types if we have root params
1397+
if (Object.keys(collectedRootParams).length > 0) {
1398+
const serverTypesFilePath = path.join(
1399+
distDir,
1400+
'types',
1401+
'server.d.ts'
1402+
)
1403+
await writeServerTypesFile(routeTypesManifest, serverTypesFilePath)
1404+
}
1405+
1406+
// Generate cache life types if we have cache life config
1407+
if (config.experimental?.cacheLife) {
1408+
const cacheLifeTypesFilePath = path.join(
1409+
distDir,
1410+
'types',
1411+
'cache-life.d.ts'
1412+
)
1413+
await writeCacheLifeTypesFile(
1414+
routeTypesManifest,
1415+
cacheLifeTypesFilePath
1416+
)
1417+
}
13611418
})
13621419

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

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

Lines changed: 0 additions & 17 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,20 +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-
typedRoutes: true, // TODO: I think this does more than just enable typed routes??
2155-
cacheLifeConfig: config.experimental.cacheLife,
2156-
originalRewrites,
2157-
originalRedirects,
2158-
}),
21592142
!dev &&
21602143
isClient &&
21612144
!!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,8 +10,11 @@ import {
1010
generateRouteTypesFile,
1111
generateLinkTypesFile,
1212
generateValidatorFile,
13+
generateServerTypesFile,
14+
generateCacheLifeTypesFile,
1315
} from './typegen'
1416
import { tryToParsePath } from '../../../lib/try-to-parse-path'
17+
import type { CacheLife } from '../../../server/use-cache/cache-life'
1518

1619
interface RouteInfo {
1720
path: string
@@ -33,8 +36,12 @@ export interface RouteTypesManifest {
3336
layoutPaths: Set<string>
3437
appRouteHandlers: Set<string>
3538
pageApiRoutes: Set<string>
36-
/** Direct mapping from file paths to routes for validation */
39+
/** Direct mapping from file paths to routes for validation (resolves intercepting routes) */
3740
filePathToRoute: Map<string, string>
41+
/** Layout parameters for root params extraction */
42+
collectedRootParams?: Record<string, string[]>
43+
/** Cache life configuration */
44+
cacheLifeConfig?: { [profile: string]: CacheLife }
3845
}
3946

4047
// Convert a custom-route source string (`/blog/:slug`, `/docs/:path*`, ...)
@@ -342,3 +349,53 @@ export async function writeValidatorFile(
342349

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

0 commit comments

Comments
 (0)