@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125

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 (260) hide show
  1. package/dist/bin/rango.js +10 -6
  2. package/dist/testing/vitest.js +82 -0
  3. package/dist/vite/index.js +55 -48
  4. package/package.json +61 -21
  5. package/skills/caching/SKILL.md +2 -1
  6. package/skills/hooks/SKILL.md +40 -29
  7. package/skills/host-router/SKILL.md +16 -2
  8. package/skills/intercept/SKILL.md +4 -2
  9. package/skills/layout/SKILL.md +11 -6
  10. package/skills/loader/SKILL.md +6 -2
  11. package/skills/middleware/SKILL.md +4 -2
  12. package/skills/migrate-nextjs/SKILL.md +3 -1
  13. package/skills/parallel/SKILL.md +9 -4
  14. package/skills/rango/SKILL.md +12 -0
  15. package/skills/route/SKILL.md +10 -2
  16. package/skills/testing/SKILL.md +129 -0
  17. package/skills/testing/bindings.md +89 -0
  18. package/skills/testing/cache-prerender.md +98 -0
  19. package/skills/testing/client-components.md +122 -0
  20. package/skills/testing/e2e-parity.md +125 -0
  21. package/skills/testing/flight.md +89 -0
  22. package/skills/testing/handles.md +129 -0
  23. package/skills/testing/loader.md +128 -0
  24. package/skills/testing/middleware.md +99 -0
  25. package/skills/testing/render-handler.md +118 -0
  26. package/skills/testing/response-routes.md +95 -0
  27. package/skills/testing/reverse-and-types.md +84 -0
  28. package/skills/testing/server-actions.md +107 -0
  29. package/skills/testing/server-tree.md +128 -0
  30. package/skills/testing/setup.md +120 -0
  31. package/src/__internal.ts +0 -65
  32. package/src/browser/action-coordinator.ts +1 -1
  33. package/src/browser/action-fence.ts +47 -0
  34. package/src/browser/cookie-name.ts +140 -0
  35. package/src/browser/event-controller.ts +1 -83
  36. package/src/browser/invalidate-client-cache.ts +52 -0
  37. package/src/browser/navigation-bridge.ts +14 -1
  38. package/src/browser/navigation-client.ts +14 -1
  39. package/src/browser/navigation-store-handle.ts +38 -0
  40. package/src/browser/navigation-store.ts +26 -51
  41. package/src/browser/navigation-transaction.ts +0 -32
  42. package/src/browser/partial-update.ts +1 -83
  43. package/src/browser/prefetch/cache.ts +6 -45
  44. package/src/browser/prefetch/fetch.ts +7 -0
  45. package/src/browser/prefetch/queue.ts +6 -3
  46. package/src/browser/rango-state.ts +157 -99
  47. package/src/browser/react/Link.tsx +0 -2
  48. package/src/browser/react/NavigationProvider.tsx +2 -1
  49. package/src/browser/react/ScrollRestoration.tsx +10 -6
  50. package/src/browser/react/filter-segment-order.ts +0 -2
  51. package/src/browser/react/index.ts +0 -51
  52. package/src/browser/react/location-state-shared.ts +0 -13
  53. package/src/browser/react/location-state.ts +0 -1
  54. package/src/browser/react/use-action.ts +6 -15
  55. package/src/browser/react/use-handle.ts +0 -5
  56. package/src/browser/react/use-link-status.ts +0 -4
  57. package/src/browser/react/use-navigation.ts +0 -3
  58. package/src/browser/react/use-params.ts +0 -2
  59. package/src/browser/react/use-search-params.ts +0 -5
  60. package/src/browser/react/use-segments.ts +0 -13
  61. package/src/browser/rsc-router.tsx +12 -4
  62. package/src/browser/server-action-bridge.ts +77 -15
  63. package/src/browser/types.ts +7 -2
  64. package/src/browser/validate-redirect-origin.ts +4 -5
  65. package/src/build/route-trie.ts +3 -0
  66. package/src/build/route-types/param-extraction.ts +6 -3
  67. package/src/build/route-types/router-processing.ts +0 -8
  68. package/src/cache/cache-policy.ts +0 -54
  69. package/src/cache/cache-runtime.ts +27 -24
  70. package/src/cache/cache-scope.ts +0 -27
  71. package/src/cache/cache-tag.ts +0 -37
  72. package/src/cache/cf/cf-cache-store.ts +94 -46
  73. package/src/cache/cf/index.ts +0 -24
  74. package/src/cache/document-cache.ts +11 -36
  75. package/src/cache/handle-snapshot.ts +0 -40
  76. package/src/cache/index.ts +0 -27
  77. package/src/cache/memory-segment-store.ts +2 -48
  78. package/src/cache/profile-registry.ts +7 -3
  79. package/src/cache/read-through-swr.ts +41 -11
  80. package/src/cache/segment-codec.ts +0 -16
  81. package/src/cache/types.ts +0 -98
  82. package/src/client.rsc.tsx +1 -22
  83. package/src/client.tsx +14 -38
  84. package/src/component-utils.ts +19 -0
  85. package/src/deps/ssr.ts +0 -1
  86. package/src/handle.ts +28 -18
  87. package/src/handles/MetaTags.tsx +0 -14
  88. package/src/handles/meta.ts +0 -39
  89. package/src/host/cookie-handler.ts +0 -36
  90. package/src/host/errors.ts +0 -24
  91. package/src/host/index.ts +6 -0
  92. package/src/host/pattern-matcher.ts +7 -50
  93. package/src/host/router.ts +1 -65
  94. package/src/host/testing.ts +40 -27
  95. package/src/host/types.ts +6 -2
  96. package/src/href-client.ts +0 -4
  97. package/src/index.rsc.ts +42 -3
  98. package/src/index.ts +31 -1
  99. package/src/internal-debug.ts +2 -4
  100. package/src/loader.rsc.ts +19 -9
  101. package/src/loader.ts +12 -4
  102. package/src/network-error-thrower.tsx +1 -6
  103. package/src/outlet-provider.tsx +1 -5
  104. package/src/prerender/param-hash.ts +10 -11
  105. package/src/prerender/store.ts +23 -30
  106. package/src/prerender.ts +58 -3
  107. package/src/root-error-boundary.tsx +1 -19
  108. package/src/route-content-wrapper.tsx +1 -44
  109. package/src/route-definition/dsl-helpers.ts +7 -19
  110. package/src/route-definition/helpers-types.ts +3 -3
  111. package/src/route-definition/redirect.ts +11 -1
  112. package/src/route-map-builder.ts +0 -16
  113. package/src/router/basename.ts +14 -0
  114. package/src/router/content-negotiation.ts +0 -13
  115. package/src/router/error-handling.ts +12 -16
  116. package/src/router/find-match.ts +4 -30
  117. package/src/router/intercept-resolution.ts +10 -1
  118. package/src/router/lazy-includes.ts +1 -57
  119. package/src/router/loader-resolution.ts +3 -2
  120. package/src/router/logging.ts +0 -6
  121. package/src/router/manifest.ts +1 -25
  122. package/src/router/match-api.ts +0 -20
  123. package/src/router/match-context.ts +0 -22
  124. package/src/router/match-handlers.ts +57 -58
  125. package/src/router/match-middleware/background-revalidation.ts +0 -7
  126. package/src/router/match-middleware/cache-lookup.ts +1 -54
  127. package/src/router/match-middleware/cache-store.ts +0 -31
  128. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  129. package/src/router/match-middleware/segment-resolution.ts +0 -21
  130. package/src/router/match-pipelines.ts +1 -42
  131. package/src/router/match-result.ts +1 -52
  132. package/src/router/metrics.ts +0 -34
  133. package/src/router/middleware-cookies.ts +0 -13
  134. package/src/router/middleware-types.ts +0 -115
  135. package/src/router/middleware.ts +7 -30
  136. package/src/router/navigation-snapshot.ts +0 -51
  137. package/src/router/params-util.ts +23 -0
  138. package/src/router/pattern-matching.ts +1 -33
  139. package/src/router/prerender-match.ts +33 -45
  140. package/src/router/request-classification.ts +1 -38
  141. package/src/router/revalidation.ts +5 -58
  142. package/src/router/router-context.ts +0 -26
  143. package/src/router/router-interfaces.ts +7 -0
  144. package/src/router/router-options.ts +30 -0
  145. package/src/router/segment-resolution/fresh.ts +25 -57
  146. package/src/router/segment-resolution/helpers.ts +34 -0
  147. package/src/router/segment-resolution/loader-cache.ts +10 -13
  148. package/src/router/segment-resolution/revalidation.ts +5 -42
  149. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  150. package/src/router/segment-resolution.ts +4 -1
  151. package/src/router/state-cookie-name.ts +33 -0
  152. package/src/router/telemetry-otel.ts +0 -20
  153. package/src/router/telemetry.ts +96 -19
  154. package/src/router/timeout.ts +0 -20
  155. package/src/router/trie-matching.ts +63 -40
  156. package/src/router/types.ts +1 -63
  157. package/src/router/url-params.ts +0 -5
  158. package/src/router.ts +40 -9
  159. package/src/rsc/handler.ts +14 -2
  160. package/src/rsc/helpers.ts +34 -0
  161. package/src/rsc/origin-guard.ts +0 -12
  162. package/src/rsc/progressive-enhancement.ts +4 -1
  163. package/src/rsc/rsc-rendering.ts +4 -7
  164. package/src/rsc/runtime-warnings.ts +14 -0
  165. package/src/rsc/server-action.ts +30 -28
  166. package/src/rsc/types.ts +2 -1
  167. package/src/runtime-env.ts +18 -0
  168. package/src/search-params.ts +0 -16
  169. package/src/segment-loader-promise.ts +14 -2
  170. package/src/segment-system.tsx +79 -88
  171. package/src/server/cookie-store.ts +52 -1
  172. package/src/server/handle-store.ts +7 -24
  173. package/src/server/loader-registry.ts +5 -24
  174. package/src/server/request-context.ts +74 -77
  175. package/src/ssr/index.tsx +14 -14
  176. package/src/static-handler.ts +10 -13
  177. package/src/testing/cache-status.ts +119 -0
  178. package/src/testing/collect-handle.ts +40 -0
  179. package/src/testing/dispatch.ts +581 -0
  180. package/src/testing/dom.entry.ts +22 -0
  181. package/src/testing/e2e/fixture.ts +188 -0
  182. package/src/testing/e2e/index.ts +127 -0
  183. package/src/testing/e2e/matchers.ts +35 -0
  184. package/src/testing/e2e/page-helpers.ts +272 -0
  185. package/src/testing/e2e/parity.ts +387 -0
  186. package/src/testing/e2e/server.ts +195 -0
  187. package/src/testing/flight-matchers.ts +97 -0
  188. package/src/testing/flight-normalize.ts +11 -0
  189. package/src/testing/flight-runtime.d.ts +57 -0
  190. package/src/testing/flight-tree.ts +682 -0
  191. package/src/testing/flight.entry.ts +52 -0
  192. package/src/testing/flight.ts +186 -0
  193. package/src/testing/generated-routes.ts +183 -0
  194. package/src/testing/index.ts +98 -0
  195. package/src/testing/internal/context.ts +348 -0
  196. package/src/testing/internal/flight-client-globals.ts +30 -0
  197. package/src/testing/internal/seed-vars.ts +54 -0
  198. package/src/testing/render-handler.ts +311 -0
  199. package/src/testing/render-route.tsx +504 -0
  200. package/src/testing/run-loader.ts +378 -0
  201. package/src/testing/run-middleware.ts +205 -0
  202. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  203. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  204. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  205. package/src/testing/vitest-stubs/version.ts +5 -0
  206. package/src/testing/vitest.ts +305 -0
  207. package/src/theme/ThemeProvider.tsx +0 -52
  208. package/src/theme/ThemeScript.tsx +0 -6
  209. package/src/theme/constants.ts +0 -12
  210. package/src/theme/index.ts +0 -7
  211. package/src/theme/theme-context.ts +1 -5
  212. package/src/theme/theme-script.ts +0 -14
  213. package/src/theme/use-theme.ts +0 -3
  214. package/src/types/boundaries.ts +0 -35
  215. package/src/types/error-types.ts +25 -89
  216. package/src/types/global-namespace.ts +15 -15
  217. package/src/types/handler-context.ts +16 -13
  218. package/src/types/index.ts +0 -10
  219. package/src/types/request-scope.ts +0 -19
  220. package/src/types/route-config.ts +6 -50
  221. package/src/types/route-entry.ts +0 -6
  222. package/src/types/segments.ts +0 -13
  223. package/src/urls/include-helper.ts +0 -4
  224. package/src/urls/index.ts +0 -6
  225. package/src/urls/path-helper-types.ts +2 -2
  226. package/src/urls/path-helper.ts +0 -54
  227. package/src/urls/urls-function.ts +0 -13
  228. package/src/use-loader.tsx +0 -186
  229. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  230. package/src/vite/discovery/discover-routers.ts +6 -7
  231. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  232. package/src/vite/plugin-types.ts +3 -1
  233. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  234. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  235. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  236. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  237. package/src/vite/plugins/expose-action-id.ts +2 -73
  238. package/src/vite/plugins/expose-id-utils.ts +0 -55
  239. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  240. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  241. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  242. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  243. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  244. package/src/vite/plugins/performance-tracks.ts +0 -3
  245. package/src/vite/plugins/use-cache-transform.ts +0 -36
  246. package/src/vite/plugins/version-injector.ts +0 -20
  247. package/src/vite/plugins/version-plugin.ts +1 -49
  248. package/src/vite/plugins/virtual-entries.ts +0 -15
  249. package/src/vite/rango.ts +1 -108
  250. package/src/vite/router-discovery.ts +2 -1
  251. package/src/vite/utils/ast-handler-extract.ts +0 -16
  252. package/src/vite/utils/bundle-analysis.ts +6 -13
  253. package/src/vite/utils/client-chunks.ts +0 -6
  254. package/src/vite/utils/forward-user-plugins.ts +0 -22
  255. package/src/vite/utils/manifest-utils.ts +0 -4
  256. package/src/vite/utils/package-resolution.ts +1 -73
  257. package/src/vite/utils/prerender-utils.ts +0 -35
  258. package/src/vite/utils/shared-utils.ts +3 -35
  259. package/src/browser/react/use-client-cache.ts +0 -58
  260. package/src/browser/shallow.ts +0 -40
package/src/vite/rango.ts CHANGED
@@ -66,18 +66,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
66
66
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
67
67
  const preset = resolvedOptions.preset ?? "node";
68
68
  const showBanner = resolvedOptions.banner ?? true;
69
- // Client-chunking strategy (per-route/per-feature splitting of the browser
70
- // bundle). Defaults to the built-in directory strategy (`true`) pre-1.0; pass
71
- // `clientChunks: false` to opt out. Resolved once and forwarded to
72
- // @vitejs/plugin-rsc in both presets. The built-in strategy only splits where it
73
- // recognizes a route structure, so this default is a no-op for flat / host-split
74
- // apps and never duplicates the shared runtime.
75
69
  const clientChunksOption = resolvedOptions.clientChunks ?? true;
76
- // Shared context the built-in strategy reads at build time: the production
77
- // hashes of registered error/notFound fallback modules (-> app-fallback).
78
- // Populated by the discovery plugin in buildStart, before the client build
79
- // invokes the strategy. Only wired when the built-in strategy is active; a
80
- // custom function owns its own grouping.
81
70
  const useBuiltInClientChunks = clientChunksOption === true;
82
71
  const clientChunkCtx: ClientChunkContext | undefined = useBuiltInClientChunks
83
72
  ? { fallbackRefs: new Set<string>() }
@@ -124,14 +113,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
124
113
  const prerenderEnabled = true;
125
114
 
126
115
  if (preset === "cloudflare") {
127
- // Cloudflare preset: configure entries for cloudflare worker setup
128
- // Router is not needed here - worker.rsc.tsx imports it directly
129
-
130
- // Dynamically import @vitejs/plugin-rsc
131
116
  const { default: rsc } = await import("@vitejs/plugin-rsc");
132
117
 
133
- // Only client and ssr entries - rsc entry is handled by cloudflare plugin
134
- // Always use virtual modules for cloudflare preset
135
118
  const finalEntries: { client: string; ssr: string } = {
136
119
  client: VIRTUAL_IDS.browser,
137
120
  ssr: VIRTUAL_IDS.ssr,
@@ -142,10 +125,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
142
125
  enforce: "pre",
143
126
 
144
127
  config() {
145
- // Configure environments for cloudflare deployment
146
128
  return {
147
- // Exclude rsc-router modules from optimization to prevent module duplication
148
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
149
129
  optimizeDeps: {
150
130
  exclude: excludeDeps,
151
131
  rolldownOptions: sharedRolldownOptions,
@@ -168,21 +148,12 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
168
148
  client: {
169
149
  build: {
170
150
  rollupOptions: {
171
- // FILE_NAME_CONFLICT (and any other client-build warning) is
172
- // emitted by the CLIENT environment build, which consults THIS
173
- // env's onwarn -- Vite 8's environment builds do NOT propagate
174
- // the top-level build.rollupOptions.onwarn into the client env.
175
- // Wire it here so the suppression runs where the conflicts
176
- // originate (the top-level handler is invoked 0x for these; the
177
- // client-env handler is invoked for all of them).
178
151
  onwarn,
179
152
  output: {
180
153
  manualChunks: getManualChunks,
181
154
  },
182
155
  },
183
156
  },
184
- // Pre-bundle rsc-html-stream to prevent discovery during first request
185
- // Exclude rsc-router modules to ensure same Context instance
186
157
  optimizeDeps: {
187
158
  include: [nested("rsc-html-stream/client")],
188
159
  exclude: excludeDeps,
@@ -190,12 +161,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
190
161
  },
191
162
  },
192
163
  ssr: {
193
- // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
194
164
  build: {
195
165
  outDir: "./dist/rsc/ssr",
196
166
  },
197
- // Pre-bundle SSR entry and React for proper module linking with childEnvironments
198
- // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
199
167
  optimizeDeps: {
200
168
  entries: [finalEntries.ssr],
201
169
  include: [
@@ -215,10 +183,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
215
183
  },
216
184
  },
217
185
  rsc: {
218
- // RSC environment needs exclude list and esbuild options
219
- // Exclude rsc-router modules to prevent createContext in RSC environment
220
186
  optimizeDeps: {
221
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
222
187
  include: [
223
188
  "react",
224
189
  "react/jsx-runtime",
@@ -249,13 +214,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
249
214
  });
250
215
 
251
216
  plugins.push(createVirtualEntriesPlugin(finalEntries));
252
-
253
- // Dev-only: RSDW client patch for React Performance Tracks
254
217
  plugins.push(performanceTracksPlugin());
255
-
256
- // Add RSC plugin with cloudflare-specific options
257
- // Note: loadModuleDevProxy should NOT be used with childEnvironments
258
- // since SSR runs in workerd alongside RSC
259
218
  plugins.push(
260
219
  rsc({
261
220
  entries: finalEntries,
@@ -263,13 +222,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
263
222
  clientChunks,
264
223
  }) as PluginOption,
265
224
  );
266
-
267
- // Deduplicate client references from third-party packages in dev mode.
268
- // Prevents module duplication when server components import "use client"
269
- // packages that are also imported directly by client components.
270
225
  plugins.push(clientRefDedup());
271
226
  } else {
272
- // Auto-discover router using Vite's resolved root (not process.cwd())
273
227
  plugins.push({
274
228
  name: "@rangojs/router:auto-discover",
275
229
  config(userConfig) {
@@ -292,18 +246,15 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
292
246
  .join("\n");
293
247
  throw new Error(`[rango] Multiple routers found:\n${list}`);
294
248
  }
295
- // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
296
249
  },
297
250
  });
298
251
 
299
- // Always use virtual entries for client, ssr, and rsc
300
252
  const finalEntries = {
301
253
  client: VIRTUAL_IDS.browser,
302
254
  ssr: VIRTUAL_IDS.ssr,
303
255
  rsc: VIRTUAL_IDS.rsc,
304
256
  };
305
257
 
306
- // Dynamically import @vitejs/plugin-rsc
307
258
  const { default: rsc } = await import("@vitejs/plugin-rsc");
308
259
 
309
260
  let hasWarnedDuplicate = false;
@@ -336,13 +287,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
336
287
  client: {
337
288
  build: {
338
289
  rollupOptions: {
339
- // FILE_NAME_CONFLICT (and any other client-build warning) is
340
- // emitted by the CLIENT environment build, which consults THIS
341
- // env's onwarn -- Vite 8's environment builds do NOT propagate
342
- // the top-level build.rollupOptions.onwarn into the client env.
343
- // Wire it here so the suppression runs where the conflicts
344
- // originate (the top-level handler is invoked 0x for these; the
345
- // client-env handler is invoked for all of them).
346
290
  onwarn,
347
291
  output: {
348
292
  manualChunks: getManualChunks,
@@ -423,36 +367,17 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
423
367
  },
424
368
  });
425
369
 
426
- // Add virtual entries plugin (RSC entry generated lazily from routerRef)
427
370
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
428
-
429
- // Dev-only: RSDW client patch for React Performance Tracks
430
371
  plugins.push(performanceTracksPlugin());
431
-
432
372
  plugins.push(
433
373
  rsc({
434
374
  entries: finalEntries,
435
375
  clientChunks,
436
376
  }) as PluginOption,
437
377
  );
438
-
439
- // Deduplicate client references from third-party packages in dev mode.
440
- // Prevents module duplication when server components import "use client"
441
- // packages that are also imported directly by client components.
442
378
  plugins.push(clientRefDedup());
443
379
  }
444
380
 
445
- // Fix HMR for "use client" components.
446
- //
447
- // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
448
- // in the RSC environment. Vite then tries to propagate through the RSC
449
- // module graph, but the proxy module has no import.meta.hot.accept()
450
- // boundary, causing a full page reload. The client env would handle it
451
- // fine via React Refresh, but the RSC env's full-reload arrives first.
452
- //
453
- // Fix: in the RSC env, return [] for "use client" files to signal
454
- // "handled, nothing to propagate". The client env is left alone so
455
- // React Refresh processes the update normally.
456
381
  plugins.push({
457
382
  name: "@rangojs/router:client-component-hmr",
458
383
  hotUpdate(ctx) {
@@ -476,59 +401,27 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
476
401
  trimmed.startsWith('"use client"') ||
477
402
  trimmed.startsWith("'use client'")
478
403
  ) {
479
- // Consume the update in RSC/SSR envs. The proxy module was already
480
- // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
481
- // tries to propagate through the RSC/SSR module graph where the proxy
482
- // has no import.meta.hot.accept() boundary, triggering a full reload.
483
- // The actual component update is handled by React Refresh in the
484
- // client environment.
485
404
  return [];
486
405
  }
487
- } catch {
488
- // File deleted/moved during HMR, let default handling proceed
489
- }
406
+ } catch {}
490
407
  },
491
408
  });
492
409
 
493
410
  plugins.push(exposeActionId());
494
-
495
- // "use cache" directive transform (enforce: "post"):
496
- // Wraps exports with registerCachedFunction() for function-level caching.
497
411
  plugins.push(useCacheTransform());
498
-
499
- // Consolidated plugin for create* ID injection (enforce: "post"):
500
- // loaders, handles, location state, and prerender handlers.
501
412
  plugins.push(exposeInternalIds());
502
-
503
- // Router ID injection runs at normal priority (no enforce) to avoid
504
- // changing Vite's dep optimization timing.
505
413
  plugins.push(exposeRouterId());
506
-
507
- // Add version virtual module plugin for cache invalidation
508
414
  plugins.push(createVersionPlugin());
509
415
 
510
- // Entry path for discovery: user-specified value (if any) or undefined.
511
- // Auto-discovered path is passed separately via routerRef.
512
- // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
513
416
  const discoveryEntryPath =
514
417
  preset !== "cloudflare" ? routerRef.path : undefined;
515
- // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
516
418
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
517
419
 
518
- // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
519
- // For cloudflare preset, the entry is resolved lazily in configResolved.
520
- // For node preset, the virtual entry already includes these imports.
521
420
  if (preset === "cloudflare") {
522
421
  plugins.push(createVersionInjectorPlugin(undefined));
523
422
  }
524
423
 
525
- // Transform CJS vendor files to ESM for browser compatibility
526
- // optimizeDeps.include doesn't work because the file is loaded after initial optimization
527
424
  plugins.push(createCjsToEsmPlugin());
528
-
529
- // Router discovery plugin for build-time manifest generation.
530
- // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
531
- // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
532
425
  plugins.push(
533
426
  createRouterDiscoveryPlugin(discoveryEntryPath, {
534
427
  routerPathRef: discoveryRouterRef,
@@ -424,7 +424,8 @@ export function createRouterDiscoveryPlugin(
424
424
  releaseBuildEnv(s).catch(() => {});
425
425
  });
426
426
 
427
- // Mirror the build-path contract (router-discovery.ts ~line 878):
427
+ // Mirror the build-path contract (the buildStart hook below, which sets
428
+ // __rscRouterDiscoveryActive before running user modules):
428
429
  // set __rscRouterDiscoveryActive before running user modules so any
429
430
  // module-level router.reverse() calls return a placeholder instead
430
431
  // of throwing. The temp Vite server's module runner has its own
@@ -1,10 +1,6 @@
1
1
  import type MagicString from "magic-string";
2
2
  import { hashInlineId, buildExportMap } from "../plugins/expose-id-utils.js";
3
3
 
4
- // ---------------------------------------------------------------------------
5
- // Types
6
- // ---------------------------------------------------------------------------
7
-
8
4
  /** Minimal ESTree Program node — avoids importing from `rollup` (not a direct dep). */
9
5
  interface ProgramNode {
10
6
  type: "Program";
@@ -70,10 +66,6 @@ function findImportInsertionPos(
70
66
  return insertionPos;
71
67
  }
72
68
 
73
- // ---------------------------------------------------------------------------
74
- // AST walking helper
75
- // ---------------------------------------------------------------------------
76
-
77
69
  /**
78
70
  * Recursively walk an ESTree AST node, calling `enter` on each node.
79
71
  * Parent is passed for context.
@@ -111,10 +103,6 @@ function walkNode(
111
103
  ancestors.pop();
112
104
  }
113
105
 
114
- // ---------------------------------------------------------------------------
115
- // AST analysis
116
- // ---------------------------------------------------------------------------
117
-
118
106
  /**
119
107
  * Parse the file with Vite's parseAst and find all calls to `fnName`.
120
108
  * Distinguishes between `export const X = fnName(...)` (exportInfo set)
@@ -426,10 +414,6 @@ export function extractModuleLevelDeclarations(
426
414
  return declarations;
427
415
  }
428
416
 
429
- // ---------------------------------------------------------------------------
430
- // Transform
431
- // ---------------------------------------------------------------------------
432
-
433
417
  /**
434
418
  * Transform inline handler calls by extracting them into virtual modules.
435
419
  * Only processes inline calls (exportInfo === null); export const calls are
@@ -5,9 +5,7 @@ import {
5
5
 
6
6
  /**
7
7
  * Find matching close paren in bundled code using depth counting.
8
- * Uses skipStringOrComment from expose-id-utils to correctly handle
9
- * template literal ${...} expressions, comments, and nested strings.
10
- * Returns the position after the closing paren, or -1 if unmatched.
8
+ * Uses skipStringOrComment to correctly handle template literals, comments, and nested strings.
11
9
  * @internal Exported for testing only.
12
10
  */
13
11
  export function findMatchingParenInBundle(
@@ -30,9 +28,8 @@ export function findMatchingParenInBundle(
30
28
  }
31
29
 
32
30
  /**
33
- * Scan a bundled chunk for handler exports of a given type and extract
34
- * their names + $$id values. Optionally detects passthrough flag.
35
- * @internal Exported for testing only.
31
+ * Scan a bundled chunk for handler exports and extract their names + $$id values.
32
+ * Optionally detects passthrough flag. @internal Exported for testing only.
36
33
  */
37
34
  export function extractHandlerExportsFromChunk(
38
35
  chunkCode: string,
@@ -67,7 +64,7 @@ export function extractHandlerExportsFromChunk(
67
64
  const closePos = findMatchingParenInBundle(chunkCode, afterOpen);
68
65
  if (closePos !== -1) {
69
66
  const callBody = chunkCode.slice(callStart.index, closePos);
70
- isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
67
+ isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody); // !0 is minified true
71
68
  }
72
69
  }
73
70
  }
@@ -79,9 +76,8 @@ export function extractHandlerExportsFromChunk(
79
76
  }
80
77
 
81
78
  /**
82
- * Evict handler code from a bundled chunk, replacing full handler call
83
- * expressions with lightweight stub objects. Returns the modified code
84
- * and bytes saved, or null if no changes were made.
79
+ * Evict handler code from a bundled chunk, replacing full call expressions with stubs.
80
+ * Returns the modified code and bytes saved, or null if no changes were made.
85
81
  * @internal Exported for testing only.
86
82
  */
87
83
  export function evictHandlerCode(
@@ -110,13 +106,11 @@ export function evictHandlerCode(
110
106
  const closePos = findMatchingParenInBundle(modified, afterOpen);
111
107
  if (closePos === -1) continue;
112
108
 
113
- // Skip trailing whitespace and optional semicolon
114
109
  let rangeEnd = closePos;
115
110
  while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
116
111
  rangeEnd++;
117
112
  if (modified[rangeEnd] === ";") rangeEnd++;
118
113
 
119
- // Validate: matched range must contain the expected handlerId
120
114
  const matched = modified.slice(startMatch.index, rangeEnd);
121
115
  if (!matched.includes(handlerId)) continue;
122
116
 
@@ -124,7 +118,6 @@ export function evictHandlerCode(
124
118
  modified =
125
119
  modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
126
120
 
127
- // Remove the now-redundant $$id assignment line.
128
121
  modified = modified.replace(
129
122
  new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
130
123
  "",
@@ -1,9 +1,3 @@
1
- // Resolution of the public `clientChunks` option into the callback shape that
2
- // @vitejs/plugin-rsc expects. See plugin-types.ts (ClientChunks) and
3
- // docs/client-chunking.md for the contract. The mechanism: a distinct returned
4
- // name yields a distinct, dynamically-imported client chunk, independent of how
5
- // the RSC/server build chunked the importing modules.
6
-
7
1
  import type { ClientChunkMeta, ClientChunks } from "../plugin-types.js";
8
2
  import { createRangoDebugger, NS } from "../debug.js";
9
3
  import { hashRefKey } from "../plugins/client-ref-hashing.js";
@@ -1,25 +1,3 @@
1
- /**
2
- * Discovery Runner Config Parity
3
- *
4
- * The discovery temp server (createTempRscServer) runs the user's handler
5
- * graph through a throwaway Node Vite server built with `configFile: false`.
6
- * Without help, that server only sees a fixed Rango-owned plugin set, so any
7
- * user resolution is absent during discovery, prerender, and static handler
8
- * rendering — even though it applies at request time. Two flavors of user
9
- * resolution must be carried across:
10
- *
11
- * - Third-party resolveId plugins (e.g. vite-tsconfig-paths) — forwarded as
12
- * plugin instances, see selectForwardableResolvePlugins.
13
- * - Native config-driven resolution, including Vite 8's built-in
14
- * `resolve.tsconfigPaths` (which supersedes vite-tsconfig-paths) — forwarded
15
- * as the data slice, see pickForwardedRunnerConfig.
16
- *
17
- * These helpers extract the resolution-relevant slice of the user's resolved
18
- * config (resolve.*, define, oxc) and forward the user's resolution plugins
19
- * into the temp server so discovery resolves modules the same way the real
20
- * environment does.
21
- */
22
-
23
1
  import type { Plugin, ResolvedConfig, UserConfig } from "vite";
24
2
 
25
3
  /**
@@ -1,7 +1,3 @@
1
- // Pure prefix-tree walks live in the build layer so runtime code can consume
2
- // them without importing from vite/. Re-exported here for the vite-side
3
- // callers (discover-routers, virtual-module-codegen) that already import them
4
- // from this module.
5
1
  export {
6
2
  flattenLeafEntries,
7
3
  buildRouteToStaticPrefix,
@@ -1,10 +1,3 @@
1
- /**
2
- * Package Resolution Utilities
3
- *
4
- * Handles detection of workspace vs npm install context and generates
5
- * appropriate aliases and exclude lists for Vite configuration.
6
- */
7
-
8
1
  import { existsSync } from "node:fs";
9
2
  import { createRequire } from "node:module";
10
3
  import { resolve } from "node:path";
@@ -17,44 +10,20 @@ const require = createRequire(import.meta.url);
17
10
  */
18
11
  const VIRTUAL_PACKAGE_NAME = "@rangojs/router";
19
12
 
20
- /**
21
- * Get the published package name (e.g., "@rangojs/router")
22
- */
23
13
  export function getPublishedPackageName(): string {
24
14
  return packageJson.name;
25
15
  }
26
16
 
27
- /**
28
- * Check if the package is installed from npm (scoped) vs workspace (unscoped)
29
- *
30
- * In workspace development:
31
- * - Package is installed as "@rangojs/router" via pnpm workspace alias
32
- * - The scoped name (@rangojs/router) doesn't exist in node_modules
33
- *
34
- * When installed from npm:
35
- * - Package is installed as "@rangojs/router"
36
- * - We need aliases to map "@rangojs/router/*" to "@rangojs/router/*"
37
- */
38
17
  export function isInstalledFromNpm(): boolean {
39
18
  const packageName = getPublishedPackageName();
40
19
  // Check if the scoped package exists in node_modules
41
20
  return existsSync(resolve(process.cwd(), "node_modules", packageName));
42
21
  }
43
22
 
44
- /**
45
- * Check if we're in a monorepo/workspace development context
46
- */
47
23
  export function isWorkspaceDevelopment(): boolean {
48
24
  return !isInstalledFromNpm();
49
25
  }
50
26
 
51
- /**
52
- * Subpaths derived from package.json exports that use TypeScript source.
53
- * These must be excluded from Vite's dependency optimization (they ship
54
- * as .ts/.tsx, not compiled JS) and aliased when installed from npm.
55
- *
56
- * Derived automatically from the exports field to prevent drift.
57
- */
58
27
  const SOURCE_EXPORT_SUBPATHS = Object.keys(packageJson.exports)
59
28
  .filter((key) => {
60
29
  const entry = (
@@ -70,12 +39,6 @@ const SOURCE_EXPORT_SUBPATHS = Object.keys(packageJson.exports)
70
39
  })
71
40
  .map((key) => key.replace(/^\./, ""));
72
41
 
73
- /**
74
- * Generate the list of modules to exclude from Vite's dependency optimization.
75
- *
76
- * We include both the published name and the virtual name because
77
- * Vite's optimizer runs before alias resolution.
78
- */
79
42
  export function getExcludeDeps(): string[] {
80
43
  const packageName = getPublishedPackageName();
81
44
  const excludes: string[] = [];
@@ -92,24 +55,10 @@ export function getExcludeDeps(): string[] {
92
55
  return excludes;
93
56
  }
94
57
 
95
- /**
96
- * Subpaths that need aliasing — same as SOURCE_EXPORT_SUBPATHS.
97
- * When installed from npm, virtual entries may use a different package name
98
- * than the published one; aliases bridge them.
99
- */
100
58
  const ALIAS_SUBPATHS = SOURCE_EXPORT_SUBPATHS;
101
59
 
102
- /**
103
- * Generate aliases to map virtual package paths to the actual published package.
104
- *
105
- * Only needed when installed from npm, where the package is under @rangojs/router
106
- * but virtual entries import from rsc-router/*.
107
- *
108
- * Returns empty object in workspace development where rsc-router resolves directly.
109
- */
110
60
  export function getPackageAliases(): Record<string, string> {
111
61
  if (isWorkspaceDevelopment()) {
112
- // No aliases needed - rsc-router resolves directly
113
62
  return {};
114
63
  }
115
64
 
@@ -123,28 +72,7 @@ export function getPackageAliases(): Record<string, string> {
123
72
  return aliases;
124
73
  }
125
74
 
126
- /**
127
- * Plugin-rsc pushes bare specs like
128
- * `@vitejs/plugin-rsc/vendor/react-server-dom/client.edge` into
129
- * `optimizeDeps.include` for the ssr and rsc environments. In strict pnpm
130
- * consumer apps, `@vitejs/plugin-rsc` is only reachable from @rangojs/router's
131
- * node_modules, so Vite's optimizer — which resolves from the project root —
132
- * can't find them and emits "Failed to resolve dependency" warnings.
133
- *
134
- * We resolve those specs from this plugin's location (where plugin-rsc is
135
- * guaranteed to be installed as our dep) and expose them as `resolve.alias`
136
- * entries. The optimizer's resolver honors aliases, so the bare specs map to
137
- * absolute paths and resolve cleanly.
138
- */
139
75
  export function getVendorAliases(): Record<string, string> {
140
- // client.browser is intentionally NOT aliased. plugin-rsc injects it into
141
- // the client env's optimizeDeps.include; Vite's manual-include path resolves
142
- // and pre-bundles regardless of optimizeDeps.exclude, so aliasing would
143
- // trigger esbuild pre-bundling of the CJS vendor file and bypass the
144
- // cjs-to-esm transform that patches `require('react'|'react-dom')` into
145
- // real ESM imports. The consumer may still see a single "Failed to resolve"
146
- // warning for client.browser; runtime resolution from plugin-rsc's own
147
- // importer works because Vite resolves relative to the importer (not root).
148
76
  const specs = [
149
77
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
150
78
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
@@ -154,7 +82,7 @@ export function getVendorAliases(): Record<string, string> {
154
82
  try {
155
83
  aliases[spec] = require.resolve(spec);
156
84
  } catch {
157
- // Spec unresolvable (unexpected but non-fatal Vite will warn as before).
85
+ // Non-fatal; Vite will warn if it cannot resolve the spec
158
86
  }
159
87
  }
160
88
  return aliases;
@@ -9,18 +9,10 @@ import {
9
9
  } from "node:fs";
10
10
  import { resolve } from "node:path";
11
11
 
12
- /**
13
- * Escape special RegExp characters in a string for safe interpolation
14
- * into new RegExp() patterns.
15
- */
16
12
  export function escapeRegExp(str: string): string {
17
13
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
14
  }
19
15
 
20
- /**
21
- * Encode route param values for path interpolation while preserving path
22
- * separators for wildcard params (splat-style values can include `/`).
23
- */
24
16
  export function encodePathParam(value: unknown): string {
25
17
  return String(value)
26
18
  .split("/")
@@ -28,11 +20,6 @@ export function encodePathParam(value: unknown): string {
28
20
  .join("/");
29
21
  }
30
22
 
31
- /**
32
- * Substitute route params into a pattern, stripping constraint and optional
33
- * syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
34
- * Optional params not present in `params` are removed from the output.
35
- */
36
23
  export function substituteRouteParams(
37
24
  pattern: string,
38
25
  params: Record<string, string>,
@@ -41,17 +28,9 @@ export function substituteRouteParams(
41
28
  let result = pattern;
42
29
  let hadOmittedOptional = false;
43
30
 
44
- // First pass: substitute provided params.
45
- // Empty string on an optional placeholder is treated as omitted —
46
- // caller-supplied params or `getParams()` shapes may pass `""` for an
47
- // absent optional, so letting the second pass strip them keeps slash
48
- // cleanup consistent. Empty string on required `:key` or wildcard
49
- // `*key` still substitutes, matching prior behaviour.
50
31
  for (const [key, value] of Object.entries(params)) {
51
32
  const escaped = escapeRegExp(key);
52
33
  if (value === "") {
53
- // Only replace required placeholders (negative lookahead for `?`);
54
- // leave `:key?` for the second pass.
55
34
  result = result.replace(
56
35
  new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
57
36
  "",
@@ -66,13 +45,11 @@ export function substituteRouteParams(
66
45
  }
67
46
  }
68
47
 
69
- // Second pass: strip remaining optional param placeholders not in params
70
48
  result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
71
49
  hadOmittedOptional = true;
72
50
  return "";
73
51
  });
74
52
 
75
- // Clean up slashes from omitted optional segments
76
53
  if (hadOmittedOptional) {
77
54
  const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
78
55
  result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
@@ -82,10 +59,6 @@ export function substituteRouteParams(
82
59
  return result;
83
60
  }
84
61
 
85
- /**
86
- * Run an async function over items with bounded concurrency.
87
- * Errors propagate immediately and abort remaining work.
88
- */
89
62
  export async function runWithConcurrency<T>(
90
63
  items: T[],
91
64
  concurrency: number,
@@ -106,10 +79,6 @@ export async function runWithConcurrency<T>(
106
79
  await Promise.all(Array.from({ length: limit }, () => worker()));
107
80
  }
108
81
 
109
- /**
110
- * Group prerender entries by their concurrency setting so each group
111
- * can be rendered with the appropriate parallelism.
112
- */
113
82
  export function groupByConcurrency<T extends { concurrency: number }>(
114
83
  entries: T[],
115
84
  ): { concurrency: number; entries: T[] }[] {
@@ -129,10 +98,6 @@ export function groupByConcurrency<T extends { concurrency: number }>(
129
98
  }));
130
99
  }
131
100
 
132
- /**
133
- * Notify all routers' onError callbacks about a build-time error.
134
- * Uses a synthetic request since there is no real request during build.
135
- */
136
101
  export function notifyOnError(
137
102
  registry: Map<string, any>,
138
103
  error: unknown,