vinext 0.1.1 → 0.1.3

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.
Files changed (266) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/build/prerender.d.ts +9 -1
  5. package/dist/build/prerender.js +41 -12
  6. package/dist/build/run-prerender.d.ts +10 -2
  7. package/dist/build/run-prerender.js +15 -1
  8. package/dist/check.js +4 -3
  9. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  10. package/dist/client/app-nav-failure-handler.js +44 -0
  11. package/dist/client/navigation-runtime.d.ts +3 -2
  12. package/dist/client/vinext-next-data.d.ts +18 -1
  13. package/dist/client/window-next.d.ts +8 -5
  14. package/dist/client/window-next.js +12 -1
  15. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  16. package/dist/config/config-matchers.d.ts +11 -4
  17. package/dist/config/config-matchers.js +88 -16
  18. package/dist/config/next-config.d.ts +59 -4
  19. package/dist/config/next-config.js +149 -48
  20. package/dist/deploy.d.ts +30 -11
  21. package/dist/deploy.js +189 -101
  22. package/dist/entries/app-browser-entry.d.ts +9 -3
  23. package/dist/entries/app-browser-entry.js +21 -3
  24. package/dist/entries/app-rsc-entry.d.ts +2 -0
  25. package/dist/entries/app-rsc-entry.js +71 -6
  26. package/dist/entries/app-rsc-manifest.js +2 -0
  27. package/dist/entries/app-ssr-entry.js +1 -1
  28. package/dist/entries/pages-client-entry.js +54 -9
  29. package/dist/entries/pages-server-entry.js +48 -11
  30. package/dist/index.d.ts +0 -2
  31. package/dist/index.js +285 -139
  32. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  33. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  34. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  35. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  36. package/dist/plugins/og-assets.js +2 -2
  37. package/dist/plugins/optimize-imports.d.ts +10 -5
  38. package/dist/plugins/optimize-imports.js +27 -21
  39. package/dist/plugins/postcss.js +7 -7
  40. package/dist/plugins/sass.d.ts +53 -24
  41. package/dist/plugins/sass.js +249 -1
  42. package/dist/plugins/typeof-window.d.ts +14 -0
  43. package/dist/plugins/typeof-window.js +150 -0
  44. package/dist/plugins/wasm-module-import.d.ts +15 -0
  45. package/dist/plugins/wasm-module-import.js +50 -0
  46. package/dist/routing/app-route-graph.d.ts +25 -2
  47. package/dist/routing/app-route-graph.js +91 -22
  48. package/dist/routing/file-matcher.d.ts +10 -1
  49. package/dist/routing/file-matcher.js +23 -2
  50. package/dist/routing/pages-router.js +3 -3
  51. package/dist/routing/utils.d.ts +35 -6
  52. package/dist/routing/utils.js +59 -7
  53. package/dist/server/api-handler.d.ts +6 -1
  54. package/dist/server/api-handler.js +21 -15
  55. package/dist/server/app-browser-action-result.d.ts +19 -6
  56. package/dist/server/app-browser-action-result.js +19 -10
  57. package/dist/server/app-browser-entry.js +269 -297
  58. package/dist/server/app-browser-error.d.ts +10 -3
  59. package/dist/server/app-browser-error.js +47 -6
  60. package/dist/server/app-browser-history-controller.d.ts +104 -0
  61. package/dist/server/app-browser-history-controller.js +210 -0
  62. package/dist/server/app-browser-hydration.d.ts +2 -0
  63. package/dist/server/app-browser-hydration.js +1 -0
  64. package/dist/server/app-browser-navigation-controller.d.ts +7 -4
  65. package/dist/server/app-browser-navigation-controller.js +33 -9
  66. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  67. package/dist/server/app-browser-rsc-redirect.js +30 -8
  68. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  69. package/dist/server/app-browser-server-action-navigation.js +9 -0
  70. package/dist/server/app-browser-state.js +4 -7
  71. package/dist/server/app-browser-stream.js +86 -43
  72. package/dist/server/app-browser-visible-commit.js +1 -1
  73. package/dist/server/app-elements-wire.d.ts +6 -1
  74. package/dist/server/app-elements-wire.js +14 -4
  75. package/dist/server/app-elements.d.ts +2 -2
  76. package/dist/server/app-elements.js +2 -2
  77. package/dist/server/app-fallback-renderer.d.ts +3 -1
  78. package/dist/server/app-fallback-renderer.js +6 -2
  79. package/dist/server/app-middleware.js +1 -0
  80. package/dist/server/app-optimistic-routing.js +24 -3
  81. package/dist/server/app-page-boundary-render.d.ts +3 -1
  82. package/dist/server/app-page-boundary-render.js +31 -16
  83. package/dist/server/app-page-cache-render.d.ts +53 -0
  84. package/dist/server/app-page-cache-render.js +91 -0
  85. package/dist/server/app-page-cache.d.ts +16 -2
  86. package/dist/server/app-page-cache.js +71 -8
  87. package/dist/server/app-page-dispatch.d.ts +34 -0
  88. package/dist/server/app-page-dispatch.js +167 -97
  89. package/dist/server/app-page-element-builder.d.ts +23 -2
  90. package/dist/server/app-page-element-builder.js +42 -10
  91. package/dist/server/app-page-execution.d.ts +7 -2
  92. package/dist/server/app-page-execution.js +53 -18
  93. package/dist/server/app-page-probe.d.ts +1 -0
  94. package/dist/server/app-page-probe.js +4 -0
  95. package/dist/server/app-page-render-observation.d.ts +3 -1
  96. package/dist/server/app-page-render-observation.js +17 -1
  97. package/dist/server/app-page-render.d.ts +13 -2
  98. package/dist/server/app-page-render.js +48 -17
  99. package/dist/server/app-page-request.d.ts +3 -0
  100. package/dist/server/app-page-request.js +5 -3
  101. package/dist/server/app-page-response.js +1 -1
  102. package/dist/server/app-page-route-wiring.d.ts +5 -1
  103. package/dist/server/app-page-route-wiring.js +21 -11
  104. package/dist/server/app-page-stream.d.ts +16 -9
  105. package/dist/server/app-page-stream.js +12 -9
  106. package/dist/server/app-pages-bridge.d.ts +18 -0
  107. package/dist/server/app-pages-bridge.js +22 -5
  108. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  109. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  110. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  111. package/dist/server/app-ppr-fallback-shell.js +8 -1
  112. package/dist/server/app-route-handler-dispatch.js +9 -2
  113. package/dist/server/app-route-handler-policy.d.ts +1 -0
  114. package/dist/server/app-route-handler-response.js +11 -10
  115. package/dist/server/app-route-handler-runtime.js +12 -1
  116. package/dist/server/app-router-entry.js +5 -0
  117. package/dist/server/app-rsc-cache-busting.js +2 -0
  118. package/dist/server/app-rsc-handler.d.ts +25 -0
  119. package/dist/server/app-rsc-handler.js +153 -53
  120. package/dist/server/app-rsc-response-finalizer.js +1 -1
  121. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  122. package/dist/server/app-rsc-route-matching.js +2 -0
  123. package/dist/server/app-segment-config.d.ts +9 -1
  124. package/dist/server/app-segment-config.js +12 -3
  125. package/dist/server/app-server-action-execution.d.ts +12 -0
  126. package/dist/server/app-server-action-execution.js +47 -15
  127. package/dist/server/app-ssr-entry.d.ts +2 -0
  128. package/dist/server/app-ssr-entry.js +81 -8
  129. package/dist/server/app-ssr-stream.js +9 -1
  130. package/dist/server/cache-control.js +4 -0
  131. package/dist/server/dev-lockfile.js +2 -1
  132. package/dist/server/dev-server.d.ts +2 -2
  133. package/dist/server/dev-server.js +287 -63
  134. package/dist/server/headers.d.ts +8 -1
  135. package/dist/server/headers.js +8 -1
  136. package/dist/server/hybrid-route-priority.d.ts +22 -0
  137. package/dist/server/hybrid-route-priority.js +33 -0
  138. package/dist/server/image-optimization.d.ts +18 -9
  139. package/dist/server/image-optimization.js +37 -23
  140. package/dist/server/implicit-tags.d.ts +2 -1
  141. package/dist/server/implicit-tags.js +4 -1
  142. package/dist/server/instrumentation-runtime.d.ts +6 -0
  143. package/dist/server/instrumentation-runtime.js +8 -0
  144. package/dist/server/isr-decision.d.ts +79 -0
  145. package/dist/server/isr-decision.js +70 -0
  146. package/dist/server/metadata-route-response.js +5 -3
  147. package/dist/server/middleware-runtime.d.ts +13 -0
  148. package/dist/server/middleware-runtime.js +11 -7
  149. package/dist/server/middleware.js +1 -0
  150. package/dist/server/navigation-planner.d.ts +186 -22
  151. package/dist/server/navigation-planner.js +302 -0
  152. package/dist/server/navigation-trace.d.ts +18 -1
  153. package/dist/server/navigation-trace.js +18 -1
  154. package/dist/server/normalize-path.d.ts +0 -8
  155. package/dist/server/normalize-path.js +3 -1
  156. package/dist/server/otel-tracer-extension.d.ts +45 -0
  157. package/dist/server/otel-tracer-extension.js +89 -0
  158. package/dist/server/pages-api-route.d.ts +20 -3
  159. package/dist/server/pages-api-route.js +19 -3
  160. package/dist/server/pages-asset-tags.d.ts +16 -4
  161. package/dist/server/pages-asset-tags.js +22 -12
  162. package/dist/server/pages-data-route.d.ts +8 -1
  163. package/dist/server/pages-data-route.js +16 -3
  164. package/dist/server/pages-get-initial-props.d.ts +54 -4
  165. package/dist/server/pages-get-initial-props.js +43 -1
  166. package/dist/server/pages-node-compat.d.ts +3 -11
  167. package/dist/server/pages-node-compat.js +175 -122
  168. package/dist/server/pages-page-data.d.ts +39 -2
  169. package/dist/server/pages-page-data.js +261 -46
  170. package/dist/server/pages-page-handler.d.ts +5 -2
  171. package/dist/server/pages-page-handler.js +78 -25
  172. package/dist/server/pages-page-response.d.ts +47 -2
  173. package/dist/server/pages-page-response.js +73 -9
  174. package/dist/server/pages-readiness.d.ts +1 -1
  175. package/dist/server/pages-request-pipeline.d.ts +16 -1
  176. package/dist/server/pages-request-pipeline.js +96 -38
  177. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  178. package/dist/server/pregenerated-concrete-paths.js +2 -19
  179. package/dist/server/prerender-manifest.d.ts +33 -0
  180. package/dist/server/prerender-manifest.js +54 -0
  181. package/dist/server/prerender-route-params.d.ts +1 -2
  182. package/dist/server/prod-server.d.ts +39 -1
  183. package/dist/server/prod-server.js +107 -37
  184. package/dist/server/request-pipeline.d.ts +3 -15
  185. package/dist/server/request-pipeline.js +58 -47
  186. package/dist/server/rsc-stream-hints.d.ts +5 -1
  187. package/dist/server/rsc-stream-hints.js +6 -1
  188. package/dist/server/seed-cache.js +10 -18
  189. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  190. package/dist/shims/app-router-scroll-state.js +14 -2
  191. package/dist/shims/app-router-scroll.d.ts +3 -0
  192. package/dist/shims/app-router-scroll.js +28 -18
  193. package/dist/shims/cache-runtime.js +12 -4
  194. package/dist/shims/cache.d.ts +1 -0
  195. package/dist/shims/cache.js +1 -1
  196. package/dist/shims/cdn-cache.d.ts +5 -5
  197. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  198. package/dist/shims/dynamic-preload-chunks.js +79 -0
  199. package/dist/shims/dynamic.d.ts +4 -0
  200. package/dist/shims/dynamic.js +4 -2
  201. package/dist/shims/error-boundary.d.ts +6 -4
  202. package/dist/shims/error-boundary.js +7 -0
  203. package/dist/shims/error.js +38 -11
  204. package/dist/shims/error.react-server.d.ts +9 -0
  205. package/dist/shims/error.react-server.js +6 -0
  206. package/dist/shims/fetch-cache.d.ts +11 -1
  207. package/dist/shims/fetch-cache.js +55 -20
  208. package/dist/shims/hash-scroll.js +6 -1
  209. package/dist/shims/head.js +6 -1
  210. package/dist/shims/headers.d.ts +16 -2
  211. package/dist/shims/headers.js +66 -5
  212. package/dist/shims/image-config.js +7 -1
  213. package/dist/shims/internal/als-registry.js +28 -1
  214. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  215. package/dist/shims/internal/app-route-detection.js +18 -23
  216. package/dist/shims/internal/app-router-context.d.ts +5 -0
  217. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  218. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  219. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  220. package/dist/shims/internal/navigation-untracked.js +55 -0
  221. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  222. package/dist/shims/internal/pages-data-target.js +17 -8
  223. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  224. package/dist/shims/internal/pages-router-accessor.js +13 -0
  225. package/dist/shims/internal/router-context.d.ts +2 -1
  226. package/dist/shims/internal/router-context.js +3 -1
  227. package/dist/shims/link.js +12 -5
  228. package/dist/shims/metadata.d.ts +6 -2
  229. package/dist/shims/metadata.js +32 -14
  230. package/dist/shims/navigation.d.ts +14 -17
  231. package/dist/shims/navigation.js +93 -46
  232. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  233. package/dist/shims/ppr-fallback-shell.js +28 -7
  234. package/dist/shims/router.d.ts +13 -2
  235. package/dist/shims/router.js +434 -116
  236. package/dist/shims/script-nonce-context.d.ts +1 -1
  237. package/dist/shims/script-nonce-context.js +11 -3
  238. package/dist/shims/server.d.ts +33 -2
  239. package/dist/shims/server.js +75 -18
  240. package/dist/shims/slot.js +1 -1
  241. package/dist/shims/unified-request-context.js +2 -0
  242. package/dist/typegen.js +1 -0
  243. package/dist/utils/built-asset-url.d.ts +4 -0
  244. package/dist/utils/built-asset-url.js +11 -0
  245. package/dist/utils/client-build-manifest.js +15 -5
  246. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  247. package/dist/utils/client-runtime-metadata.js +63 -0
  248. package/dist/utils/commonjs-loader.d.ts +16 -0
  249. package/dist/utils/commonjs-loader.js +100 -0
  250. package/dist/utils/deployment-id.d.ts +8 -0
  251. package/dist/utils/deployment-id.js +22 -0
  252. package/dist/utils/hash.d.ts +17 -1
  253. package/dist/utils/hash.js +36 -1
  254. package/dist/utils/html-limited-bots.d.ts +18 -1
  255. package/dist/utils/html-limited-bots.js +23 -1
  256. package/dist/utils/lazy-chunks.d.ts +27 -1
  257. package/dist/utils/lazy-chunks.js +65 -1
  258. package/dist/utils/manifest-paths.d.ts +20 -2
  259. package/dist/utils/manifest-paths.js +38 -3
  260. package/dist/utils/parse-cookie.d.ts +13 -0
  261. package/dist/utils/parse-cookie.js +52 -0
  262. package/dist/utils/path.d.ts +8 -1
  263. package/dist/utils/path.js +13 -1
  264. package/package.json +2 -2
  265. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  266. package/dist/shims/internal/parse-cookie-header.js +0 -30
@@ -0,0 +1,150 @@
1
+ import { collectBindingNames, forEachAstChild, hasRange, isAstRecord, isIdentifierNamed, nodeArray } from "./ast-utils.js";
2
+ import { parseAst } from "vite";
3
+ import MagicString from "magic-string";
4
+ //#region src/plugins/typeof-window.ts
5
+ function createScope(parent) {
6
+ return {
7
+ parent,
8
+ bindings: /* @__PURE__ */ new Set()
9
+ };
10
+ }
11
+ function hasBinding(scope, name) {
12
+ for (let current = scope; current; current = current.parent) if (current.bindings.has(name)) return true;
13
+ return false;
14
+ }
15
+ function collectScopeBindings(node, scope) {
16
+ forEachAstChild(node, (child) => {
17
+ if (child.type === "ExportNamedDeclaration" || child.type === "ExportDefaultDeclaration") {
18
+ if (isAstRecord(child.declaration)) collectScopeBindings(child, scope);
19
+ return;
20
+ }
21
+ if (child.type === "FunctionDeclaration" || child.type === "ClassDeclaration") {
22
+ collectBindingNames(child.id, scope.bindings);
23
+ return;
24
+ }
25
+ if (child.type === "VariableDeclaration") {
26
+ for (const declaration of nodeArray(child.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
27
+ return;
28
+ }
29
+ if (child.type === "ImportDeclaration") {
30
+ for (const specifier of nodeArray(child.specifiers)) if (isAstRecord(specifier)) collectBindingNames(specifier.local, scope.bindings);
31
+ }
32
+ });
33
+ }
34
+ function collectVarBindings(node, scope, isRoot = true) {
35
+ if (!isRoot && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) return;
36
+ if (node.type === "VariableDeclaration" && node.kind === "var") {
37
+ for (const declaration of nodeArray(node.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
38
+ }
39
+ forEachAstChild(node, (child) => collectVarBindings(child, scope, false));
40
+ }
41
+ function isFunctionNode(node) {
42
+ return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
43
+ }
44
+ function createChildScope(node, parent) {
45
+ if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "SwitchStatement" && node.type !== "CatchClause" && node.type !== "ForStatement" && node.type !== "ForInStatement" && node.type !== "ForOfStatement" && node.type !== "ClassDeclaration" && node.type !== "ClassExpression") return null;
46
+ const scope = createScope(parent);
47
+ if (node.type === "ClassDeclaration" || node.type === "ClassExpression") collectBindingNames(node.id, scope.bindings);
48
+ else if (node.type === "CatchClause") collectBindingNames(node.param, scope.bindings);
49
+ collectScopeBindings(node, scope);
50
+ if (node.type === "SwitchStatement") {
51
+ for (const switchCase of nodeArray(node.cases)) if (isAstRecord(switchCase)) collectScopeBindings(switchCase, scope);
52
+ }
53
+ return scope;
54
+ }
55
+ function getTypeofWindowReplacement(environment) {
56
+ return environment.config.consumer === "client" ? "object" : "undefined";
57
+ }
58
+ function stringLiteralValue(node) {
59
+ if (!isAstRecord(node)) return null;
60
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
61
+ return null;
62
+ }
63
+ function evaluateTypeofWindowComparison(node, replacement, scope) {
64
+ if (!isAstRecord(node) || node.type !== "BinaryExpression") return null;
65
+ if (![
66
+ "==",
67
+ "===",
68
+ "!=",
69
+ "!=="
70
+ ].includes(String(node.operator))) return null;
71
+ const left = isAstRecord(node.left) ? node.left : null;
72
+ const right = isAstRecord(node.right) ? node.right : null;
73
+ const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !hasBinding(scope, "window");
74
+ const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !hasBinding(scope, "window");
75
+ const comparedValue = leftIsTypeofWindow ? stringLiteralValue(right) : rightIsTypeofWindow ? stringLiteralValue(left) : null;
76
+ if (comparedValue === null) return null;
77
+ const equal = replacement === comparedValue;
78
+ return node.operator === "==" || node.operator === "===" ? equal : !equal;
79
+ }
80
+ function replaceTypeofWindow(code, replacement) {
81
+ if (!/typeof\s+window/.test(code)) return null;
82
+ let ast;
83
+ try {
84
+ ast = parseAst(code);
85
+ } catch {
86
+ return null;
87
+ }
88
+ const output = new MagicString(code);
89
+ let changed = false;
90
+ if (!isAstRecord(ast)) return null;
91
+ const rootScope = createScope(null);
92
+ collectScopeBindings(ast, rootScope);
93
+ collectVarBindings(ast, rootScope);
94
+ function visit(node, parentScope) {
95
+ if (isFunctionNode(node)) {
96
+ const parameterScope = createScope(parentScope);
97
+ collectBindingNames(node.id, parameterScope.bindings);
98
+ for (const parameter of nodeArray(node.params)) {
99
+ collectBindingNames(parameter, parameterScope.bindings);
100
+ if (isAstRecord(parameter)) visit(parameter, parameterScope);
101
+ }
102
+ if (isAstRecord(node.body)) if (node.body.type === "BlockStatement") {
103
+ const bodyScope = createScope(parameterScope);
104
+ collectVarBindings(node.body, bodyScope);
105
+ visit(node.body, bodyScope);
106
+ } else visit(node.body, parameterScope);
107
+ return;
108
+ }
109
+ const scope = createChildScope(node, parentScope) ?? parentScope;
110
+ if (node.type === "IfStatement" && hasRange(node)) {
111
+ const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
112
+ if (result !== null) {
113
+ const selected = result ? node.consequent : node.alternate;
114
+ if (isAstRecord(selected) && hasRange(selected)) {
115
+ output.remove(node.start, selected.start);
116
+ output.remove(selected.end, node.end);
117
+ visit(selected, scope);
118
+ } else output.overwrite(node.start, node.end, ";");
119
+ changed = true;
120
+ return;
121
+ }
122
+ }
123
+ if (node.type === "ConditionalExpression" && hasRange(node)) {
124
+ const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
125
+ const selected = result ? node.consequent : node.alternate;
126
+ if (result !== null && isAstRecord(selected) && hasRange(selected)) {
127
+ output.overwrite(node.start, selected.start, "(");
128
+ if (selected.end < node.end) output.overwrite(selected.end, node.end, ")");
129
+ else output.appendLeft(selected.end, ")");
130
+ visit(selected, scope);
131
+ changed = true;
132
+ return;
133
+ }
134
+ }
135
+ if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !hasBinding(scope, "window") && hasRange(node)) {
136
+ output.overwrite(node.start, node.end, JSON.stringify(replacement));
137
+ changed = true;
138
+ return;
139
+ }
140
+ forEachAstChild(node, (child) => visit(child, scope));
141
+ }
142
+ for (const node of ast.body) if (isAstRecord(node)) visit(node, rootScope);
143
+ if (!changed) return null;
144
+ return {
145
+ code: output.toString(),
146
+ map: output.generateMap({ hires: "boundary" })
147
+ };
148
+ }
149
+ //#endregion
150
+ export { getTypeofWindowReplacement, replaceTypeofWindow };
@@ -0,0 +1,15 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/wasm-module-import.d.ts
4
+ /**
5
+ * vinext:wasm-module-import — handle `import x from '*.wasm?module'`.
6
+ *
7
+ * Resolutions marked external by Vite or a target adapter are preserved. For
8
+ * non-external resolutions, this plugin reads the WASM file, inlines it as
9
+ * base64, and exports a compiled WebAssembly.Module.
10
+ *
11
+ * Fixes #1351.
12
+ */
13
+ declare function createWasmModuleImportPlugin(): Plugin;
14
+ //#endregion
15
+ export { createWasmModuleImportPlugin };
@@ -0,0 +1,50 @@
1
+ import { stripViteModuleQuery } from "../utils/path.js";
2
+ import fs from "node:fs";
3
+ //#region src/plugins/wasm-module-import.ts
4
+ /**
5
+ * vinext:wasm-module-import — handle `import x from '*.wasm?module'`.
6
+ *
7
+ * Resolutions marked external by Vite or a target adapter are preserved. For
8
+ * non-external resolutions, this plugin reads the WASM file, inlines it as
9
+ * base64, and exports a compiled WebAssembly.Module.
10
+ *
11
+ * Fixes #1351.
12
+ */
13
+ function createWasmModuleImportPlugin() {
14
+ return {
15
+ name: "vinext:wasm-module-import",
16
+ enforce: "pre",
17
+ resolveId: {
18
+ filter: { id: /\.wasm\?module$/ },
19
+ async handler(source, importer) {
20
+ if (this.environment?.name === "client") return null;
21
+ if ((importer ? (importer.startsWith("\0") ? importer.slice(1) : importer).split("?")[0] : "").includes("@vercel/og")) return null;
22
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
23
+ if (!resolved) return null;
24
+ if (resolved.external) return resolved;
25
+ return `\0vinext-wasm-module:${stripViteModuleQuery(resolved.id)}`;
26
+ }
27
+ },
28
+ load: {
29
+ filter: { id: /^\u0000vinext-wasm-module:/ },
30
+ handler(id) {
31
+ const filePath = id.replace(/^\u0000vinext-wasm-module:/, "");
32
+ this.addWatchFile(filePath);
33
+ let bytes;
34
+ try {
35
+ bytes = fs.readFileSync(filePath);
36
+ } catch {
37
+ return this.error(`[vinext] Could not read WASM file: ${filePath}`);
38
+ }
39
+ const base64 = bytes.toString("base64");
40
+ return [
41
+ `const _b64 = ${JSON.stringify(base64)};`,
42
+ `const _buf = Uint8Array.from(atob(_b64), c => c.charCodeAt(0));`,
43
+ `export default await WebAssembly.compile(_buf.buffer);`
44
+ ].join("\n");
45
+ }
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { createWasmModuleImportPlugin };
@@ -21,7 +21,8 @@ type InterceptingRoute = {
21
21
  * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/interception-routes.ts
22
22
  */
23
23
  sourceMatchPattern: string; /** Absolute path to the intercepting page component */
24
- pagePath: string; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
24
+ pagePath: string; /** Filesystem segments from app/ root to the intercepting page directory. */
25
+ sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
25
26
  layoutPaths: string[]; /** Parameter names for dynamic segments */
26
27
  params: string[];
27
28
  /**
@@ -276,6 +277,28 @@ declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher):
276
277
  routeManifest: RouteManifest;
277
278
  }>;
278
279
  declare function computeRootParamNames(routeSegments: readonly string[], layoutTreePositions: readonly number[]): string[];
280
+ /**
281
+ * Find the best route to attach a sibling intercept to, given the directory
282
+ * that contains the interception marker.
283
+ *
284
+ * 1. Exact hit: a route whose page/handler lives directly in `dir`.
285
+ * 2. Subtree hit: shallowest route whose page lives anywhere under `dir`
286
+ * (handles catch-all routes like `/templates/:catchAll+`).
287
+ * 3. Ancestor walk: walk up the directory tree toward `appDir` looking for
288
+ * any of the above. This handles the case where the marker directory has
289
+ * no sibling pages at all (e.g. `deep/path/(...)target` with no
290
+ * `deep/path/page.tsx`).
291
+ *
292
+ * All comparisons happen in forward-slash space: `appDir` is forward-slash
293
+ * (normalized once in the config hook), but `dir` and route file paths
294
+ * descend through native `path.join`/`path.dirname`, which reintroduce
295
+ * backslashes on Windows. Without normalizing, the `current === appDir`
296
+ * termination never fires there and the walk overshoots the app root.
297
+ * `routesByDir` keys must be forward-slash dirnames of the route file paths.
298
+ *
299
+ * Exported for tests.
300
+ */
301
+ declare function findOwnerRouteForDir(dir: string, appDir: string, routes: readonly AppRouteGraphRoute[], routesByDir: Map<string, AppRouteGraphRoute>): AppRouteGraphRoute | null;
279
302
  /**
280
303
  * Convert filesystem path segments to URL route parts, skipping invisible segments
281
304
  * (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
@@ -310,4 +333,4 @@ declare function computeAppRouteStaticSiblings(allRoutes: readonly {
310
333
  patternParts?: readonly string[] | null;
311
334
  }): string[];
312
335
  //#endregion
313
- export { AppRoute, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, RootBoundaryId, RouteManifest, RouteManifestInterception, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestSlotBinding, StaticSegmentGraph, buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, isInvisibleSegment };
336
+ export { AppRoute, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, RootBoundaryId, RouteManifest, RouteManifestInterception, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestSlotBinding, StaticSegmentGraph, buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, findOwnerRouteForDir, isInvisibleSegment };
@@ -1,4 +1,5 @@
1
- import { compareRoutes, decodeRouteSegment, isInvisibleSegment } from "./utils.js";
1
+ import { normalizePathSeparators } from "../utils/path.js";
2
+ import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
2
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
3
4
  import { validateRoutePatterns } from "./route-validation.js";
4
5
  import { compareStrings } from "../utils/compare.js";
@@ -345,23 +346,25 @@ function createRouteManifestGraphVersion(segmentGraph) {
345
346
  }
346
347
  async function buildAppRouteGraph(appDir, matcher) {
347
348
  const routes = [];
349
+ const scanMatcher = { ...matcher };
350
+ findFileProbeCache.set(scanMatcher, /* @__PURE__ */ new Map());
348
351
  const excludeDir = (name) => name.startsWith("@") && name !== "@children" || name.startsWith("_") || isInterceptionMarkerDir(name);
349
- for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions, excludeDir)) {
350
- const route = fileToAppRoute(file, appDir, "page", matcher);
352
+ for await (const file of scanWithExtensions("**/page", appDir, scanMatcher.extensions, excludeDir)) {
353
+ const route = fileToAppRoute(file, appDir, "page", scanMatcher);
351
354
  if (route) routes.push(route);
352
355
  }
353
- for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions, excludeDir)) {
354
- const route = fileToAppRoute(file, appDir, "route", matcher);
356
+ for await (const file of scanWithExtensions("**/route", appDir, scanMatcher.extensions, excludeDir)) {
357
+ const route = fileToAppRoute(file, appDir, "route", scanMatcher);
355
358
  if (route) routes.push(route);
356
359
  }
357
360
  const routePatterns = new Set(routes.map((route) => route.pattern));
358
361
  const ghostParentRoutes = [];
359
- for await (const file of scanWithExtensions("**/layout", appDir, matcher.extensions, excludeDir)) {
362
+ for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
360
363
  const dir = path.dirname(file);
361
364
  const routeDir = dir === "." ? appDir : path.join(appDir, dir);
362
365
  if (!hasParallelSlotDirectory(routeDir)) continue;
363
- if (discoverParallelSlots(routeDir, appDir, matcher).length === 0) continue;
364
- const route = directoryToAppRoute(dir, appDir, matcher, null, null);
366
+ if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
367
+ const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
365
368
  if (!route) continue;
366
369
  if (routePatterns.has(route.pattern)) {
367
370
  ghostParentRoutes.push(route);
@@ -370,13 +373,13 @@ async function buildAppRouteGraph(appDir, matcher) {
370
373
  routes.push(route);
371
374
  routePatterns.add(route.pattern);
372
375
  }
373
- const slotSubRoutes = discoverSlotSubRoutes(routes, matcher, ghostParentRoutes);
376
+ const slotSubRoutes = discoverSlotSubRoutes(routes, scanMatcher, ghostParentRoutes);
374
377
  routes.push(...slotSubRoutes);
375
- discoverSiblingInterceptingRoutes(routes, appDir, matcher);
378
+ discoverSiblingInterceptingRoutes(routes, appDir, scanMatcher);
376
379
  validatePageRouteConflicts(routes, appDir);
377
380
  validateRoutePatterns(routes.map((route) => route.pattern));
378
381
  validateRoutePatterns([...new Set(routes.flatMap((route) => [...route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern)), ...route.siblingIntercepts.map((intercept) => intercept.targetPattern)]))]);
379
- routes.sort(compareRoutes);
382
+ sortRoutes(routes);
380
383
  return {
381
384
  routes,
382
385
  routeManifest: createRouteManifest(routes)
@@ -485,7 +488,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
485
488
  const existingRoute = routesByPattern.get(pattern);
486
489
  if (existingRoute) {
487
490
  if (existingRoute.routePath && !existingRoute.pagePath) throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
488
- applySlotSubPages(existingRoute, slotPages, rawSegments);
491
+ if (urlParts.length > 0) applySlotSubPages(existingRoute, slotPages, rawSegments);
489
492
  continue;
490
493
  }
491
494
  const syntheticParts = [...parentRoute.patternParts, ...urlParts];
@@ -1026,6 +1029,34 @@ function patternsStructurallyEquivalent(a, b) {
1026
1029
  return true;
1027
1030
  }
1028
1031
  /**
1032
+ * Find a page file at the root URL level of a parallel slot directory, including
1033
+ * through transparent route-group subdirectories (e.g. `@slot/(group)/page.tsx`
1034
+ * is equivalent to `@slot/page.tsx` since `(group)` is invisible in the URL).
1035
+ *
1036
+ * Returns the absolute page path, or null if no root-level page is found.
1037
+ *
1038
+ * Only descends into route-group directories (those whose name starts with `(`
1039
+ * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1040
+ * are not transparent and are therefore not searched.
1041
+ */
1042
+ function findSlotRootPage(slotDir, matcher) {
1043
+ const directPage = findFile(slotDir, "page", matcher);
1044
+ if (directPage) return directPage;
1045
+ let entries;
1046
+ try {
1047
+ entries = fs.readdirSync(slotDir, { withFileTypes: true });
1048
+ } catch {
1049
+ return null;
1050
+ }
1051
+ for (const entry of entries) {
1052
+ if (!entry.isDirectory()) continue;
1053
+ if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1054
+ const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1055
+ if (found) return found;
1056
+ }
1057
+ return null;
1058
+ }
1059
+ /**
1029
1060
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
1030
1061
  * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
1031
1062
  */
@@ -1038,7 +1069,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
1038
1069
  if (entry.name === "@children") continue;
1039
1070
  const slotName = entry.name.slice(1);
1040
1071
  const slotDir = path.join(dir, entry.name);
1041
- const pagePath = findFile(slotDir, "page", matcher);
1072
+ const pagePath = findSlotRootPage(slotDir, matcher);
1042
1073
  const defaultPath = findFile(slotDir, "default", matcher);
1043
1074
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1044
1075
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
@@ -1127,7 +1158,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1127
1158
  for (const route of routes) {
1128
1159
  const filePath = route.pagePath ?? route.routePath;
1129
1160
  if (!filePath) continue;
1130
- const routeDir = path.dirname(filePath);
1161
+ const routeDir = normalizePathSeparators(path.dirname(filePath));
1131
1162
  if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1132
1163
  }
1133
1164
  function walk(dir) {
@@ -1171,22 +1202,32 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1171
1202
  * any of the above. This handles the case where the marker directory has
1172
1203
  * no sibling pages at all (e.g. `deep/path/(...)target` with no
1173
1204
  * `deep/path/page.tsx`).
1205
+ *
1206
+ * All comparisons happen in forward-slash space: `appDir` is forward-slash
1207
+ * (normalized once in the config hook), but `dir` and route file paths
1208
+ * descend through native `path.join`/`path.dirname`, which reintroduce
1209
+ * backslashes on Windows. Without normalizing, the `current === appDir`
1210
+ * termination never fires there and the walk overshoots the app root.
1211
+ * `routesByDir` keys must be forward-slash dirnames of the route file paths.
1212
+ *
1213
+ * Exported for tests.
1174
1214
  */
1175
1215
  function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1176
- let current = dir;
1216
+ const appRoot = normalizePathSeparators(appDir);
1217
+ let current = normalizePathSeparators(dir);
1177
1218
  while (true) {
1178
1219
  const exact = routesByDir.get(current);
1179
1220
  if (exact) return exact;
1180
- const currentWithSep = current + path.sep;
1221
+ const currentWithSep = current + "/";
1181
1222
  let best = null;
1182
1223
  for (const route of routes) {
1183
1224
  const filePath = route.pagePath ?? route.routePath;
1184
1225
  if (!filePath) continue;
1185
- if (!filePath.startsWith(currentWithSep)) continue;
1226
+ if (!normalizePathSeparators(filePath).startsWith(currentWithSep)) continue;
1186
1227
  if (!best || route.patternParts.length < best.patternParts.length) best = route;
1187
1228
  }
1188
1229
  if (best) return best;
1189
- if (current === appDir) break;
1230
+ if (current === appRoot) break;
1190
1231
  const parent = path.dirname(current);
1191
1232
  if (parent === current) break;
1192
1233
  current = parent;
@@ -1236,6 +1277,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1236
1277
  targetPattern: targetPattern.pattern,
1237
1278
  sourceMatchPattern,
1238
1279
  pagePath: page,
1280
+ sourcePageSegments: normalizePathSeparators(path.relative(appDir, path.dirname(page))).split("/").filter(Boolean),
1239
1281
  params: targetPattern.params
1240
1282
  });
1241
1283
  }
@@ -1345,10 +1387,37 @@ function markerForInterceptionConvention(convention) {
1345
1387
  }
1346
1388
  }
1347
1389
  /**
1348
- * Find a file by name (without extension) in a directory.
1349
- * Checks configured pageExtensions.
1390
+ * Scan-scoped cache of convention-file probes, keyed by the per-scan matcher
1391
+ * created in `buildAppRouteGraph`. A single scan walks the appDir→leaf chain
1392
+ * separately for every route (layouts, templates, errors, boundaries, slots),
1393
+ * so shared ancestor directories — the `app/` root above all — get re-probed
1394
+ * once per descendant route. The probe result is deterministic within one scan
1395
+ * (the filesystem does not change mid-build), so memoizing it removes the
1396
+ * dominant cross-route redundancy.
1397
+ *
1398
+ * Keyed by matcher so the cache lifetime is exactly one `buildAppRouteGraph`
1399
+ * call: the scan registers a fresh matcher clone, and the entry is unreachable
1400
+ * (and GC-eligible) once the scan returns. A fresh key per scan is also what
1401
+ * makes this concurrency-safe — overlapping builds never share probe state.
1350
1402
  */
1351
- const findFile = findFileWithExts;
1403
+ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
1404
+ /**
1405
+ * Find a file by name (without extension) in a directory, checking configured
1406
+ * pageExtensions. Memoizes through `findFileProbeCache` when the matcher has a
1407
+ * registered per-scan cache; otherwise falls back to a direct probe (identical
1408
+ * result). The `null` "not found" outcome is cached too, so repeated misses on
1409
+ * shared ancestors cost a single set of `existsSync` calls per scan.
1410
+ */
1411
+ function findFile(dir, name, matcher) {
1412
+ const cache = findFileProbeCache.get(matcher);
1413
+ if (!cache) return findFileWithExts(dir, name, matcher);
1414
+ const key = `${dir}\0${name}`;
1415
+ const cached = cache.get(key);
1416
+ if (cached !== void 0) return cached;
1417
+ const result = findFileWithExts(dir, name, matcher);
1418
+ cache.set(key, result);
1419
+ return result;
1420
+ }
1352
1421
  /**
1353
1422
  * Convert filesystem path segments to URL route parts, skipping invisible segments
1354
1423
  * (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
@@ -1446,4 +1515,4 @@ function computeAppRouteStaticSiblings(allRoutes, matchedRoute) {
1446
1515
  return Array.from(siblings);
1447
1516
  }
1448
1517
  //#endregion
1449
- export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, isInvisibleSegment };
1518
+ export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, findOwnerRouteForDir, isInvisibleSegment };
@@ -43,9 +43,18 @@ declare function findFileWithExts(dir: string, name: string, matcher: ValidFileM
43
43
  * See: cloudflare/vinext#1502
44
44
  */
45
45
  declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
46
+ /**
47
+ * Normalize an explicit Next.js resolver extension list for Vite.
48
+ *
49
+ * Unlike `pageExtensions`, both Turbopack's `resolveExtensions` and webpack's
50
+ * `resolve.extensions` replace their resolver defaults. The empty string is a
51
+ * webpack/Turbopack convention for trying the import exactly as written; Vite
52
+ * already does that before appending extensions, so it must be omitted here.
53
+ */
54
+ declare function normalizeViteResolveExtensions(extensions: readonly string[]): string[];
46
55
  /**
47
56
  * Use function-form exclude for Node < 22.14 compatibility.
48
57
  */
49
58
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
50
59
  //#endregion
51
- export { ValidFileMatcher, buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, scanWithExtensions };
60
+ export { ValidFileMatcher, buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -69,7 +69,7 @@ function findFileWithExtensions(basePath, matcher) {
69
69
  */
70
70
  function findFileWithExts(dir, name, matcher) {
71
71
  for (const ext of matcher.dottedExtensions) {
72
- const filePath = path.join(dir, name + ext);
72
+ const filePath = path.posix.join(dir, name + ext);
73
73
  if (existsSync(filePath)) return filePath;
74
74
  }
75
75
  return null;
@@ -113,6 +113,27 @@ function buildViteResolveExtensions(pageExtensions, viteDefaults = [
113
113
  return result;
114
114
  }
115
115
  /**
116
+ * Normalize an explicit Next.js resolver extension list for Vite.
117
+ *
118
+ * Unlike `pageExtensions`, both Turbopack's `resolveExtensions` and webpack's
119
+ * `resolve.extensions` replace their resolver defaults. The empty string is a
120
+ * webpack/Turbopack convention for trying the import exactly as written; Vite
121
+ * already does that before appending extensions, so it must be omitted here.
122
+ */
123
+ function normalizeViteResolveExtensions(extensions) {
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const result = [];
126
+ for (const extension of extensions) {
127
+ const trimmed = extension.trim();
128
+ if (!trimmed) continue;
129
+ const dotted = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
130
+ if (seen.has(dotted)) continue;
131
+ seen.add(dotted);
132
+ result.push(dotted);
133
+ }
134
+ return result;
135
+ }
136
+ /**
116
137
  * Use function-form exclude for Node < 22.14 compatibility.
117
138
  */
118
139
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
@@ -123,4 +144,4 @@ async function* scanWithExtensions(stem, cwd, extensions, exclude) {
123
144
  })) yield file;
124
145
  }
125
146
  //#endregion
126
- export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, scanWithExtensions };
147
+ export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -1,4 +1,4 @@
1
- import { compareRoutes, decodeRouteSegment } from "./utils.js";
1
+ import { decodeRouteSegment, sortRoutes } from "./utils.js";
2
2
  import { createValidFileMatcher, scanWithExtensions } from "./file-matcher.js";
3
3
  import { patternToNextFormat, validateRoutePatterns } from "./route-validation.js";
4
4
  import { createRouteTrieCache, matchRouteWithTrie } from "./route-matching.js";
@@ -54,7 +54,7 @@ async function scanPageRoutes(pagesDir, matcher) {
54
54
  if (route) routes.push(route);
55
55
  }
56
56
  validateRoutePatterns(routes.map((route) => route.pattern));
57
- routes.sort(compareRoutes);
57
+ sortRoutes(routes);
58
58
  return routes;
59
59
  }
60
60
  /**
@@ -156,7 +156,7 @@ async function scanApiRoutes(pagesDir, matcher) {
156
156
  if (route) routes.push(route);
157
157
  }
158
158
  validateRoutePatterns(routes.map((route) => route.pattern));
159
- routes.sort(compareRoutes);
159
+ sortRoutes(routes);
160
160
  return routes;
161
161
  }
162
162
  //#endregion
@@ -1,13 +1,42 @@
1
1
  //#region src/routing/utils.d.ts
2
2
  /**
3
- * Sort comparator for routes — lower precedence score sorts first (higher priority).
4
- * Lexicographic tiebreaker on pattern for determinism.
3
+ * Sort routes by precedence — lower score sorts first (higher priority), with a
4
+ * lexicographic tiebreaker on the pattern for determinism. Sorts in place and
5
+ * returns the same array (mirrors `Array.prototype.sort`).
5
6
  *
6
- * Usage: routes.sort(compareRoutes)
7
+ * `routePrecedence` is a pure function of the pattern, so each pattern's score
8
+ * is computed exactly once up front (decorate-sort) instead of ~2·log n times
9
+ * by a comparator that re-parses on every comparison. The `localeCompare`
10
+ * tiebreaker already guarantees a total order, so the result is byte-identical
11
+ * to comparing precedence inline.
12
+ *
13
+ * Usage: sortRoutes(routes)
7
14
  */
8
- declare function compareRoutes<T extends {
15
+ declare function sortRoutes<T extends {
9
16
  pattern: string;
10
- }>(a: T, b: T): number;
17
+ }>(routes: T[]): T[];
18
+ /**
19
+ * Single source of truth for hybrid App/Pages route ownership.
20
+ *
21
+ * Mirrors Next.js's DefaultRouteMatcherManager ordering: Pages providers
22
+ * are registered before App providers, then merged dynamic matchers sort
23
+ * together. Returns the router that should own a request/navigation to
24
+ * a URL that matched BOTH routers.
25
+ *
26
+ * Centralised so the server's request handling and the client's link /
27
+ * prefetch / programmatic-navigation paths all reach the same owner for
28
+ * the same (pages pattern, app pattern) pair. This intentionally implements
29
+ * Next.js's segment-tree ordering directly instead of vinext's broader
30
+ * `sortRoutes()` score heuristic. It only arbitrates two routes that already
31
+ * matched the same URL; each router's own trie ordering remains unchanged.
32
+ *
33
+ * Usage:
34
+ * compareHybridRoutePatterns("/:slug", true, "/:slug", true) // → "pages"
35
+ * compareHybridRoutePatterns("/_sites/:slug*", true, "/:slug*", true) // → "pages"
36
+ * compareHybridRoutePatterns("/:path+", true, "/dashboard", false) // → "app"
37
+ * compareHybridRoutePatterns("/", false, "/", false) // → "app"
38
+ */
39
+ declare function compareHybridRoutePatterns(pagesPattern: string, pagesIsDynamic: boolean, appPattern: string, appIsDynamic: boolean): "app" | "pages";
11
40
  /**
12
41
  * Decode a filesystem or URL path segment while preserving encoded path delimiters.
13
42
  * Mirrors Next.js segment-wise decoding so "%5F" becomes "_" but "%2F" stays "%2F".
@@ -85,4 +114,4 @@ declare function isOptionalCatchAllSegment(segment: string): boolean;
85
114
  */
86
115
  declare function countConsumedPathnameSegments(visibleTreePathSegments: readonly string[], pathnameSegmentCount: number): number;
87
116
  //#endregion
88
- export { buildParams, compareRoutes, countConsumedPathnameSegments, decodeMatchedParams, decodeRouteSegment, isCatchAllSegment, isInvisibleSegment, isOptionalCatchAllSegment, normalizePathnameForRouteMatch, normalizePathnameForRouteMatchStrict, splitPathSegments, splitPathnameForRouteMatch };
117
+ export { buildParams, compareHybridRoutePatterns, countConsumedPathnameSegments, decodeMatchedParams, decodeRouteSegment, isCatchAllSegment, isInvisibleSegment, isOptionalCatchAllSegment, normalizePathnameForRouteMatch, normalizePathnameForRouteMatchStrict, sortRoutes, splitPathSegments, splitPathnameForRouteMatch };