vinext 0.0.28 → 0.0.30
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/report.d.ts +117 -0
- package/dist/build/report.d.ts.map +1 -0
- package/dist/build/report.js +303 -0
- package/dist/build/report.js.map +1 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +11 -7
- package/dist/check.js.map +1 -1
- package/dist/cli.js +106 -9
- package/dist/cli.js.map +1 -1
- package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
- package/dist/cloudflare/kv-cache-handler.js +58 -42
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/cloudflare/tpr.d.ts +10 -0
- package/dist/cloudflare/tpr.d.ts.map +1 -1
- package/dist/cloudflare/tpr.js +36 -41
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js +16 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +1 -1
- package/dist/entries/app-rsc-entry.d.ts.map +1 -1
- package/dist/entries/app-rsc-entry.js +225 -186
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.d.ts.map +1 -1
- package/dist/entries/pages-server-entry.js +192 -91
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -40
- package/dist/index.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -0
- package/dist/routing/app-router.d.ts.map +1 -1
- package/dist/routing/app-router.js +131 -104
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/pages-router.d.ts.map +1 -1
- package/dist/routing/pages-router.js +17 -57
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.d.ts +57 -0
- package/dist/routing/route-trie.d.ts.map +1 -0
- package/dist/routing/route-trie.js +160 -0
- package/dist/routing/route-trie.js.map +1 -0
- package/dist/routing/route-validation.d.ts.map +1 -1
- package/dist/routing/route-validation.js +13 -1
- package/dist/routing/route-validation.js.map +1 -1
- package/dist/routing/utils.d.ts +19 -0
- package/dist/routing/utils.d.ts.map +1 -1
- package/dist/routing/utils.js +47 -0
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.d.ts.map +1 -1
- package/dist/server/api-handler.js +28 -13
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-router-entry.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 +167 -115
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts.map +1 -1
- package/dist/server/image-optimization.js +24 -12
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +17 -8
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/isr-cache.d.ts.map +1 -1
- package/dist/server/isr-cache.js +8 -3
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/metadata-routes.js +56 -18
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts +10 -0
- package/dist/server/middleware-codegen.d.ts.map +1 -1
- package/dist/server/middleware-codegen.js +76 -4
- package/dist/server/middleware-codegen.js.map +1 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +52 -7
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-i18n.d.ts +50 -0
- package/dist/server/pages-i18n.d.ts.map +1 -0
- package/dist/server/pages-i18n.js +152 -0
- package/dist/server/pages-i18n.js.map +1 -0
- 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 +60 -20
- package/dist/server/prod-server.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +3 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -1
- package/dist/shims/cache-runtime.js +22 -5
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +3 -0
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +21 -12
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/constants.d.ts.map +1 -1
- package/dist/shims/constants.js +0 -1
- package/dist/shims/constants.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +14 -0
- package/dist/shims/fetch-cache.d.ts.map +1 -1
- package/dist/shims/fetch-cache.js +102 -37
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head-state.d.ts +4 -0
- package/dist/shims/head-state.d.ts.map +1 -1
- package/dist/shims/head-state.js +14 -11
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +4 -2
- package/dist/shims/head.d.ts.map +1 -1
- package/dist/shims/head.js +162 -52
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +8 -1
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +23 -34
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-context.d.ts +27 -0
- package/dist/shims/i18n-context.d.ts.map +1 -0
- package/dist/shims/i18n-context.js +57 -0
- package/dist/shims/i18n-context.js.map +1 -0
- package/dist/shims/i18n-state.d.ts +20 -0
- package/dist/shims/i18n-state.d.ts.map +1 -0
- package/dist/shims/i18n-state.js +53 -0
- package/dist/shims/i18n-state.js.map +1 -0
- package/dist/shims/image.d.ts +2 -0
- package/dist/shims/image.d.ts.map +1 -1
- package/dist/shims/image.js +14 -6
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/internal/utils.d.ts.map +1 -1
- package/dist/shims/internal/utils.js.map +1 -1
- package/dist/shims/link.d.ts.map +1 -1
- package/dist/shims/link.js +38 -54
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +78 -22
- package/dist/shims/metadata.d.ts.map +1 -1
- package/dist/shims/metadata.js +96 -28
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +14 -0
- package/dist/shims/navigation-state.d.ts.map +1 -1
- package/dist/shims/navigation-state.js +33 -15
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +2 -0
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/shims/navigation.js +80 -51
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/request-context.d.ts.map +1 -1
- package/dist/shims/request-context.js +9 -0
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +11 -0
- package/dist/shims/request-state-types.d.ts.map +1 -0
- package/dist/shims/request-state-types.js +2 -0
- package/dist/shims/request-state-types.js.map +1 -0
- package/dist/shims/router-state.d.ts +11 -0
- package/dist/shims/router-state.d.ts.map +1 -1
- package/dist/shims/router-state.js +10 -8
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +4 -0
- package/dist/shims/router.d.ts.map +1 -1
- package/dist/shims/router.js +130 -40
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.d.ts +8 -1
- package/dist/shims/server.d.ts.map +1 -1
- package/dist/shims/server.js +52 -6
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +66 -0
- package/dist/shims/unified-request-context.d.ts.map +1 -0
- package/dist/shims/unified-request-context.js +116 -0
- package/dist/shims/unified-request-context.js.map +1 -0
- package/dist/shims/url-utils.d.ts +20 -6
- package/dist/shims/url-utils.d.ts.map +1 -1
- package/dist/shims/url-utils.js +79 -0
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/domain-locale.d.ts +18 -0
- package/dist/utils/domain-locale.d.ts.map +1 -0
- package/dist/utils/domain-locale.js +64 -0
- package/dist/utils/domain-locale.js.map +1 -0
- package/package.json +2 -2
|
@@ -12,6 +12,8 @@ export interface InterceptingRoute {
|
|
|
12
12
|
export interface ParallelSlot {
|
|
13
13
|
/** Slot name (e.g. "team" from @team) */
|
|
14
14
|
name: string;
|
|
15
|
+
/** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */
|
|
16
|
+
ownerDir: string;
|
|
15
17
|
/** Absolute path to the slot's page component */
|
|
16
18
|
pagePath: string | null;
|
|
17
19
|
/** Absolute path to the slot's default.tsx fallback */
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAI3B,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,2FAA2F;IAC3F,QAAQ,EAAE,MAAM,CAAC;IACjB,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;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kFAAkF;IAClF,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;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,CAkDrB;AA40BD;;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,CASvE"}
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import fs from "node:fs";
|
|
18
|
-
import { compareRoutes } from "./utils.js";
|
|
18
|
+
import { compareRoutes, decodeRouteSegment, normalizePathnameForRouteMatch } from "./utils.js";
|
|
19
19
|
import { createValidFileMatcher, scanWithExtensions, } from "./file-matcher.js";
|
|
20
20
|
import { validateRoutePatterns } from "./route-validation.js";
|
|
21
|
+
import { buildRouteTrie, trieMatch } from "./route-trie.js";
|
|
21
22
|
// Cache for app routes
|
|
22
23
|
let cachedRoutes = null;
|
|
23
24
|
let cachedAppDir = null;
|
|
@@ -38,16 +39,18 @@ export async function appRouter(appDir, pageExtensions, matcher) {
|
|
|
38
39
|
}
|
|
39
40
|
// Find all page.tsx and route.ts files, excluding @slot directories
|
|
40
41
|
// (slot pages are not standalone routes — they're rendered as props of their parent layout)
|
|
42
|
+
// and _private folders (Next.js convention for colocated non-route files).
|
|
41
43
|
const routes = [];
|
|
44
|
+
const excludeDir = (name) => name.startsWith("@") || name.startsWith("_");
|
|
42
45
|
// Process page files in a single pass
|
|
43
46
|
// Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)
|
|
44
|
-
for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions,
|
|
47
|
+
for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions, excludeDir)) {
|
|
45
48
|
const route = fileToAppRoute(file, appDir, "page", matcher);
|
|
46
49
|
if (route)
|
|
47
50
|
routes.push(route);
|
|
48
51
|
}
|
|
49
52
|
// Process route handler files (API routes) in a single pass
|
|
50
|
-
for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions,
|
|
53
|
+
for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions, excludeDir)) {
|
|
51
54
|
const route = fileToAppRoute(file, appDir, "route", matcher);
|
|
52
55
|
if (route)
|
|
53
56
|
routes.push(route);
|
|
@@ -80,7 +83,16 @@ export async function appRouter(appDir, pageExtensions, matcher) {
|
|
|
80
83
|
*/
|
|
81
84
|
function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
82
85
|
const syntheticRoutes = [];
|
|
83
|
-
|
|
86
|
+
// O(1) lookup for existing routes by pattern — avoids O(n) routes.find() per sub-path per parent.
|
|
87
|
+
// Updated as new synthetic routes are pushed so that later parents can see earlier synthetic entries.
|
|
88
|
+
const routesByPattern = new Map(routes.map((r) => [r.pattern, r]));
|
|
89
|
+
const slotKey = (slotName, ownerDir) => `${slotName}\u0000${ownerDir}`;
|
|
90
|
+
const applySlotSubPages = (route, slotPages) => {
|
|
91
|
+
route.parallelSlots = route.parallelSlots.map((slot) => ({
|
|
92
|
+
...slot,
|
|
93
|
+
pagePath: slotPages.get(slotKey(slot.name, slot.ownerDir)) ?? slot.pagePath,
|
|
94
|
+
}));
|
|
95
|
+
};
|
|
84
96
|
for (const parentRoute of routes) {
|
|
85
97
|
if (parentRoute.parallelSlots.length === 0)
|
|
86
98
|
continue;
|
|
@@ -88,7 +100,8 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
88
100
|
continue;
|
|
89
101
|
const parentPageDir = path.dirname(parentRoute.pagePath);
|
|
90
102
|
// Collect sub-paths from all slots.
|
|
91
|
-
// Map:
|
|
103
|
+
// Map: normalized visible sub-path -> slot pages, raw filesystem segments (for routeSegments),
|
|
104
|
+
// and the pre-computed convertedSubRoute (to avoid a redundant re-conversion in the merge loop).
|
|
92
105
|
const subPathMap = new Map();
|
|
93
106
|
for (const slot of parentRoute.parallelSlots) {
|
|
94
107
|
const slotDir = path.join(parentPageDir, `@${slot.name}`);
|
|
@@ -96,37 +109,55 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
96
109
|
continue;
|
|
97
110
|
const subPages = findSlotSubPages(slotDir, matcher);
|
|
98
111
|
for (const { relativePath, pagePath } of subPages) {
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
const subSegments = relativePath.split(path.sep);
|
|
113
|
+
const convertedSubRoute = convertSegmentsToRouteParts(subSegments);
|
|
114
|
+
if (!convertedSubRoute)
|
|
115
|
+
continue;
|
|
116
|
+
const { urlSegments } = convertedSubRoute;
|
|
117
|
+
const normalizedSubPath = urlSegments.join("/");
|
|
118
|
+
let subPathEntry = subPathMap.get(normalizedSubPath);
|
|
119
|
+
if (!subPathEntry) {
|
|
120
|
+
subPathEntry = {
|
|
121
|
+
rawSegments: subSegments,
|
|
122
|
+
converted: convertedSubRoute,
|
|
123
|
+
slotPages: new Map(),
|
|
124
|
+
};
|
|
125
|
+
subPathMap.set(normalizedSubPath, subPathEntry);
|
|
126
|
+
}
|
|
127
|
+
const slotId = slotKey(slot.name, slot.ownerDir);
|
|
128
|
+
const existingSlotPage = subPathEntry.slotPages.get(slotId);
|
|
129
|
+
if (existingSlotPage) {
|
|
130
|
+
const pattern = joinRoutePattern(parentRoute.pattern, normalizedSubPath);
|
|
131
|
+
throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
|
|
101
132
|
}
|
|
102
|
-
|
|
133
|
+
subPathEntry.slotPages.set(slotId, pagePath);
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
if (subPathMap.size === 0)
|
|
106
137
|
continue;
|
|
107
138
|
// Find the default.tsx for the children slot at the parent directory
|
|
108
139
|
const childrenDefault = findFile(parentPageDir, "default", matcher);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const convertedSubRoute = convertSegmentsToRouteParts(subSegments);
|
|
113
|
-
if (!convertedSubRoute)
|
|
114
|
-
continue;
|
|
140
|
+
if (!childrenDefault)
|
|
141
|
+
continue;
|
|
142
|
+
for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
|
|
115
143
|
const { urlSegments: urlParts, params: subParams, isDynamic: subIsDynamic, } = convertedSubRoute;
|
|
116
144
|
const subUrlPath = urlParts.join("/");
|
|
117
|
-
const pattern = parentRoute.pattern
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
145
|
+
const pattern = joinRoutePattern(parentRoute.pattern, subUrlPath);
|
|
146
|
+
const existingRoute = routesByPattern.get(pattern);
|
|
147
|
+
if (existingRoute) {
|
|
148
|
+
if (existingRoute.routePath && !existingRoute.pagePath) {
|
|
149
|
+
throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
|
|
150
|
+
}
|
|
151
|
+
applySlotSubPages(existingRoute, slotPages);
|
|
122
152
|
continue;
|
|
153
|
+
}
|
|
123
154
|
// Build parallel slots for this sub-route: matching slots get the sub-page,
|
|
124
155
|
// non-matching slots get null pagePath (rendering falls back to defaultPath)
|
|
125
156
|
const subSlots = parentRoute.parallelSlots.map((slot) => ({
|
|
126
157
|
...slot,
|
|
127
|
-
pagePath: slotPages.get(slot.name) || null,
|
|
158
|
+
pagePath: slotPages.get(slotKey(slot.name, slot.ownerDir)) || null,
|
|
128
159
|
}));
|
|
129
|
-
|
|
160
|
+
const newRoute = {
|
|
130
161
|
pattern,
|
|
131
162
|
pagePath: childrenDefault, // children slot uses parent's default.tsx as page
|
|
132
163
|
routePath: null,
|
|
@@ -140,12 +171,14 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
140
171
|
notFoundPaths: parentRoute.notFoundPaths,
|
|
141
172
|
forbiddenPath: parentRoute.forbiddenPath,
|
|
142
173
|
unauthorizedPath: parentRoute.unauthorizedPath,
|
|
143
|
-
routeSegments: [...parentRoute.routeSegments, ...
|
|
174
|
+
routeSegments: [...parentRoute.routeSegments, ...rawSegments],
|
|
144
175
|
layoutTreePositions: parentRoute.layoutTreePositions,
|
|
145
176
|
isDynamic: parentRoute.isDynamic || subIsDynamic,
|
|
146
177
|
params: [...parentRoute.params, ...subParams],
|
|
147
178
|
patternParts: [...parentRoute.patternParts, ...urlParts],
|
|
148
|
-
}
|
|
179
|
+
};
|
|
180
|
+
syntheticRoutes.push(newRoute);
|
|
181
|
+
routesByPattern.set(pattern, newRoute);
|
|
149
182
|
}
|
|
150
183
|
}
|
|
151
184
|
return syntheticRoutes;
|
|
@@ -444,6 +477,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
|
|
|
444
477
|
continue;
|
|
445
478
|
slots.push({
|
|
446
479
|
name: slotName,
|
|
480
|
+
ownerDir: slotDir,
|
|
447
481
|
pagePath,
|
|
448
482
|
defaultPath,
|
|
449
483
|
layoutPath: findFile(slotDir, "layout", matcher),
|
|
@@ -494,6 +528,9 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
|
|
|
494
528
|
for (const entry of entries) {
|
|
495
529
|
if (!entry.isDirectory())
|
|
496
530
|
continue;
|
|
531
|
+
// Skip private folders (prefixed with _)
|
|
532
|
+
if (entry.name.startsWith("_"))
|
|
533
|
+
continue;
|
|
497
534
|
// Check if this directory name starts with an interception convention
|
|
498
535
|
const interceptMatch = matchInterceptConvention(entry.name);
|
|
499
536
|
if (interceptMatch) {
|
|
@@ -546,38 +583,69 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
|
|
|
546
583
|
for (const entry of entries) {
|
|
547
584
|
if (!entry.isDirectory())
|
|
548
585
|
continue;
|
|
586
|
+
// Skip private folders (prefixed with _)
|
|
587
|
+
if (entry.name.startsWith("_"))
|
|
588
|
+
continue;
|
|
549
589
|
collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, results, matcher);
|
|
550
590
|
}
|
|
551
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* Check whether a path segment is invisible in the URL (route groups, parallel slots, ".").
|
|
594
|
+
*
|
|
595
|
+
* Used by computeInterceptTarget, convertSegmentsToRouteParts, and
|
|
596
|
+
* hasRemainingVisibleSegments — keep this the single source of truth.
|
|
597
|
+
*/
|
|
598
|
+
function isInvisibleSegment(segment) {
|
|
599
|
+
if (segment === ".")
|
|
600
|
+
return true;
|
|
601
|
+
if (segment.startsWith("(") && segment.endsWith(")"))
|
|
602
|
+
return true;
|
|
603
|
+
if (segment.startsWith("@"))
|
|
604
|
+
return true;
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
552
607
|
/**
|
|
553
608
|
* Compute the target URL pattern for an intercepting route.
|
|
554
609
|
*
|
|
610
|
+
* Interception conventions (..), (..)(..)" climb by *visible route segments*
|
|
611
|
+
* (not filesystem directories). Route groups like (marketing) and parallel
|
|
612
|
+
* slots like @modal are invisible and must be skipped when counting levels.
|
|
613
|
+
*
|
|
555
614
|
* - (.) same level: resolve relative to routeDir
|
|
556
|
-
* - (..) one level up:
|
|
557
|
-
* - (..)(..)
|
|
615
|
+
* - (..) one level up: climb 1 visible segment
|
|
616
|
+
* - (..)(..) two levels up: climb 2 visible segments
|
|
558
617
|
* - (...) root: resolve from appDir
|
|
559
618
|
*/
|
|
560
619
|
function computeInterceptTarget(convention, interceptSegment, currentDir, interceptRoot, routeDir, appDir) {
|
|
561
|
-
// Determine the base
|
|
562
|
-
|
|
620
|
+
// Determine the base segments for target resolution.
|
|
621
|
+
// We work on route segments (not filesystem paths) so that route groups
|
|
622
|
+
// and parallel slots are properly skipped when climbing.
|
|
623
|
+
const routeSegments = path.relative(appDir, routeDir).split(path.sep).filter(Boolean);
|
|
624
|
+
let baseParts;
|
|
563
625
|
switch (convention) {
|
|
564
626
|
case ".":
|
|
565
|
-
|
|
627
|
+
baseParts = routeSegments;
|
|
566
628
|
break;
|
|
567
629
|
case "..":
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
630
|
+
case "../..": {
|
|
631
|
+
const levelsToClimb = convention === ".." ? 1 : 2;
|
|
632
|
+
let climbed = 0;
|
|
633
|
+
let cutIndex = routeSegments.length;
|
|
634
|
+
while (cutIndex > 0 && climbed < levelsToClimb) {
|
|
635
|
+
cutIndex--;
|
|
636
|
+
if (!isInvisibleSegment(routeSegments[cutIndex])) {
|
|
637
|
+
climbed++;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
baseParts = routeSegments.slice(0, cutIndex);
|
|
572
641
|
break;
|
|
642
|
+
}
|
|
573
643
|
case "...":
|
|
574
|
-
|
|
644
|
+
baseParts = [];
|
|
575
645
|
break;
|
|
576
646
|
default:
|
|
577
647
|
return null;
|
|
578
648
|
}
|
|
579
|
-
// Build the target URL segments from baseDir relative to appDir
|
|
580
|
-
const baseParts = path.relative(appDir, baseDir).split(path.sep).filter(Boolean);
|
|
581
649
|
// Add the intercept segment and any nested path segments
|
|
582
650
|
const nestedParts = path.relative(interceptRoot, currentDir).split(path.sep).filter(Boolean);
|
|
583
651
|
const allSegments = [...baseParts, interceptSegment, ...nestedParts];
|
|
@@ -600,19 +668,18 @@ function findFile(dir, name, matcher) {
|
|
|
600
668
|
}
|
|
601
669
|
return null;
|
|
602
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Convert filesystem path segments to URL route parts, skipping invisible segments
|
|
673
|
+
* (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
|
|
674
|
+
* patterns (e.g. "[id]" → ":id", "[...slug]" → ":slug+").
|
|
675
|
+
*/
|
|
603
676
|
function convertSegmentsToRouteParts(segments) {
|
|
604
677
|
const urlSegments = [];
|
|
605
678
|
const params = [];
|
|
606
679
|
let isDynamic = false;
|
|
607
680
|
for (let i = 0; i < segments.length; i++) {
|
|
608
681
|
const segment = segments[i];
|
|
609
|
-
if (segment
|
|
610
|
-
continue;
|
|
611
|
-
// Route groups are transparent in the URL.
|
|
612
|
-
if (segment.startsWith("(") && segment.endsWith(")"))
|
|
613
|
-
continue;
|
|
614
|
-
// Parallel slots are also transparent.
|
|
615
|
-
if (segment.startsWith("@"))
|
|
682
|
+
if (isInvisibleSegment(segment))
|
|
616
683
|
continue;
|
|
617
684
|
// Catch-all segments are only valid in terminal URL position.
|
|
618
685
|
const catchAllMatch = segment.match(/^\[\.\.\.([\w-]+)\]$/);
|
|
@@ -640,82 +707,42 @@ function convertSegmentsToRouteParts(segments) {
|
|
|
640
707
|
urlSegments.push(`:${dynamicMatch[1]}`);
|
|
641
708
|
continue;
|
|
642
709
|
}
|
|
643
|
-
|
|
644
|
-
urlSegments.push(decodeURIComponent(segment));
|
|
645
|
-
}
|
|
646
|
-
catch {
|
|
647
|
-
urlSegments.push(segment);
|
|
648
|
-
}
|
|
710
|
+
urlSegments.push(decodeRouteSegment(segment));
|
|
649
711
|
}
|
|
650
712
|
return { urlSegments, params, isDynamic };
|
|
651
713
|
}
|
|
652
714
|
function hasRemainingVisibleSegments(segments, startIndex) {
|
|
653
715
|
for (let i = startIndex; i < segments.length; i++) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
continue;
|
|
657
|
-
if (segment.startsWith("@"))
|
|
658
|
-
continue;
|
|
659
|
-
return true;
|
|
716
|
+
if (!isInvisibleSegment(segments[i]))
|
|
717
|
+
return true;
|
|
660
718
|
}
|
|
661
719
|
return false;
|
|
662
720
|
}
|
|
721
|
+
// Trie cache — keyed by route array identity (same array = same trie)
|
|
722
|
+
const appTrieCache = new WeakMap();
|
|
723
|
+
function getOrBuildAppTrie(routes) {
|
|
724
|
+
let trie = appTrieCache.get(routes);
|
|
725
|
+
if (!trie) {
|
|
726
|
+
trie = buildRouteTrie(routes);
|
|
727
|
+
appTrieCache.set(routes, trie);
|
|
728
|
+
}
|
|
729
|
+
return trie;
|
|
730
|
+
}
|
|
731
|
+
function joinRoutePattern(basePattern, subPath) {
|
|
732
|
+
if (!subPath)
|
|
733
|
+
return basePattern;
|
|
734
|
+
return basePattern === "/" ? `/${subPath}` : `${basePattern}/${subPath}`;
|
|
735
|
+
}
|
|
663
736
|
/**
|
|
664
737
|
* Match a URL against App Router routes.
|
|
665
738
|
*/
|
|
666
739
|
export function matchAppRoute(url, routes) {
|
|
667
740
|
const pathname = url.split("?")[0];
|
|
668
741
|
let normalizedUrl = pathname === "/" ? "/" : pathname.replace(/\/$/, "");
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
catch {
|
|
673
|
-
/* malformed percent-encoding — match as-is */
|
|
674
|
-
}
|
|
675
|
-
// Split URL once, reuse across all route match attempts
|
|
742
|
+
normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);
|
|
743
|
+
// Split URL once, look up via trie
|
|
676
744
|
const urlParts = normalizedUrl.split("/").filter(Boolean);
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (params !== null) {
|
|
680
|
-
return { route, params };
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
function matchPattern(urlParts, patternParts) {
|
|
686
|
-
const params = Object.create(null);
|
|
687
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
688
|
-
const pp = patternParts[i];
|
|
689
|
-
if (pp.endsWith("+")) {
|
|
690
|
-
if (i !== patternParts.length - 1)
|
|
691
|
-
return null;
|
|
692
|
-
const paramName = pp.slice(1, -1);
|
|
693
|
-
const remaining = urlParts.slice(i);
|
|
694
|
-
if (remaining.length === 0)
|
|
695
|
-
return null;
|
|
696
|
-
params[paramName] = remaining;
|
|
697
|
-
return params;
|
|
698
|
-
}
|
|
699
|
-
if (pp.endsWith("*")) {
|
|
700
|
-
if (i !== patternParts.length - 1)
|
|
701
|
-
return null;
|
|
702
|
-
const paramName = pp.slice(1, -1);
|
|
703
|
-
const remaining = urlParts.slice(i);
|
|
704
|
-
params[paramName] = remaining;
|
|
705
|
-
return params;
|
|
706
|
-
}
|
|
707
|
-
if (pp.startsWith(":")) {
|
|
708
|
-
const paramName = pp.slice(1);
|
|
709
|
-
if (i >= urlParts.length)
|
|
710
|
-
return null;
|
|
711
|
-
params[paramName] = urlParts[i];
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
if (i >= urlParts.length || urlParts[i] !== pp)
|
|
715
|
-
return null;
|
|
716
|
-
}
|
|
717
|
-
if (urlParts.length !== patternParts.length)
|
|
718
|
-
return null;
|
|
719
|
-
return params;
|
|
745
|
+
const trie = getOrBuildAppTrie(routes);
|
|
746
|
+
return trieMatch(trie, urlParts);
|
|
720
747
|
}
|
|
721
748
|
//# sourceMappingURL=app-router.js.map
|