vinext 0.0.21 → 0.0.23
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/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 +17 -4
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +381 -38
- 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 +74 -107
- 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 +25 -44
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/utils.d.ts +25 -0
- package/dist/routing/utils.d.ts.map +1 -0
- package/dist/routing/utils.js +70 -0
- package/dist/routing/utils.js.map +1 -0
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +123 -47
- 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 +93 -18
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +33 -2
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-log.d.ts +34 -0
- package/dist/server/request-log.d.ts.map +1 -0
- package/dist/server/request-log.js +65 -0
- package/dist/server/request-log.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -1
- package/dist/shims/cache-runtime.js +5 -1
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +7 -1
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +30 -5
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/head.d.ts +11 -0
- package/dist/shims/head.d.ts.map +1 -1
- package/dist/shims/head.js +21 -0
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +8 -0
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +41 -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/shims/script.d.ts.map +1 -1
- package/dist/shims/script.js +7 -1
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts.map +1 -1
- package/dist/shims/server.js +2 -1
- package/dist/shims/server.js.map +1 -1
- package/package.json +1 -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,33 +15,41 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import fs from "node:fs";
|
|
18
|
-
import {
|
|
18
|
+
import { routePrecedence } from "./utils.js";
|
|
19
|
+
import { createValidFileMatcher, scanWithExtensions, } from "./file-matcher.js";
|
|
19
20
|
// Cache for app routes
|
|
20
21
|
let cachedRoutes = null;
|
|
21
22
|
let cachedAppDir = null;
|
|
23
|
+
let cachedPageExtensionsKey = null;
|
|
22
24
|
export function invalidateAppRouteCache() {
|
|
23
25
|
cachedRoutes = null;
|
|
24
26
|
cachedAppDir = null;
|
|
27
|
+
cachedPageExtensionsKey = null;
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Scan the app/ directory and return a list of routes.
|
|
28
31
|
*/
|
|
29
|
-
export async function appRouter(appDir) {
|
|
30
|
-
|
|
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) {
|
|
31
38
|
return cachedRoutes;
|
|
39
|
+
}
|
|
32
40
|
// Find all page.tsx and route.ts files, excluding @slot directories
|
|
33
41
|
// (slot pages are not standalone routes — they're rendered as props of their parent layout)
|
|
34
42
|
const routes = [];
|
|
35
43
|
// Process page files in a single pass
|
|
36
44
|
// Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)
|
|
37
|
-
for await (const file of
|
|
38
|
-
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);
|
|
39
47
|
if (route)
|
|
40
48
|
routes.push(route);
|
|
41
49
|
}
|
|
42
50
|
// Process route handler files (API routes) in a single pass
|
|
43
|
-
for await (const file of
|
|
44
|
-
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);
|
|
45
53
|
if (route)
|
|
46
54
|
routes.push(route);
|
|
47
55
|
}
|
|
@@ -49,7 +57,7 @@ export async function appRouter(appDir) {
|
|
|
49
57
|
// In Next.js, pages nested inside @slot directories create additional URL routes.
|
|
50
58
|
// For example, @audience/demographics/page.tsx at app/parallel-routes/ creates
|
|
51
59
|
// a route at /parallel-routes/demographics.
|
|
52
|
-
const slotSubRoutes = discoverSlotSubRoutes(routes, appDir);
|
|
60
|
+
const slotSubRoutes = discoverSlotSubRoutes(routes, appDir, matcher);
|
|
53
61
|
routes.push(...slotSubRoutes);
|
|
54
62
|
// Sort: static routes first, then dynamic, then catch-all
|
|
55
63
|
routes.sort((a, b) => {
|
|
@@ -58,6 +66,7 @@ export async function appRouter(appDir) {
|
|
|
58
66
|
});
|
|
59
67
|
cachedRoutes = routes;
|
|
60
68
|
cachedAppDir = appDir;
|
|
69
|
+
cachedPageExtensionsKey = pageExtensionsKey;
|
|
61
70
|
return routes;
|
|
62
71
|
}
|
|
63
72
|
/**
|
|
@@ -71,7 +80,7 @@ export async function appRouter(appDir) {
|
|
|
71
80
|
* - @audience slot → @audience/demographics/page.tsx (matched)
|
|
72
81
|
* - other slots → their default.tsx (fallback)
|
|
73
82
|
*/
|
|
74
|
-
function discoverSlotSubRoutes(routes, _appDir) {
|
|
83
|
+
function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
75
84
|
const syntheticRoutes = [];
|
|
76
85
|
const existingPatterns = new Set(routes.map((r) => r.pattern));
|
|
77
86
|
for (const parentRoute of routes) {
|
|
@@ -87,7 +96,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
87
96
|
const slotDir = path.join(parentPageDir, `@${slot.name}`);
|
|
88
97
|
if (!fs.existsSync(slotDir))
|
|
89
98
|
continue;
|
|
90
|
-
const subPages = findSlotSubPages(slotDir);
|
|
99
|
+
const subPages = findSlotSubPages(slotDir, matcher);
|
|
91
100
|
for (const { relativePath, pagePath } of subPages) {
|
|
92
101
|
if (!subPathMap.has(relativePath)) {
|
|
93
102
|
subPathMap.set(relativePath, new Map());
|
|
@@ -98,7 +107,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
98
107
|
if (subPathMap.size === 0)
|
|
99
108
|
continue;
|
|
100
109
|
// Find the default.tsx for the children slot at the parent directory
|
|
101
|
-
const childrenDefault = findFile(parentPageDir, "default");
|
|
110
|
+
const childrenDefault = findFile(parentPageDir, "default", matcher);
|
|
102
111
|
for (const [subPath, slotPages] of subPathMap) {
|
|
103
112
|
// Convert sub-path segments to URL pattern parts
|
|
104
113
|
const subSegments = subPath.split(path.sep);
|
|
@@ -175,7 +184,7 @@ function discoverSlotSubRoutes(routes, _appDir) {
|
|
|
175
184
|
* Skips the root page.tsx (already handled as the slot's main page)
|
|
176
185
|
* and intercepting route directories.
|
|
177
186
|
*/
|
|
178
|
-
function findSlotSubPages(slotDir) {
|
|
187
|
+
function findSlotSubPages(slotDir, matcher) {
|
|
179
188
|
const results = [];
|
|
180
189
|
function scan(dir) {
|
|
181
190
|
if (!fs.existsSync(dir))
|
|
@@ -191,7 +200,7 @@ function findSlotSubPages(slotDir) {
|
|
|
191
200
|
if (entry.name.startsWith("_"))
|
|
192
201
|
continue;
|
|
193
202
|
const subDir = path.join(dir, entry.name);
|
|
194
|
-
const page = findFile(subDir, "page");
|
|
203
|
+
const page = findFile(subDir, "page", matcher);
|
|
195
204
|
if (page) {
|
|
196
205
|
const relativePath = path.relative(slotDir, subDir);
|
|
197
206
|
results.push({ relativePath, pagePath: page });
|
|
@@ -206,7 +215,7 @@ function findSlotSubPages(slotDir) {
|
|
|
206
215
|
/**
|
|
207
216
|
* Convert a file path relative to app/ into an AppRoute.
|
|
208
217
|
*/
|
|
209
|
-
function fileToAppRoute(file, appDir, type) {
|
|
218
|
+
function fileToAppRoute(file, appDir, type, matcher) {
|
|
210
219
|
// Remove the filename (page.tsx or route.ts)
|
|
211
220
|
const dir = path.dirname(file);
|
|
212
221
|
const segments = dir === "." ? [] : dir.split(path.sep);
|
|
@@ -256,33 +265,33 @@ function fileToAppRoute(file, appDir, type) {
|
|
|
256
265
|
}
|
|
257
266
|
const pattern = "/" + urlSegments.join("/");
|
|
258
267
|
// Discover layouts and templates from root to leaf
|
|
259
|
-
const layouts = discoverLayouts(segments, appDir);
|
|
260
|
-
const templates = discoverTemplates(segments, appDir);
|
|
268
|
+
const layouts = discoverLayouts(segments, appDir, matcher);
|
|
269
|
+
const templates = discoverTemplates(segments, appDir, matcher);
|
|
261
270
|
// Compute the URL segment depth for each layout.
|
|
262
271
|
// Each layout corresponds to a directory level. We need to count how many
|
|
263
272
|
// of the filesystem segments up to that layout's level contribute URL segments
|
|
264
273
|
// (i.e., are not route groups or parallel slots).
|
|
265
|
-
const layoutSegmentDepths = computeLayoutSegmentDepths(segments, appDir, layouts);
|
|
274
|
+
const layoutSegmentDepths = computeLayoutSegmentDepths(segments, appDir, layouts, matcher);
|
|
266
275
|
// Discover per-layout error boundaries (aligned with layouts array).
|
|
267
276
|
// In Next.js, each segment independently wraps its children with an ErrorBoundary.
|
|
268
277
|
// This array enables interleaving error boundaries with layouts in the rendering.
|
|
269
|
-
const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir);
|
|
278
|
+
const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher);
|
|
270
279
|
// Discover loading, error in the route's directory
|
|
271
280
|
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
272
|
-
const loadingPath = findFile(routeDir, "loading");
|
|
273
|
-
const errorPath = findFile(routeDir, "error");
|
|
281
|
+
const loadingPath = findFile(routeDir, "loading", matcher);
|
|
282
|
+
const errorPath = findFile(routeDir, "error", matcher);
|
|
274
283
|
// Discover not-found/forbidden/unauthorized: walk from route directory up to root (nearest wins).
|
|
275
|
-
const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found");
|
|
276
|
-
const forbiddenPath = discoverBoundaryFile(segments, appDir, "forbidden");
|
|
277
|
-
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);
|
|
278
287
|
// Discover per-layout not-found files (one per layout directory).
|
|
279
288
|
// These are used for per-layout NotFoundBoundary to match Next.js behavior where
|
|
280
289
|
// notFound() thrown from a layout is caught by the parent layout's boundary.
|
|
281
|
-
const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found");
|
|
290
|
+
const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
|
|
282
291
|
// Discover parallel slots (@team, @analytics, etc.).
|
|
283
292
|
// Slots at the route's own directory use page.tsx; slots at ancestor directories
|
|
284
293
|
// (inherited from parent layouts) use default.tsx as fallback.
|
|
285
|
-
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir);
|
|
294
|
+
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
|
|
286
295
|
return {
|
|
287
296
|
pattern: pattern === "/" ? "/" : pattern,
|
|
288
297
|
pagePath: type === "page" ? path.join(appDir, file) : null,
|
|
@@ -307,12 +316,12 @@ function fileToAppRoute(file, appDir, type) {
|
|
|
307
316
|
* Root layout = 0, then each directory level that contributes a URL segment
|
|
308
317
|
* increments the depth. Route groups and parallel slots don't contribute.
|
|
309
318
|
*/
|
|
310
|
-
function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
319
|
+
function computeLayoutSegmentDepths(segments, appDir, layouts, matcher) {
|
|
311
320
|
// Build a map: layout file path → depth in URL segments
|
|
312
321
|
// Walk the segments directory-by-directory, tracking cumulative URL depth
|
|
313
322
|
const depthMap = new Map();
|
|
314
323
|
// Root layout (at appDir) always has depth 0
|
|
315
|
-
const rootLayout = findFile(appDir, "layout");
|
|
324
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
316
325
|
if (rootLayout)
|
|
317
326
|
depthMap.set(rootLayout, 0);
|
|
318
327
|
let urlDepth = 0;
|
|
@@ -325,7 +334,7 @@ function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
|
325
334
|
if (!isRouteGroup && !isParallelSlot) {
|
|
326
335
|
urlDepth++;
|
|
327
336
|
}
|
|
328
|
-
const layout = findFile(currentDir, "layout");
|
|
337
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
329
338
|
if (layout) {
|
|
330
339
|
depthMap.set(layout, urlDepth);
|
|
331
340
|
}
|
|
@@ -337,17 +346,17 @@ function computeLayoutSegmentDepths(segments, appDir, layouts) {
|
|
|
337
346
|
* Discover all layout files from root to the given directory.
|
|
338
347
|
* Each level of the directory tree may have a layout.tsx.
|
|
339
348
|
*/
|
|
340
|
-
function discoverLayouts(segments, appDir) {
|
|
349
|
+
function discoverLayouts(segments, appDir, matcher) {
|
|
341
350
|
const layouts = [];
|
|
342
351
|
// Check root layout
|
|
343
|
-
const rootLayout = findFile(appDir, "layout");
|
|
352
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
344
353
|
if (rootLayout)
|
|
345
354
|
layouts.push(rootLayout);
|
|
346
355
|
// Check each directory level
|
|
347
356
|
let currentDir = appDir;
|
|
348
357
|
for (const segment of segments) {
|
|
349
358
|
currentDir = path.join(currentDir, segment);
|
|
350
|
-
const layout = findFile(currentDir, "layout");
|
|
359
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
351
360
|
if (layout)
|
|
352
361
|
layouts.push(layout);
|
|
353
362
|
}
|
|
@@ -358,17 +367,17 @@ function discoverLayouts(segments, appDir) {
|
|
|
358
367
|
* Each level of the directory tree may have a template.tsx.
|
|
359
368
|
* Templates are like layouts but re-mount on navigation.
|
|
360
369
|
*/
|
|
361
|
-
function discoverTemplates(segments, appDir) {
|
|
370
|
+
function discoverTemplates(segments, appDir, matcher) {
|
|
362
371
|
const templates = [];
|
|
363
372
|
// Check root template
|
|
364
|
-
const rootTemplate = findFile(appDir, "template");
|
|
373
|
+
const rootTemplate = findFile(appDir, "template", matcher);
|
|
365
374
|
if (rootTemplate)
|
|
366
375
|
templates.push(rootTemplate);
|
|
367
376
|
// Check each directory level
|
|
368
377
|
let currentDir = appDir;
|
|
369
378
|
for (const segment of segments) {
|
|
370
379
|
currentDir = path.join(currentDir, segment);
|
|
371
|
-
const template = findFile(currentDir, "template");
|
|
380
|
+
const template = findFile(currentDir, "template", matcher);
|
|
372
381
|
if (template)
|
|
373
382
|
templates.push(template);
|
|
374
383
|
}
|
|
@@ -385,20 +394,20 @@ function discoverTemplates(segments, appDir) {
|
|
|
385
394
|
* rendering tree, matching Next.js behavior where each segment independently
|
|
386
395
|
* wraps its children with an error boundary.
|
|
387
396
|
*/
|
|
388
|
-
function discoverLayoutAlignedErrors(segments, appDir) {
|
|
397
|
+
function discoverLayoutAlignedErrors(segments, appDir, matcher) {
|
|
389
398
|
const errors = [];
|
|
390
399
|
// Root level (only if root has a layout — matching discoverLayouts logic)
|
|
391
|
-
const rootLayout = findFile(appDir, "layout");
|
|
400
|
+
const rootLayout = findFile(appDir, "layout", matcher);
|
|
392
401
|
if (rootLayout) {
|
|
393
|
-
errors.push(findFile(appDir, "error"));
|
|
402
|
+
errors.push(findFile(appDir, "error", matcher));
|
|
394
403
|
}
|
|
395
404
|
// Check each directory level
|
|
396
405
|
let currentDir = appDir;
|
|
397
406
|
for (const segment of segments) {
|
|
398
407
|
currentDir = path.join(currentDir, segment);
|
|
399
|
-
const layout = findFile(currentDir, "layout");
|
|
408
|
+
const layout = findFile(currentDir, "layout", matcher);
|
|
400
409
|
if (layout) {
|
|
401
|
-
errors.push(findFile(currentDir, "error"));
|
|
410
|
+
errors.push(findFile(currentDir, "error", matcher));
|
|
402
411
|
}
|
|
403
412
|
}
|
|
404
413
|
return errors;
|
|
@@ -408,7 +417,7 @@ function discoverLayoutAlignedErrors(segments, appDir) {
|
|
|
408
417
|
* by walking from the route's directory up to the app root.
|
|
409
418
|
* Returns the first (closest) file found, or null.
|
|
410
419
|
*/
|
|
411
|
-
function discoverBoundaryFile(segments, appDir, fileName) {
|
|
420
|
+
function discoverBoundaryFile(segments, appDir, fileName, matcher) {
|
|
412
421
|
// Build all directory paths from leaf to root
|
|
413
422
|
const dirs = [];
|
|
414
423
|
let dir = appDir;
|
|
@@ -419,7 +428,7 @@ function discoverBoundaryFile(segments, appDir, fileName) {
|
|
|
419
428
|
}
|
|
420
429
|
// Walk from leaf (last) to root (first)
|
|
421
430
|
for (let i = dirs.length - 1; i >= 0; i--) {
|
|
422
|
-
const f = findFile(dirs[i], fileName);
|
|
431
|
+
const f = findFile(dirs[i], fileName, matcher);
|
|
423
432
|
if (f)
|
|
424
433
|
return f;
|
|
425
434
|
}
|
|
@@ -434,10 +443,10 @@ function discoverBoundaryFile(segments, appDir, fileName) {
|
|
|
434
443
|
* has its own boundary that wraps the layout's children. When notFound() is thrown
|
|
435
444
|
* from a layout, it propagates up to the parent layout's boundary.
|
|
436
445
|
*/
|
|
437
|
-
function discoverBoundaryFilePerLayout(layouts, fileName) {
|
|
446
|
+
function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
|
|
438
447
|
return layouts.map((layoutPath) => {
|
|
439
448
|
const layoutDir = path.dirname(layoutPath);
|
|
440
|
-
return findFile(layoutDir, fileName);
|
|
449
|
+
return findFile(layoutDir, fileName, matcher);
|
|
441
450
|
});
|
|
442
451
|
}
|
|
443
452
|
/**
|
|
@@ -452,25 +461,25 @@ function discoverBoundaryFilePerLayout(layouts, fileName) {
|
|
|
452
461
|
* that has @slot dirs, collect them. Slots at the route's own directory level
|
|
453
462
|
* use page.tsx; slots at ancestor levels use default.tsx only.
|
|
454
463
|
*/
|
|
455
|
-
function discoverInheritedParallelSlots(segments, appDir, routeDir) {
|
|
464
|
+
function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
456
465
|
const slotMap = new Map();
|
|
457
466
|
// Walk from appDir through each segment, tracking layout indices.
|
|
458
467
|
// layoutIndex tracks which position in the route's layouts[] array corresponds
|
|
459
468
|
// to a given directory. Only directories with a layout.tsx file increment.
|
|
460
469
|
let currentDir = appDir;
|
|
461
470
|
const dirsToCheck = [];
|
|
462
|
-
let layoutIdx = findFile(appDir, "layout") ? 0 : -1;
|
|
471
|
+
let layoutIdx = findFile(appDir, "layout", matcher) ? 0 : -1;
|
|
463
472
|
dirsToCheck.push({ dir: appDir, layoutIdx: Math.max(layoutIdx, 0) });
|
|
464
473
|
for (const segment of segments) {
|
|
465
474
|
currentDir = path.join(currentDir, segment);
|
|
466
|
-
if (findFile(currentDir, "layout")) {
|
|
475
|
+
if (findFile(currentDir, "layout", matcher)) {
|
|
467
476
|
layoutIdx++;
|
|
468
477
|
}
|
|
469
478
|
dirsToCheck.push({ dir: currentDir, layoutIdx: Math.max(layoutIdx, 0) });
|
|
470
479
|
}
|
|
471
480
|
for (const { dir, layoutIdx: lvlLayoutIdx } of dirsToCheck) {
|
|
472
481
|
const isOwnDir = dir === routeDir;
|
|
473
|
-
const slotsAtLevel = discoverParallelSlots(dir, appDir);
|
|
482
|
+
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
|
|
474
483
|
for (const slot of slotsAtLevel) {
|
|
475
484
|
if (isOwnDir) {
|
|
476
485
|
// At the route's own directory: use page.tsx (normal behavior)
|
|
@@ -499,7 +508,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir) {
|
|
|
499
508
|
* Discover parallel route slots (@team, @analytics, etc.) in a directory.
|
|
500
509
|
* Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
|
|
501
510
|
*/
|
|
502
|
-
function discoverParallelSlots(dir, appDir) {
|
|
511
|
+
function discoverParallelSlots(dir, appDir, matcher) {
|
|
503
512
|
if (!fs.existsSync(dir))
|
|
504
513
|
return [];
|
|
505
514
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -509,9 +518,9 @@ function discoverParallelSlots(dir, appDir) {
|
|
|
509
518
|
continue;
|
|
510
519
|
const slotName = entry.name.slice(1); // "@team" -> "team"
|
|
511
520
|
const slotDir = path.join(dir, entry.name);
|
|
512
|
-
const pagePath = findFile(slotDir, "page");
|
|
513
|
-
const defaultPath = findFile(slotDir, "default");
|
|
514
|
-
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);
|
|
515
524
|
// Only include slots that have at least a page, default, or intercepting route
|
|
516
525
|
if (!pagePath && !defaultPath && interceptingRoutes.length === 0)
|
|
517
526
|
continue;
|
|
@@ -519,9 +528,9 @@ function discoverParallelSlots(dir, appDir) {
|
|
|
519
528
|
name: slotName,
|
|
520
529
|
pagePath,
|
|
521
530
|
defaultPath,
|
|
522
|
-
layoutPath: findFile(slotDir, "layout"),
|
|
523
|
-
loadingPath: findFile(slotDir, "loading"),
|
|
524
|
-
errorPath: findFile(slotDir, "error"),
|
|
531
|
+
layoutPath: findFile(slotDir, "layout", matcher),
|
|
532
|
+
loadingPath: findFile(slotDir, "loading", matcher),
|
|
533
|
+
errorPath: findFile(slotDir, "error", matcher),
|
|
525
534
|
interceptingRoutes,
|
|
526
535
|
layoutIndex: -1, // Will be set by discoverInheritedParallelSlots
|
|
527
536
|
});
|
|
@@ -548,19 +557,19 @@ const INTERCEPT_PATTERNS = [
|
|
|
548
557
|
* @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
|
|
549
558
|
* @param appDir - The root app directory
|
|
550
559
|
*/
|
|
551
|
-
function discoverInterceptingRoutes(slotDir, routeDir, appDir) {
|
|
560
|
+
function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
|
|
552
561
|
if (!fs.existsSync(slotDir))
|
|
553
562
|
return [];
|
|
554
563
|
const results = [];
|
|
555
564
|
// Recursively scan for page files inside intercepting directories
|
|
556
|
-
scanForInterceptingPages(slotDir, routeDir, appDir, results);
|
|
565
|
+
scanForInterceptingPages(slotDir, routeDir, appDir, results, matcher);
|
|
557
566
|
return results;
|
|
558
567
|
}
|
|
559
568
|
/**
|
|
560
569
|
* Recursively scan a directory tree for page.tsx files that are inside
|
|
561
570
|
* intercepting route directories.
|
|
562
571
|
*/
|
|
563
|
-
function scanForInterceptingPages(currentDir, routeDir, appDir, results) {
|
|
572
|
+
function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
|
|
564
573
|
if (!fs.existsSync(currentDir))
|
|
565
574
|
return;
|
|
566
575
|
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
@@ -575,11 +584,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results) {
|
|
|
575
584
|
const restOfName = entry.name.slice(interceptMatch.prefix.length);
|
|
576
585
|
const interceptDir = path.join(currentDir, entry.name);
|
|
577
586
|
// Find page files within this intercepting directory tree
|
|
578
|
-
collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, results);
|
|
587
|
+
collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, results, matcher);
|
|
579
588
|
}
|
|
580
589
|
else {
|
|
581
590
|
// Regular subdirectory — keep scanning for intercepting dirs
|
|
582
|
-
scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results);
|
|
591
|
+
scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results, matcher);
|
|
583
592
|
}
|
|
584
593
|
}
|
|
585
594
|
}
|
|
@@ -598,9 +607,9 @@ function matchInterceptConvention(name) {
|
|
|
598
607
|
* Collect page.tsx files inside an intercepting route directory tree
|
|
599
608
|
* and compute their target URL patterns.
|
|
600
609
|
*/
|
|
601
|
-
function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, results) {
|
|
610
|
+
function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, results, matcher) {
|
|
602
611
|
// Check for page.tsx in current directory
|
|
603
|
-
const page = findFile(currentDir, "page");
|
|
612
|
+
const page = findFile(currentDir, "page", matcher);
|
|
604
613
|
if (page) {
|
|
605
614
|
const targetPattern = computeInterceptTarget(convention, interceptSegment, currentDir, interceptRoot, routeDir, appDir);
|
|
606
615
|
if (targetPattern) {
|
|
@@ -619,7 +628,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
|
|
|
619
628
|
for (const entry of entries) {
|
|
620
629
|
if (!entry.isDirectory())
|
|
621
630
|
continue;
|
|
622
|
-
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);
|
|
623
632
|
}
|
|
624
633
|
}
|
|
625
634
|
/**
|
|
@@ -703,11 +712,10 @@ function computeInterceptTarget(convention, interceptSegment, currentDir, interc
|
|
|
703
712
|
}
|
|
704
713
|
/**
|
|
705
714
|
* Find a file by name (without extension) in a directory.
|
|
706
|
-
* Checks
|
|
715
|
+
* Checks configured pageExtensions.
|
|
707
716
|
*/
|
|
708
|
-
function findFile(dir, name) {
|
|
709
|
-
const
|
|
710
|
-
for (const ext of extensions) {
|
|
717
|
+
function findFile(dir, name, matcher) {
|
|
718
|
+
for (const ext of matcher.dottedExtensions) {
|
|
711
719
|
const filePath = path.join(dir, name + ext);
|
|
712
720
|
if (fs.existsSync(filePath))
|
|
713
721
|
return filePath;
|
|
@@ -768,45 +776,4 @@ function matchPattern(url, pattern) {
|
|
|
768
776
|
return null;
|
|
769
777
|
return params;
|
|
770
778
|
}
|
|
771
|
-
/**
|
|
772
|
-
* Route precedence — lower score is higher priority.
|
|
773
|
-
* Matches Next.js specificity rules:
|
|
774
|
-
* 1. Static routes first (scored by segment count, more = more specific)
|
|
775
|
-
* 2. Dynamic segments penalized by position
|
|
776
|
-
* 3. Catch-all comes after dynamic
|
|
777
|
-
* 4. Optional catch-all last
|
|
778
|
-
* 5. Lexicographic tiebreaker for determinism
|
|
779
|
-
*
|
|
780
|
-
* Key insight: routes with static prefix segments should have higher priority
|
|
781
|
-
* than catch-all routes without them. E.g., /_sites/:subdomain/:slug* should
|
|
782
|
-
* match before /:slug* because "_sites" must match exactly.
|
|
783
|
-
*/
|
|
784
|
-
function routePrecedence(pattern) {
|
|
785
|
-
const parts = pattern.split("/").filter(Boolean);
|
|
786
|
-
let score = 0;
|
|
787
|
-
let staticPrefixCount = 0;
|
|
788
|
-
// Count static prefix segments (before first dynamic/catch-all)
|
|
789
|
-
for (const p of parts) {
|
|
790
|
-
if (p.startsWith(":") || p.endsWith("+") || p.endsWith("*"))
|
|
791
|
-
break;
|
|
792
|
-
staticPrefixCount++;
|
|
793
|
-
}
|
|
794
|
-
// Static prefix segments dramatically reduce score (increase priority).
|
|
795
|
-
// Each static prefix segment gives -10000 priority boost.
|
|
796
|
-
score -= staticPrefixCount * 10000;
|
|
797
|
-
for (let i = 0; i < parts.length; i++) {
|
|
798
|
-
const p = parts[i];
|
|
799
|
-
if (p.endsWith("+")) {
|
|
800
|
-
score += 1000 + i; // catch-all: moderate penalty
|
|
801
|
-
}
|
|
802
|
-
else if (p.endsWith("*")) {
|
|
803
|
-
score += 2000 + i; // optional catch-all: high penalty
|
|
804
|
-
}
|
|
805
|
-
else if (p.startsWith(":")) {
|
|
806
|
-
score += 100 + i; // dynamic: small penalty by position
|
|
807
|
-
}
|
|
808
|
-
// static segments after first dynamic don't contribute extra
|
|
809
|
-
}
|
|
810
|
-
return score;
|
|
811
|
-
}
|
|
812
779
|
//# sourceMappingURL=app-router.js.map
|