@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126

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 (235) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/rango.js +3 -4
  3. package/dist/vite/index.js +315 -68
  4. package/package.json +19 -18
  5. package/skills/breadcrumbs/SKILL.md +60 -0
  6. package/skills/hooks/SKILL.md +2 -2
  7. package/skills/route/SKILL.md +6 -0
  8. package/skills/server-actions/SKILL.md +25 -1
  9. package/skills/testing/SKILL.md +17 -17
  10. package/skills/testing/cache-prerender.md +29 -3
  11. package/skills/testing/flight.md +13 -10
  12. package/skills/testing/render-handler.md +3 -0
  13. package/skills/testing/server-tree.md +1 -1
  14. package/skills/testing/setup.md +1 -1
  15. package/src/__internal.ts +0 -65
  16. package/src/browser/action-coordinator.ts +1 -1
  17. package/src/browser/action-fence.ts +10 -0
  18. package/src/browser/event-controller.ts +1 -83
  19. package/src/browser/navigation-store-handle.ts +3 -4
  20. package/src/browser/navigation-store.ts +0 -39
  21. package/src/browser/navigation-transaction.ts +0 -32
  22. package/src/browser/partial-update.ts +23 -84
  23. package/src/browser/prefetch/cache.ts +6 -45
  24. package/src/browser/prefetch/queue.ts +6 -3
  25. package/src/browser/rango-state.ts +2 -23
  26. package/src/browser/react/Link.tsx +0 -2
  27. package/src/browser/react/NavigationProvider.tsx +2 -1
  28. package/src/browser/react/ScrollRestoration.tsx +10 -6
  29. package/src/browser/react/filter-segment-order.ts +0 -2
  30. package/src/browser/react/index.ts +0 -45
  31. package/src/browser/react/location-state-shared.ts +0 -13
  32. package/src/browser/react/location-state.ts +0 -1
  33. package/src/browser/react/use-action.ts +6 -15
  34. package/src/browser/react/use-handle.ts +0 -5
  35. package/src/browser/react/use-link-status.ts +0 -4
  36. package/src/browser/react/use-navigation.ts +0 -3
  37. package/src/browser/react/use-params.ts +0 -2
  38. package/src/browser/react/use-router.ts +2 -1
  39. package/src/browser/react/use-search-params.ts +0 -5
  40. package/src/browser/react/use-segments.ts +0 -13
  41. package/src/browser/rsc-router.tsx +10 -3
  42. package/src/browser/server-action-bridge.ts +51 -3
  43. package/src/browser/types.ts +23 -5
  44. package/src/browser/validate-redirect-origin.ts +43 -16
  45. package/src/build/index.ts +8 -9
  46. package/src/build/route-trie.ts +46 -11
  47. package/src/build/route-types/param-extraction.ts +6 -3
  48. package/src/build/route-types/router-processing.ts +0 -8
  49. package/src/cache/cache-policy.ts +0 -54
  50. package/src/cache/cache-runtime.ts +48 -24
  51. package/src/cache/cache-scope.ts +0 -27
  52. package/src/cache/cache-tag.ts +0 -37
  53. package/src/cache/cf/cf-cache-store.ts +72 -45
  54. package/src/cache/cf/index.ts +0 -24
  55. package/src/cache/document-cache.ts +10 -36
  56. package/src/cache/handle-snapshot.ts +0 -40
  57. package/src/cache/index.ts +0 -27
  58. package/src/cache/memory-segment-store.ts +0 -52
  59. package/src/cache/profile-registry.ts +6 -30
  60. package/src/cache/read-through-swr.ts +41 -11
  61. package/src/cache/segment-codec.ts +0 -16
  62. package/src/cache/types.ts +0 -98
  63. package/src/client.rsc.tsx +4 -22
  64. package/src/client.tsx +19 -32
  65. package/src/context-var.ts +12 -0
  66. package/src/defer.ts +196 -0
  67. package/src/deps/ssr.ts +0 -1
  68. package/src/handle.ts +2 -12
  69. package/src/handles/MetaTags.tsx +0 -14
  70. package/src/handles/breadcrumbs.ts +16 -5
  71. package/src/handles/meta.ts +0 -39
  72. package/src/host/cookie-handler.ts +0 -36
  73. package/src/host/errors.ts +0 -24
  74. package/src/host/index.ts +6 -0
  75. package/src/host/pattern-matcher.ts +7 -50
  76. package/src/host/router.ts +1 -65
  77. package/src/host/testing.ts +0 -16
  78. package/src/host/types.ts +6 -2
  79. package/src/href-client.ts +0 -4
  80. package/src/index.rsc.ts +27 -2
  81. package/src/index.ts +7 -0
  82. package/src/internal-debug.ts +2 -4
  83. package/src/loader.rsc.ts +4 -15
  84. package/src/loader.ts +3 -9
  85. package/src/network-error-thrower.tsx +1 -6
  86. package/src/outlet-provider.tsx +1 -5
  87. package/src/prerender/param-hash.ts +10 -11
  88. package/src/prerender/store.ts +23 -30
  89. package/src/prerender.ts +34 -0
  90. package/src/redirect-origin.ts +100 -0
  91. package/src/root-error-boundary.tsx +1 -19
  92. package/src/route-content-wrapper.tsx +1 -44
  93. package/src/route-definition/dsl-helpers.ts +7 -19
  94. package/src/route-definition/helpers-types.ts +3 -3
  95. package/src/route-definition/redirect.ts +43 -9
  96. package/src/route-definition/resolve-handler-use.ts +6 -0
  97. package/src/route-map-builder.ts +0 -16
  98. package/src/router/content-negotiation.ts +0 -13
  99. package/src/router/error-handling.ts +12 -16
  100. package/src/router/find-match.ts +4 -31
  101. package/src/router/intercept-resolution.ts +10 -1
  102. package/src/router/lazy-includes.ts +1 -57
  103. package/src/router/loader-resolution.ts +25 -23
  104. package/src/router/logging.ts +0 -6
  105. package/src/router/manifest.ts +1 -25
  106. package/src/router/match-api.ts +0 -20
  107. package/src/router/match-context.ts +0 -22
  108. package/src/router/match-handlers.ts +0 -43
  109. package/src/router/match-middleware/background-revalidation.ts +0 -7
  110. package/src/router/match-middleware/cache-lookup.ts +96 -179
  111. package/src/router/match-middleware/cache-store.ts +0 -31
  112. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  113. package/src/router/match-middleware/segment-resolution.ts +0 -22
  114. package/src/router/match-pipelines.ts +1 -42
  115. package/src/router/match-result.ts +1 -52
  116. package/src/router/metrics.ts +0 -34
  117. package/src/router/middleware-types.ts +0 -116
  118. package/src/router/middleware.ts +77 -60
  119. package/src/router/navigation-snapshot.ts +0 -51
  120. package/src/router/params-util.ts +23 -0
  121. package/src/router/pattern-matching.ts +5 -56
  122. package/src/router/prerender-match.ts +56 -51
  123. package/src/router/request-classification.ts +1 -38
  124. package/src/router/revalidation.ts +14 -62
  125. package/src/router/route-snapshot.ts +0 -1
  126. package/src/router/router-context.ts +0 -27
  127. package/src/router/router-interfaces.ts +10 -0
  128. package/src/router/segment-resolution/fresh.ts +25 -57
  129. package/src/router/segment-resolution/helpers.ts +34 -0
  130. package/src/router/segment-resolution/loader-cache.ts +35 -23
  131. package/src/router/segment-resolution/revalidation.ts +188 -283
  132. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  133. package/src/router/segment-resolution.ts +4 -1
  134. package/src/router/segment-wrappers.ts +0 -3
  135. package/src/router/telemetry-otel.ts +0 -20
  136. package/src/router/telemetry.ts +0 -22
  137. package/src/router/timeout.ts +0 -20
  138. package/src/router/trie-matching.ts +66 -45
  139. package/src/router/types.ts +1 -63
  140. package/src/router/url-params.ts +0 -5
  141. package/src/router.ts +8 -11
  142. package/src/rsc/handler-context.ts +1 -0
  143. package/src/rsc/handler.ts +20 -4
  144. package/src/rsc/helpers.ts +71 -3
  145. package/src/rsc/json-route-result.ts +38 -0
  146. package/src/rsc/origin-guard.ts +9 -15
  147. package/src/rsc/progressive-enhancement.ts +10 -1
  148. package/src/rsc/redirect-guard.ts +99 -0
  149. package/src/rsc/response-route-handler.ts +23 -18
  150. package/src/rsc/rsc-rendering.ts +2 -7
  151. package/src/rsc/runtime-warnings.ts +14 -0
  152. package/src/rsc/server-action.ts +34 -29
  153. package/src/rsc/types.ts +6 -3
  154. package/src/search-params.ts +0 -16
  155. package/src/segment-loader-promise.ts +14 -2
  156. package/src/segment-system.tsx +79 -88
  157. package/src/server/handle-store.ts +7 -24
  158. package/src/server/loader-registry.ts +5 -24
  159. package/src/server/request-context.ts +29 -92
  160. package/src/ssr/index.tsx +14 -14
  161. package/src/static-handler.ts +2 -27
  162. package/src/testing/cache-status.ts +44 -48
  163. package/src/testing/collect-handle.ts +1 -24
  164. package/src/testing/dispatch.ts +43 -6
  165. package/src/testing/e2e/index.ts +1 -22
  166. package/src/testing/e2e/matchers.ts +0 -16
  167. package/src/testing/flight-matchers.ts +0 -13
  168. package/src/testing/flight-normalize.ts +3 -30
  169. package/src/testing/flight.ts +46 -48
  170. package/src/testing/generated-routes.ts +1 -41
  171. package/src/testing/index.ts +1 -21
  172. package/src/testing/internal/context.ts +3 -45
  173. package/src/testing/internal/seed-vars.ts +0 -26
  174. package/src/testing/render-handler.ts +31 -61
  175. package/src/testing/render-route.tsx +75 -103
  176. package/src/testing/run-loader.ts +0 -96
  177. package/src/testing/run-middleware.ts +0 -26
  178. package/src/theme/ThemeProvider.tsx +0 -52
  179. package/src/theme/ThemeScript.tsx +0 -6
  180. package/src/theme/constants.ts +0 -12
  181. package/src/theme/index.ts +0 -7
  182. package/src/theme/theme-context.ts +1 -5
  183. package/src/theme/theme-script.ts +0 -14
  184. package/src/theme/use-theme.ts +0 -3
  185. package/src/types/boundaries.ts +0 -35
  186. package/src/types/error-types.ts +25 -89
  187. package/src/types/global-namespace.ts +4 -14
  188. package/src/types/handler-context.ts +28 -9
  189. package/src/types/index.ts +0 -10
  190. package/src/types/request-scope.ts +0 -19
  191. package/src/types/route-config.ts +6 -50
  192. package/src/types/route-entry.ts +0 -6
  193. package/src/types/segments.ts +0 -13
  194. package/src/urls/include-helper.ts +0 -4
  195. package/src/urls/index.ts +0 -6
  196. package/src/urls/path-helper-types.ts +2 -2
  197. package/src/urls/path-helper.ts +0 -54
  198. package/src/urls/urls-function.ts +0 -13
  199. package/src/use-loader.tsx +0 -186
  200. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  201. package/src/vite/discovery/discover-routers.ts +28 -18
  202. package/src/vite/discovery/prerender-collection.ts +2 -4
  203. package/src/vite/discovery/state.ts +5 -0
  204. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  205. package/src/vite/plugin-types.ts +35 -9
  206. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  207. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  208. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  209. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  210. package/src/vite/plugins/expose-action-id.ts +2 -73
  211. package/src/vite/plugins/expose-id-utils.ts +0 -55
  212. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  213. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  214. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  215. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  216. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  217. package/src/vite/plugins/performance-tracks.ts +0 -3
  218. package/src/vite/plugins/refresh-cmd.ts +1 -1
  219. package/src/vite/plugins/use-cache-transform.ts +21 -46
  220. package/src/vite/plugins/version-injector.ts +0 -20
  221. package/src/vite/plugins/version-plugin.ts +1 -49
  222. package/src/vite/plugins/virtual-entries.ts +0 -15
  223. package/src/vite/rango.ts +2 -108
  224. package/src/vite/router-discovery.ts +9 -1
  225. package/src/vite/utils/ast-handler-extract.ts +0 -16
  226. package/src/vite/utils/bundle-analysis.ts +6 -13
  227. package/src/vite/utils/client-chunks.ts +0 -6
  228. package/src/vite/utils/forward-user-plugins.ts +0 -22
  229. package/src/vite/utils/manifest-utils.ts +0 -4
  230. package/src/vite/utils/package-resolution.ts +1 -73
  231. package/src/vite/utils/prerender-utils.ts +0 -35
  232. package/src/vite/utils/shared-utils.ts +3 -35
  233. package/src/browser/shallow.ts +0 -40
  234. package/src/handles/index.ts +0 -7
  235. package/src/router/middleware-cookies.ts +0 -55
@@ -9,18 +9,6 @@ export function hasCreateLoaderImport(code: string): boolean {
9
9
  );
10
10
  }
11
11
 
12
- /**
13
- * Generate lightweight client stubs for loader files.
14
- *
15
- * When a loader file is imported from a client component (e.g., for useLoader()),
16
- * the client only needs { __brand: "loader", $$id: "..." } objects.
17
- * This function replaces the entire file contents with just those stub exports,
18
- * preventing server-only data (constants, DB queries, etc.) from leaking into
19
- * the client bundle.
20
- *
21
- * Only applies when ALL named exports are createLoader() calls (plus type exports
22
- * which are erased at compile time). Files with mixed exports are left untouched.
23
- */
24
12
  export function generateClientLoaderStubs(
25
13
  bindings: CreateExportBinding[],
26
14
  code: string,
@@ -56,9 +44,6 @@ export function transformLoaders(
56
44
 
57
45
  const loaderId = makeStubId(filePath, exportName, isBuild);
58
46
 
59
- // Inject $$id as hidden third parameter.
60
- // createLoader(fn) -> createLoader(fn, undefined, "id")
61
- // createLoader(fn, true) -> createLoader(fn, true, "id")
62
47
  const paramInjection =
63
48
  binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
64
49
  s.appendLeft(binding.callCloseParenPos, paramInjection);
@@ -32,15 +32,11 @@ export function transformRouter(
32
32
  const callStart = match.index;
33
33
  const parenPos = match.index + match[0].length - 1;
34
34
 
35
- // Scope the $$id check to within this call's arguments only,
36
- // not the entire remaining file.
37
35
  const closeParen = findMatchingParen(code, parenPos + 1);
38
36
  const callArgs = code.slice(parenPos + 1, closeParen);
39
37
 
40
- // Skip if $$id is already present in this call
41
38
  if (callArgs.includes("$$id")) continue;
42
39
 
43
- // Compute line number for this call
44
40
  const lineNumber = code.slice(0, callStart).split("\n").length;
45
41
  const hash = createHash("sha256")
46
42
  .update(`${filePath}:${lineNumber}`)
@@ -48,9 +44,6 @@ export function transformRouter(
48
44
  .slice(0, 8);
49
45
 
50
46
  changed = true;
51
- // $$sourceFile uses the absolute path so that downstream consumers
52
- // (virtual-module-codegen, runtime-discovery) can resolve gen file
53
- // imports correctly via path.dirname / path.join.
54
47
  const sourceFilePath = absolutePath ?? filePath;
55
48
  const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
56
49
 
@@ -65,8 +58,6 @@ export function transformRouter(
65
58
 
66
59
  if (!changed) return null;
67
60
 
68
- // Prepend the static import as the first line. MagicString tracks the
69
- // offset so all downstream source maps remain correct.
70
61
  s.prepend(
71
62
  `import { NamedRoutes as ${routeNamesVar} } from "${routeNamesImport}";\n`,
72
63
  );
@@ -96,10 +87,6 @@ export function exposeRouterId(): Plugin {
96
87
  },
97
88
  transform(code, id) {
98
89
  if (!code.includes("createRouter")) return null;
99
- // Accepts both @rangojs/router and @rangojs/router/server subpath.
100
- // NOTE: detectImports in expose-id-utils has a stricter check that
101
- // excludes /server for its router flag -- that's intentional since
102
- // detectImports is only used in exposeInternalIds, not here.
103
90
  if (
104
91
  !/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
105
92
  code,
@@ -184,6 +184,16 @@ ${lazyImports.join(",\n")}
184
184
  async buildStart() {
185
185
  if (!isBuild) return;
186
186
 
187
+ // The loader pre-scan walks and reads the entire project, but the
188
+ // loaderRegistry it populates is consumed only by the RSC loader-manifest
189
+ // virtual module (and the transform hook already gates its registry writes
190
+ // with isRscEnv). plugin-rsc runs ~5 build passes (rsc-scan, ssr-scan, rsc,
191
+ // client, ssr) over this single shared plugin instance; without this gate
192
+ // the full-tree I/O ran on every pass with no consumer on the non-RSC
193
+ // ones. Skip only when the environment is known and not RSC, so an
194
+ // unavailable environment still falls through (no empty registry).
195
+ if (this.environment && this.environment.name !== "rsc") return;
196
+
187
197
  const fs = await import("node:fs/promises");
188
198
 
189
199
  const SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
@@ -54,9 +54,6 @@ export function performanceTracksOptimizeDepsPlugin(): {
54
54
  /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
55
55
  return {
56
56
  name: "@rangojs/router:performance-tracks-optimize-deps",
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
57
  async load(id: string): Promise<{ code: string } | null> {
61
58
  const cleanId = id.split("?")[0] ?? id;
62
59
  if (!RSDW_CLIENT_RE.test(cleanId)) return null;
@@ -20,7 +20,7 @@ import type { Plugin } from "vite";
20
20
  */
21
21
  export function poke(): Plugin {
22
22
  return {
23
- name: "vite-plugin-poke",
23
+ name: "@rangojs/router:poke",
24
24
  apply: "serve",
25
25
 
26
26
  configureServer(server) {
@@ -30,11 +30,6 @@ const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
30
30
  // and should not be wrapped (children can't be cache-keyed).
31
31
  const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
32
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
33
  export const USE_CACHE_DIRECTIVE_RE: RegExp = /^use cache(:\s*[\w-]+)?$/;
39
34
 
40
35
  export function useCacheTransform(): Plugin {
@@ -72,7 +67,6 @@ export function useCacheTransform(): Plugin {
72
67
 
73
68
  const start = counter ? performance.now() : 0;
74
69
  try {
75
- // Lazy-load transform helpers
76
70
  if (!rscTransforms) {
77
71
  try {
78
72
  rscTransforms = await import("@vitejs/plugin-rsc/transforms");
@@ -87,7 +81,6 @@ export function useCacheTransform(): Plugin {
87
81
  transformHoistInlineDirective,
88
82
  } = rscTransforms;
89
83
 
90
- // Parse AST
91
84
  let ast: any;
92
85
  try {
93
86
  const { parseAst } = await import("vite");
@@ -99,7 +92,6 @@ export function useCacheTransform(): Plugin {
99
92
  const filePath = normalizePath(path.relative(projectRoot, id));
100
93
  const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
101
94
 
102
- // Check for file-level "use cache"
103
95
  if (hasDirective(ast.body, "use cache")) {
104
96
  return transformFileLevelUseCache(
105
97
  code,
@@ -112,8 +104,6 @@ export function useCacheTransform(): Plugin {
112
104
  );
113
105
  }
114
106
 
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
107
  const functionResult = transformFunctionLevelUseCache(
118
108
  code,
119
109
  ast,
@@ -123,9 +113,6 @@ export function useCacheTransform(): Plugin {
123
113
  transformHoistInlineDirective,
124
114
  );
125
115
 
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
116
  warnOnNearMissDirectives(ast, id, this.warn.bind(this));
130
117
 
131
118
  if (functionResult) return functionResult;
@@ -145,8 +132,7 @@ function transformFileLevelUseCache(
145
132
  isLayoutOrTemplate: boolean,
146
133
  transformWrapExport: (typeof import("@vitejs/plugin-rsc/transforms"))["transformWrapExport"],
147
134
  ) {
148
- // Collect non-function exports to report after wrapping
149
- const nonFunctionExports: string[] = [];
135
+ const unconfirmedExports: string[] = [];
150
136
 
151
137
  const { exportNames, output } = transformWrapExport(code, ast, {
152
138
  runtime: (value: string, name: string) => {
@@ -155,30 +141,38 @@ function transformFileLevelUseCache(
155
141
  },
156
142
  rejectNonAsyncFunction: false,
157
143
  filter: (name: string, meta: { isFunction?: boolean }) => {
158
- // Skip default export of layout/template files (they receive children)
159
144
  if (name === "default" && isLayoutOrTemplate) return false;
160
- // Non-function exports cannot be wrapped with registerCachedFunction
161
- if (meta.isFunction === false) {
162
- nonFunctionExports.push(name);
145
+ // isFunction is boolean | undefined: true = confirmed function, false =
146
+ // confirmed non-function, undefined = cannot tell statically (e.g. a
147
+ // factory/HOF initializer `const x = makeCached(fn)`). Deliberate policy:
148
+ // require a confirmed function and reject everything else, including
149
+ // indeterminate initializers that may be functions at runtime -- rewrite
150
+ // those as direct async functions. (Pre-#1246 plugin-rsc reported false,
151
+ // not undefined, here, so === false would wrongly wrap them post-bump.)
152
+ if (meta.isFunction !== true) {
153
+ unconfirmedExports.push(name);
163
154
  return false;
164
155
  }
165
156
  return true;
166
157
  },
167
158
  });
168
159
 
169
- if (nonFunctionExports.length > 0) {
160
+ if (unconfirmedExports.length > 0) {
161
+ const plural = unconfirmedExports.length > 1;
170
162
  throw new Error(
171
- `[rango:use-cache] File-level "use cache" in ${sourceId} cannot wrap ` +
172
- `non-function export${nonFunctionExports.length > 1 ? "s" : ""}: ` +
173
- `${nonFunctionExports.map((n) => `"${n}"`).join(", ")}. ` +
174
- `Only function exports can be cached. Either remove "use cache" from ` +
175
- `the file level and add it inside individual functions, or move the ` +
176
- `non-function exports to a separate module.`,
163
+ `[rango:use-cache] File-level "use cache" in ${sourceId} only wraps ` +
164
+ `exports that are statically-confirmed functions. ` +
165
+ `${plural ? "These exports are" : "This export is"} not: ` +
166
+ `${unconfirmedExports.map((n) => `"${n}"`).join(", ")}. ` +
167
+ `Declare them directly (export async function foo() {} or ` +
168
+ `export const foo = async () => {}). A factory or otherwise ` +
169
+ `statically-indeterminate initializer (export const foo = makeCached(fn)) ` +
170
+ `is rejected even if it returns a function at runtime -- rewrite it as a ` +
171
+ `direct async function, or move non-function exports to a separate module.`,
177
172
  );
178
173
  }
179
174
 
180
175
  if (exportNames.length === 0) {
181
- // Even if no exports were wrapped, strip the directive
182
176
  const s = new MagicString(code);
183
177
  const directive = findFileLevelDirective(ast);
184
178
  if (directive) {
@@ -195,12 +189,10 @@ function transformFileLevelUseCache(
195
189
  return;
196
190
  }
197
191
 
198
- // Prepend the import
199
192
  output.prepend(
200
193
  `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
201
194
  );
202
195
 
203
- // Replace the directive with a comment
204
196
  const directive = findFileLevelDirective(ast);
205
197
  if (directive) {
206
198
  output.overwrite(
@@ -244,9 +236,6 @@ function transformFunctionLevelUseCache(
244
236
 
245
237
  if (names.length === 0) return;
246
238
 
247
- // Use a top-level import instead of await import() — the hoisted wrapper
248
- // may be placed in a non-async context (e.g., inside a synchronous
249
- // urls() callback) where await is not allowed.
250
239
  output.prepend(
251
240
  `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
252
241
  );
@@ -261,9 +250,6 @@ function transformFunctionLevelUseCache(
261
250
  }
262
251
  }
263
252
 
264
- /**
265
- * Find the file-level "use cache" directive AST node for removal.
266
- */
267
253
  function findFileLevelDirective(
268
254
  ast: any,
269
255
  ): { start: number; end: number } | null {
@@ -280,18 +266,8 @@ function findFileLevelDirective(
280
266
  return null;
281
267
  }
282
268
 
283
- /**
284
- * Regex for near-miss: starts with "use cache:" but has invalid tokens.
285
- */
286
269
  const NEAR_MISS_RE = /^use cache:\s*.+$/;
287
270
 
288
- /**
289
- * Walk the AST looking for string literals that look like malformed
290
- * "use cache" directives and emit a Vite warning for each.
291
- *
292
- * This catches cases like `"use cache: bad.name"` or `"use cache: "`
293
- * that the transform regex silently ignores.
294
- */
295
271
  function warnOnNearMissDirectives(
296
272
  ast: any,
297
273
  fileId: string,
@@ -319,7 +295,6 @@ function warnOnNearMissDirectives(
319
295
  }
320
296
  }
321
297
 
322
- // Walk into function bodies where directives appear
323
298
  for (const key of Object.keys(node)) {
324
299
  const child = node[key];
325
300
  if (Array.isArray(child)) {
@@ -29,7 +29,6 @@ export function createVersionInjectorPlugin(
29
29
 
30
30
  transform(code, id) {
31
31
  if (!resolvedEntryPath) return null;
32
- // Only transform the RSC entry file
33
32
  const normalizedId = Vite.normalizePath(id);
34
33
  const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
35
34
 
@@ -37,25 +36,10 @@ export function createVersionInjectorPlugin(
37
36
  return null;
38
37
  }
39
38
 
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
39
  const prepend: string[] = [
55
40
  `import "virtual:rsc-router/routes-manifest";`,
56
41
  ];
57
42
 
58
- // Auto-inject VERSION if file uses createRSCHandler without version
59
43
  let newCode = code;
60
44
  const needsVersion =
61
45
  code.includes("createRSCHandler") &&
@@ -70,10 +54,6 @@ export function createVersionInjectorPlugin(
70
54
  );
71
55
  }
72
56
 
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
57
  const lines = newCode.split("\n");
78
58
  let insertAt = 0;
79
59
  while (insertAt < lines.length) {
@@ -138,8 +138,6 @@ export function createVersionPlugin(): Plugin {
138
138
 
139
139
  let versionCounter = 0;
140
140
  const bumpVersion = (reason: string) => {
141
- // Use timestamp + counter to guarantee uniqueness even when multiple
142
- // bumps happen within the same millisecond (e.g. cascading HMR events).
143
141
  currentVersion = Date.now().toString(16) + String(++versionCounter);
144
142
  console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
145
143
 
@@ -158,9 +156,6 @@ export function createVersionPlugin(): Plugin {
158
156
 
159
157
  configResolved(config) {
160
158
  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
159
  resolvedCacheDir = config.cacheDir
165
160
  ? String(config.cacheDir).replace(/\\/g, "/")
166
161
  : undefined;
@@ -210,27 +205,15 @@ export function createVersionPlugin(): Plugin {
210
205
  return null;
211
206
  },
212
207
 
213
- // Track RSC module changes and update version
214
208
  async hotUpdate(ctx) {
215
209
  if (!isDev) return;
216
210
 
217
- // Check if this is an RSC environment update (not client/ssr)
218
- // RSC modules affect server-rendered content and cached payloads
219
- // In Vite 6, environment is accessed via `this.environment`
220
211
  const isRscModule = this.environment?.name === "rsc";
221
212
 
222
213
  if (!isRscModule) return;
223
214
 
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
215
  if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
231
216
 
232
- // Skip re-bumping when the version virtual module itself is invalidated
233
- // (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
234
217
  if (
235
218
  ctx.modules.length === 1 &&
236
219
  ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
@@ -245,9 +228,6 @@ export function createVersionPlugin(): Plugin {
245
228
  const source = await ctx.read();
246
229
  const nextSignature = getClientModuleSignature(source);
247
230
  if (nextSignature) {
248
- // "use client" file — compare export signatures.
249
- // client-component-hmr may have cleared ctx.modules, so we
250
- // cannot rely on ctx.modules.length for these files.
251
231
  clientModuleSignatures.set(filePath, nextSignature);
252
232
  if (
253
233
  previousSignature &&
@@ -258,20 +238,11 @@ export function createVersionPlugin(): Plugin {
258
238
  } else {
259
239
  clientModuleSignatures.delete(filePath);
260
240
  if (!previousSignature) {
261
- // Not and never was "use client" — use module graph check.
262
- // ctx.modules is reliable for pure server files (only
263
- // client-component-hmr clears it for "use client" modules).
264
241
  if (ctx.modules.length === 0) return;
265
242
  }
266
- // Was "use client" but directive removed — boundary changed,
267
- // bump below.
268
243
  }
269
- } catch {
270
- // Fail open: if we can't read or parse the update, invalidate.
271
- }
244
+ } catch {}
272
245
  } else {
273
- // Non-code file (json, css, etc.) — only bump if it's actually
274
- // referenced by the RSC module graph.
275
246
  if (ctx.modules.length === 0) return;
276
247
  }
277
248
 
@@ -280,20 +251,6 @@ export function createVersionPlugin(): Plugin {
280
251
  };
281
252
  }
282
253
 
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
254
  export function isViteDepCachePath(
298
255
  filePath: string | undefined,
299
256
  cacheDir?: string,
@@ -311,11 +268,6 @@ export function isViteDepCachePath(
311
268
  }
312
269
  }
313
270
 
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
271
  return (
320
272
  /\/node_modules\/\.vite[^/]*\//.test(normalized) ||
321
273
  normalized.includes("/.vite-isolated/")
@@ -1,8 +1,3 @@
1
- /**
2
- * Default virtual entry file contents for rsc-router.
3
- * These are used when users don't provide their own entry files.
4
- */
5
-
6
1
  export const VIRTUAL_ENTRY_BROWSER: string = `
7
2
  import {
8
3
  createFromReadableStream,
@@ -51,9 +46,6 @@ export const renderHTML = createSSRHandler({
51
46
  });
52
47
  `.trim();
53
48
 
54
- /**
55
- * Generate the RSC entry content with the specified router path
56
- */
57
49
  export function getVirtualEntryRSC(routerPath: string): string {
58
50
  return `
59
51
  import {
@@ -104,9 +96,6 @@ export default function handler(request, env) {
104
96
  `.trim();
105
97
  }
106
98
 
107
- /**
108
- * Virtual module IDs
109
- */
110
99
  export const VIRTUAL_IDS = {
111
100
  browser: "virtual:rsc-router/entry.browser.js",
112
101
  ssr: "virtual:rsc-router/entry.ssr.js",
@@ -114,10 +103,6 @@ export const VIRTUAL_IDS = {
114
103
  version: "@rangojs/router:version",
115
104
  } as const;
116
105
 
117
- /**
118
- * Virtual module content for version.
119
- * Exports VERSION - a timestamp that changes on server restart (dev) or at build time (production).
120
- */
121
106
  export function getVirtualVersionContent(version: string): string {
122
107
  return `export const VERSION = ${JSON.stringify(version)};`;
123
108
  }