Skip to content

Commit 6e1f178

Browse files
committed
feat: generate type guard file
1 parent 61f0853 commit 6e1f178

File tree

16 files changed

+835
-110
lines changed

16 files changed

+835
-110
lines changed

packages/next/src/build/entries.ts

Lines changed: 130 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
import type { LoadedEnvFiles } from '@next/env'
1313
import type { AppLoaderOptions } from './webpack/loaders/next-app-loader'
1414

15-
import { posix, join, dirname, extname, normalize } from 'path'
15+
import { posix, join, dirname, extname, normalize, relative } from 'path'
1616
import { stringify } from 'querystring'
1717
import fs from 'fs'
1818
import {
@@ -76,37 +76,36 @@ import type { MappedPages } from './build-context'
7676
import { PAGE_TYPES } from '../lib/page-types'
7777
import { isAppPageRoute } from '../lib/is-app-page-route'
7878
import { recursiveReadDir } from '../lib/recursive-readdir'
79-
import { createValidFileMatcher } from '../server/lib/find-page-file'
79+
import type { createValidFileMatcher } from '../server/lib/find-page-file'
8080
import { isReservedPage } from './utils'
8181
import { isParallelRouteSegment } from '../shared/lib/segment'
8282
import { ensureLeadingSlash } from '../shared/lib/page-path/ensure-leading-slash'
8383

8484
/**
85-
* Collect app pages and layouts from the app directory
85+
* Collect app pages, layouts, and default files from the app directory
8686
* @param appDir - The app directory path
87-
* @param pageExtensions - The configured page extensions
88-
* @param options - Optional configuration
89-
* @returns Object containing appPaths and layoutPaths arrays
87+
* @param validFileMatcher - File matcher object
88+
* @returns Object containing appPaths, layoutPaths, and defaultPaths arrays
9089
*/
9190
export async function collectAppFiles(
9291
appDir: string,
93-
pageExtensions: PageExtensions
92+
validFileMatcher: ReturnType<typeof createValidFileMatcher>
9493
): Promise<{
9594
appPaths: string[]
9695
layoutPaths: string[]
96+
defaultPaths: string[]
9797
}> {
98-
const validFileMatcher = createValidFileMatcher(pageExtensions, appDir)
99-
100-
// Collect both app pages and layouts in a single directory traversal
98+
// Collect app pages, layouts, and default files in a single directory traversal
10199
const allAppFiles = await recursiveReadDir(appDir, {
102100
pathnameFilter: (absolutePath) =>
103101
validFileMatcher.isAppRouterPage(absolutePath) ||
104102
validFileMatcher.isRootNotFound(absolutePath) ||
105-
validFileMatcher.isAppLayoutPage(absolutePath),
103+
validFileMatcher.isAppLayoutPage(absolutePath) ||
104+
validFileMatcher.isAppDefaultPage(absolutePath),
106105
ignorePartFilter: (part) => part.startsWith('_'),
107106
})
108107

109-
// Separate app pages from layouts
108+
// Separate app pages, layouts, and defaults
110109
const appPaths = allAppFiles.filter(
111110
(absolutePath) =>
112111
validFileMatcher.isAppRouterPage(absolutePath) ||
@@ -115,26 +114,25 @@ export async function collectAppFiles(
115114
const layoutPaths = allAppFiles.filter((absolutePath) =>
116115
validFileMatcher.isAppLayoutPage(absolutePath)
117116
)
117+
const defaultPaths = allAppFiles.filter((absolutePath) =>
118+
validFileMatcher.isAppDefaultPage(absolutePath)
119+
)
118120

119-
return { appPaths, layoutPaths }
121+
return { appPaths, layoutPaths, defaultPaths }
120122
}
121123

122124
/**
123125
* Collect pages from the pages directory
124126
* @param pagesDir - The pages directory path
125-
* @param pageExtensions - The configured page extensions
127+
* @param validFileMatcher - File matcher object
126128
* @returns Array of page file paths
127129
*/
128130
export async function collectPagesFiles(
129131
pagesDir: string,
130-
pageExtensions: PageExtensions
132+
validFileMatcher: ReturnType<typeof createValidFileMatcher>
131133
): Promise<string[]> {
132134
return recursiveReadDir(pagesDir, {
133-
pathnameFilter: (absolutePath) => {
134-
const relativePath = absolutePath.replace(pagesDir + '/', '')
135-
return pageExtensions.some((ext) => relativePath.endsWith(`.${ext}`))
136-
},
137-
ignorePartFilter: (part) => part.startsWith('_'),
135+
pathnameFilter: validFileMatcher.isPageFile,
138136
})
139137
}
140138

@@ -154,30 +152,39 @@ export type SlotInfo = {
154152
* @param baseDir - The base directory path
155153
* @param filePath - The mapped file path (with private prefix)
156154
* @param prefix - The directory prefix ('pages' or 'app')
155+
* @param isSrcDir - Whether the project uses src directory structure
157156
* @returns The relative file path
158157
*/
159158
export function createRelativeFilePath(
160159
baseDir: string,
161160
filePath: string,
162-
prefix: 'pages' | 'app'
161+
prefix: 'pages' | 'app',
162+
isSrcDir: boolean
163163
): string {
164164
const privatePrefix =
165165
prefix === 'pages' ? 'private-next-pages' : 'private-next-app-dir'
166-
return join(
166+
const srcPrefix = isSrcDir ? 'src/' : ''
167+
const absolutePath = join(
167168
baseDir,
168-
filePath.replace(new RegExp(`^${privatePrefix}/`), `${prefix}/`)
169+
filePath.replace(new RegExp(`^${privatePrefix}/`), `${srcPrefix}${prefix}/`)
169170
)
171+
172+
// For validator.ts generation, calculate path relative to .next/types/ directory
173+
const validatorDir = join(baseDir, '.next', 'types')
174+
return relative(validatorDir, absolutePath)
170175
}
171176

172177
/**
173178
* Process pages routes from mapped pages
174179
* @param mappedPages - The mapped pages object
175180
* @param baseDir - The base directory path
181+
* @param isSrcDir - Whether the project uses src directory structure
176182
* @returns Object containing pageRoutes and pageApiRoutes
177183
*/
178184
export function processPageRoutes(
179185
mappedPages: { [page: string]: string },
180-
baseDir: string
186+
baseDir: string,
187+
isSrcDir: boolean
181188
): {
182189
pageRoutes: RouteInfo[]
183190
pageApiRoutes: RouteInfo[]
@@ -186,7 +193,12 @@ export function processPageRoutes(
186193
const pageApiRoutes: RouteInfo[] = []
187194

188195
for (const [route, filePath] of Object.entries(mappedPages)) {
189-
const relativeFilePath = createRelativeFilePath(baseDir, filePath, 'pages')
196+
const relativeFilePath = createRelativeFilePath(
197+
baseDir,
198+
filePath,
199+
'pages',
200+
isSrcDir
201+
)
190202

191203
if (route.startsWith('/api/')) {
192204
pageApiRoutes.push({
@@ -243,46 +255,129 @@ export function extractSlotsFromAppRoutes(mappedAppPages: {
243255
return slots
244256
}
245257

258+
/**
259+
* Extract slots from default files
260+
* @param mappedDefaultFiles - The mapped default files object
261+
* @returns Array of slot information
262+
*/
263+
export function extractSlotsFromDefaultFiles(mappedDefaultFiles: {
264+
[page: string]: string
265+
}): SlotInfo[] {
266+
const slots: SlotInfo[] = []
267+
268+
for (const [route] of Object.entries(mappedDefaultFiles)) {
269+
const segments = route.split('/')
270+
for (let i = segments.length - 1; i >= 0; i--) {
271+
const segment = segments[i]
272+
if (isParallelRouteSegment(segment)) {
273+
const parentPath = normalizeAppPath(segments.slice(0, i).join('/'))
274+
const slotName = segment.slice(1)
275+
276+
// Check if the slot already exists
277+
if (slots.some((s) => s.name === slotName && s.parent === parentPath))
278+
continue
279+
280+
slots.push({
281+
name: slotName,
282+
parent: parentPath,
283+
})
284+
break
285+
}
286+
}
287+
}
288+
289+
return slots
290+
}
291+
292+
/**
293+
* Combine and deduplicate slot arrays using a Set
294+
* @param slotArrays - Arrays of slot information to combine
295+
* @returns Deduplicated array of slots
296+
*/
297+
export function combineSlots(...slotArrays: SlotInfo[][]): SlotInfo[] {
298+
const slotSet = new Set<string>()
299+
const result: SlotInfo[] = []
300+
301+
for (const slots of slotArrays) {
302+
for (const slot of slots) {
303+
const key = `${slot.name}:${slot.parent}`
304+
if (!slotSet.has(key)) {
305+
slotSet.add(key)
306+
result.push(slot)
307+
}
308+
}
309+
}
310+
311+
return result
312+
}
313+
246314
/**
247315
* Process app routes from mapped app pages
248316
* @param mappedAppPages - The mapped app pages object
317+
* @param validFileMatcher - File matcher object
249318
* @param baseDir - The base directory path
319+
* @param isSrcDir - Whether the project uses src directory structure
250320
* @returns Array of route information
251321
*/
252322
export function processAppRoutes(
253323
mappedAppPages: { [page: string]: string },
254-
baseDir: string
255-
): RouteInfo[] {
324+
validFileMatcher: ReturnType<typeof createValidFileMatcher>,
325+
baseDir: string,
326+
isSrcDir: boolean
327+
): {
328+
appRoutes: RouteInfo[]
329+
appRouteHandlers: RouteInfo[]
330+
} {
256331
const appRoutes: RouteInfo[] = []
332+
const appRouteHandlers: RouteInfo[] = []
257333

258334
for (const [route, filePath] of Object.entries(mappedAppPages)) {
259335
if (route === '/_not-found/page') continue
260336

261-
const relativeFilePath = createRelativeFilePath(baseDir, filePath, 'app')
337+
const relativeFilePath = createRelativeFilePath(
338+
baseDir,
339+
filePath,
340+
'app',
341+
isSrcDir
342+
)
262343

263-
appRoutes.push({
264-
route: normalizeAppPath(normalizePathSep(route)),
265-
filePath: relativeFilePath,
266-
})
344+
if (validFileMatcher.isAppRouterRoute(filePath)) {
345+
appRouteHandlers.push({
346+
route: normalizeAppPath(normalizePathSep(route)),
347+
filePath: relativeFilePath,
348+
})
349+
} else {
350+
appRoutes.push({
351+
route: normalizeAppPath(normalizePathSep(route)),
352+
filePath: relativeFilePath,
353+
})
354+
}
267355
}
268356

269-
return appRoutes
357+
return { appRoutes, appRouteHandlers }
270358
}
271359

272360
/**
273361
* Process layout routes from mapped app layouts
274362
* @param mappedAppLayouts - The mapped app layouts object
275363
* @param baseDir - The base directory path
364+
* @param isSrcDir - Whether the project uses src directory structure
276365
* @returns Array of layout route information
277366
*/
278367
export function processLayoutRoutes(
279368
mappedAppLayouts: { [page: string]: string },
280-
baseDir: string
369+
baseDir: string,
370+
isSrcDir: boolean
281371
): RouteInfo[] {
282372
const layoutRoutes: RouteInfo[] = []
283373

284374
for (const [route, filePath] of Object.entries(mappedAppLayouts)) {
285-
const relativeFilePath = createRelativeFilePath(baseDir, filePath, 'app')
375+
const relativeFilePath = createRelativeFilePath(
376+
baseDir,
377+
filePath,
378+
'app',
379+
isSrcDir
380+
)
286381
layoutRoutes.push({
287382
route: ensureLeadingSlash(
288383
normalizeAppPath(normalizePathSep(route)).replace(/\/layout$/, '')

0 commit comments

Comments
 (0)