vinext 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/static-export.d.ts.map +1 -1
- package/dist/build/static-export.js +9 -7
- package/dist/build/static-export.js.map +1 -1
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +13 -3
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +4 -1
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js +10 -5
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +83 -24
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +36 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -40
- package/dist/index.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -1
- package/dist/routing/app-router.d.ts.map +1 -1
- package/dist/routing/app-router.js +73 -66
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +24 -0
- package/dist/routing/file-matcher.d.ts.map +1 -0
- package/dist/routing/file-matcher.js +75 -0
- package/dist/routing/file-matcher.js.map +1 -0
- package/dist/routing/pages-router.d.ts +3 -2
- package/dist/routing/pages-router.d.ts.map +1 -1
- package/dist/routing/pages-router.js +24 -17
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +110 -64
- package/dist/server/app-dev-server.js.map +1 -1
- package/dist/server/dev-server.d.ts +2 -1
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +16 -14
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/prod-server.d.ts +8 -2
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +71 -16
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/worker-utils.d.ts +15 -0
- package/dist/server/worker-utils.d.ts.map +1 -0
- package/dist/server/worker-utils.js +41 -0
- package/dist/server/worker-utils.js.map +1 -0
- package/dist/shims/cache.d.ts +1 -1
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +8 -3
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/headers.d.ts +6 -0
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +8 -0
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/metadata.d.ts +1 -0
- package/dist/shims/metadata.d.ts.map +1 -1
- package/dist/shims/metadata.js +5 -1
- package/dist/shims/metadata.js.map +1 -1
- package/dist/utils/project.d.ts +13 -1
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/project.js +63 -13
- package/dist/utils/project.js.map +1 -1
- package/package.json +6 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ValidFileMatcher } from "./file-matcher.js";
|
|
1
2
|
export interface InterceptingRoute {
|
|
2
3
|
/** The interception convention: "." | ".." | "../.." | "..." */
|
|
3
4
|
convention: string;
|
|
@@ -85,7 +86,7 @@ export declare function invalidateAppRouteCache(): void;
|
|
|
85
86
|
/**
|
|
86
87
|
* Scan the app/ directory and return a list of routes.
|
|
87
88
|
*/
|
|
88
|
-
export declare function appRouter(appDir: string): Promise<AppRoute[]>;
|
|
89
|
+
export declare function appRouter(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRoute[]>;
|
|
89
90
|
/**
|
|
90
91
|
* Match a URL against App Router routes.
|
|
91
92
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-router.d.ts","sourceRoot":"","sources":["../../src/routing/app-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-router.d.ts","sourceRoot":"","sources":["../../src/routing/app-router.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2CAA2C;IAC3C,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mFAAmF;IACnF,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACpC,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACjC,qCAAqC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAOD,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAuDrB;AAk2BD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,QAAQ,EAAE,GACjB;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CAiBvE"}
|
|
@@ -15,34 +15,41 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import fs from "node:fs";
|
|
18
|
-
import { glob } from "node:fs/promises";
|
|
19
18
|
import { routePrecedence } from "./utils.js";
|
|
19
|
+
import { createValidFileMatcher, scanWithExtensions, } from "./file-matcher.js";
|
|
20
20
|
// Cache for app routes
|
|
21
21
|
let cachedRoutes = null;
|
|
22
22
|
let cachedAppDir = null;
|
|
23
|
+
let cachedPageExtensionsKey = null;
|
|
23
24
|
export function invalidateAppRouteCache() {
|
|
24
25
|
cachedRoutes = null;
|
|
25
26
|
cachedAppDir = null;
|
|
27
|
+
cachedPageExtensionsKey = null;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Scan the app/ directory and return a list of routes.
|
|
29
31
|
*/
|
|
30
|
-
export async function appRouter(appDir) {
|
|
31
|
-
|
|
32
|
+
export async function appRouter(appDir, pageExtensions, matcher) {
|
|
33
|
+
matcher ??= createValidFileMatcher(pageExtensions);
|
|
34
|
+
const pageExtensionsKey = JSON.stringify(matcher.extensions);
|
|
35
|
+
if (cachedRoutes &&
|
|
36
|
+
cachedAppDir === appDir &&
|
|
37
|
+
cachedPageExtensionsKey === pageExtensionsKey) {
|
|
32
38
|
return cachedRoutes;
|
|
39
|
+
}
|
|
33
40
|
// Find all page.tsx and route.ts files, excluding @slot directories
|
|
34
41
|
// (slot pages are not standalone routes — they're rendered as props of their parent layout)
|
|
35
42
|
const routes = [];
|
|
36
43
|
// Process page files in a single pass
|
|
37
44
|
// Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)
|
|
38
|
-
for await (const file of
|
|
39
|
-
const route = fileToAppRoute(file, appDir, "page");
|
|
45
|
+
for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions, (name) => name.startsWith("@"))) {
|
|
46
|
+
const route = fileToAppRoute(file, appDir, "page", matcher);
|
|
40
47
|
if (route)
|
|
41
48
|
routes.push(route);
|
|
42
49
|
}
|
|
43
50
|
// Process route handler files (API routes) in a single pass
|
|
44
|
-
for await (const file of
|
|
45
|
-
const route = fileToAppRoute(file, appDir, "route");
|
|
51
|
+
for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions, (name) => name.startsWith("@"))) {
|
|
52
|
+
const route = fileToAppRoute(file, appDir, "route", matcher);
|
|
46
53
|
if (route)
|
|
47
54
|
routes.push(route);
|
|
48
55
|
}
|
|
@@ -50,7 +57,7 @@ export async function appRouter(appDir) {
|
|
|
50
57
|
// In Next.js, pages nested inside @slot directories create additional URL routes.
|
|
51
58
|
// For example, @audience/demographics/page.tsx at app/parallel-routes/ creates
|
|
52
59
|
// a route at /parallel-routes/demographics.
|
|
53
|
-
const slotSubRoutes = discoverSlotSubRoutes(routes, appDir);
|
|
60
|
+
const slotSubRoutes = discoverSlotSubRoutes(routes, appDir, matcher);
|
|
54
61
|
routes.push(...slotSubRoutes);
|
|
55
62
|
// Sort: static routes first, then dynamic, then catch-all
|
|
56
63
|
routes.sort((a, b) => {
|
|
@@ -59,6 +66,7 @@ export async function appRouter(appDir) {
|
|
|
59
66
|
});
|
|
60
67
|
cachedRoutes = routes;
|
|
61
68
|
cachedAppDir = appDir;
|
|
69
|
+
cachedPageExtensionsKey = pageExtensionsKey;
|
|
62
70
|
return routes;
|
|
63
71
|
}
|
|
64
72
|
/**
|
|
@@ -72,7 +80,7 @@ export async function appRouter(appDir) {
|
|
|
72
80
|
* - @audience slot → @audience/demographics/page.tsx (matched)
|
|
73
81
|
* - other slots → their default.tsx (fallback)
|
|
74
82
|
*/
|
|
75
|
-
function discoverSlotSubRoutes(routes, _appDir) {
|
|
83
|
+
function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
76
84
|
const syntheticRoutes = [];
|
|
77
85
|
const existingPatterns = new Set(routes.map((r) => r.pattern));
|
|
78
86
|
for (const parentRoute of routes) {
|
|
@@ -88,7 +96,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
88
96
|
const slotDir = path.join(parentPageDir, `@${slot.name}`);
|
|
89
97
|
if (!fs.existsSync(slotDir))
|
|
90
98
|
continue;
|
|
91
|
-
const subPages = findSlotSubPages(slotDir);
|
|
99
|
+
const subPages = findSlotSubPages(slotDir, matcher);
|
|
92
100
|
for (const { relativePath, pagePath } of subPages) {
|
|
93
101
|
if (!subPathMap.has(relativePath)) {
|
|
94
102
|
subPathMap.set(relativePath, new Map());
|
|
@@ -99,7 +107,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
99
107
|
if (subPathMap.size === 0)
|
|
100
108
|
continue;
|
|
101
109
|
// Find the default.tsx for the children slot at the parent directory
|
|
102
|
-
const childrenDefault = findFile(parentPageDir, "default");
|
|
110
|
+
const childrenDefault = findFile(parentPageDir, "default", matcher);
|
|
103
111
|
for (const [subPath, slotPages] of subPathMap) {
|
|
104
112
|
// Convert sub-path segments to URL pattern parts
|
|
105
113
|
const subSegments = subPath.split(path.sep);
|
|
@@ -176,7 +184,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
176
184
|
* Skips the root page.tsx (already handled as the slot's main page)
|
|
177
185
|
* and intercepting route directories.
|
|
178
186
|
*/
|
|
179
|
-
function findSlotSubPages(slotDir) {
|
|
187
|
+
function findSlotSubPages(slotDir, matcher) {
|
|
180
188
|
const results = [];
|
|
181
189
|
function scan(dir) {
|
|
182
190
|
if (!fs.existsSync(dir))
|
|
@@ -192,7 +200,7 @@ function findSlotSubPages(slotDir) {
|
|
|
192
200
|
if (entry.name.startsWith("_"))
|
|
193
201
|
continue;
|
|
194
202
|
const subDir = path.join(dir, entry.name);
|
|
195
|
-
const page = findFile(subDir, "page");
|
|
203
|
+
const page = findFile(subDir, "page", matcher);
|
|
196
204
|
if (page) {
|
|
197
205
|
const relativePath = path.relative(slotDir, subDir);
|
|
198
206
|
results.push({ relativePath, pagePath: page });
|
|
@@ -207,7 +215,7 @@ function findSlotSubPages(slotDir) {
|
|
|
207
215
|
/**
|
|
208
216
|
* Convert a file path relative to app/ into an AppRoute.
|
|
209
217
|
*/
|
|
210
|
-
function fileToAppRoute(file, appDir, type) {
|
|
218
|
+
function fileToAppRoute(file, appDir, type, matcher) {
|
|
211
219
|
// Remove the filename (page.tsx or route.ts)
|
|
212
220
|
const dir = path.dirname(file);
|
|
213
221
|
const segments = dir === "." ? [] : dir.split(path.sep);
|
|
@@ -257,33 +265,33 @@ function fileToAppRoute(file, appDir, type) {
|
|
|
257
265
|
}
|
|
258
266
|
const pattern = "/" + urlSegments.join("/");
|
|
259
267
|
// Discover layouts and templates from root to leaf
|
|
260
|
-
const layouts = discoverLayouts(segments, appDir);
|
|
261
|
-
const templates = discoverTemplates(segments, appDir);
|
|
268
|
+
const layouts = discoverLayouts(segments, appDir, matcher);
|
|
269
|
+
const templates = discoverTemplates(segments, appDir, matcher);
|
|
262
270
|
// Compute the URL segment depth for each layout.
|
|
263
271
|
// Each layout corresponds to a directory level. We need to count how many
|
|
264
272
|
// of the filesystem segments up to that layout's level contribute URL segments
|
|
265
273
|
// (i.e., are not route groups or parallel slots).
|
|
266
|
-
const layoutSegmentDepths = computeLayoutSegmentDepths(segments, appDir, layouts);
|
|
274
|
+
const layoutSegmentDepths = computeLayoutSegmentDepths(segments, appDir, layouts, matcher);
|
|
267
275
|
// Discover per-layout error boundaries (aligned with layouts array).
|
|
268
276
|
// In Next.js, each segment independently wraps its children with an ErrorBoundary.
|
|
269
277
|
// This array enables interleaving error boundaries with layouts in the rendering.
|
|
270
|
-
const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir);
|
|
278
|
+
const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher);
|
|
271
279
|
// Discover loading, error in the route's directory
|
|
272
280
|
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
273
|
-
const loadingPath = findFile(routeDir, "loading");
|
|
274
|
-
const errorPath = findFile(routeDir, "error");
|
|
281
|
+
const loadingPath = findFile(routeDir, "loading", matcher);
|
|
282
|
+
const errorPath = findFile(routeDir, "error", matcher);
|
|
275
283
|
// Discover not-found/forbidden/unauthorized: walk from route directory up to root (nearest wins).
|
|
276
|
-
const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found");
|
|
277
|
-
const forbiddenPath = discoverBoundaryFile(segments, appDir, "forbidden");
|
|
278
|
-
const unauthorizedPath = discoverBoundaryFile(segments, appDir, "unauthorized");
|
|
284
|
+
const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
|
|
285
|
+
const forbiddenPath = discoverBoundaryFile(segments, appDir, "forbidden", matcher);
|
|
286
|
+
const unauthorizedPath = discoverBoundaryFile(segments, appDir, "unauthorized", matcher);
|
|
279
287
|
// Discover per-layout not-found files (one per layout directory).
|
|
280
288
|
// These are used for per-layout NotFoundBoundary to match Next.js behavior where
|
|
281
289
|
// notFound() thrown from a layout is caught by the parent layout's boundary.
|
|
282
|
-
const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found");
|
|
290
|
+
const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
|
|
283
291
|
// Discover parallel slots (@team, @analytics, etc.).
|
|
284
292
|
// Slots at the route's own directory use page.tsx; slots at ancestor directories
|
|
285
293
|
// (inherited from parent layouts) use default.tsx as fallback.
|
|
286
|
-
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir);
|
|
294
|
+
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
|
|
287
295
|
return {
|
|
288
296
|
pattern: pattern === "/" ? "/" : pattern,
|
|
289
297
|
pagePath: type === "page" ? path.join(appDir, file) : null,
|
|
@@ -308,12 +316,12 @@ function fileToAppRoute(file, appDir, type) {
|
|
|
308
316
|
* Root layout = 0, then each directory level that contributes a URL segment
|
|
309
317
|
* increments the depth. Route groups and parallel slots don't contribute.
|
|
310
318
|
*/
|
|
311
|
-
function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
319
|
+
function computeLayoutSegmentDepths(segments, appDir, layouts, matcher) {
|
|
312
320
|
// Build a map: layout file path → depth in URL segments
|
|
313
321
|
// Walk the segments directory-by-directory, tracking cumulative URL depth
|
|
314
322
|
const depthMap = new Map();
|
|
315
323
|
// Root layout (at appDir) always has depth 0
|
|
316
|
-
const rootLayout = findFile(appDir, "layout");
|
|
324
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
317
325
|
if (rootLayout)
|
|
318
326
|
depthMap.set(rootLayout, 0);
|
|
319
327
|
let urlDepth = 0;
|
|
@@ -326,7 +334,7 @@ function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
|
326
334
|
if (!isRouteGroup && !isParallelSlot) {
|
|
327
335
|
urlDepth++;
|
|
328
336
|
}
|
|
329
|
-
const layout = findFile(currentDir, "layout");
|
|
337
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
330
338
|
if (layout) {
|
|
331
339
|
depthMap.set(layout, urlDepth);
|
|
332
340
|
}
|
|
@@ -338,17 +346,17 @@ function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
|
338
346
|
* Discover all layout files from root to the given directory.
|
|
339
347
|
* Each level of the directory tree may have a layout.tsx.
|
|
340
348
|
*/
|
|
341
|
-
function discoverLayouts(segments, appDir) {
|
|
349
|
+
function discoverLayouts(segments, appDir, matcher) {
|
|
342
350
|
const layouts = [];
|
|
343
351
|
// Check root layout
|
|
344
|
-
const rootLayout = findFile(appDir, "layout");
|
|
352
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
345
353
|
if (rootLayout)
|
|
346
354
|
layouts.push(rootLayout);
|
|
347
355
|
// Check each directory level
|
|
348
356
|
let currentDir = appDir;
|
|
349
357
|
for (const segment of segments) {
|
|
350
358
|
currentDir = path.join(currentDir, segment);
|
|
351
|
-
const layout = findFile(currentDir, "layout");
|
|
359
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
352
360
|
if (layout)
|
|
353
361
|
layouts.push(layout);
|
|
354
362
|
}
|
|
@@ -359,17 +367,17 @@ function discoverLayouts(segments, appDir) {
|
|
|
359
367
|
* Each level of the directory tree may have a template.tsx.
|
|
360
368
|
* Templates are like layouts but re-mount on navigation.
|
|
361
369
|
*/
|
|
362
|
-
function discoverTemplates(segments, appDir) {
|
|
370
|
+
function discoverTemplates(segments, appDir, matcher) {
|
|
363
371
|
const templates = [];
|
|
364
372
|
// Check root template
|
|
365
|
-
const rootTemplate = findFile(appDir, "template");
|
|
373
|
+
const rootTemplate = findFile(appDir, "template", matcher);
|
|
366
374
|
if (rootTemplate)
|
|
367
375
|
templates.push(rootTemplate);
|
|
368
376
|
// Check each directory level
|
|
369
377
|
let currentDir = appDir;
|
|
370
378
|
for (const segment of segments) {
|
|
371
379
|
currentDir = path.join(currentDir, segment);
|
|
372
|
-
const template = findFile(currentDir, "template");
|
|
380
|
+
const template = findFile(currentDir, "template", matcher);
|
|
373
381
|
if (template)
|
|
374
382
|
templates.push(template);
|
|
375
383
|
}
|
|
@@ -386,20 +394,20 @@ function discoverTemplates(segments, appDir) {
|
|
|
386
394
|
* rendering tree, matching Next.js behavior where each segment independently
|
|
387
395
|
* wraps its children with an error boundary.
|
|
388
396
|
*/
|
|
389
|
-
function discoverLayoutAlignedErrors(segments, appDir) {
|
|
397
|
+
function discoverLayoutAlignedErrors(segments, appDir, matcher) {
|
|
390
398
|
const errors = [];
|
|
391
399
|
// Root level (only if root has a layout — matching discoverLayouts logic)
|
|
392
|
-
const rootLayout = findFile(appDir, "layout");
|
|
400
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
393
401
|
if (rootLayout) {
|
|
394
|
-
errors.push(findFile(appDir, "error"));
|
|
402
|
+
errors.push(findFile(appDir, "error", matcher));
|
|
395
403
|
}
|
|
396
404
|
// Check each directory level
|
|
397
405
|
let currentDir = appDir;
|
|
398
406
|
for (const segment of segments) {
|
|
399
407
|
currentDir = path.join(currentDir, segment);
|
|
400
|
-
const layout = findFile(currentDir, "layout");
|
|
408
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
401
409
|
if (layout) {
|
|
402
|
-
errors.push(findFile(currentDir, "error"));
|
|
410
|
+
errors.push(findFile(currentDir, "error", matcher));
|
|
403
411
|
}
|
|
404
412
|
}
|
|
405
413
|
return errors;
|
|
@@ -409,7 +417,7 @@ function discoverLayoutAlignedErrors(segments, appDir) {
|
|
|
409
417
|
* by walking from the route's directory up to the app root.
|
|
410
418
|
* Returns the first (closest) file found, or null.
|
|
411
419
|
*/
|
|
412
|
-
function discoverBoundaryFile(segments, appDir, fileName) {
|
|
420
|
+
function discoverBoundaryFile(segments, appDir, fileName, matcher) {
|
|
413
421
|
// Build all directory paths from leaf to root
|
|
414
422
|
const dirs = [];
|
|
415
423
|
let dir = appDir;
|
|
@@ -420,7 +428,7 @@ function discoverBoundaryFile(segments, appDir, fileName) {
|
|
|
420
428
|
}
|
|
421
429
|
// Walk from leaf (last) to root (first)
|
|
422
430
|
for (let i = dirs.length - 1; i >= 0; i--) {
|
|
423
|
-
const f = findFile(dirs[i], fileName);
|
|
431
|
+
const f = findFile(dirs[i], fileName, matcher);
|
|
424
432
|
if (f)
|
|
425
433
|
return f;
|
|
426
434
|
}
|
|
@@ -435,10 +443,10 @@ function discoverBoundaryFile(segments, appDir, fileName) {
|
|
|
435
443
|
* has its own boundary that wraps the layout's children. When notFound() is thrown
|
|
436
444
|
* from a layout, it propagates up to the parent layout's boundary.
|
|
437
445
|
*/
|
|
438
|
-
function discoverBoundaryFilePerLayout(layouts, fileName) {
|
|
446
|
+
function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
|
|
439
447
|
return layouts.map((layoutPath) => {
|
|
440
448
|
const layoutDir = path.dirname(layoutPath);
|
|
441
|
-
return findFile(layoutDir, fileName);
|
|
449
|
+
return findFile(layoutDir, fileName, matcher);
|
|
442
450
|
});
|
|
443
451
|
}
|
|
444
452
|
/**
|
|
@@ -453,25 +461,25 @@ function discoverBoundaryFilePerLayout(layouts, fileName) {
|
|
|
453
461
|
* that has @slot dirs, collect them. Slots at the route's own directory level
|
|
454
462
|
* use page.tsx; slots at ancestor levels use default.tsx only.
|
|
455
463
|
*/
|
|
456
|
-
function discoverInheritedParallelSlots(segments, appDir, routeDir) {
|
|
464
|
+
function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
457
465
|
const slotMap = new Map();
|
|
458
466
|
// Walk from appDir through each segment, tracking layout indices.
|
|
459
467
|
// layoutIndex tracks which position in the route's layouts[] array corresponds
|
|
460
468
|
// to a given directory. Only directories with a layout.tsx file increment.
|
|
461
469
|
let currentDir = appDir;
|
|
462
470
|
const dirsToCheck = [];
|
|
463
|
-
let layoutIdx = findFile(appDir, "layout") ? 0 : -1;
|
|
471
|
+
let layoutIdx = findFile(appDir, "layout", matcher) ? 0 : -1;
|
|
464
472
|
dirsToCheck.push({ dir: appDir, layoutIdx: Math.max(layoutIdx, 0) });
|
|
465
473
|
for (const segment of segments) {
|
|
466
474
|
currentDir = path.join(currentDir, segment);
|
|
467
|
-
if (findFile(currentDir, "layout")) {
|
|
475
|
+
if (findFile(currentDir, "layout", matcher)) {
|
|
468
476
|
layoutIdx++;
|
|
469
477
|
}
|
|
470
478
|
dirsToCheck.push({ dir: currentDir, layoutIdx: Math.max(layoutIdx, 0) });
|
|
471
479
|
}
|
|
472
480
|
for (const { dir, layoutIdx: lvlLayoutIdx } of dirsToCheck) {
|
|
473
481
|
const isOwnDir = dir === routeDir;
|
|
474
|
-
const slotsAtLevel = discoverParallelSlots(dir, appDir);
|
|
482
|
+
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
|
|
475
483
|
for (const slot of slotsAtLevel) {
|
|
476
484
|
if (isOwnDir) {
|
|
477
485
|
// At the route's own directory: use page.tsx (normal behavior)
|
|
@@ -500,7 +508,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir) {
|
|
|
500
508
|
* Discover parallel route slots (@team, @analytics, etc.) in a directory.
|
|
501
509
|
* Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
|
|
502
510
|
*/
|
|
503
|
-
function discoverParallelSlots(dir, appDir) {
|
|
511
|
+
function discoverParallelSlots(dir, appDir, matcher) {
|
|
504
512
|
if (!fs.existsSync(dir))
|
|
505
513
|
return [];
|
|
506
514
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -510,9 +518,9 @@ function discoverParallelSlots(dir, appDir) {
|
|
|
510
518
|
continue;
|
|
511
519
|
const slotName = entry.name.slice(1); // "@team" -> "team"
|
|
512
520
|
const slotDir = path.join(dir, entry.name);
|
|
513
|
-
const pagePath = findFile(slotDir, "page");
|
|
514
|
-
const defaultPath = findFile(slotDir, "default");
|
|
515
|
-
const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir);
|
|
521
|
+
const pagePath = findFile(slotDir, "page", matcher);
|
|
522
|
+
const defaultPath = findFile(slotDir, "default", matcher);
|
|
523
|
+
const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
|
|
516
524
|
// Only include slots that have at least a page, default, or intercepting route
|
|
517
525
|
if (!pagePath && !defaultPath && interceptingRoutes.length === 0)
|
|
518
526
|
continue;
|
|
@@ -520,9 +528,9 @@ function discoverParallelSlots(dir, appDir) {
|
|
|
520
528
|
name: slotName,
|
|
521
529
|
pagePath,
|
|
522
530
|
defaultPath,
|
|
523
|
-
layoutPath: findFile(slotDir, "layout"),
|
|
524
|
-
loadingPath: findFile(slotDir, "loading"),
|
|
525
|
-
errorPath: findFile(slotDir, "error"),
|
|
531
|
+
layoutPath: findFile(slotDir, "layout", matcher),
|
|
532
|
+
loadingPath: findFile(slotDir, "loading", matcher),
|
|
533
|
+
errorPath: findFile(slotDir, "error", matcher),
|
|
526
534
|
interceptingRoutes,
|
|
527
535
|
layoutIndex: -1, // Will be set by discoverInheritedParallelSlots
|
|
528
536
|
});
|
|
@@ -549,19 +557,19 @@ const INTERCEPT_PATTERNS = [
|
|
|
549
557
|
* @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
|
|
550
558
|
* @param appDir - The root app directory
|
|
551
559
|
*/
|
|
552
|
-
function discoverInterceptingRoutes(slotDir, routeDir, appDir) {
|
|
560
|
+
function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
|
|
553
561
|
if (!fs.existsSync(slotDir))
|
|
554
562
|
return [];
|
|
555
563
|
const results = [];
|
|
556
564
|
// Recursively scan for page files inside intercepting directories
|
|
557
|
-
scanForInterceptingPages(slotDir, routeDir, appDir, results);
|
|
565
|
+
scanForInterceptingPages(slotDir, routeDir, appDir, results, matcher);
|
|
558
566
|
return results;
|
|
559
567
|
}
|
|
560
568
|
/**
|
|
561
569
|
* Recursively scan a directory tree for page.tsx files that are inside
|
|
562
570
|
* intercepting route directories.
|
|
563
571
|
*/
|
|
564
|
-
function scanForInterceptingPages(currentDir, routeDir, appDir, results) {
|
|
572
|
+
function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
|
|
565
573
|
if (!fs.existsSync(currentDir))
|
|
566
574
|
return;
|
|
567
575
|
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
@@ -576,11 +584,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results) {
|
|
|
576
584
|
const restOfName = entry.name.slice(interceptMatch.prefix.length);
|
|
577
585
|
const interceptDir = path.join(currentDir, entry.name);
|
|
578
586
|
// Find page files within this intercepting directory tree
|
|
579
|
-
collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, results);
|
|
587
|
+
collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, results, matcher);
|
|
580
588
|
}
|
|
581
589
|
else {
|
|
582
590
|
// Regular subdirectory — keep scanning for intercepting dirs
|
|
583
|
-
scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results);
|
|
591
|
+
scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results, matcher);
|
|
584
592
|
}
|
|
585
593
|
}
|
|
586
594
|
}
|
|
@@ -599,9 +607,9 @@ function matchInterceptConvention(name) {
|
|
|
599
607
|
* Collect page.tsx files inside an intercepting route directory tree
|
|
600
608
|
* and compute their target URL patterns.
|
|
601
609
|
*/
|
|
602
|
-
function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, results) {
|
|
610
|
+
function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, results, matcher) {
|
|
603
611
|
// Check for page.tsx in current directory
|
|
604
|
-
const page = findFile(currentDir, "page");
|
|
612
|
+
const page = findFile(currentDir, "page", matcher);
|
|
605
613
|
if (page) {
|
|
606
614
|
const targetPattern = computeInterceptTarget(convention, interceptSegment, currentDir, interceptRoot, routeDir, appDir);
|
|
607
615
|
if (targetPattern) {
|
|
@@ -620,7 +628,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
|
|
|
620
628
|
for (const entry of entries) {
|
|
621
629
|
if (!entry.isDirectory())
|
|
622
630
|
continue;
|
|
623
|
-
collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, results);
|
|
631
|
+
collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, results, matcher);
|
|
624
632
|
}
|
|
625
633
|
}
|
|
626
634
|
/**
|
|
@@ -704,11 +712,10 @@ function computeInterceptTarget(convention, interceptSegment, currentDir, interc
|
|
|
704
712
|
}
|
|
705
713
|
/**
|
|
706
714
|
* Find a file by name (without extension) in a directory.
|
|
707
|
-
* Checks
|
|
715
|
+
* Checks configured pageExtensions.
|
|
708
716
|
*/
|
|
709
|
-
function findFile(dir, name) {
|
|
710
|
-
const
|
|
711
|
-
for (const ext of extensions) {
|
|
717
|
+
function findFile(dir, name, matcher) {
|
|
718
|
+
for (const ext of matcher.dottedExtensions) {
|
|
712
719
|
const filePath = path.join(dir, name + ext);
|
|
713
720
|
if (fs.existsSync(filePath))
|
|
714
721
|
return filePath;
|