@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2

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 (222) hide show
  1. package/AGENTS.md +6 -10
  2. package/README.md +289 -938
  3. package/dist/bin/rango.js +271 -46
  4. package/dist/vite/index.js +673 -193
  5. package/package.json +10 -8
  6. package/skills/api-client/SKILL.md +1 -1
  7. package/skills/breadcrumbs/SKILL.md +31 -14
  8. package/skills/cache-guide/SKILL.md +5 -2
  9. package/skills/caching/SKILL.md +59 -4
  10. package/skills/catalog.json +271 -0
  11. package/skills/comparison/SKILL.md +50 -0
  12. package/skills/comparison/agents/openai.yaml +4 -0
  13. package/skills/comparison/references/framework-comparison.md +837 -0
  14. package/skills/composability/SKILL.md +83 -2
  15. package/skills/debug-manifest/SKILL.md +1 -1
  16. package/skills/defer-hydration/SKILL.md +235 -0
  17. package/skills/document-cache/SKILL.md +9 -1
  18. package/skills/fonts/SKILL.md +1 -1
  19. package/skills/handler-use/SKILL.md +8 -8
  20. package/skills/hooks/SKILL.md +54 -892
  21. package/skills/hooks/data.md +273 -0
  22. package/skills/hooks/handle-and-actions.md +103 -0
  23. package/skills/hooks/navigation.md +110 -0
  24. package/skills/hooks/outlets.md +41 -0
  25. package/skills/hooks/state.md +228 -0
  26. package/skills/hooks/urls.md +135 -0
  27. package/skills/host-router/SKILL.md +4 -4
  28. package/skills/i18n/SKILL.md +1 -1
  29. package/skills/intercept/SKILL.md +46 -14
  30. package/skills/layout/SKILL.md +27 -10
  31. package/skills/links/SKILL.md +1 -1
  32. package/skills/loader/SKILL.md +23 -1
  33. package/skills/middleware/SKILL.md +7 -3
  34. package/skills/migrate-nextjs/SKILL.md +167 -6
  35. package/skills/migrate-react-router/SKILL.md +59 -677
  36. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  37. package/skills/migrate-react-router/component-migration.md +196 -0
  38. package/skills/migrate-react-router/data-and-actions.md +225 -0
  39. package/skills/migrate-react-router/route-mapping.md +271 -0
  40. package/skills/mime-routes/SKILL.md +1 -1
  41. package/skills/observability/SKILL.md +9 -1
  42. package/skills/parallel/SKILL.md +23 -4
  43. package/skills/ppr/SKILL.md +622 -0
  44. package/skills/prerender/SKILL.md +28 -18
  45. package/skills/rango/SKILL.md +84 -25
  46. package/skills/response-routes/SKILL.md +15 -1
  47. package/skills/route/SKILL.md +71 -4
  48. package/skills/router-setup/SKILL.md +14 -3
  49. package/skills/scripts/SKILL.md +1 -1
  50. package/skills/server-actions/SKILL.md +3 -2
  51. package/skills/shell-manifest/SKILL.md +185 -0
  52. package/skills/streams-and-websockets/SKILL.md +1 -1
  53. package/skills/tailwind/SKILL.md +1 -1
  54. package/skills/testing/SKILL.md +2 -1
  55. package/skills/testing/handles.md +4 -2
  56. package/skills/testing/render-handler.md +15 -14
  57. package/skills/testing/reverse-and-types.md +8 -7
  58. package/skills/theme/SKILL.md +1 -1
  59. package/skills/typesafety/SKILL.md +45 -919
  60. package/skills/typesafety/env-and-bindings.md +254 -0
  61. package/skills/typesafety/generated-files-and-cli.md +335 -0
  62. package/skills/typesafety/params-and-search.md +153 -0
  63. package/skills/typesafety/route-types.md +209 -0
  64. package/skills/use-cache/SKILL.md +30 -3
  65. package/skills/vercel/SKILL.md +1 -1
  66. package/skills/view-transitions/SKILL.md +44 -1
  67. package/src/browser/event-controller.ts +62 -10
  68. package/src/browser/logging.ts +28 -0
  69. package/src/browser/merge-segment-loaders.ts +6 -4
  70. package/src/browser/navigation-bridge.ts +65 -16
  71. package/src/browser/navigation-client.ts +32 -2
  72. package/src/browser/navigation-store.ts +128 -14
  73. package/src/browser/network-error-handler.ts +34 -7
  74. package/src/browser/partial-update.ts +76 -17
  75. package/src/browser/prefetch/cache.ts +51 -11
  76. package/src/browser/prefetch/fetch.ts +59 -21
  77. package/src/browser/prefetch/queue.ts +19 -4
  78. package/src/browser/react/Link.tsx +13 -3
  79. package/src/browser/react/NavigationProvider.tsx +108 -4
  80. package/src/browser/response-adapter.ts +38 -9
  81. package/src/browser/rsc-router.tsx +54 -4
  82. package/src/browser/scroll-restoration.ts +7 -5
  83. package/src/browser/segment-reconciler.ts +31 -21
  84. package/src/browser/server-action-bridge.ts +22 -10
  85. package/src/browser/types.ts +54 -1
  86. package/src/build/generate-manifest.ts +155 -131
  87. package/src/build/index.ts +3 -1
  88. package/src/build/route-trie.ts +35 -7
  89. package/src/build/route-types/include-resolution.ts +347 -47
  90. package/src/build/runtime-discovery.ts +4 -1
  91. package/src/cache/cache-key-utils.ts +29 -0
  92. package/src/cache/cache-runtime.ts +262 -71
  93. package/src/cache/cache-scope.ts +2 -17
  94. package/src/cache/cache-tag.ts +60 -14
  95. package/src/cache/cf/cf-cache-store.ts +243 -20
  96. package/src/cache/document-cache.ts +54 -21
  97. package/src/cache/index.ts +1 -0
  98. package/src/cache/memory-segment-store.ts +110 -3
  99. package/src/cache/profile-registry.ts +15 -0
  100. package/src/cache/read-through-swr.ts +15 -1
  101. package/src/cache/segment-codec.ts +4 -4
  102. package/src/cache/shell-snapshot.ts +417 -0
  103. package/src/cache/types.ts +158 -0
  104. package/src/cache/vercel/vercel-cache-store.ts +401 -124
  105. package/src/client.rsc.tsx +0 -3
  106. package/src/client.tsx +0 -3
  107. package/src/cloudflare/tracing.ts +7 -8
  108. package/src/defer.ts +11 -22
  109. package/src/handle.ts +37 -15
  110. package/src/handles/MetaTags.tsx +16 -82
  111. package/src/handles/breadcrumbs.ts +12 -14
  112. package/src/handles/deferred-resolution.ts +127 -0
  113. package/src/handles/is-thenable.ts +7 -8
  114. package/src/handles/meta.ts +7 -44
  115. package/src/host/errors.ts +15 -0
  116. package/src/host/index.ts +1 -0
  117. package/src/index.rsc.ts +8 -2
  118. package/src/index.ts +19 -13
  119. package/src/internal-debug.ts +11 -8
  120. package/src/prerender.ts +17 -4
  121. package/src/redirect-origin.ts +14 -0
  122. package/src/render-error-thrower.tsx +20 -0
  123. package/src/route-content-wrapper.tsx +12 -5
  124. package/src/route-definition/dsl-helpers.ts +21 -32
  125. package/src/route-definition/helper-factories.ts +0 -2
  126. package/src/route-definition/helpers-types.ts +43 -43
  127. package/src/route-definition/index.ts +1 -2
  128. package/src/route-definition/resolve-handler-use.ts +0 -1
  129. package/src/route-definition/use-item-types.ts +3 -6
  130. package/src/route-map-builder.ts +41 -4
  131. package/src/route-types.ts +0 -5
  132. package/src/router/find-match.ts +86 -8
  133. package/src/router/instrument.ts +9 -4
  134. package/src/router/lazy-includes.ts +72 -12
  135. package/src/router/loader-resolution.ts +14 -2
  136. package/src/router/manifest.ts +56 -11
  137. package/src/router/match-api.ts +76 -32
  138. package/src/router/match-handlers.ts +181 -135
  139. package/src/router/match-middleware/background-revalidation.ts +40 -23
  140. package/src/router/match-middleware/cache-store.ts +39 -24
  141. package/src/router/match-result.ts +35 -15
  142. package/src/router/middleware.ts +64 -38
  143. package/src/router/navigation-snapshot.ts +7 -5
  144. package/src/router/parse-pattern.ts +115 -0
  145. package/src/router/pattern-matching.ts +53 -64
  146. package/src/router/prefetch-limits.ts +37 -0
  147. package/src/router/prerender-match.ts +11 -5
  148. package/src/router/preview-match.ts +3 -1
  149. package/src/router/request-classification.ts +23 -8
  150. package/src/router/route-snapshot.ts +14 -2
  151. package/src/router/router-context.ts +3 -1
  152. package/src/router/router-interfaces.ts +32 -1
  153. package/src/router/router-options.ts +30 -0
  154. package/src/router/segment-resolution/fresh.ts +39 -3
  155. package/src/router/segment-resolution/loader-cache.ts +93 -2
  156. package/src/router/segment-resolution/loader-mask.ts +60 -0
  157. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  158. package/src/router/segment-resolution/mask-nested.ts +83 -0
  159. package/src/router/segment-resolution/revalidation.ts +3 -0
  160. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  161. package/src/router/substitute-pattern-params.ts +54 -35
  162. package/src/router/telemetry-otel.ts +6 -8
  163. package/src/router/telemetry.ts +9 -1
  164. package/src/router/tracing.ts +14 -5
  165. package/src/router/trie-matching.ts +19 -11
  166. package/src/router/url-params.ts +13 -0
  167. package/src/router.ts +47 -16
  168. package/src/rsc/full-payload.ts +70 -0
  169. package/src/rsc/handler.ts +60 -33
  170. package/src/rsc/manifest-init.ts +1 -1
  171. package/src/rsc/nonce.ts +10 -1
  172. package/src/rsc/progressive-enhancement.ts +61 -4
  173. package/src/rsc/redirect-guard.ts +2 -1
  174. package/src/rsc/rsc-rendering.ts +429 -37
  175. package/src/rsc/server-action.ts +25 -2
  176. package/src/rsc/shell-capture.ts +1190 -0
  177. package/src/rsc/shell-serve.ts +181 -0
  178. package/src/rsc/transition-gate.ts +89 -0
  179. package/src/rsc/types.ts +30 -0
  180. package/src/segment-loader-promise.ts +18 -0
  181. package/src/segment-system.tsx +149 -14
  182. package/src/server/context.ts +67 -9
  183. package/src/server/cookie-store.ts +73 -1
  184. package/src/server/loader-registry.ts +13 -1
  185. package/src/server/request-context.ts +169 -10
  186. package/src/ssr/index.tsx +462 -178
  187. package/src/ssr/inject-rsc-eager.ts +167 -0
  188. package/src/ssr/ssr-root.tsx +228 -0
  189. package/src/testing/collect-handle.ts +14 -8
  190. package/src/testing/dispatch.ts +152 -40
  191. package/src/testing/generated-routes.ts +27 -11
  192. package/src/testing/index.ts +6 -0
  193. package/src/testing/render-handler.ts +14 -0
  194. package/src/testing/render-route.tsx +13 -10
  195. package/src/testing/run-transition-when.ts +164 -0
  196. package/src/theme/ThemeProvider.tsx +36 -26
  197. package/src/types/handler-context.ts +1 -1
  198. package/src/types/index.ts +2 -0
  199. package/src/types/route-config.ts +19 -7
  200. package/src/types/segments.ts +100 -0
  201. package/src/urls/include-helper.ts +10 -8
  202. package/src/urls/include-provider.ts +71 -0
  203. package/src/urls/index.ts +1 -0
  204. package/src/urls/path-helper-types.ts +44 -12
  205. package/src/urls/path-helper.ts +5 -0
  206. package/src/urls/pattern-types.ts +36 -0
  207. package/src/urls/type-extraction.ts +43 -18
  208. package/src/urls/urls-function.ts +0 -1
  209. package/src/vercel/tracing.ts +7 -7
  210. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  211. package/src/vite/discovery/discover-routers.ts +1 -1
  212. package/src/vite/discovery/discovery-errors.ts +61 -0
  213. package/src/vite/index.ts +7 -0
  214. package/src/vite/inject-client-debug.ts +88 -0
  215. package/src/vite/plugins/vercel-output.ts +114 -25
  216. package/src/vite/plugins/version-injector.ts +22 -7
  217. package/src/vite/plugins/virtual-entries.ts +80 -22
  218. package/src/vite/rango.ts +29 -19
  219. package/src/vite/router-discovery.ts +171 -43
  220. package/src/vite/utils/prerender-utils.ts +17 -4
  221. package/src/vite/utils/shared-utils.ts +47 -0
  222. package/src/network-error-thrower.tsx +0 -18
@@ -1,6 +1,6 @@
1
1
  // src/vite/rango.ts
2
- import { readFileSync as readFileSync7 } from "node:fs";
3
- import { resolve as resolve10 } from "node:path";
2
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
3
+ import { resolve as resolve11 } from "node:path";
4
4
 
5
5
  // src/vite/plugins/expose-action-id.ts
6
6
  import MagicString from "magic-string";
@@ -2212,9 +2212,14 @@ initializeApp().catch(console.error);
2212
2212
  `.trim();
2213
2213
  var VIRTUAL_ENTRY_SSR = `
2214
2214
  import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
2215
- import { renderToReadableStream } from "react-dom/server.edge";
2215
+ import { renderToReadableStream, resume } from "react-dom/server.edge";
2216
+ import { prerender } from "react-dom/static.edge";
2216
2217
  import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
2217
- import { createSSRHandler } from "@rangojs/router/ssr";
2218
+ import {
2219
+ createSSRHandler,
2220
+ createShellCaptureHandler,
2221
+ createShellResumeHandler,
2222
+ } from "@rangojs/router/ssr";
2218
2223
 
2219
2224
  export const renderHTML = createSSRHandler({
2220
2225
  createFromReadableStream,
@@ -2223,8 +2228,35 @@ export const renderHTML = createSSRHandler({
2223
2228
  loadBootstrapScriptContent: () =>
2224
2229
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2225
2230
  });
2231
+
2232
+ export const captureShellHTML = createShellCaptureHandler({
2233
+ createFromReadableStream,
2234
+ renderToReadableStream,
2235
+ injectRSCPayload,
2236
+ prerender,
2237
+ resume,
2238
+ loadBootstrapScriptContent: () =>
2239
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
2240
+ });
2241
+
2242
+ export const resumeShellHTML = createShellResumeHandler({
2243
+ createFromReadableStream,
2244
+ renderToReadableStream,
2245
+ injectRSCPayload,
2246
+ prerender,
2247
+ resume,
2248
+ loadBootstrapScriptContent: () =>
2249
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
2250
+ });
2226
2251
  `.trim();
2252
+ var RSC_ENTRY_BOOTSTRAP_IMPORTS = [
2253
+ "virtual:rsc-router/routes-manifest",
2254
+ "virtual:rsc-router/loader-manifest"
2255
+ ];
2227
2256
  function getVirtualEntryRSC(routerPath) {
2257
+ const bootstrapImports = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
2258
+ (id) => `import "${id}";`
2259
+ ).join("\n");
2228
2260
  return `
2229
2261
  import {
2230
2262
  renderToReadableStream,
@@ -2238,15 +2270,9 @@ import { router } from "${routerPath}";
2238
2270
  import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
2239
2271
  import { VERSION } from "@rangojs/router:version";
2240
2272
 
2241
- // Import loader manifest to ensure all fetchable loaders are registered at startup
2242
- // This is critical for serverless/multi-process deployments where the loader module
2243
- // might not be imported before a GET request arrives
2244
- import "virtual:rsc-router/loader-manifest";
2245
-
2246
- // Import pre-generated route manifest so href() works immediately on cold start.
2247
- // In build mode, this contains the full route map generated at build time.
2248
- // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
2249
- import "virtual:rsc-router/routes-manifest";
2273
+ // Startup bootstrap imports (routes + loader manifests). See
2274
+ // RSC_ENTRY_BOOTSTRAP_IMPORTS \u2014 the same list the custom-entry injector uses.
2275
+ ${bootstrapImports}
2250
2276
 
2251
2277
  // Lazily create the handler on first request so that ESM live bindings
2252
2278
  // have resolved by the time we read \`router\`. During HMR the module may
@@ -2281,7 +2307,7 @@ export default function handler(request, env) {
2281
2307
  function getVirtualEntryRSCHost(hostEntryPath) {
2282
2308
  return `
2283
2309
  import * as __hostEntry from "${hostEntryPath}";
2284
- import { NoRouteMatchError } from "@rangojs/router/host";
2310
+ import { isNoRouteMatchError } from "@rangojs/router/host";
2285
2311
 
2286
2312
  // Register every sub-app's fetchable loaders + route manifests at startup, same
2287
2313
  // as the single-router entry. Discovery's host fallback populates these for all
@@ -2291,11 +2317,12 @@ import "virtual:rsc-router/routes-manifest";
2291
2317
 
2292
2318
  // The host entry module must export the HostRouter instance (createHostRouter()),
2293
2319
  // as a default export or a named \`hostRouter\`/\`router\` export. A Cloudflare-style
2294
- // \`export default { fetch }\` object is not a HostRouter and is rejected here.
2320
+ // \`export default { fetch }\` object is not a HostRouter and is rejected (on first
2321
+ // request; see the lazy resolution below).
2295
2322
  // We require BOTH .match() and .host(): a regular createRouter() also exposes
2296
2323
  // .match(), so matching on .match() alone would accept an ordinary router and then
2297
2324
  // return its MatchResult (not a Response) at runtime. .host() is unique to a
2298
- // HostRouter, so it disambiguates a mistaken \`hostRouter\` path at build time.
2325
+ // HostRouter, so it disambiguates a mistaken \`hostRouter\` path.
2299
2326
  // Exports are read dynamically (m[name]) so Rollup does not emit IMPORT_IS_UNDEFINED
2300
2327
  // warnings for the named exports a default-only host module legitimately omits.
2301
2328
  const __resolveHostRouter = (m) => {
@@ -2310,13 +2337,14 @@ const __resolveHostRouter = (m) => {
2310
2337
  }
2311
2338
  return undefined;
2312
2339
  };
2313
- const hostRouter = __resolveHostRouter(__hostEntry);
2314
2340
 
2315
- if (!hostRouter) {
2316
- throw new Error(
2317
- "[rango] The host entry (${hostEntryPath}) must export a HostRouter instance (createHostRouter()) for the node/vercel preset: a default export, or a named 'hostRouter'/'router' export. An ordinary createRouter() is not a host router (it has no .host()), and a Cloudflare-style 'export default { fetch }' object is not supported on this preset."
2318
- );
2319
- }
2341
+ // Resolve + validate the HostRouter lazily on first request, mirroring the
2342
+ // single-router entry's \`_handler\`. During HMR the host module can re-evaluate
2343
+ // before its createHostRouter() export has resolved; validating at module-
2344
+ // evaluation time would then throw a spurious "must export a HostRouter" error
2345
+ // overlay for a perfectly valid app. Resolving on first request lets the live
2346
+ // binding settle first.
2347
+ let _hostRouter;
2320
2348
 
2321
2349
  // input = { env, ctx } from the launcher / node server. The host router threads
2322
2350
  // it unchanged to each matched sub-app's handler and cache factory.
@@ -2324,10 +2352,22 @@ if (!hostRouter) {
2324
2352
  // an unmatched host into a response: catch NoRouteMatchError and return 404
2325
2353
  // (parity with the documented Cloudflare catch). Other errors propagate.
2326
2354
  export default async function handler(request, input) {
2355
+ if (!_hostRouter) {
2356
+ _hostRouter = __resolveHostRouter(__hostEntry);
2357
+ if (!_hostRouter) {
2358
+ throw new Error(
2359
+ "[rango] The host entry (${hostEntryPath}) must export a HostRouter instance (createHostRouter()) for the node/vercel preset: a default export, or a named 'hostRouter'/'router' export. An ordinary createRouter() is not a host router (it has no .host()), and a Cloudflare-style 'export default { fetch }' object is not supported on this preset."
2360
+ );
2361
+ }
2362
+ }
2327
2363
  try {
2328
- return await hostRouter.match(request, input);
2364
+ return await _hostRouter.match(request, input);
2329
2365
  } catch (err) {
2330
- if (err instanceof NoRouteMatchError) {
2366
+ // isNoRouteMatchError also matches by name: a workspace with a duplicated
2367
+ // @rangojs/router copy can throw a NoRouteMatchError whose prototype differs
2368
+ // from this module's import, so a bare instanceof would turn an
2369
+ // unmatched-host 404 into a 500.
2370
+ if (isNoRouteMatchError(err)) {
2331
2371
  return new Response("Not Found", { status: 404 });
2332
2372
  }
2333
2373
  throw err;
@@ -2353,7 +2393,7 @@ import { resolve } from "node:path";
2353
2393
  // package.json
2354
2394
  var package_default = {
2355
2395
  name: "@rangojs/router",
2356
- version: "0.0.0-experimental.e9c0b2f2",
2396
+ version: "0.0.0-experimental.ea9f40f2",
2357
2397
  description: "Django-inspired RSC router with composable URL patterns",
2358
2398
  keywords: [
2359
2399
  "react",
@@ -2533,7 +2573,7 @@ var package_default = {
2533
2573
  },
2534
2574
  dependencies: {
2535
2575
  "@types/debug": "^4.1.12",
2536
- "@vitejs/plugin-rsc": "^0.5.26",
2576
+ "@vitejs/plugin-rsc": "^0.5.27",
2537
2577
  debug: "^4.4.1",
2538
2578
  "magic-string": "^0.30.17",
2539
2579
  picomatch: "^4.0.4",
@@ -2543,6 +2583,8 @@ var package_default = {
2543
2583
  },
2544
2584
  devDependencies: {
2545
2585
  "@opentelemetry/api": "^1.9.0",
2586
+ "@opentelemetry/context-async-hooks": "^2.9.0",
2587
+ "@opentelemetry/sdk-trace-base": "^2.9.0",
2546
2588
  "@playwright/test": "^1.49.1",
2547
2589
  "@shared/e2e": "workspace:*",
2548
2590
  "@testing-library/dom": "^10.4.1",
@@ -2550,24 +2592,24 @@ var package_default = {
2550
2592
  "@types/node": "^24.10.1",
2551
2593
  "@types/react": "catalog:",
2552
2594
  "@types/react-dom": "catalog:",
2553
- esbuild: "^0.27.0",
2595
+ esbuild: "^0.28.1",
2554
2596
  "happy-dom": "^20.10.1",
2555
- jiti: "^2.6.1",
2597
+ jiti: "^2.7.0",
2556
2598
  react: "catalog:",
2557
2599
  "react-dom": "catalog:",
2558
2600
  typescript: "^5.3.0",
2559
- vitest: "^4.0.0"
2601
+ vitest: "^4.1.9"
2560
2602
  },
2561
2603
  peerDependencies: {
2562
- "@cloudflare/vite-plugin": "^1.38.0",
2604
+ "@cloudflare/vite-plugin": "^1.42.1",
2563
2605
  "@opentelemetry/api": "^1.9.0",
2564
2606
  "@playwright/test": "^1.49.1",
2565
2607
  "@testing-library/react": ">=16",
2566
2608
  "@vercel/functions": "^3.0.0",
2567
- "@vitejs/plugin-rsc": "^0.5.26",
2609
+ "@vitejs/plugin-rsc": "^0.5.27",
2568
2610
  react: ">=19.2.6 <20",
2569
2611
  "react-dom": ">=19.2.6 <20",
2570
- vite: "^8.0.0",
2612
+ vite: "^8.0.16",
2571
2613
  vitest: ">=3"
2572
2614
  },
2573
2615
  peerDependenciesMeta: {
@@ -2866,6 +2908,96 @@ function extractNamePrefixFromInclude(node) {
2866
2908
  }
2867
2909
  return null;
2868
2910
  }
2911
+ function thenSelectsNonDefaultMember(expr) {
2912
+ const findThenCall = (e) => {
2913
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression) && e.expression.name.text === "then") {
2914
+ return e;
2915
+ }
2916
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
2917
+ return findThenCall(e.expression.expression);
2918
+ }
2919
+ if (ts3.isPropertyAccessExpression(e)) return findThenCall(e.expression);
2920
+ if (ts3.isParenthesizedExpression(e)) return findThenCall(e.expression);
2921
+ if (ts3.isAwaitExpression(e)) return findThenCall(e.expression);
2922
+ return null;
2923
+ };
2924
+ const thenCall = findThenCall(expr);
2925
+ if (!thenCall || thenCall.arguments.length === 0) return false;
2926
+ const cb = thenCall.arguments[0];
2927
+ if (!ts3.isArrowFunction(cb) && !ts3.isFunctionExpression(cb)) return false;
2928
+ let ret;
2929
+ if (ts3.isBlock(cb.body)) {
2930
+ for (const stmt of cb.body.statements) {
2931
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
2932
+ ret = stmt.expression;
2933
+ break;
2934
+ }
2935
+ }
2936
+ } else {
2937
+ ret = cb.body;
2938
+ }
2939
+ if (!ret) return false;
2940
+ if (ts3.isPropertyAccessExpression(ret)) {
2941
+ return ret.name.text !== "default";
2942
+ }
2943
+ return false;
2944
+ }
2945
+ function extractDynamicImportSpecifier(node) {
2946
+ let body;
2947
+ if (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node)) {
2948
+ body = node.body;
2949
+ }
2950
+ if (!body) return null;
2951
+ let expr;
2952
+ if (ts3.isBlock(body)) {
2953
+ for (const stmt of body.statements) {
2954
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
2955
+ expr = stmt.expression;
2956
+ break;
2957
+ }
2958
+ }
2959
+ } else {
2960
+ expr = body;
2961
+ }
2962
+ if (!expr) return null;
2963
+ const findImportCall = (e) => {
2964
+ if (ts3.isCallExpression(e) && e.expression.kind === ts3.SyntaxKind.ImportKeyword) {
2965
+ return e;
2966
+ }
2967
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
2968
+ return findImportCall(e.expression.expression);
2969
+ }
2970
+ if (ts3.isPropertyAccessExpression(e)) return findImportCall(e.expression);
2971
+ if (ts3.isParenthesizedExpression(e)) return findImportCall(e.expression);
2972
+ if (ts3.isAwaitExpression(e)) return findImportCall(e.expression);
2973
+ return null;
2974
+ };
2975
+ const importCall = findImportCall(expr);
2976
+ if (!importCall || importCall.arguments.length === 0) return null;
2977
+ if (thenSelectsNonDefaultMember(expr)) return null;
2978
+ return getStringValue(importCall.arguments[0]);
2979
+ }
2980
+ function resolveDefaultExportTarget(code, sourceFile) {
2981
+ let result = null;
2982
+ function visit(node) {
2983
+ if (result) return;
2984
+ if (ts3.isExportAssignment(node) && !node.isExportEquals) {
2985
+ const expr = node.expression;
2986
+ if (ts3.isIdentifier(expr)) {
2987
+ result = { variableName: expr.text };
2988
+ } else if (ts3.isCallExpression(expr)) {
2989
+ const callee = expr.expression;
2990
+ if (ts3.isIdentifier(callee) && callee.text === "urls") {
2991
+ result = { inlineBlock: expr.getText(sourceFile) };
2992
+ }
2993
+ }
2994
+ return;
2995
+ }
2996
+ ts3.forEachChild(node, visit);
2997
+ }
2998
+ visit(sourceFile);
2999
+ return result;
3000
+ }
2869
3001
  function extractIncludesWithDiagnostics(code, sourceFileArg) {
2870
3002
  const sourceFile = sourceFileArg ?? ts3.createSourceFile(
2871
3003
  "input.tsx",
@@ -2891,12 +3023,19 @@ function extractIncludesWithDiagnostics(code, sourceFileArg) {
2891
3023
  }
2892
3024
  const secondArg = node.arguments[1];
2893
3025
  const namePrefix = extractNamePrefixFromInclude(node);
3026
+ const dynamicImportSpec = extractDynamicImportSpecifier(secondArg);
2894
3027
  if (ts3.isIdentifier(secondArg)) {
2895
3028
  resolved.push({
2896
3029
  pathPrefix,
2897
3030
  variableName: secondArg.text,
2898
3031
  namePrefix
2899
3032
  });
3033
+ } else if (dynamicImportSpec !== null) {
3034
+ resolved.push({
3035
+ pathPrefix,
3036
+ moduleSpecifier: dynamicImportSpec,
3037
+ namePrefix
3038
+ });
2900
3039
  } else if (ts3.isCallExpression(secondArg)) {
2901
3040
  const callText = secondArg.expression.getText(sourceFile);
2902
3041
  unresolvable.push({
@@ -3000,57 +3139,94 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
3000
3139
  diagnosticsOut.push({ ...entry, sourceFile: filePath });
3001
3140
  }
3002
3141
  }
3003
- for (const { pathPrefix, variableName, namePrefix } of includes) {
3142
+ for (const inc of includes) {
3143
+ const { pathPrefix, namePrefix } = inc;
3004
3144
  let childResult;
3005
- const imported = resolveImportedVariable(fullSource, variableName);
3006
- if (imported) {
3007
- const targetFile = resolveImportPath(imported.specifier, filePath);
3145
+ if (inc.moduleSpecifier) {
3146
+ const targetFile = resolveImportPath(inc.moduleSpecifier, filePath);
3008
3147
  if (!targetFile) {
3009
- if (diagnosticsOut) {
3010
- diagnosticsOut.push({
3011
- pathPrefix,
3012
- namePrefix,
3013
- reason: "file-not-found",
3014
- sourceFile: filePath,
3015
- detail: `import "${imported.specifier}" resolved to no file`
3016
- });
3017
- }
3148
+ diagnosticsOut?.push({
3149
+ pathPrefix,
3150
+ namePrefix,
3151
+ reason: "file-not-found",
3152
+ sourceFile: filePath,
3153
+ detail: `import("${inc.moduleSpecifier}") resolved to no file`
3154
+ });
3018
3155
  continue;
3019
3156
  }
3020
- childResult = buildCombinedRouteMapWithSearch(
3021
- targetFile,
3022
- imported.exportedName,
3023
- visited,
3024
- diagnosticsOut,
3025
- void 0,
3026
- memo
3027
- );
3028
- } else {
3029
- const sameFileBlock = extractUrlsBlockForVariable(
3030
- fullSource,
3031
- variableName,
3032
- parseBlock(memo, fullSource)
3157
+ let targetSource;
3158
+ try {
3159
+ targetSource = readSourceMemoized(memo, resolve2(targetFile));
3160
+ } catch (err) {
3161
+ diagnosticsOut?.push({
3162
+ pathPrefix,
3163
+ namePrefix,
3164
+ reason: "file-not-found",
3165
+ sourceFile: filePath,
3166
+ detail: `import("${inc.moduleSpecifier}") resolved to "${targetFile}" but reading it failed: ${err?.message ?? String(err)}`
3167
+ });
3168
+ continue;
3169
+ }
3170
+ const def = resolveDefaultExportTarget(
3171
+ targetSource,
3172
+ parseBlock(memo, targetSource)
3033
3173
  );
3034
- if (!sameFileBlock) {
3035
- if (diagnosticsOut) {
3036
- diagnosticsOut.push({
3037
- pathPrefix,
3038
- namePrefix,
3039
- reason: "unresolvable-import",
3040
- sourceFile: filePath,
3041
- detail: `variable "${variableName}" not found in imports or same-file scope`
3042
- });
3043
- }
3174
+ if (!def) {
3175
+ diagnosticsOut?.push({
3176
+ pathPrefix,
3177
+ namePrefix,
3178
+ reason: "unresolvable-import",
3179
+ sourceFile: filePath,
3180
+ detail: `import("${inc.moduleSpecifier}") has no resolvable \`export default urls(...)\``
3181
+ });
3044
3182
  continue;
3045
3183
  }
3046
- childResult = buildCombinedRouteMapWithSearch(
3047
- filePath,
3184
+ if (def.inlineBlock) {
3185
+ childResult = buildCombinedRouteMapWithSearch(
3186
+ targetFile,
3187
+ void 0,
3188
+ visited,
3189
+ diagnosticsOut,
3190
+ def.inlineBlock,
3191
+ memo
3192
+ );
3193
+ } else {
3194
+ const variableName = def.variableName;
3195
+ const resolved = resolveIncludedVariable({
3196
+ variableName,
3197
+ resolutionFile: targetFile,
3198
+ resolutionSource: targetSource,
3199
+ reportFile: filePath,
3200
+ memo,
3201
+ visited,
3202
+ diagnosticsOut,
3203
+ pathPrefix,
3204
+ namePrefix,
3205
+ fileNotFoundDetail: (specifier) => `import("${inc.moduleSpecifier}") default re-exports "${variableName}" from "${specifier}", which resolved to no file`,
3206
+ notFoundDetail: () => `import("${inc.moduleSpecifier}") default "${variableName}" not found in its imports or same-file scope`
3207
+ });
3208
+ if (!resolved) continue;
3209
+ childResult = resolved;
3210
+ }
3211
+ } else if (inc.variableName) {
3212
+ const variableName = inc.variableName;
3213
+ const resolved = resolveIncludedVariable({
3048
3214
  variableName,
3215
+ resolutionFile: filePath,
3216
+ resolutionSource: fullSource,
3217
+ reportFile: filePath,
3218
+ memo,
3049
3219
  visited,
3050
3220
  diagnosticsOut,
3051
- void 0,
3052
- memo
3053
- );
3221
+ pathPrefix,
3222
+ namePrefix,
3223
+ fileNotFoundDetail: (specifier) => `import "${specifier}" resolved to no file`,
3224
+ notFoundDetail: () => `variable "${variableName}" not found in imports or same-file scope`
3225
+ });
3226
+ if (!resolved) continue;
3227
+ childResult = resolved;
3228
+ } else {
3229
+ continue;
3054
3230
  }
3055
3231
  if (namePrefix === null) {
3056
3232
  continue;
@@ -3073,6 +3249,64 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
3073
3249
  }
3074
3250
  return routeMap;
3075
3251
  }
3252
+ function resolveIncludedVariable(opts) {
3253
+ const {
3254
+ variableName,
3255
+ resolutionFile,
3256
+ resolutionSource,
3257
+ reportFile,
3258
+ memo,
3259
+ visited,
3260
+ diagnosticsOut,
3261
+ pathPrefix,
3262
+ namePrefix
3263
+ } = opts;
3264
+ const imported = resolveImportedVariable(resolutionSource, variableName);
3265
+ if (imported) {
3266
+ const targetFile = resolveImportPath(imported.specifier, resolutionFile);
3267
+ if (!targetFile) {
3268
+ diagnosticsOut?.push({
3269
+ pathPrefix,
3270
+ namePrefix,
3271
+ reason: "file-not-found",
3272
+ sourceFile: reportFile,
3273
+ detail: opts.fileNotFoundDetail(imported.specifier)
3274
+ });
3275
+ return null;
3276
+ }
3277
+ return buildCombinedRouteMapWithSearch(
3278
+ targetFile,
3279
+ imported.exportedName,
3280
+ visited,
3281
+ diagnosticsOut,
3282
+ void 0,
3283
+ memo
3284
+ );
3285
+ }
3286
+ const sameFileBlock = extractUrlsBlockForVariable(
3287
+ resolutionSource,
3288
+ variableName,
3289
+ parseBlock(memo, resolutionSource)
3290
+ );
3291
+ if (!sameFileBlock) {
3292
+ diagnosticsOut?.push({
3293
+ pathPrefix,
3294
+ namePrefix,
3295
+ reason: "unresolvable-import",
3296
+ sourceFile: reportFile,
3297
+ detail: opts.notFoundDetail()
3298
+ });
3299
+ return null;
3300
+ }
3301
+ return buildCombinedRouteMapWithSearch(
3302
+ resolutionFile,
3303
+ variableName,
3304
+ visited,
3305
+ diagnosticsOut,
3306
+ void 0,
3307
+ memo
3308
+ );
3309
+ }
3076
3310
  function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock, memo) {
3077
3311
  visited = visited ?? /* @__PURE__ */ new Set();
3078
3312
  memo = memo ?? createScanMemo();
@@ -3685,6 +3919,7 @@ function isViteDepCachePath(filePath, cacheDir) {
3685
3919
 
3686
3920
  // src/vite/utils/shared-utils.ts
3687
3921
  import * as Vite from "vite";
3922
+ import { isAbsolute, resolve as resolve4 } from "node:path";
3688
3923
 
3689
3924
  // src/vite/plugins/performance-tracks.ts
3690
3925
  import { readFile } from "node:fs/promises";
@@ -3767,6 +4002,21 @@ var versionRolldownPlugin = {
3767
4002
  var sharedRolldownOptions = {
3768
4003
  plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()]
3769
4004
  };
4005
+ function normalizeHostRouterEntry(rawInput, root, exists) {
4006
+ const raw = rawInput.replaceAll("\\", "/");
4007
+ const isRelative = raw.startsWith("./") || raw.startsWith("../");
4008
+ const isRooted = raw.startsWith("/") || isAbsolute(rawInput);
4009
+ if (isRooted) return raw;
4010
+ if (isRelative) {
4011
+ if (!exists(resolve4(root, raw))) {
4012
+ throw new Error(
4013
+ `[rango] hostRouter entry not found: "${rawInput}" (resolved under ${root}). Point it at your createHostRouter() module, e.g. rango({ hostRouter: "./src/worker.rsc.tsx" }).`
4014
+ );
4015
+ }
4016
+ return raw;
4017
+ }
4018
+ return exists(resolve4(root, raw)) ? "./" + raw : raw;
4019
+ }
3770
4020
  function createVirtualEntriesPlugin(entries, routerPathRef) {
3771
4021
  const virtualModules = {};
3772
4022
  if (entries.client === VIRTUAL_IDS.browser) {
@@ -3982,7 +4232,7 @@ function resolveClientChunks(option, ctx) {
3982
4232
  // src/vite/plugins/vercel-output.ts
3983
4233
  import { rm, mkdir, cp, writeFile } from "node:fs/promises";
3984
4234
  import { existsSync as existsSync4 } from "node:fs";
3985
- import { resolve as resolve4, join as join3 } from "node:path";
4235
+ import { resolve as resolve5, join as join3 } from "node:path";
3986
4236
  import { createRequire as createRequire2 } from "node:module";
3987
4237
  import { pathToFileURL } from "node:url";
3988
4238
  var LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
@@ -4011,9 +4261,55 @@ function assertVercelNodeRuntime(runtime) {
4011
4261
  );
4012
4262
  }
4013
4263
  }
4014
- async function assemble(root, options) {
4264
+ function assertValidVercelFunctionName(functionName) {
4265
+ if (!/^[A-Za-z0-9._-]+$/.test(functionName)) {
4266
+ throw new Error(
4267
+ `[rango] preset "vercel": invalid functionName ${JSON.stringify(
4268
+ functionName
4269
+ )}. Use letters, digits, ".", "_" or "-" only (it becomes the .func directory and the routing dest).`
4270
+ );
4271
+ }
4272
+ }
4273
+ function buildVercelVcConfig(vercel) {
4274
+ const vcConfig = {
4275
+ runtime: vercel.runtime ?? "nodejs22.x",
4276
+ handler: "index.mjs",
4277
+ launcherType: "Nodejs",
4278
+ shouldAddHelpers: false,
4279
+ supportsResponseStreaming: true,
4280
+ maxDuration: vercel.maxDuration ?? 30
4281
+ };
4282
+ if (vercel.memory != null) vcConfig.memory = vercel.memory;
4283
+ if (vercel.regions != null) vcConfig.regions = vercel.regions;
4284
+ return vcConfig;
4285
+ }
4286
+ function buildVercelOutputConfig(functionName, assetsDir) {
4287
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
4288
+ const assetHeaderRoute = assetsPrefix ? [
4289
+ {
4290
+ src: `/${escapeRegExp(assetsPrefix)}/(.*)`,
4291
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
4292
+ continue: true
4293
+ }
4294
+ ] : [];
4295
+ return {
4296
+ version: 3,
4297
+ routes: [
4298
+ ...assetHeaderRoute,
4299
+ { handle: "filesystem" },
4300
+ { src: "/(.*)", dest: `/${functionName}` }
4301
+ ]
4302
+ };
4303
+ }
4304
+ async function assemble(root, options, assetsDir, publicDir) {
4015
4305
  const vercel = options.vercel ?? {};
4016
4306
  assertVercelNodeRuntime(vercel.runtime);
4307
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
4308
+ if (publicDir && assetsPrefix && existsSync4(join3(publicDir, assetsPrefix))) {
4309
+ console.warn(
4310
+ `[rango] preset "vercel": ${join3(publicDir, assetsPrefix)} exists. Files under public/${assetsPrefix}/ share the /${assetsPrefix}/ URL prefix with hashed build assets and will be served with a one-year immutable cache-control header. Move un-hashed public files out of "${assetsPrefix}/".`
4311
+ );
4312
+ }
4017
4313
  const dist = join3(root, "dist");
4018
4314
  for (const dir of ["client", "rsc", "ssr"]) {
4019
4315
  if (!existsSync4(join3(dist, dir))) {
@@ -4023,6 +4319,7 @@ async function assemble(root, options) {
4023
4319
  }
4024
4320
  }
4025
4321
  const functionName = vercel.functionName ?? "index";
4322
+ assertValidVercelFunctionName(functionName);
4026
4323
  const out = join3(root, ".vercel", "output");
4027
4324
  const funcDir = join3(out, "functions", `${functionName}.func`);
4028
4325
  await rm(out, { recursive: true, force: true });
@@ -4108,33 +4405,13 @@ async function assemble(root, options) {
4108
4405
  join3(funcDir, "package.json"),
4109
4406
  JSON.stringify({ type: "module" }, null, 2) + "\n"
4110
4407
  );
4111
- const vcConfig = {
4112
- runtime: vercel.runtime ?? "nodejs22.x",
4113
- handler: "index.mjs",
4114
- launcherType: "Nodejs",
4115
- shouldAddHelpers: false,
4116
- supportsResponseStreaming: true,
4117
- maxDuration: vercel.maxDuration ?? 30
4118
- };
4119
- if (vercel.memory != null) vcConfig.memory = vercel.memory;
4120
- if (vercel.regions != null) vcConfig.regions = vercel.regions;
4121
4408
  await writeFile(
4122
4409
  join3(funcDir, ".vc-config.json"),
4123
- JSON.stringify(vcConfig, null, 2) + "\n"
4410
+ JSON.stringify(buildVercelVcConfig(vercel), null, 2) + "\n"
4124
4411
  );
4125
4412
  await writeFile(
4126
4413
  join3(out, "config.json"),
4127
- JSON.stringify(
4128
- {
4129
- version: 3,
4130
- routes: [
4131
- { handle: "filesystem" },
4132
- { src: "/(.*)", dest: `/${functionName}` }
4133
- ]
4134
- },
4135
- null,
4136
- 2
4137
- ) + "\n"
4414
+ JSON.stringify(buildVercelOutputConfig(functionName, assetsDir), null, 2) + "\n"
4138
4415
  );
4139
4416
  console.log(
4140
4417
  `[rango] assembled .vercel/output (function: ${functionName}.func)`
@@ -4143,11 +4420,15 @@ async function assemble(root, options) {
4143
4420
  function createVercelOutputPlugin(options) {
4144
4421
  let root = process.cwd();
4145
4422
  let isBuild = false;
4423
+ let assetsDir = "assets";
4424
+ let publicDir = "";
4146
4425
  return {
4147
4426
  name: "@rangojs/router:vercel-output",
4148
4427
  configResolved(config) {
4149
- root = resolve4(config.root);
4428
+ root = resolve5(config.root);
4150
4429
  isBuild = config.command === "build";
4430
+ assetsDir = config.environments?.client?.build?.assetsDir ?? config.build?.assetsDir ?? "assets";
4431
+ publicDir = config.publicDir || "";
4151
4432
  },
4152
4433
  // buildApp runs once after the whole multi-environment build (rsc, client,
4153
4434
  // ssr), so dist/ is complete here. closeBundle is unusable for this: it
@@ -4157,7 +4438,7 @@ function createVercelOutputPlugin(options) {
4157
4438
  order: "post",
4158
4439
  async handler() {
4159
4440
  if (!isBuild) return;
4160
- await assemble(root, options);
4441
+ await assemble(root, options, assetsDir, publicDir);
4161
4442
  }
4162
4443
  }
4163
4444
  };
@@ -4190,7 +4471,7 @@ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550$
4190
4471
  }
4191
4472
 
4192
4473
  // src/vite/plugins/version-injector.ts
4193
- import { resolve as resolve5 } from "node:path";
4474
+ import { resolve as resolve6 } from "node:path";
4194
4475
  import * as Vite2 from "vite";
4195
4476
  function createVersionInjectorPlugin(rscEntryPath) {
4196
4477
  let resolvedEntryPath = "";
@@ -4201,7 +4482,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
4201
4482
  let entryPath = rscEntryPath;
4202
4483
  if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
4203
4484
  if (entryPath) {
4204
- resolvedEntryPath = resolve5(config.root, entryPath);
4485
+ resolvedEntryPath = resolve6(config.root, entryPath);
4205
4486
  }
4206
4487
  },
4207
4488
  transform(code, id) {
@@ -4211,9 +4492,9 @@ function createVersionInjectorPlugin(rscEntryPath) {
4211
4492
  if (normalizedId !== normalizedEntry) {
4212
4493
  return null;
4213
4494
  }
4214
- const prepend = [
4215
- `import "virtual:rsc-router/routes-manifest";`
4216
- ];
4495
+ const prepend = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
4496
+ (id2) => `import "${id2}";`
4497
+ );
4217
4498
  let newCode = code;
4218
4499
  const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
4219
4500
  if (needsVersion) {
@@ -4310,11 +4591,40 @@ function createCjsToEsmPlugin() {
4310
4591
 
4311
4592
  // src/vite/router-discovery.ts
4312
4593
  import { createServer as createViteServer } from "vite";
4313
- import { resolve as resolve9 } from "node:path";
4594
+ import { resolve as resolve10 } from "node:path";
4314
4595
  import { readFileSync as readFileSync6 } from "node:fs";
4315
4596
  import { createRequire as createRequire3, register } from "node:module";
4316
4597
  import { pathToFileURL as pathToFileURL2 } from "node:url";
4317
4598
 
4599
+ // src/vite/inject-client-debug.ts
4600
+ function isRouterInternalDebugId(id) {
4601
+ if (!id.includes("internal-debug")) return false;
4602
+ const norm = id.replace(/\\/g, "/");
4603
+ return /\/internal-debug\.[cm]?[jt]sx?(\?|$)/.test(norm) && (norm.includes("/@rangojs/router/") || norm.includes("/packages/rangojs-router/"));
4604
+ }
4605
+ function injectClientDebugFlag(id) {
4606
+ if (!isRouterInternalDebugId(id)) return null;
4607
+ return {
4608
+ code: `export const INTERNAL_RANGO_DEBUG = ${!!process.env.INTERNAL_RANGO_DEBUG};
4609
+ `,
4610
+ map: null
4611
+ };
4612
+ }
4613
+ function internalDebugNoCacheMiddleware() {
4614
+ return function rangoInternalDebugNoCache(req, res, next) {
4615
+ if (req.url && isRouterInternalDebugId(req.url)) {
4616
+ const setHeader = res.setHeader.bind(res);
4617
+ res.setHeader = (name, value) => {
4618
+ return setHeader(
4619
+ name,
4620
+ name.toLowerCase() === "cache-control" ? "no-cache" : value
4621
+ );
4622
+ };
4623
+ }
4624
+ next();
4625
+ };
4626
+ }
4627
+
4318
4628
  // src/vite/plugins/virtual-stub-plugin.ts
4319
4629
  function createVirtualStubPlugin() {
4320
4630
  const STUB_PREFIXES = [
@@ -4602,15 +4912,36 @@ function checkSelfGenWrite(state, filePath, consume) {
4602
4912
  import { AsyncLocalStorage } from "node:async_hooks";
4603
4913
 
4604
4914
  // src/internal-debug.ts
4605
- var INTERNAL_RANGO_DEBUG = typeof __RANGO_DEBUG__ !== "undefined" ? __RANGO_DEBUG__ : typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
4915
+ var INTERNAL_RANGO_DEBUG = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
4606
4916
 
4607
4917
  // src/router/logging.ts
4608
4918
  var routerLogContext = new AsyncLocalStorage();
4609
4919
 
4610
- // src/router/pattern-matching.ts
4920
+ // src/router/url-params.ts
4921
+ var PATH_SAFE_ESCAPES = {
4922
+ "%3A": ":",
4923
+ "%40": "@",
4924
+ "%24": "$",
4925
+ "%26": "&",
4926
+ "%2B": "+",
4927
+ "%2C": ",",
4928
+ "%3B": ";",
4929
+ "%3D": "="
4930
+ };
4931
+ function encodePathSegment(value) {
4932
+ return encodeURIComponent(value).replace(
4933
+ /%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
4934
+ (match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match
4935
+ );
4936
+ }
4937
+ function encodePathRemainder(value, encode = encodePathSegment) {
4938
+ return value.split("/").map(encode).join("/");
4939
+ }
4940
+
4941
+ // src/router/parse-pattern.ts
4611
4942
  function parsePattern(pattern) {
4612
4943
  const segments = [];
4613
- const segmentRegex = /\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
4944
+ const segmentRegex = /\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([+*])?([^/]*)|(\*)|([^/]+))/g;
4614
4945
  let match;
4615
4946
  while ((match = segmentRegex.exec(pattern)) !== null) {
4616
4947
  const [
@@ -4620,6 +4951,7 @@ function parsePattern(pattern) {
4620
4951
  ,
4621
4952
  constraint,
4622
4953
  optional,
4954
+ repeat,
4623
4955
  suffix,
4624
4956
  wildcard,
4625
4957
  staticText
@@ -4627,17 +4959,38 @@ function parsePattern(pattern) {
4627
4959
  if (wildcard) {
4628
4960
  segments.push({ type: "wildcard", value: "*", optional: false });
4629
4961
  } else if (paramName) {
4630
- segments.push({
4631
- type: "param",
4632
- value: paramName,
4633
- optional: optional === "?",
4634
- constraint: constraint ? constraint.split("|") : void 0,
4635
- suffix: suffix || void 0
4636
- });
4962
+ if (repeat && !suffix && optional !== "?" && !constraint) {
4963
+ segments.push({
4964
+ type: "wildcard",
4965
+ value: paramName,
4966
+ optional: false,
4967
+ oneOrMore: repeat === "+"
4968
+ });
4969
+ } else {
4970
+ segments.push({
4971
+ type: "param",
4972
+ value: paramName,
4973
+ optional: optional === "?",
4974
+ constraint: constraint ? constraint.split("|") : void 0,
4975
+ // Fold a non-modifier `+`/`*` back into the literal suffix.
4976
+ suffix: (repeat ?? "") + (suffix ?? "") || void 0
4977
+ });
4978
+ }
4637
4979
  } else if (staticText) {
4638
4980
  segments.push({ type: "static", value: staticText, optional: false });
4639
4981
  }
4640
4982
  }
4983
+ for (let i = 0; i < segments.length - 1; i++) {
4984
+ const s = segments[i];
4985
+ if (s.type === "wildcard" && s.value !== "*") {
4986
+ segments[i] = {
4987
+ type: "param",
4988
+ value: s.value,
4989
+ optional: false,
4990
+ suffix: s.oneOrMore ? "+" : "*"
4991
+ };
4992
+ }
4993
+ }
4641
4994
  return segments;
4642
4995
  }
4643
4996
 
@@ -4840,11 +5193,18 @@ function insertSegments(node, segments, index, leafBase, paramNames) {
4840
5193
  } else if (segment.type === "wildcard") {
4841
5194
  const wildLeaf = {
4842
5195
  ...buildLeaf(leafBase, paramNames),
4843
- pn: "*"
5196
+ pn: segment.value,
5197
+ ...segment.oneOrMore ? { w1: true } : {}
4844
5198
  };
4845
- const existing = node.w ? { ...node.w } : void 0;
4846
- const merged = mergeLeaves(existing, wildLeaf);
4847
- node.w = merged;
5199
+ const existing = node.w;
5200
+ const canMerge = existing === void 0 || Boolean(existing.rt) || Boolean(wildLeaf.rt) || existing.pn === wildLeaf.pn && Boolean(existing.w1) === Boolean(wildLeaf.w1);
5201
+ if (canMerge) {
5202
+ const merged = mergeLeaves(
5203
+ existing ? { ...existing } : void 0,
5204
+ wildLeaf
5205
+ );
5206
+ node.w = merged;
5207
+ }
4848
5208
  }
4849
5209
  }
4850
5210
 
@@ -4944,15 +5304,25 @@ import {
4944
5304
  statSync,
4945
5305
  writeFileSync as writeFileSync2
4946
5306
  } from "node:fs";
4947
- import { resolve as resolve6 } from "node:path";
5307
+ import { resolve as resolve7 } from "node:path";
4948
5308
  function encodePathParam(value) {
4949
- return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
5309
+ return encodePathRemainder(String(value), encodeURIComponent);
4950
5310
  }
4951
5311
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
4952
5312
  let result = pattern;
4953
5313
  let hadOmittedOptional = false;
4954
5314
  for (const [key, value] of Object.entries(params)) {
4955
5315
  const escaped = escapeRegExp(key);
5316
+ const catchAllRe = new RegExp(`:${escaped}[+*]`);
5317
+ if (catchAllRe.test(result)) {
5318
+ if (value === "") {
5319
+ result = result.replace(catchAllRe, "");
5320
+ hadOmittedOptional = true;
5321
+ } else {
5322
+ result = result.replace(catchAllRe, encodePathRemainder(value, encode));
5323
+ }
5324
+ continue;
5325
+ }
4956
5326
  if (value === "") {
4957
5327
  result = result.replace(
4958
5328
  new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
@@ -5067,7 +5437,7 @@ function resolvePrerenderError(registry, error, onError, label, elapsed, phase,
5067
5437
  throw error;
5068
5438
  }
5069
5439
  function getStagedAssetDir(projectRoot) {
5070
- return resolve6(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
5440
+ return resolve7(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
5071
5441
  }
5072
5442
  function resetStagedBuildAssets(projectRoot) {
5073
5443
  rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
@@ -5077,7 +5447,7 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
5077
5447
  mkdirSync(stagedDir, { recursive: true });
5078
5448
  const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
5079
5449
  const fileName = `${prefix}-${contentHash}.js`;
5080
- const filePath = resolve6(stagedDir, fileName);
5450
+ const filePath = resolve7(stagedDir, fileName);
5081
5451
  if (!existsSync5(filePath)) {
5082
5452
  writeFileSync2(filePath, `export default ${exportValue};
5083
5453
  `);
@@ -5086,12 +5456,12 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
5086
5456
  }
5087
5457
  function copyStagedBuildAssets(projectRoot, fileNames) {
5088
5458
  const stagedDir = getStagedAssetDir(projectRoot);
5089
- const distAssetsDir = resolve6(projectRoot, "dist/rsc/assets");
5459
+ const distAssetsDir = resolve7(projectRoot, "dist/rsc/assets");
5090
5460
  mkdirSync(distAssetsDir, { recursive: true });
5091
5461
  let totalBytes = 0;
5092
5462
  for (const fileName of new Set(fileNames)) {
5093
- const stagedPath = resolve6(stagedDir, fileName);
5094
- const distPath = resolve6(distAssetsDir, fileName);
5463
+ const stagedPath = resolve7(stagedDir, fileName);
5464
+ const distPath = resolve7(distAssetsDir, fileName);
5095
5465
  copyFileSync(stagedPath, distPath);
5096
5466
  totalBytes += statSync(stagedPath).size;
5097
5467
  }
@@ -5532,6 +5902,28 @@ var DiscoveryError = class _DiscoveryError extends Error {
5532
5902
  Object.setPrototypeOf(this, _DiscoveryError.prototype);
5533
5903
  }
5534
5904
  };
5905
+ function describeDiscoveryFailure(err, opts = {}) {
5906
+ if (err instanceof DiscoveryError && err.caught.length === 0) {
5907
+ const entry = err.entryPath ?? "the router entry";
5908
+ if (opts.reoptimizeObserved) {
5909
+ return {
5910
+ level: "warn",
5911
+ message: `[rango] No routers found while Vite was re-optimizing dependencies on dev boot. This is transient: routes are served per-request and discovery re-runs automatically, so it clears on the next boot. If routes still 404, confirm ${entry} calls createRouter().`
5912
+ };
5913
+ }
5914
+ return {
5915
+ level: "error",
5916
+ message: `${err.message}
5917
+ Ensure ${entry} calls createRouter() at module top level and that the configured router entry path is correct.`
5918
+ };
5919
+ }
5920
+ const e = err;
5921
+ const detail = e?.stack ?? e?.message ?? String(err);
5922
+ return {
5923
+ level: "error",
5924
+ message: `[rango] Router discovery failed: ${detail}`
5925
+ };
5926
+ }
5535
5927
 
5536
5928
  // src/vite/discovery/discover-routers.ts
5537
5929
  var debug10 = createRangoDebugger(NS.discovery);
@@ -5616,7 +6008,7 @@ async function discoverRouters(state, rscEnv) {
5616
6008
  if (!router.urlpatterns || !generateManifestFull) {
5617
6009
  continue;
5618
6010
  }
5619
- const manifest = generateManifestFull(
6011
+ const manifest = await generateManifestFull(
5620
6012
  router.urlpatterns,
5621
6013
  routerMountIndex,
5622
6014
  {
@@ -5759,8 +6151,45 @@ async function discoverRouters(state, rscEnv) {
5759
6151
  return serverMod;
5760
6152
  }
5761
6153
 
6154
+ // src/vite/discovery/dev-prerender-cache.ts
6155
+ function payloadBodiesFromResult(result) {
6156
+ const main = JSON.stringify({
6157
+ segments: result.segments,
6158
+ handles: result.handles
6159
+ });
6160
+ const intercept = result.interceptSegments?.length ? JSON.stringify({
6161
+ segments: [...result.segments, ...result.interceptSegments],
6162
+ handles: result.interceptHandles ?? ""
6163
+ }) : main;
6164
+ return { main, intercept };
6165
+ }
6166
+ function devPrerenderCacheKey(pathname, opts) {
6167
+ return `${pathname}|i=${opts.intercept ? 1 : 0}|p=${opts.passthrough ? 1 : 0}|r=${opts.routeName ?? ""}`;
6168
+ }
6169
+ var MAX_ENTRIES_PER_ROUTER = 500;
6170
+ function createDevPrerenderCache(maxEntriesPerRouter = MAX_ENTRIES_PER_ROUTER) {
6171
+ const buckets = /* @__PURE__ */ new WeakMap();
6172
+ return {
6173
+ get(router, key) {
6174
+ return buckets.get(router)?.get(key);
6175
+ },
6176
+ set(router, key, body) {
6177
+ let bucket = buckets.get(router);
6178
+ if (!bucket) {
6179
+ bucket = /* @__PURE__ */ new Map();
6180
+ buckets.set(router, bucket);
6181
+ }
6182
+ if (!bucket.has(key) && bucket.size >= maxEntriesPerRouter) {
6183
+ const oldest = bucket.keys().next().value;
6184
+ if (oldest !== void 0) bucket.delete(oldest);
6185
+ }
6186
+ bucket.set(key, body);
6187
+ }
6188
+ };
6189
+ }
6190
+
5762
6191
  // src/vite/discovery/route-types-writer.ts
5763
- import { dirname as dirname3, join as join4, resolve as resolve7 } from "node:path";
6192
+ import { dirname as dirname3, join as join4, resolve as resolve8 } from "node:path";
5764
6193
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
5765
6194
  function filterUserNamedRoutes(manifest) {
5766
6195
  const filtered = {};
@@ -5790,7 +6219,7 @@ function writeRouteTypesFiles(state) {
5790
6219
  if (state.perRouterManifests.length === 0) return;
5791
6220
  try {
5792
6221
  const entryDir = dirname3(
5793
- resolve7(state.projectRoot, state.resolvedEntryPath)
6222
+ resolve8(state.projectRoot, state.resolvedEntryPath)
5794
6223
  );
5795
6224
  const oldCombinedPath = join4(entryDir, "named-routes.gen.ts");
5796
6225
  if (existsSync6(oldCombinedPath)) {
@@ -6001,13 +6430,13 @@ function generatePerRouterModule(state, routerId) {
6001
6430
  }
6002
6431
 
6003
6432
  // src/vite/discovery/bundle-postprocess.ts
6004
- import { resolve as resolve8 } from "node:path";
6433
+ import { resolve as resolve9 } from "node:path";
6005
6434
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
6006
6435
  function postprocessBundle(state) {
6007
6436
  const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
6008
6437
  const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
6009
6438
  if (!hasPrerenderData && !hasStaticData) return;
6010
- const rscEntryPath = resolve8(
6439
+ const rscEntryPath = resolve9(
6011
6440
  state.projectRoot,
6012
6441
  "dist/rsc",
6013
6442
  state.rscEntryFileName ?? "index.js"
@@ -6028,7 +6457,7 @@ function postprocessBundle(state) {
6028
6457
  ];
6029
6458
  for (const target of evictionTargets) {
6030
6459
  for (const info of target.infos) {
6031
- const chunkPath = resolve8(state.projectRoot, "dist/rsc", info.fileName);
6460
+ const chunkPath = resolve9(state.projectRoot, "dist/rsc", info.fileName);
6032
6461
  try {
6033
6462
  const code = readFileSync5(chunkPath, "utf-8");
6034
6463
  const result = evictHandlerCode(
@@ -6073,7 +6502,7 @@ function postprocessBundle(state) {
6073
6502
  `export default m;`,
6074
6503
  ""
6075
6504
  ].join("\n");
6076
- const manifestPath = resolve8(
6505
+ const manifestPath = resolve9(
6077
6506
  state.projectRoot,
6078
6507
  "dist/rsc/__prerender-manifest.js"
6079
6508
  );
@@ -6111,7 +6540,7 @@ function postprocessBundle(state) {
6111
6540
  }
6112
6541
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
6113
6542
  `;
6114
- const manifestPath = resolve8(
6543
+ const manifestPath = resolve9(
6115
6544
  state.projectRoot,
6116
6545
  "dist/rsc/__static-manifest.js"
6117
6546
  );
@@ -6143,8 +6572,8 @@ function createDiscoveryGate(s, debug11) {
6143
6572
  let pendingEvents = false;
6144
6573
  const beginGate = () => {
6145
6574
  if (gatePending) return;
6146
- s.discoveryDone = new Promise((resolve11) => {
6147
- gateResolver = resolve11;
6575
+ s.discoveryDone = new Promise((resolve12) => {
6576
+ gateResolver = resolve12;
6148
6577
  });
6149
6578
  gatePending = true;
6150
6579
  };
@@ -6236,15 +6665,15 @@ function selectForwardableResolvePlugins(plugins) {
6236
6665
  }
6237
6666
  function pickForwardedRunnerConfig(config) {
6238
6667
  const r = config.resolve ?? {};
6239
- const resolve11 = {};
6240
- if (r.alias !== void 0) resolve11.alias = r.alias;
6241
- if (r.dedupe !== void 0) resolve11.dedupe = r.dedupe;
6242
- if (r.conditions !== void 0) resolve11.conditions = r.conditions;
6243
- if (r.mainFields !== void 0) resolve11.mainFields = r.mainFields;
6244
- if (r.extensions !== void 0) resolve11.extensions = r.extensions;
6668
+ const resolve12 = {};
6669
+ if (r.alias !== void 0) resolve12.alias = r.alias;
6670
+ if (r.dedupe !== void 0) resolve12.dedupe = r.dedupe;
6671
+ if (r.conditions !== void 0) resolve12.conditions = r.conditions;
6672
+ if (r.mainFields !== void 0) resolve12.mainFields = r.mainFields;
6673
+ if (r.extensions !== void 0) resolve12.extensions = r.extensions;
6245
6674
  if (r.preserveSymlinks !== void 0)
6246
- resolve11.preserveSymlinks = r.preserveSymlinks;
6247
- if (r.tsconfigPaths !== void 0) resolve11.tsconfigPaths = r.tsconfigPaths;
6675
+ resolve12.preserveSymlinks = r.preserveSymlinks;
6676
+ if (r.tsconfigPaths !== void 0) resolve12.tsconfigPaths = r.tsconfigPaths;
6248
6677
  const userOxc = config.oxc;
6249
6678
  const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
6250
6679
  const oxc = userOxc && typeof userOxc === "object" ? {
@@ -6252,7 +6681,7 @@ function pickForwardedRunnerConfig(config) {
6252
6681
  jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
6253
6682
  } : { jsx: { runtime: "automatic", importSource: "react" } };
6254
6683
  return {
6255
- resolve: resolve11,
6684
+ resolve: resolve12,
6256
6685
  define: config.define,
6257
6686
  oxc
6258
6687
  };
@@ -6331,7 +6760,7 @@ async function resolveBuildEnv(option, factoryCtx) {
6331
6760
  }
6332
6761
  try {
6333
6762
  const userRequire = createRequire3(
6334
- resolve9(factoryCtx.root, "package.json")
6763
+ resolve10(factoryCtx.root, "package.json")
6335
6764
  );
6336
6765
  const wranglerPath = userRequire.resolve("wrangler");
6337
6766
  const { getPlatformProxy } = await import(pathToFileURL2(wranglerPath).href);
@@ -6386,13 +6815,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6386
6815
  let viteMode = "production";
6387
6816
  return {
6388
6817
  name: "@rangojs/router:discovery",
6389
- config() {
6390
- const config = {
6391
- define: {
6392
- __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
6393
- }
6394
- };
6395
- return config;
6818
+ // Make INTERNAL_RANGO_DEBUG reach the CLIENT debug logs by just setting the
6819
+ // env var. See injectClientDebugFlag: bakes the resolved flag into the
6820
+ // internal-debug module so FE debug no longer depends on Vite delivering the
6821
+ // `__RANGO_DEBUG__` define to the client (which it does only as an injected
6822
+ // global whose presence varies across consumer setups). Runs in dev and build.
6823
+ transform(_code, id) {
6824
+ return injectClientDebugFlag(id);
6396
6825
  },
6397
6826
  configResolved(config) {
6398
6827
  s.projectRoot = config.root;
@@ -6430,9 +6859,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6430
6859
  if (s.isBuildMode) return;
6431
6860
  if (globalThis.__rscRouterDiscoveryActive) return;
6432
6861
  s.devServer = server;
6862
+ server.middlewares.use(internalDebugNoCacheMiddleware());
6433
6863
  let resolveDiscovery;
6434
- const discoveryPromise = new Promise((resolve11) => {
6435
- resolveDiscovery = resolve11;
6864
+ const discoveryPromise = new Promise((resolve12) => {
6865
+ resolveDiscovery = resolve12;
6436
6866
  });
6437
6867
  const gate = createDiscoveryGate(s, debugDiscovery);
6438
6868
  const beginDiscoveryGate = gate.beginGate;
@@ -6539,6 +6969,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6539
6969
  );
6540
6970
  console.warn(`[rango] Failed to create temp runner: ${err.message}`);
6541
6971
  }
6972
+ await prerenderTempServer?.close().catch(() => {
6973
+ });
6974
+ prerenderTempServer = null;
6975
+ prerenderNodeRegistry = null;
6542
6976
  return null;
6543
6977
  }
6544
6978
  async function clearTempRegistries(tempRscEnv) {
@@ -6590,6 +7024,15 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6590
7024
  await importEntryAndRegistry(tempRscEnv);
6591
7025
  return tempRscEnv;
6592
7026
  }
7027
+ const emitDiscoveryFailure = (err, hashBefore, hashAfter) => {
7028
+ const reoptimizeObserved = hashBefore !== void 0 && hashAfter !== void 0 && hashBefore !== hashAfter;
7029
+ const report = describeDiscoveryFailure(err, { reoptimizeObserved });
7030
+ if (report.level === "warn") {
7031
+ console.warn(report.message);
7032
+ } else {
7033
+ console.error(report.message);
7034
+ }
7035
+ };
6593
7036
  const discover = async () => {
6594
7037
  const discoverStart = performance.now();
6595
7038
  const rscEnv = server.environments?.rsc;
@@ -6599,18 +7042,21 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6599
7042
  globalThis.__rscRouterDiscoveryActive ?? false
6600
7043
  );
6601
7044
  s.devServerOrigin = getDevServerOrigin();
7045
+ let tempRscEnv;
7046
+ let optimizerHashBefore2;
6602
7047
  try {
6603
7048
  await timed(
6604
7049
  debugDiscovery,
6605
7050
  "acquireBuildEnv",
6606
7051
  () => acquireBuildEnv(s, viteCommand, viteMode)
6607
7052
  );
6608
- const tempRscEnv = await timed(
7053
+ tempRscEnv = await timed(
6609
7054
  debugDiscovery,
6610
7055
  "getOrCreateTempServer",
6611
7056
  () => getOrCreateTempServer()
6612
7057
  );
6613
7058
  if (tempRscEnv) {
7059
+ optimizerHashBefore2 = tempRscEnv.depsOptimizer?.metadata?.browserHash;
6614
7060
  await timed(
6615
7061
  debugDiscovery,
6616
7062
  "discoverRouters (cloudflare)",
@@ -6623,9 +7069,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6623
7069
  );
6624
7070
  }
6625
7071
  } catch (err) {
6626
- console.warn(
6627
- `[rango] Cloudflare dev discovery failed: ${err.message}
6628
- ${err.stack}`
7072
+ emitDiscoveryFailure(
7073
+ err,
7074
+ optimizerHashBefore2,
7075
+ tempRscEnv?.depsOptimizer?.metadata?.browserHash
6629
7076
  );
6630
7077
  }
6631
7078
  debugDiscovery?.(
@@ -6635,6 +7082,7 @@ ${err.stack}`
6635
7082
  resolveDiscovery();
6636
7083
  return;
6637
7084
  }
7085
+ const optimizerHashBefore = rscEnv.depsOptimizer?.metadata?.browserHash;
6638
7086
  try {
6639
7087
  debugDiscovery?.("dev: node path start");
6640
7088
  await timed(
@@ -6644,17 +7092,12 @@ ${err.stack}`
6644
7092
  );
6645
7093
  const serverMod = await timed(
6646
7094
  debugDiscovery,
6647
- "import @rangojs/router/server",
6648
- () => rscEnv.runner.import("@rangojs/router/server")
7095
+ "discoverRouters",
7096
+ () => discoverRouters(s, rscEnv)
6649
7097
  );
6650
7098
  if (serverMod?.setManifestReadyPromise) {
6651
7099
  serverMod.setManifestReadyPromise(discoveryPromise);
6652
7100
  }
6653
- await timed(
6654
- debugDiscovery,
6655
- "discoverRouters",
6656
- () => discoverRouters(s, rscEnv)
6657
- );
6658
7101
  s.devServerOrigin = getDevServerOrigin();
6659
7102
  timedSync(
6660
7103
  debugDiscovery,
@@ -6667,9 +7110,10 @@ ${err.stack}`
6667
7110
  () => propagateDiscoveryState(rscEnv)
6668
7111
  );
6669
7112
  } catch (err) {
6670
- console.warn(
6671
- `[rango] Router discovery failed: ${err.message}
6672
- ${err.stack}`
7113
+ emitDiscoveryFailure(
7114
+ err,
7115
+ optimizerHashBefore,
7116
+ rscEnv.depsOptimizer?.metadata?.browserHash
6673
7117
  );
6674
7118
  } finally {
6675
7119
  debugDiscovery?.(
@@ -6685,6 +7129,7 @@ ${err.stack}`
6685
7129
  0
6686
7130
  );
6687
7131
  let mainRegistry = null;
7132
+ const devPrerenderCache = createDevPrerenderCache();
6688
7133
  const propagateDiscoveryState = async (rscEnv) => {
6689
7134
  const serverMod = await rscEnv.runner.import("@rangojs/router/server");
6690
7135
  if (!serverMod) return;
@@ -6754,8 +7199,19 @@ ${err.stack}`
6754
7199
  registry = mainRegistry;
6755
7200
  }
6756
7201
  if (!registry) {
6757
- if (!prerenderNodeRegistry) {
6758
- await getOrCreateTempServer();
7202
+ const tempRscEnv = await getOrCreateTempServer();
7203
+ if (tempRscEnv) {
7204
+ try {
7205
+ await importEntryAndRegistry(tempRscEnv);
7206
+ } catch (err) {
7207
+ console.warn(
7208
+ `[rango] Dev prerender module refresh failed: ${err.message}`
7209
+ );
7210
+ res.statusCode = 500;
7211
+ res.end(`Prerender handler error: ${err.message}`);
7212
+ logResult(500, "temp module refresh failed");
7213
+ return;
7214
+ }
6759
7215
  }
6760
7216
  registry = prerenderNodeRegistry;
6761
7217
  }
@@ -6768,8 +7224,29 @@ ${err.stack}`
6768
7224
  const wantIntercept = url.searchParams.get("intercept") === "1";
6769
7225
  const wantRouteName = url.searchParams.get("routeName");
6770
7226
  const wantPassthrough = url.searchParams.get("passthrough") === "1";
7227
+ const variantDims = {
7228
+ passthrough: wantPassthrough,
7229
+ routeName: wantRouteName
7230
+ };
7231
+ const keyMain = devPrerenderCacheKey(pathname, {
7232
+ intercept: false,
7233
+ ...variantDims
7234
+ });
7235
+ const keyIntercept = devPrerenderCacheKey(pathname, {
7236
+ intercept: true,
7237
+ ...variantDims
7238
+ });
7239
+ const requestedKey = wantIntercept ? keyIntercept : keyMain;
6771
7240
  for (const [, routerInstance] of registry) {
6772
7241
  if (!routerInstance.matchForPrerender) continue;
7242
+ const cached = devPrerenderCache.get(routerInstance, requestedKey);
7243
+ if (cached !== void 0) {
7244
+ res.setHeader("content-type", "application/json");
7245
+ res.setHeader("x-rango-prerender-cache", "HIT");
7246
+ res.end(cached);
7247
+ logResult(200, "cache hit");
7248
+ return;
7249
+ }
6773
7250
  try {
6774
7251
  const result = await routerInstance.matchForPrerender(
6775
7252
  pathname,
@@ -6783,19 +7260,16 @@ ${err.stack}`
6783
7260
  if (!result) continue;
6784
7261
  if (result.passthrough) continue;
6785
7262
  if (wantRouteName && result.routeName !== wantRouteName) continue;
7263
+ const bodies = payloadBodiesFromResult(result);
7264
+ devPrerenderCache.set(routerInstance, keyMain, bodies.main);
7265
+ devPrerenderCache.set(
7266
+ routerInstance,
7267
+ keyIntercept,
7268
+ bodies.intercept
7269
+ );
6786
7270
  res.setHeader("content-type", "application/json");
6787
- let payload;
6788
- if (wantIntercept && result.interceptSegments?.length) {
6789
- payload = {
6790
- segments: [...result.segments, ...result.interceptSegments],
6791
- // Pre-encoded MERGED handle string from the producer (handles are
6792
- // Flight-encoded so Promise/ReactNode values survive the wire).
6793
- handles: result.interceptHandles ?? ""
6794
- };
6795
- } else {
6796
- payload = { segments: result.segments, handles: result.handles };
6797
- }
6798
- res.end(JSON.stringify(payload));
7271
+ res.setHeader("x-rango-prerender-cache", "MISS");
7272
+ res.end(wantIntercept ? bodies.intercept : bodies.main);
6799
7273
  logResult(200, `match ${result.routeName}`);
6800
7274
  return;
6801
7275
  } catch (err) {
@@ -7010,7 +7484,7 @@ ${err.stack}`
7010
7484
  if (hasCreateRouter) {
7011
7485
  const nestedRouterConflict = findNestedRouterConflict([
7012
7486
  ...s.cachedRouterFiles ?? [],
7013
- resolve9(filePath)
7487
+ resolve10(filePath)
7014
7488
  ]);
7015
7489
  if (nestedRouterConflict) {
7016
7490
  server.config.logger.error(
@@ -7434,17 +7908,25 @@ async function rango(options) {
7434
7908
  name: "@rangojs/router:auto-discover",
7435
7909
  config(userConfig) {
7436
7910
  if (routerRef.path) return;
7437
- const root = userConfig.root ? resolve10(process.cwd(), userConfig.root) : process.cwd();
7911
+ const root = userConfig.root ? resolve11(process.cwd(), userConfig.root) : process.cwd();
7438
7912
  const toRootRelative = (abs) => (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
7439
7913
  const bulletList = (files) => files.map((f) => " - " + toRootRelative(f)).join("\n");
7440
7914
  if (explicitHostRouter) {
7441
- routerRef.path = explicitHostRouter.replaceAll("\\", "/");
7915
+ routerRef.path = normalizeHostRouterEntry(
7916
+ explicitHostRouter,
7917
+ root,
7918
+ existsSync8
7919
+ );
7442
7920
  routerRef.kind = "host";
7443
7921
  return;
7444
7922
  }
7445
7923
  const hostCandidates = findHostRouterFiles(root);
7446
7924
  if (hostCandidates.length === 1) {
7447
- routerRef.path = toRootRelative(hostCandidates[0]);
7925
+ const hostPath = toRootRelative(hostCandidates[0]);
7926
+ console.info(
7927
+ `[rango] Serving host router entry ${hostPath} (auto-detected). Set the \`hostRouter\` option to override.`
7928
+ );
7929
+ routerRef.path = hostPath;
7448
7930
  routerRef.kind = "host";
7449
7931
  return;
7450
7932
  }
@@ -7484,8 +7966,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
7484
7966
  enforce: "pre",
7485
7967
  config(_userConfig, configEnv) {
7486
7968
  const vercelDefine = preset === "vercel" && configEnv.command === "build" ? { "process.env.NODE_ENV": JSON.stringify("production") } : void 0;
7487
- const vercelServerEnv = preset === "vercel" ? { resolve: { noExternal: true } } : void 0;
7488
- const vercelServerInclude = preset === "vercel" && configEnv.command === "serve" ? ["@vercel/functions"] : [];
7969
+ const vercelServerEnv = preset === "vercel" && configEnv.command === "build" ? { resolve: { noExternal: true } } : void 0;
7489
7970
  return {
7490
7971
  ...vercelDefine ? { define: vercelDefine } : {},
7491
7972
  optimizeDeps: {
@@ -7542,8 +8023,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
7542
8023
  "react/jsx-dev-runtime",
7543
8024
  nested(
7544
8025
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
7545
- ),
7546
- ...vercelServerInclude
8026
+ )
7547
8027
  ],
7548
8028
  exclude: excludeDeps,
7549
8029
  rolldownOptions: sharedRolldownOptions
@@ -7559,8 +8039,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
7559
8039
  "react/jsx-dev-runtime",
7560
8040
  nested(
7561
8041
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
7562
- ),
7563
- ...vercelServerInclude
8042
+ )
7564
8043
  ],
7565
8044
  // Vite 8 does not propagate the top-level optimizeDeps.exclude
7566
8045
  // (set in config()) to non-client envs, so the rsc env must set
@@ -7738,6 +8217,7 @@ function poke() {
7738
8217
  };
7739
8218
  }
7740
8219
  export {
8220
+ directoryClientChunks,
7741
8221
  poke,
7742
8222
  rango
7743
8223
  };