@rangojs/router 0.0.0-experimental.0f44aca1

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 (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,179 @@
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
+ * Inject $$id into export const handler calls in RSC environments.
143
+ */
144
+ export function transformHandlerIds(
145
+ cfg: HandlerTransformConfig,
146
+ bindings: CreateExportBinding[],
147
+ s: MagicString,
148
+ filePath: string,
149
+ isBuild: boolean,
150
+ ): boolean {
151
+ let hasChanges = false;
152
+ for (const binding of bindings) {
153
+ const exportName = binding.exportNames[0];
154
+
155
+ const handlerId = isBuild
156
+ ? hashId(filePath, exportName)
157
+ : `${filePath}#${exportName}`;
158
+
159
+ // Injection strategy matches the runtime overload signatures:
160
+ // 0 args -> inject undefined, "id"
161
+ // 1 arg (handler) -> inject , undefined, "id"
162
+ // 2+ args -> inject , "id"
163
+ let paramInjection: string;
164
+ if (binding.argCount === 0) {
165
+ paramInjection = `undefined, "${handlerId}"`;
166
+ } else if (binding.argCount === 1) {
167
+ paramInjection = `, undefined, "${handlerId}"`;
168
+ } else {
169
+ paramInjection = `, "${handlerId}"`;
170
+ }
171
+ s.appendLeft(binding.callCloseParenPos, paramInjection);
172
+
173
+ const propInjection = `\n${binding.localName}.$$id = "${handlerId}";`;
174
+ s.appendRight(binding.statementEnd, propInjection);
175
+ hasChanges = true;
176
+ }
177
+
178
+ return hasChanges;
179
+ }
@@ -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,110 @@
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
+
8
+ export function transformRouter(
9
+ code: string,
10
+ filePath: string,
11
+ routerFnNames: string[],
12
+ absolutePath?: string,
13
+ ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
14
+ const pat = new RegExp(
15
+ `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
16
+ "g",
17
+ );
18
+ let match: RegExpExecArray | null;
19
+ const s = new MagicString(code);
20
+ let changed = false;
21
+
22
+ // Compute the import path for the generated route names file.
23
+ // filePath is relative to project root (e.g., "src/router.tsx")
24
+ const basename = path.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
25
+ const routeNamesImport = `./${basename}.named-routes.gen.js`;
26
+ const routeNamesVar = `__rsc_rn`;
27
+
28
+ while ((match = pat.exec(code)) !== null) {
29
+ const callStart = match.index;
30
+ const parenPos = match.index + match[0].length - 1;
31
+
32
+ // Scope the $$id check to within this call's arguments only,
33
+ // not the entire remaining file.
34
+ const closeParen = findMatchingParen(code, parenPos + 1);
35
+ const callArgs = code.slice(parenPos + 1, closeParen);
36
+
37
+ // Skip if $$id is already present in this call
38
+ if (callArgs.includes("$$id")) continue;
39
+
40
+ // Compute line number for this call
41
+ const lineNumber = code.slice(0, callStart).split("\n").length;
42
+ const hash = createHash("sha256")
43
+ .update(`${filePath}:${lineNumber}`)
44
+ .digest("hex")
45
+ .slice(0, 8);
46
+
47
+ changed = true;
48
+ // $$sourceFile uses the absolute path so that downstream consumers
49
+ // (virtual-module-codegen, runtime-discovery) can resolve gen file
50
+ // imports correctly via path.dirname / path.join.
51
+ const sourceFilePath = absolutePath ?? filePath;
52
+ const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
53
+
54
+ const afterParen = callArgs.trimStart();
55
+ if (afterParen.startsWith("{")) {
56
+ const bracePos = code.indexOf("{", parenPos + 1);
57
+ s.appendRight(bracePos + 1, injected);
58
+ } else if (afterParen.startsWith(")")) {
59
+ s.appendRight(parenPos + 1, `{${injected} }`);
60
+ }
61
+ }
62
+
63
+ if (!changed) return null;
64
+
65
+ // Prepend the static import as the first line. MagicString tracks the
66
+ // offset so all downstream source maps remain correct.
67
+ s.prepend(
68
+ `import { NamedRoutes as ${routeNamesVar} } from "${routeNamesImport}";\n`,
69
+ );
70
+
71
+ return {
72
+ code: s.toString(),
73
+ map: s.generateMap({ hires: true }),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Inject stable $$id into createRouter() calls at compile time.
79
+ * This must be a separate plugin without enforce:"post" because running
80
+ * at "post" priority changes Vite's dep optimization timing and can cause
81
+ * ERR_OUTDATED_OPTIMIZED_DEP / React dual-instance issues.
82
+ */
83
+ export function exposeRouterId(): Plugin {
84
+ let projectRoot = "";
85
+ return {
86
+ name: "@rangojs/router:expose-router-id",
87
+ configResolved(config) {
88
+ projectRoot = config.root;
89
+ },
90
+ transform(code, id) {
91
+ if (!code.includes("createRouter")) return null;
92
+ // Accepts both @rangojs/router and @rangojs/router/server subpath.
93
+ // NOTE: detectImports in expose-id-utils has a stricter check that
94
+ // excludes /server for its router flag -- that's intentional since
95
+ // detectImports is only used in exposeInternalIds, not here.
96
+ if (
97
+ !/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
98
+ code,
99
+ )
100
+ ) {
101
+ return null;
102
+ }
103
+ if (id.includes("node_modules")) return null;
104
+
105
+ const filePath = normalizePath(path.relative(projectRoot, id));
106
+ const routerFnNames = getImportedFnNames(code, "createRouter");
107
+ return transformRouter(code, filePath, routerFnNames, normalizePath(id));
108
+ },
109
+ };
110
+ }
@@ -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
+ }