@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.dacec167

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 (255) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +2151 -846
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +57 -11
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +220 -30
  10. package/skills/caching/SKILL.md +116 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/document-cache/SKILL.md +78 -55
  13. package/skills/handler-use/SKILL.md +364 -0
  14. package/skills/hooks/SKILL.md +229 -20
  15. package/skills/host-router/SKILL.md +45 -20
  16. package/skills/i18n/SKILL.md +276 -0
  17. package/skills/intercept/SKILL.md +46 -4
  18. package/skills/layout/SKILL.md +28 -7
  19. package/skills/links/SKILL.md +247 -17
  20. package/skills/loader/SKILL.md +219 -9
  21. package/skills/middleware/SKILL.md +47 -12
  22. package/skills/migrate-nextjs/SKILL.md +562 -0
  23. package/skills/migrate-react-router/SKILL.md +769 -0
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +71 -6
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +242 -22
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +66 -9
  31. package/skills/route/SKILL.md +57 -4
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +751 -0
  34. package/skills/streams-and-websockets/SKILL.md +283 -0
  35. package/skills/testing/SKILL.md +778 -0
  36. package/skills/typesafety/SKILL.md +319 -27
  37. package/skills/use-cache/SKILL.md +34 -5
  38. package/skills/view-transitions/SKILL.md +294 -0
  39. package/src/__augment-tests__/augment.ts +81 -0
  40. package/src/__augment-tests__/augmented.check.ts +117 -0
  41. package/src/browser/action-coordinator.ts +53 -36
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/event-controller.ts +86 -70
  44. package/src/browser/history-state.ts +21 -0
  45. package/src/browser/index.ts +3 -3
  46. package/src/browser/navigation-bridge.ts +84 -11
  47. package/src/browser/navigation-client.ts +76 -28
  48. package/src/browser/navigation-store.ts +32 -9
  49. package/src/browser/navigation-transaction.ts +10 -28
  50. package/src/browser/partial-update.ts +64 -26
  51. package/src/browser/prefetch/cache.ts +129 -21
  52. package/src/browser/prefetch/fetch.ts +148 -16
  53. package/src/browser/prefetch/queue.ts +36 -5
  54. package/src/browser/rango-state.ts +53 -13
  55. package/src/browser/react/Link.tsx +30 -2
  56. package/src/browser/react/NavigationProvider.tsx +72 -31
  57. package/src/browser/react/filter-segment-order.ts +51 -7
  58. package/src/browser/react/index.ts +3 -0
  59. package/src/browser/react/location-state-shared.ts +175 -4
  60. package/src/browser/react/location-state.ts +39 -13
  61. package/src/browser/react/use-handle.ts +17 -9
  62. package/src/browser/react/use-navigation.ts +22 -2
  63. package/src/browser/react/use-params.ts +20 -8
  64. package/src/browser/react/use-reverse.ts +106 -0
  65. package/src/browser/react/use-router.ts +22 -2
  66. package/src/browser/react/use-segments.ts +11 -8
  67. package/src/browser/response-adapter.ts +25 -0
  68. package/src/browser/rsc-router.tsx +64 -22
  69. package/src/browser/scroll-restoration.ts +22 -14
  70. package/src/browser/segment-reconciler.ts +36 -14
  71. package/src/browser/segment-structure-assert.ts +2 -2
  72. package/src/browser/server-action-bridge.ts +23 -30
  73. package/src/browser/types.ts +21 -0
  74. package/src/build/collect-fallback-refs.ts +107 -0
  75. package/src/build/generate-manifest.ts +60 -35
  76. package/src/build/generate-route-types.ts +2 -0
  77. package/src/build/index.ts +2 -0
  78. package/src/build/route-trie.ts +52 -25
  79. package/src/build/route-types/codegen.ts +4 -4
  80. package/src/build/route-types/include-resolution.ts +1 -1
  81. package/src/build/route-types/per-module-writer.ts +7 -4
  82. package/src/build/route-types/router-processing.ts +55 -14
  83. package/src/build/route-types/scan-filter.ts +1 -1
  84. package/src/build/route-types/source-scan.ts +118 -0
  85. package/src/build/runtime-discovery.ts +9 -20
  86. package/src/cache/cache-scope.ts +28 -42
  87. package/src/cache/cf/cf-cache-store.ts +54 -13
  88. package/src/client.rsc.tsx +3 -0
  89. package/src/client.tsx +92 -182
  90. package/src/context-var.ts +5 -5
  91. package/src/decode-loader-results.ts +36 -0
  92. package/src/errors.ts +30 -1
  93. package/src/handle.ts +26 -13
  94. package/src/host/index.ts +2 -2
  95. package/src/host/router.ts +129 -57
  96. package/src/host/types.ts +31 -2
  97. package/src/host/utils.ts +1 -1
  98. package/src/href-client.ts +140 -20
  99. package/src/index.rsc.ts +9 -4
  100. package/src/index.ts +53 -15
  101. package/src/loader-store.ts +500 -0
  102. package/src/loader.rsc.ts +21 -6
  103. package/src/loader.ts +3 -10
  104. package/src/missing-id-error.ts +68 -0
  105. package/src/outlet-context.ts +1 -1
  106. package/src/prerender.ts +4 -4
  107. package/src/response-utils.ts +37 -0
  108. package/src/reverse.ts +65 -36
  109. package/src/route-content-wrapper.tsx +6 -28
  110. package/src/route-definition/dsl-helpers.ts +384 -257
  111. package/src/route-definition/helper-factories.ts +29 -139
  112. package/src/route-definition/helpers-types.ts +100 -28
  113. package/src/route-definition/resolve-handler-use.ts +6 -0
  114. package/src/route-definition/use-item-types.ts +32 -0
  115. package/src/route-types.ts +26 -41
  116. package/src/router/basename.ts +14 -0
  117. package/src/router/content-negotiation.ts +15 -2
  118. package/src/router/error-handling.ts +1 -1
  119. package/src/router/handler-context.ts +21 -38
  120. package/src/router/intercept-resolution.ts +4 -18
  121. package/src/router/lazy-includes.ts +8 -8
  122. package/src/router/loader-resolution.ts +19 -2
  123. package/src/router/manifest.ts +22 -13
  124. package/src/router/match-api.ts +4 -3
  125. package/src/router/match-handlers.ts +63 -20
  126. package/src/router/match-middleware/cache-lookup.ts +44 -91
  127. package/src/router/match-middleware/cache-store.ts +3 -2
  128. package/src/router/match-result.ts +53 -32
  129. package/src/router/metrics.ts +1 -1
  130. package/src/router/middleware-types.ts +15 -26
  131. package/src/router/middleware.ts +99 -84
  132. package/src/router/pattern-matching.ts +101 -17
  133. package/src/router/prerender-match.ts +1 -1
  134. package/src/router/preview-match.ts +3 -1
  135. package/src/router/request-classification.ts +4 -28
  136. package/src/router/revalidation.ts +58 -2
  137. package/src/router/router-interfaces.ts +45 -28
  138. package/src/router/router-options.ts +40 -1
  139. package/src/router/router-registry.ts +2 -5
  140. package/src/router/segment-resolution/fresh.ts +27 -6
  141. package/src/router/segment-resolution/revalidation.ts +147 -106
  142. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  143. package/src/router/substitute-pattern-params.ts +56 -0
  144. package/src/router/telemetry.ts +99 -0
  145. package/src/router/trie-matching.ts +18 -13
  146. package/src/router/types.ts +8 -0
  147. package/src/router/url-params.ts +49 -0
  148. package/src/router.ts +38 -23
  149. package/src/rsc/handler-context.ts +2 -2
  150. package/src/rsc/handler.ts +28 -69
  151. package/src/rsc/helpers.ts +91 -43
  152. package/src/rsc/index.ts +1 -1
  153. package/src/rsc/origin-guard.ts +28 -10
  154. package/src/rsc/progressive-enhancement.ts +4 -0
  155. package/src/rsc/response-route-handler.ts +46 -53
  156. package/src/rsc/rsc-rendering.ts +35 -51
  157. package/src/rsc/runtime-warnings.ts +9 -10
  158. package/src/rsc/server-action.ts +17 -37
  159. package/src/rsc/ssr-setup.ts +16 -0
  160. package/src/rsc/types.ts +8 -2
  161. package/src/search-params.ts +4 -4
  162. package/src/segment-content-promise.ts +67 -0
  163. package/src/segment-loader-promise.ts +122 -0
  164. package/src/segment-system.tsx +132 -116
  165. package/src/serialize.ts +243 -0
  166. package/src/server/context.ts +143 -53
  167. package/src/server/cookie-store.ts +28 -4
  168. package/src/server/request-context.ts +20 -42
  169. package/src/ssr/index.tsx +5 -1
  170. package/src/static-handler.ts +1 -1
  171. package/src/testing/cache-status.ts +166 -0
  172. package/src/testing/collect-handle.ts +63 -0
  173. package/src/testing/dispatch.ts +440 -0
  174. package/src/testing/dom.entry.ts +22 -0
  175. package/src/testing/e2e/fixture.ts +154 -0
  176. package/src/testing/e2e/index.ts +149 -0
  177. package/src/testing/e2e/matchers.ts +51 -0
  178. package/src/testing/e2e/page-helpers.ts +272 -0
  179. package/src/testing/e2e/parity.ts +306 -0
  180. package/src/testing/e2e/server.ts +183 -0
  181. package/src/testing/flight-matchers.ts +104 -0
  182. package/src/testing/flight-runtime.d.ts +57 -0
  183. package/src/testing/flight-tree.ts +320 -0
  184. package/src/testing/flight.entry.ts +39 -0
  185. package/src/testing/flight.ts +197 -0
  186. package/src/testing/generated-routes.ts +223 -0
  187. package/src/testing/index.ts +106 -0
  188. package/src/testing/internal/context.ts +331 -0
  189. package/src/testing/internal/flight-client-globals.ts +30 -0
  190. package/src/testing/render-route.tsx +565 -0
  191. package/src/testing/run-loader.ts +341 -0
  192. package/src/testing/run-middleware.ts +188 -0
  193. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  194. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  195. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  196. package/src/testing/vitest-stubs/version.ts +5 -0
  197. package/src/testing/vitest.ts +270 -0
  198. package/src/types/global-namespace.ts +39 -26
  199. package/src/types/handler-context.ts +68 -50
  200. package/src/types/index.ts +1 -0
  201. package/src/types/loader-types.ts +5 -6
  202. package/src/types/request-scope.ts +126 -0
  203. package/src/types/route-entry.ts +11 -0
  204. package/src/types/segments.ts +35 -2
  205. package/src/urls/include-helper.ts +34 -67
  206. package/src/urls/index.ts +0 -3
  207. package/src/urls/path-helper-types.ts +41 -7
  208. package/src/urls/path-helper.ts +17 -52
  209. package/src/urls/pattern-types.ts +36 -19
  210. package/src/urls/response-types.ts +22 -29
  211. package/src/urls/type-extraction.ts +26 -116
  212. package/src/urls/urls-function.ts +1 -5
  213. package/src/use-loader.tsx +413 -42
  214. package/src/vite/debug.ts +185 -0
  215. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  216. package/src/vite/discovery/discover-routers.ts +101 -51
  217. package/src/vite/discovery/discovery-errors.ts +194 -0
  218. package/src/vite/discovery/gate-state.ts +171 -0
  219. package/src/vite/discovery/prerender-collection.ts +67 -26
  220. package/src/vite/discovery/route-types-writer.ts +40 -84
  221. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  222. package/src/vite/discovery/state.ts +33 -0
  223. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  224. package/src/vite/index.ts +2 -0
  225. package/src/vite/plugin-types.ts +67 -0
  226. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  227. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  228. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  229. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  230. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  231. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  232. package/src/vite/plugins/expose-action-id.ts +54 -30
  233. package/src/vite/plugins/expose-id-utils.ts +12 -8
  234. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  235. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  236. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  237. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  238. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  239. package/src/vite/plugins/performance-tracks.ts +29 -25
  240. package/src/vite/plugins/use-cache-transform.ts +65 -50
  241. package/src/vite/plugins/version-injector.ts +39 -23
  242. package/src/vite/plugins/version-plugin.ts +59 -2
  243. package/src/vite/plugins/virtual-entries.ts +2 -2
  244. package/src/vite/rango.ts +116 -29
  245. package/src/vite/router-discovery.ts +750 -100
  246. package/src/vite/utils/ast-handler-extract.ts +15 -15
  247. package/src/vite/utils/banner.ts +1 -1
  248. package/src/vite/utils/bundle-analysis.ts +4 -2
  249. package/src/vite/utils/client-chunks.ts +190 -0
  250. package/src/vite/utils/forward-user-plugins.ts +193 -0
  251. package/src/vite/utils/manifest-utils.ts +21 -5
  252. package/src/vite/utils/package-resolution.ts +41 -1
  253. package/src/vite/utils/prerender-utils.ts +21 -6
  254. package/src/vite/utils/shared-utils.ts +107 -26
  255. package/src/browser/action-response-classifier.ts +0 -99
@@ -14,6 +14,9 @@
14
14
 
15
15
  import type { Plugin } from "vite";
16
16
  import { readFile } from "node:fs/promises";
17
+ import { createRangoDebugger, createCounter, NS } from "../debug.js";
18
+
19
+ const debug = createRangoDebugger(NS.transform);
17
20
 
18
21
  const RSDW_PATCH_RE =
19
22
  /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
@@ -45,44 +48,45 @@ export function patchRsdwClientDebugInfoRecovery(code: string): {
45
48
 
46
49
  export function performanceTracksOptimizeDepsPlugin(): {
47
50
  name: string;
48
- setup(build: any): void;
51
+ load(id: string): Promise<{ code: string } | null>;
49
52
  } {
53
+ const RSDW_CLIENT_RE =
54
+ /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
50
55
  return {
51
56
  name: "@rangojs/router:performance-tracks-optimize-deps",
52
- setup(build: any): void {
53
- build.onLoad(
54
- {
55
- filter:
56
- /react-server-dom-webpack-client\.browser\.(development|production)\.js$/,
57
- },
58
- async (args: { path: string }) => {
59
- const code = await readFile(args.path, "utf8");
60
- const patched = patchRsdwClientDebugInfoRecovery(code);
61
- return {
62
- contents: patched.code,
63
- loader: "js",
64
- };
65
- },
66
- );
57
+ // Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
58
+ // pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
59
+ // Returning code overrides Rolldown's default filesystem read for the module.
60
+ async load(id: string): Promise<{ code: string } | null> {
61
+ const cleanId = id.split("?")[0] ?? id;
62
+ if (!RSDW_CLIENT_RE.test(cleanId)) return null;
63
+ const code = await readFile(cleanId, "utf8");
64
+ const patched = patchRsdwClientDebugInfoRecovery(code);
65
+ return { code: patched.code };
67
66
  },
68
67
  };
69
68
  }
70
69
 
71
70
  export function performanceTracksPlugin(): Plugin {
71
+ const counter = createCounter(debug, "performance-tracks");
72
72
  return {
73
73
  name: "@rangojs/router:performance-tracks",
74
74
 
75
+ buildEnd() {
76
+ counter?.flush();
77
+ },
78
+
75
79
  transform(code, id) {
76
80
  if (!id.includes("react-server-dom") || !id.includes("client")) return;
77
- const patched = patchRsdwClientDebugInfoRecovery(code);
78
- if (!patched.debugInfoVar) return;
79
- if (process.env.INTERNAL_RANGO_DEBUG)
80
- console.log(
81
- "[perf-tracks] patched RSDW client (var:",
82
- patched.debugInfoVar,
83
- ")",
84
- );
85
- return patched.code;
81
+ const start = counter ? performance.now() : 0;
82
+ try {
83
+ const patched = patchRsdwClientDebugInfoRecovery(code);
84
+ if (!patched.debugInfoVar) return;
85
+ debug?.("patched RSDW client (var: %s)", patched.debugInfoVar);
86
+ return patched.code;
87
+ } finally {
88
+ counter?.record(id, performance.now() - start);
89
+ }
86
90
  },
87
91
  };
88
92
  }
@@ -20,6 +20,9 @@ import type { Plugin } from "vite";
20
20
  import path from "node:path";
21
21
  import MagicString from "magic-string";
22
22
  import { normalizePath, hashId } from "./expose-id-utils.js";
23
+ import { createRangoDebugger, createCounter, NS } from "../debug.js";
24
+
25
+ const debug = createRangoDebugger(NS.transform);
23
26
 
24
27
  const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
25
28
 
@@ -27,11 +30,19 @@ const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
27
30
  // and should not be wrapped (children can't be cache-keyed).
28
31
  const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
29
32
 
33
+ /**
34
+ * Grammar for a valid function-level directive: `use cache` optionally followed
35
+ * by `: <profile-name>`. The single source of truth for both the transform and
36
+ * the near-miss validator below.
37
+ */
38
+ export const USE_CACHE_DIRECTIVE_RE: RegExp = /^use cache(:\s*[\w-]+)?$/;
39
+
30
40
  export function useCacheTransform(): Plugin {
31
41
  let projectRoot = "";
32
42
  let isBuild = false;
33
43
  let rscTransforms: typeof import("@vitejs/plugin-rsc/transforms") | null =
34
44
  null;
45
+ const counter = createCounter(debug, "use-cache");
35
46
 
36
47
  return {
37
48
  name: "@rangojs/router:use-cache",
@@ -42,6 +53,10 @@ export function useCacheTransform(): Plugin {
42
53
  isBuild = config.command === "build";
43
54
  },
44
55
 
56
+ buildEnd() {
57
+ counter?.flush();
58
+ },
59
+
45
60
  async transform(code, id) {
46
61
  // Only process in RSC environment
47
62
  if (this.environment?.name !== "rsc") return;
@@ -55,63 +70,68 @@ export function useCacheTransform(): Plugin {
55
70
  // Only JS/TS files
56
71
  if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
57
72
 
58
- // Lazy-load transform helpers
59
- if (!rscTransforms) {
73
+ const start = counter ? performance.now() : 0;
74
+ try {
75
+ // Lazy-load transform helpers
76
+ if (!rscTransforms) {
77
+ try {
78
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
79
+ } catch {
80
+ return;
81
+ }
82
+ }
83
+
84
+ const {
85
+ hasDirective,
86
+ transformWrapExport,
87
+ transformHoistInlineDirective,
88
+ } = rscTransforms;
89
+
90
+ // Parse AST
91
+ let ast: any;
60
92
  try {
61
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
93
+ const { parseAst } = await import("vite");
94
+ ast = parseAst(code, { lang: "tsx" });
62
95
  } catch {
63
96
  return;
64
97
  }
65
- }
66
-
67
- const {
68
- hasDirective,
69
- transformWrapExport,
70
- transformHoistInlineDirective,
71
- } = rscTransforms;
72
98
 
73
- // Parse AST
74
- let ast: any;
75
- try {
76
- const { parseAst } = await import("vite");
77
- ast = parseAst(code);
78
- } catch {
79
- return;
80
- }
81
-
82
- const filePath = normalizePath(path.relative(projectRoot, id));
83
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
99
+ const filePath = normalizePath(path.relative(projectRoot, id));
100
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
101
+
102
+ // Check for file-level "use cache"
103
+ if (hasDirective(ast.body, "use cache")) {
104
+ return transformFileLevelUseCache(
105
+ code,
106
+ ast,
107
+ filePath,
108
+ id,
109
+ isBuild,
110
+ isLayoutOrTemplate,
111
+ transformWrapExport,
112
+ );
113
+ }
84
114
 
85
- // Check for file-level "use cache"
86
- if (hasDirective(ast.body, "use cache")) {
87
- return transformFileLevelUseCache(
115
+ // Check for function-level "use cache" / "use cache: profileName"
116
+ // (only if there's no file-level directive but code still contains the string)
117
+ const functionResult = transformFunctionLevelUseCache(
88
118
  code,
89
119
  ast,
90
120
  filePath,
91
121
  id,
92
122
  isBuild,
93
- isLayoutOrTemplate,
94
- transformWrapExport,
123
+ transformHoistInlineDirective,
95
124
  );
96
- }
97
125
 
98
- // Check for function-level "use cache" / "use cache: profileName"
99
- // (only if there's no file-level directive but code still contains the string)
100
- const functionResult = transformFunctionLevelUseCache(
101
- code,
102
- ast,
103
- filePath,
104
- id,
105
- isBuild,
106
- transformHoistInlineDirective,
107
- );
108
-
109
- // Always check for near-miss directives, even when valid directives
110
- // exist. A file may contain both valid and invalid "use cache" directives
111
- // in different functions — the invalid ones should still warn.
112
- warnOnNearMissDirectives(ast, id, this.warn.bind(this));
126
+ // Check for near-miss directives on the function-level path. The
127
+ // file-level branch above returns earlier (it wraps every export
128
+ // regardless), so this runs only when there is no file-level directive.
129
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
113
130
 
114
- if (functionResult) return functionResult;
131
+ if (functionResult) return functionResult;
132
+ } finally {
133
+ counter?.record(id, performance.now() - start);
134
+ }
115
135
  },
116
136
  };
117
137
  }
@@ -206,7 +226,7 @@ function transformFunctionLevelUseCache(
206
226
  ) {
207
227
  try {
208
228
  const { output, names } = transformHoistInlineDirective(code, ast, {
209
- directive: /^use cache(:\s*[\w-]+)?$/,
229
+ directive: USE_CACHE_DIRECTIVE_RE,
210
230
  runtime: (
211
231
  value: string,
212
232
  name: string,
@@ -260,11 +280,6 @@ function findFileLevelDirective(
260
280
  return null;
261
281
  }
262
282
 
263
- /**
264
- * The valid directive regex (must stay in sync with transformFunctionLevelUseCache).
265
- */
266
- const VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
267
-
268
283
  /**
269
284
  * Regex for near-miss: starts with "use cache:" but has invalid tokens.
270
285
  */
@@ -294,7 +309,7 @@ function warnOnNearMissDirectives(
294
309
  if (
295
310
  value.startsWith("use cache") &&
296
311
  NEAR_MISS_RE.test(value) &&
297
- !VALID_DIRECTIVE_RE.test(value)
312
+ !USE_CACHE_DIRECTIVE_RE.test(value)
298
313
  ) {
299
314
  const profilePart = value.slice("use cache:".length).trim();
300
315
  warn(
@@ -1,6 +1,7 @@
1
1
  import type { Plugin } from "vite";
2
2
  import { resolve } from "node:path";
3
3
  import * as Vite from "vite";
4
+ import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
4
5
 
5
6
  /**
6
7
  * Plugin that auto-injects VERSION and routes-manifest into custom entry.rsc files.
@@ -20,18 +21,7 @@ export function createVersionInjectorPlugin(
20
21
 
21
22
  configResolved(config) {
22
23
  let entryPath = rscEntryPath;
23
- // Cloudflare preset: read entry from resolved environment config.
24
- // The @cloudflare/vite-plugin reads wrangler config (toml/json/jsonc)
25
- // and sets optimizeDeps.entries on the RSC environment.
26
- if (!entryPath) {
27
- const rscEnvConfig = (config.environments as any)?.["rsc"];
28
- const entries = rscEnvConfig?.optimizeDeps?.entries;
29
- if (typeof entries === "string") {
30
- entryPath = entries;
31
- } else if (Array.isArray(entries) && entries.length > 0) {
32
- entryPath = entries[0];
33
- }
34
- }
24
+ if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
35
25
  if (entryPath) {
36
26
  resolvedEntryPath = resolve(config.root, entryPath);
37
27
  }
@@ -47,16 +37,26 @@ export function createVersionInjectorPlugin(
47
37
  return null;
48
38
  }
49
39
 
50
- // Prepend imports at the top of the file. ES imports are hoisted
51
- // by the module system, so source position is irrelevant.
52
- const prepend: string[] = [];
53
- let newCode = code;
54
-
55
- if (!code.includes("virtual:rsc-router/routes-manifest")) {
56
- prepend.push(`import "virtual:rsc-router/routes-manifest";`);
57
- }
40
+ // Always prepend `import "virtual:rsc-router/routes-manifest"` as the
41
+ // first side-effect import. The manifest virtual module's `load()` hook
42
+ // awaits `s.discoveryDone` so that, by the time the rest of the entry
43
+ // including any module-level `router.reverse()` calls under `./router.js`
44
+ // evaluates, runtime discovery has rewritten `router.named-routes.gen.ts`
45
+ // with the full route table.
46
+ //
47
+ // ES module evaluation order matters here: while imports are *parsed*
48
+ // hoisted, side-effect imports are evaluated in source order in the
49
+ // dependency graph. A user-authored `import "virtual:rsc-router/..."`
50
+ // placed after `import "./router.js"` runs too late: the manifest
51
+ // gate fires after router.tsx has already crashed on a stale gen file.
52
+ // We always prepend; ESM dedups any user-written duplicate, so module
53
+ // initialization still runs once.
54
+ const prepend: string[] = [
55
+ `import "virtual:rsc-router/routes-manifest";`,
56
+ ];
58
57
 
59
58
  // Auto-inject VERSION if file uses createRSCHandler without version
59
+ let newCode = code;
60
60
  const needsVersion =
61
61
  code.includes("createRSCHandler") &&
62
62
  !code.includes("@rangojs/router:version") &&
@@ -70,9 +70,25 @@ export function createVersionInjectorPlugin(
70
70
  );
71
71
  }
72
72
 
73
- if (prepend.length === 0 && newCode === code) return null;
74
-
75
- newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
73
+ // Insert after any leading `/// <reference ... />` triple-slash
74
+ // directives (and surrounding blank lines). TypeScript requires those
75
+ // directives to precede all other code; putting our imports above
76
+ // them silently demotes the directives to plain comments.
77
+ const lines = newCode.split("\n");
78
+ let insertAt = 0;
79
+ while (insertAt < lines.length) {
80
+ const trimmed = lines[insertAt]!.trim();
81
+ if (trimmed === "" || /^\/\/\/\s*<reference\b/.test(trimmed)) {
82
+ insertAt++;
83
+ } else {
84
+ break;
85
+ }
86
+ }
87
+ newCode = [
88
+ ...lines.slice(0, insertAt),
89
+ ...prepend,
90
+ ...lines.slice(insertAt),
91
+ ].join("\n");
76
92
 
77
93
  return {
78
94
  code: newCode,
@@ -18,7 +18,7 @@ function getClientModuleSignature(
18
18
  ): ClientModuleSignature | undefined {
19
19
  let program: any;
20
20
  try {
21
- program = parseAst(source, { jsx: true });
21
+ program = parseAst(source, { lang: "tsx" });
22
22
  } catch {
23
23
  return undefined;
24
24
  }
@@ -133,6 +133,7 @@ export function createVersionPlugin(): Plugin {
133
133
  let currentVersion = buildVersion;
134
134
  let isDev = false;
135
135
  let server: any = null;
136
+ let resolvedCacheDir: string | undefined;
136
137
  const clientModuleSignatures = new Map<string, ClientModuleSignature>();
137
138
 
138
139
  let versionCounter = 0;
@@ -140,7 +141,7 @@ export function createVersionPlugin(): Plugin {
140
141
  // Use timestamp + counter to guarantee uniqueness even when multiple
141
142
  // bumps happen within the same millisecond (e.g. cascading HMR events).
142
143
  currentVersion = Date.now().toString(16) + String(++versionCounter);
143
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
144
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
144
145
 
145
146
  const rscEnv = server?.environments?.rsc;
146
147
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -157,6 +158,12 @@ export function createVersionPlugin(): Plugin {
157
158
 
158
159
  configResolved(config) {
159
160
  isDev = config.command === "serve";
161
+ // Capture the resolved cacheDir so we can ignore optimizer-output
162
+ // writes inside it. Vite resolves cacheDir against the project root,
163
+ // so this is a stable absolute path for the lifetime of the server.
164
+ resolvedCacheDir = config.cacheDir
165
+ ? String(config.cacheDir).replace(/\\/g, "/")
166
+ : undefined;
160
167
  },
161
168
 
162
169
  configureServer(devServer) {
@@ -214,6 +221,14 @@ export function createVersionPlugin(): Plugin {
214
221
 
215
222
  if (!isRscModule) return;
216
223
 
224
+ // Skip Vite's own pre-bundled dep cache writes. The optimizer rewrites
225
+ // files inside the configured `cacheDir` on every discovery cycle
226
+ // (and when other dev servers under the same cwd populate their own
227
+ // isolated cache dirs). These are not user-source changes, so bumping
228
+ // the app version on them produces spurious version mismatches that
229
+ // surface as forced reloads on in-flight actions.
230
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
231
+
217
232
  // Skip re-bumping when the version virtual module itself is invalidated
218
233
  // (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
219
234
  if (
@@ -264,3 +279,45 @@ export function createVersionPlugin(): Plugin {
264
279
  },
265
280
  };
266
281
  }
282
+
283
+ /**
284
+ * Match Vite's pre-bundled dep cache directories. These paths are rewritten
285
+ * by the dep optimizer (and by isolated test fixtures sharing the same cwd),
286
+ * not by user source changes, so they should not bump the app version (which
287
+ * would force a client reload mid-request).
288
+ *
289
+ * Two checks:
290
+ * 1. Anything inside the resolved `cacheDir` (precise — covers custom paths
291
+ * like the `RANGO_E2E_VITE_CACHE_DIR` overrides in the test fixtures).
292
+ * 2. Heuristic match for any `node_modules/.vite*` directory or a
293
+ * `.vite-isolated/` segment anywhere in the path. This catches the
294
+ * *other* dev servers in the same cwd whose cacheDir we cannot read
295
+ * (we only see config of the server we're attached to).
296
+ */
297
+ export function isViteDepCachePath(
298
+ filePath: string | undefined,
299
+ cacheDir?: string,
300
+ ): boolean {
301
+ if (!filePath) return false;
302
+ const normalized = filePath.replace(/\\/g, "/");
303
+
304
+ if (cacheDir) {
305
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
306
+ if (
307
+ normalized === normalizedCacheDir ||
308
+ normalized.startsWith(normalizedCacheDir + "/")
309
+ ) {
310
+ return true;
311
+ }
312
+ }
313
+
314
+ // Vite/optimizer convention: cache dirs always sit directly under
315
+ // `node_modules/` and start with `.vite` (e.g. `.vite`, `.vite-temp`,
316
+ // `.vite_rango_generate`, `.vite-e2e-test-app`). The `/.vite-isolated/`
317
+ // segment covers the test-fixture pattern that places the cache outside
318
+ // node_modules.
319
+ return (
320
+ /\/node_modules\/\.vite[^/]*\//.test(normalized) ||
321
+ normalized.includes("/.vite-isolated/")
322
+ );
323
+ }
@@ -14,7 +14,7 @@ import {
14
14
  import { createElement, StrictMode } from "react";
15
15
  import { hydrateRoot } from "react-dom/client";
16
16
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
17
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
17
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
18
18
 
19
19
  async function initializeApp() {
20
20
  const deps = {
@@ -29,7 +29,7 @@ async function initializeApp() {
29
29
 
30
30
  hydrateRoot(
31
31
  document,
32
- createElement(StrictMode, null, createElement(RSCRouter))
32
+ createElement(StrictMode, null, createElement(Rango))
33
33
  );
34
34
  }
35
35