@@ -76,37 +76,36 @@ import type { MappedPages } from './build-context'
76
76
import { PAGE_TYPES } from '../lib/page-types'
77
77
import { isAppPageRoute } from '../lib/is-app-page-route'
78
78
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'
80
80
import { isReservedPage } from './utils'
81
81
import { isParallelRouteSegment } from '../shared/lib/segment'
82
82
import { ensureLeadingSlash } from '../shared/lib/page-path/ensure-leading-slash'
83
83
84
84
/**
85
- * Collect app pages and layouts from the app directory
85
+ * Collect app pages, layouts, and default files from the app directory
86
86
* @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
90
89
*/
91
90
export async function collectAppFiles (
92
91
appDir : string ,
93
- pageExtensions : PageExtensions
92
+ validFileMatcher : ReturnType < typeof createValidFileMatcher >
94
93
) : Promise < {
95
94
appPaths : string [ ]
96
95
layoutPaths : string [ ]
96
+ defaultPaths : string [ ]
97
97
} > {
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
101
99
const allAppFiles = await recursiveReadDir ( appDir , {
102
100
pathnameFilter : ( absolutePath ) =>
103
101
validFileMatcher . isAppRouterPage ( absolutePath ) ||
104
102
validFileMatcher . isRootNotFound ( absolutePath ) ||
105
- validFileMatcher . isAppLayoutPage ( absolutePath ) ,
103
+ validFileMatcher . isAppLayoutPage ( absolutePath ) ||
104
+ validFileMatcher . isAppDefaultPage ( absolutePath ) ,
106
105
ignorePartFilter : ( part ) => part . startsWith ( '_' ) ,
107
106
} )
108
107
109
- // Separate app pages from layouts
108
+ // Separate app pages, layouts, and defaults
110
109
const appPaths = allAppFiles . filter (
111
110
( absolutePath ) =>
112
111
validFileMatcher . isAppRouterPage ( absolutePath ) ||
@@ -115,26 +114,25 @@ export async function collectAppFiles(
115
114
const layoutPaths = allAppFiles . filter ( ( absolutePath ) =>
116
115
validFileMatcher . isAppLayoutPage ( absolutePath )
117
116
)
117
+ const defaultPaths = allAppFiles . filter ( ( absolutePath ) =>
118
+ validFileMatcher . isAppDefaultPage ( absolutePath )
119
+ )
118
120
119
- return { appPaths, layoutPaths }
121
+ return { appPaths, layoutPaths, defaultPaths }
120
122
}
121
123
122
124
/**
123
125
* Collect pages from the pages directory
124
126
* @param pagesDir - The pages directory path
125
- * @param pageExtensions - The configured page extensions
127
+ * @param validFileMatcher - File matcher object
126
128
* @returns Array of page file paths
127
129
*/
128
130
export async function collectPagesFiles (
129
131
pagesDir : string ,
130
- pageExtensions : PageExtensions
132
+ validFileMatcher : ReturnType < typeof createValidFileMatcher >
131
133
) : Promise < string [ ] > {
132
134
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 ,
138
136
} )
139
137
}
140
138
@@ -154,30 +152,35 @@ export type SlotInfo = {
154
152
* @param baseDir - The base directory path
155
153
* @param filePath - The mapped file path (with private prefix)
156
154
* @param prefix - The directory prefix ('pages' or 'app')
155
+ * @param isSrcDir - Whether the project uses src directory structure
157
156
* @returns The relative file path
158
157
*/
159
158
export function createRelativeFilePath (
160
159
baseDir : string ,
161
160
filePath : string ,
162
- prefix : 'pages' | 'app'
161
+ prefix : 'pages' | 'app' ,
162
+ isSrcDir : boolean
163
163
) : string {
164
164
const privatePrefix =
165
165
prefix === 'pages' ? 'private-next-pages' : 'private-next-app-dir'
166
+ const srcPrefix = isSrcDir ? 'src/' : ''
166
167
return join (
167
168
baseDir ,
168
- filePath . replace ( new RegExp ( `^${ privatePrefix } /` ) , `${ prefix } /` )
169
+ filePath . replace ( new RegExp ( `^${ privatePrefix } /` ) , `${ srcPrefix } ${ prefix } /` )
169
170
)
170
171
}
171
172
172
173
/**
173
174
* Process pages routes from mapped pages
174
175
* @param mappedPages - The mapped pages object
175
176
* @param baseDir - The base directory path
177
+ * @param isSrcDir - Whether the project uses src directory structure
176
178
* @returns Object containing pageRoutes and pageApiRoutes
177
179
*/
178
180
export function processPageRoutes (
179
181
mappedPages : { [ page : string ] : string } ,
180
- baseDir : string
182
+ baseDir : string ,
183
+ isSrcDir : boolean
181
184
) : {
182
185
pageRoutes : RouteInfo [ ]
183
186
pageApiRoutes : RouteInfo [ ]
@@ -186,7 +189,12 @@ export function processPageRoutes(
186
189
const pageApiRoutes : RouteInfo [ ] = [ ]
187
190
188
191
for ( const [ route , filePath ] of Object . entries ( mappedPages ) ) {
189
- const relativeFilePath = createRelativeFilePath ( baseDir , filePath , 'pages' )
192
+ const relativeFilePath = createRelativeFilePath (
193
+ baseDir ,
194
+ filePath ,
195
+ 'pages' ,
196
+ isSrcDir
197
+ )
190
198
191
199
if ( route . startsWith ( '/api/' ) ) {
192
200
pageApiRoutes . push ( {
@@ -243,46 +251,129 @@ export function extractSlotsFromAppRoutes(mappedAppPages: {
243
251
return slots
244
252
}
245
253
254
+ /**
255
+ * Extract slots from default files
256
+ * @param mappedDefaultFiles - The mapped default files object
257
+ * @returns Array of slot information
258
+ */
259
+ export function extractSlotsFromDefaultFiles ( mappedDefaultFiles : {
260
+ [ page : string ] : string
261
+ } ) : SlotInfo [ ] {
262
+ const slots : SlotInfo [ ] = [ ]
263
+
264
+ for ( const [ route ] of Object . entries ( mappedDefaultFiles ) ) {
265
+ const segments = route . split ( '/' )
266
+ for ( let i = segments . length - 1 ; i >= 0 ; i -- ) {
267
+ const segment = segments [ i ]
268
+ if ( isParallelRouteSegment ( segment ) ) {
269
+ const parentPath = normalizeAppPath ( segments . slice ( 0 , i ) . join ( '/' ) )
270
+ const slotName = segment . slice ( 1 )
271
+
272
+ // Check if the slot already exists
273
+ if ( slots . some ( ( s ) => s . name === slotName && s . parent === parentPath ) )
274
+ continue
275
+
276
+ slots . push ( {
277
+ name : slotName ,
278
+ parent : parentPath ,
279
+ } )
280
+ break
281
+ }
282
+ }
283
+ }
284
+
285
+ return slots
286
+ }
287
+
288
+ /**
289
+ * Combine and deduplicate slot arrays using a Set
290
+ * @param slotArrays - Arrays of slot information to combine
291
+ * @returns Deduplicated array of slots
292
+ */
293
+ export function combineSlots ( ...slotArrays : SlotInfo [ ] [ ] ) : SlotInfo [ ] {
294
+ const slotSet = new Set < string > ( )
295
+ const result : SlotInfo [ ] = [ ]
296
+
297
+ for ( const slots of slotArrays ) {
298
+ for ( const slot of slots ) {
299
+ const key = `${ slot . name } :${ slot . parent } `
300
+ if ( ! slotSet . has ( key ) ) {
301
+ slotSet . add ( key )
302
+ result . push ( slot )
303
+ }
304
+ }
305
+ }
306
+
307
+ return result
308
+ }
309
+
246
310
/**
247
311
* Process app routes from mapped app pages
248
312
* @param mappedAppPages - The mapped app pages object
313
+ * @param validFileMatcher - File matcher object
249
314
* @param baseDir - The base directory path
315
+ * @param isSrcDir - Whether the project uses src directory structure
250
316
* @returns Array of route information
251
317
*/
252
318
export function processAppRoutes (
253
319
mappedAppPages : { [ page : string ] : string } ,
254
- baseDir : string
255
- ) : RouteInfo [ ] {
320
+ validFileMatcher : ReturnType < typeof createValidFileMatcher > ,
321
+ baseDir : string ,
322
+ isSrcDir : boolean
323
+ ) : {
324
+ appRoutes : RouteInfo [ ]
325
+ appRouteHandlers : RouteInfo [ ]
326
+ } {
256
327
const appRoutes : RouteInfo [ ] = [ ]
328
+ const appRouteHandlers : RouteInfo [ ] = [ ]
257
329
258
330
for ( const [ route , filePath ] of Object . entries ( mappedAppPages ) ) {
259
331
if ( route === '/_not-found/page' ) continue
260
332
261
- const relativeFilePath = createRelativeFilePath ( baseDir , filePath , 'app' )
333
+ const relativeFilePath = createRelativeFilePath (
334
+ baseDir ,
335
+ filePath ,
336
+ 'app' ,
337
+ isSrcDir
338
+ )
262
339
263
- appRoutes . push ( {
264
- route : normalizeAppPath ( normalizePathSep ( route ) ) ,
265
- filePath : relativeFilePath ,
266
- } )
340
+ if ( validFileMatcher . isAppRouterRoute ( filePath ) ) {
341
+ appRouteHandlers . push ( {
342
+ route : normalizeAppPath ( normalizePathSep ( route ) ) ,
343
+ filePath : relativeFilePath ,
344
+ } )
345
+ } else {
346
+ appRoutes . push ( {
347
+ route : normalizeAppPath ( normalizePathSep ( route ) ) ,
348
+ filePath : relativeFilePath ,
349
+ } )
350
+ }
267
351
}
268
352
269
- return appRoutes
353
+ return { appRoutes, appRouteHandlers }
270
354
}
271
355
272
356
/**
273
357
* Process layout routes from mapped app layouts
274
358
* @param mappedAppLayouts - The mapped app layouts object
275
359
* @param baseDir - The base directory path
360
+ * @param isSrcDir - Whether the project uses src directory structure
276
361
* @returns Array of layout route information
277
362
*/
278
363
export function processLayoutRoutes (
279
364
mappedAppLayouts : { [ page : string ] : string } ,
280
- baseDir : string
365
+ baseDir : string ,
366
+ isSrcDir : boolean
281
367
) : RouteInfo [ ] {
282
368
const layoutRoutes : RouteInfo [ ] = [ ]
283
369
284
370
for ( const [ route , filePath ] of Object . entries ( mappedAppLayouts ) ) {
285
- const relativeFilePath = createRelativeFilePath ( baseDir , filePath , 'app' )
371
+ const relativeFilePath = createRelativeFilePath (
372
+ baseDir ,
373
+ filePath ,
374
+ 'app' ,
375
+ isSrcDir
376
+ )
286
377
layoutRoutes . push ( {
287
378
route : ensureLeadingSlash (
288
379
normalizeAppPath ( normalizePathSep ( route ) ) . replace ( / \/ l a y o u t $ / , '' )
0 commit comments