@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,209 @@
1
+ import MagicString from "magic-string";
2
+ import { hashId } from "../expose-id-utils.js";
3
+ import type { HandlerTransformConfig, CreateExportBinding } from "./types.js";
4
+ import { isExportOnlyFile } from "./export-analysis.js";
5
+
6
+ function analyzeCreateHandleArgs(
7
+ code: string,
8
+ startPos: number,
9
+ endPos: number,
10
+ ): { hasArgs: boolean } {
11
+ const content = code.slice(startPos, endPos).trim();
12
+ return { hasArgs: content.length > 0 };
13
+ }
14
+
15
+ export function transformHandles(
16
+ bindings: CreateExportBinding[],
17
+ s: MagicString,
18
+ code: string,
19
+ filePath: string,
20
+ isBuild: boolean,
21
+ ): boolean {
22
+ let hasChanges = false;
23
+ for (const binding of bindings) {
24
+ const exportName = binding.exportNames[0];
25
+ const args = analyzeCreateHandleArgs(
26
+ code,
27
+ binding.callOpenParenPos + 1,
28
+ binding.callCloseParenPos,
29
+ );
30
+
31
+ const handleId = isBuild
32
+ ? hashId(filePath, exportName)
33
+ : `${filePath}#${exportName}`;
34
+
35
+ let paramInjection: string;
36
+ if (!args.hasArgs) {
37
+ paramInjection = `undefined, "${handleId}"`;
38
+ } else {
39
+ paramInjection = `, "${handleId}"`;
40
+ }
41
+ s.appendLeft(binding.callCloseParenPos, paramInjection);
42
+
43
+ const propInjection = `\n${binding.localName}.$$id = "${handleId}";`;
44
+ s.appendRight(binding.statementEnd, propInjection);
45
+ hasChanges = true;
46
+ }
47
+
48
+ return hasChanges;
49
+ }
50
+
51
+ export function transformLocationState(
52
+ bindings: CreateExportBinding[],
53
+ s: MagicString,
54
+ filePath: string,
55
+ isBuild: boolean,
56
+ ): boolean {
57
+ let hasChanges = false;
58
+ for (const binding of bindings) {
59
+ const exportName = binding.exportNames[0];
60
+
61
+ const stateKey = isBuild
62
+ ? hashId(filePath, exportName)
63
+ : `${filePath}#${exportName}`;
64
+
65
+ // Key is injected as a property assignment (not as a function argument).
66
+ // This allows createLocationState to accept options like { flash: true }
67
+ // without conflicting with key injection.
68
+ const propInjection = `\n${binding.localName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
69
+ s.appendRight(binding.statementEnd, propInjection);
70
+ hasChanges = true;
71
+ }
72
+
73
+ return hasChanges;
74
+ }
75
+
76
+ /**
77
+ * Replace the entire file with lightweight stubs when ALL non-type exports are
78
+ * handler calls of the given type. Returns null for files with mixed exports.
79
+ */
80
+ export function generateWholeFileStubs(
81
+ cfg: HandlerTransformConfig,
82
+ bindings: CreateExportBinding[],
83
+ code: string,
84
+ filePath: string,
85
+ isBuild: boolean,
86
+ ): { code: string; map: null } | null {
87
+ if (!isExportOnlyFile(code, bindings)) return null;
88
+
89
+ const exportNames = bindings.flatMap((b) => b.exportNames);
90
+ const stubs = exportNames.map((name) => {
91
+ const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
92
+ return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
93
+ });
94
+
95
+ return { code: stubs.join("\n") + "\n", map: null };
96
+ }
97
+
98
+ /**
99
+ * Replace handler call expressions with lightweight stub objects in non-RSC
100
+ * environments. Other exports, imports, and module-level code remain untouched.
101
+ */
102
+ export function generateExprStubs(
103
+ cfg: HandlerTransformConfig,
104
+ bindings: CreateExportBinding[],
105
+ code: string,
106
+ filePath: string,
107
+ sourceId: string,
108
+ isBuild: boolean,
109
+ ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
110
+ if (bindings.length === 0) return null;
111
+
112
+ const s = new MagicString(code);
113
+ let hasChanges = false;
114
+
115
+ for (const binding of bindings) {
116
+ const exportName = binding.exportNames[0];
117
+ const handlerId = isBuild
118
+ ? hashId(filePath, exportName)
119
+ : `${filePath}#${exportName}`;
120
+
121
+ s.overwrite(
122
+ binding.callExprStart,
123
+ binding.callCloseParenPos + 1,
124
+ `{ __brand: "${cfg.brand}", $$id: "${handlerId}" }`,
125
+ );
126
+ hasChanges = true;
127
+ }
128
+
129
+ if (!hasChanges) return null;
130
+
131
+ return {
132
+ code: s.toString(),
133
+ map: s.generateMap({
134
+ source: sourceId,
135
+ includeContent: true,
136
+ hires: "boundary",
137
+ }),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Replace handler call expressions with lightweight stub objects on an
143
+ * existing MagicString. Unlike generateExprStubs (which creates its own
144
+ * MagicString and returns the full result), this integrates into the
145
+ * unified transform pipeline so all transforms share one sourcemap.
146
+ */
147
+ export function stubHandlerExprs(
148
+ cfg: HandlerTransformConfig,
149
+ bindings: CreateExportBinding[],
150
+ s: MagicString,
151
+ filePath: string,
152
+ isBuild: boolean,
153
+ ): boolean {
154
+ let hasChanges = false;
155
+ for (const binding of bindings) {
156
+ const exportName = binding.exportNames[0];
157
+ const handlerId = isBuild
158
+ ? hashId(filePath, exportName)
159
+ : `${filePath}#${exportName}`;
160
+
161
+ s.overwrite(
162
+ binding.callExprStart,
163
+ binding.callCloseParenPos + 1,
164
+ `{ __brand: "${cfg.brand}", $$id: "${handlerId}" }`,
165
+ );
166
+ hasChanges = true;
167
+ }
168
+ return hasChanges;
169
+ }
170
+
171
+ /**
172
+ * Inject $$id into export const handler calls in RSC environments.
173
+ */
174
+ export function transformHandlerIds(
175
+ cfg: HandlerTransformConfig,
176
+ bindings: CreateExportBinding[],
177
+ s: MagicString,
178
+ filePath: string,
179
+ isBuild: boolean,
180
+ ): boolean {
181
+ let hasChanges = false;
182
+ for (const binding of bindings) {
183
+ const exportName = binding.exportNames[0];
184
+
185
+ const handlerId = isBuild
186
+ ? hashId(filePath, exportName)
187
+ : `${filePath}#${exportName}`;
188
+
189
+ // Injection strategy matches the runtime overload signatures:
190
+ // 0 args -> inject undefined, "id"
191
+ // 1 arg (handler) -> inject , undefined, "id"
192
+ // 2+ args -> inject , "id"
193
+ let paramInjection: string;
194
+ if (binding.argCount === 0) {
195
+ paramInjection = `undefined, "${handlerId}"`;
196
+ } else if (binding.argCount === 1) {
197
+ paramInjection = `, undefined, "${handlerId}"`;
198
+ } else {
199
+ paramInjection = `, "${handlerId}"`;
200
+ }
201
+ s.appendLeft(binding.callCloseParenPos, paramInjection);
202
+
203
+ const propInjection = `\n${binding.localName}.$$id = "${handlerId}";`;
204
+ s.appendRight(binding.statementEnd, propInjection);
205
+ hasChanges = true;
206
+ }
207
+
208
+ return hasChanges;
209
+ }
@@ -0,0 +1,74 @@
1
+ import type MagicString from "magic-string";
2
+ import { hashId } from "../expose-id-utils.js";
3
+ import type { CreateExportBinding } from "./types.js";
4
+ import { isExportOnlyFile } from "./export-analysis.js";
5
+
6
+ export function hasCreateLoaderImport(code: string): boolean {
7
+ return /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
8
+ code,
9
+ );
10
+ }
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
+ export function generateClientLoaderStubs(
25
+ bindings: CreateExportBinding[],
26
+ code: string,
27
+ filePath: string,
28
+ isBuild: boolean,
29
+ ): { code: string; map?: undefined } | null {
30
+ if (!isExportOnlyFile(code, bindings)) return null;
31
+
32
+ const lines: string[] = [];
33
+
34
+ for (const binding of bindings) {
35
+ for (const name of binding.exportNames) {
36
+ const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
37
+ lines.push(
38
+ `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`,
39
+ );
40
+ }
41
+ }
42
+
43
+ return { code: lines.join("\n") + "\n" };
44
+ }
45
+
46
+ export function transformLoaders(
47
+ bindings: CreateExportBinding[],
48
+ s: MagicString,
49
+ filePath: string,
50
+ isBuild: boolean,
51
+ ): boolean {
52
+ let hasChanges = false;
53
+
54
+ for (const binding of bindings) {
55
+ const exportName = binding.exportNames[0];
56
+
57
+ const loaderId = isBuild
58
+ ? hashId(filePath, exportName)
59
+ : `${filePath}#${exportName}`;
60
+
61
+ // Inject $$id as hidden third parameter.
62
+ // createLoader(fn) -> createLoader(fn, undefined, "id")
63
+ // createLoader(fn, true) -> createLoader(fn, true, "id")
64
+ const paramInjection =
65
+ binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
66
+ s.appendLeft(binding.callCloseParenPos, paramInjection);
67
+
68
+ const propInjection = `\n${binding.localName}.$$id = "${loaderId}";`;
69
+ s.appendRight(binding.statementEnd, propInjection);
70
+ hasChanges = true;
71
+ }
72
+
73
+ return hasChanges;
74
+ }
@@ -0,0 +1,127 @@
1
+ import type { Plugin } from "vite";
2
+ import MagicString from "magic-string";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { normalizePath, findMatchingParen } from "../expose-id-utils.js";
6
+ import { getImportedFnNames } from "./export-analysis.js";
7
+ import { createRangoDebugger, createCounter, NS } from "../../debug.js";
8
+
9
+ const debug = createRangoDebugger(NS.transform);
10
+
11
+ export function transformRouter(
12
+ code: string,
13
+ filePath: string,
14
+ routerFnNames: string[],
15
+ absolutePath?: string,
16
+ ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
17
+ const pat = new RegExp(
18
+ `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
19
+ "g",
20
+ );
21
+ let match: RegExpExecArray | null;
22
+ const s = new MagicString(code);
23
+ let changed = false;
24
+
25
+ // Compute the import path for the generated route names file.
26
+ // filePath is relative to project root (e.g., "src/router.tsx")
27
+ const basename = path.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
28
+ const routeNamesImport = `./${basename}.named-routes.gen.js`;
29
+ const routeNamesVar = `__rsc_rn`;
30
+
31
+ while ((match = pat.exec(code)) !== null) {
32
+ const callStart = match.index;
33
+ const parenPos = match.index + match[0].length - 1;
34
+
35
+ // Scope the $$id check to within this call's arguments only,
36
+ // not the entire remaining file.
37
+ const closeParen = findMatchingParen(code, parenPos + 1);
38
+ const callArgs = code.slice(parenPos + 1, closeParen);
39
+
40
+ // Skip if $$id is already present in this call
41
+ if (callArgs.includes("$$id")) continue;
42
+
43
+ // Compute line number for this call
44
+ const lineNumber = code.slice(0, callStart).split("\n").length;
45
+ const hash = createHash("sha256")
46
+ .update(`${filePath}:${lineNumber}`)
47
+ .digest("hex")
48
+ .slice(0, 8);
49
+
50
+ 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
+ const sourceFilePath = absolutePath ?? filePath;
55
+ const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
56
+
57
+ const afterParen = callArgs.trimStart();
58
+ if (afterParen.startsWith("{")) {
59
+ const bracePos = code.indexOf("{", parenPos + 1);
60
+ s.appendRight(bracePos + 1, injected);
61
+ } else if (afterParen.startsWith(")")) {
62
+ s.appendRight(parenPos + 1, `{${injected} }`);
63
+ }
64
+ }
65
+
66
+ if (!changed) return null;
67
+
68
+ // Prepend the static import as the first line. MagicString tracks the
69
+ // offset so all downstream source maps remain correct.
70
+ s.prepend(
71
+ `import { NamedRoutes as ${routeNamesVar} } from "${routeNamesImport}";\n`,
72
+ );
73
+
74
+ return {
75
+ code: s.toString(),
76
+ map: s.generateMap({ hires: true }),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Inject stable $$id into createRouter() calls at compile time.
82
+ * This must be a separate plugin without enforce:"post" because running
83
+ * at "post" priority changes Vite's dep optimization timing and can cause
84
+ * ERR_OUTDATED_OPTIMIZED_DEP / React dual-instance issues.
85
+ */
86
+ export function exposeRouterId(): Plugin {
87
+ let projectRoot = "";
88
+ const counter = createCounter(debug, "expose-router-id");
89
+ return {
90
+ name: "@rangojs/router:expose-router-id",
91
+ configResolved(config) {
92
+ projectRoot = config.root;
93
+ },
94
+ buildEnd() {
95
+ counter?.flush();
96
+ },
97
+ transform(code, id) {
98
+ 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
+ if (
104
+ !/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
105
+ code,
106
+ )
107
+ ) {
108
+ return null;
109
+ }
110
+ if (id.includes("node_modules")) return null;
111
+
112
+ const start = counter ? performance.now() : 0;
113
+ try {
114
+ const filePath = normalizePath(path.relative(projectRoot, id));
115
+ const routerFnNames = getImportedFnNames(code, "createRouter");
116
+ return transformRouter(
117
+ code,
118
+ filePath,
119
+ routerFnNames,
120
+ normalizePath(id),
121
+ );
122
+ } finally {
123
+ counter?.record(id, performance.now() - start);
124
+ }
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,45 @@
1
+ export interface HandlerTransformConfig {
2
+ fnName: string;
3
+ brand: string;
4
+ }
5
+
6
+ export interface CreateExportBinding {
7
+ localName: string;
8
+ exportNames: string[];
9
+ callExprStart: number;
10
+ callOpenParenPos: number;
11
+ callCloseParenPos: number;
12
+ argCount: number;
13
+ statementEnd: number;
14
+ }
15
+
16
+ export interface StrictCreateTransformConfig {
17
+ fnName: "createLoader" | "createHandle" | "createLocationState";
18
+ }
19
+
20
+ export const PRERENDER_CONFIG: HandlerTransformConfig = {
21
+ fnName: "Prerender",
22
+ brand: "prerenderHandler",
23
+ };
24
+
25
+ export const STATIC_CONFIG: HandlerTransformConfig = {
26
+ fnName: "Static",
27
+ brand: "staticHandler",
28
+ };
29
+
30
+ export const STRICT_CREATE_CONFIGS: StrictCreateTransformConfig[] = [
31
+ { fnName: "createLoader" },
32
+ { fnName: "createHandle" },
33
+ { fnName: "createLocationState" },
34
+ ];
35
+
36
+ export interface ExposeInternalIdsApi {
37
+ /** Tracks absolute module IDs that contain prerender handler exports.
38
+ * key: absolute module ID (filesystem path)
39
+ * value: array of export names (e.g., ["ArticlesIndex", "ArticleDetail"]) */
40
+ prerenderHandlerModules: Map<string, string[]>;
41
+ /** Tracks absolute module IDs that contain static handler exports.
42
+ * key: absolute module ID (filesystem path)
43
+ * value: array of export names (e.g., ["DocsNav", "DocShell"]) */
44
+ staticHandlerModules: Map<string, string[]>;
45
+ }