@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -8,21 +8,12 @@ export function normalizePath(p: string): string {
8
8
  return p.split(path.sep).join("/");
9
9
  }
10
10
 
11
- /**
12
- * Generate a short hash for an ID.
13
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short.
14
- * Appends export name for easier debugging in production: "abc123#ExportName"
15
- */
16
11
  export function hashId(filePath: string, exportName: string): string {
17
12
  const input = `${filePath}#${exportName}`;
18
13
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
14
  return `${hash.slice(0, 8)}#${exportName}`;
20
15
  }
21
16
 
22
- /**
23
- * Build a stable ID for an export binding. Uses hashed IDs in production
24
- * builds (short + opaque) and readable path#name IDs in dev.
25
- */
26
17
  export function makeStubId(
27
18
  filePath: string,
28
19
  exportName: string,
@@ -31,19 +22,12 @@ export function makeStubId(
31
22
  return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
32
23
  }
33
24
 
34
- /**
35
- * Generate an 8-char hex hash for an inline static handler call site.
36
- * Uses file path and line number (plus optional index for same-line collisions).
37
- */
38
25
  export function hashInlineId(
39
26
  filePath: string,
40
- lineNumber: number,
41
- index?: number,
27
+ fnName: string,
28
+ index: number,
42
29
  ): string {
43
- const input =
44
- index !== undefined && index > 0
45
- ? `${filePath}:${lineNumber}:${index}`
46
- : `${filePath}:${lineNumber}`;
30
+ const input = `${filePath}:${fnName}:${index}`;
47
31
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
48
32
  }
49
33
 
@@ -57,11 +41,6 @@ export interface DetectedImports {
57
41
  any: boolean;
58
42
  }
59
43
 
60
- /**
61
- * Build a map from local binding name to exported names by walking
62
- * ExportNamedDeclaration nodes. Handles `export const X`, `export { X }`,
63
- * and `export { X as Y }`. Skips re-exports (`export { X } from "..."`).
64
- */
65
44
  export function buildExportMap(program: any): Map<string, string[]> {
66
45
  const exportMap = new Map<string, string[]>();
67
46
 
@@ -101,10 +80,6 @@ export function buildExportMap(program: any): Map<string, string[]> {
101
80
  return exportMap;
102
81
  }
103
82
 
104
- /**
105
- * Single-pass detection of all create* imports from @rangojs/router.
106
- * Returns which create functions are imported so we can skip unnecessary transforms.
107
- */
108
83
  export function detectImports(code: string): DetectedImports {
109
84
  // Extract all import declarations from @rangojs/router in one scan
110
85
  const importPattern =
@@ -131,10 +106,6 @@ export function detectImports(code: string): DetectedImports {
131
106
  if (/\bcreateRouter\b/.test(imports)) result.router = true;
132
107
  }
133
108
 
134
- // createRouter has a stricter check: only from "@rangojs/router" (not sub-paths).
135
- // NOTE: This is intentional — detectImports is used as a fast pre-filter in
136
- // exposeInternalIds (which does NOT handle router transforms). The separate
137
- // exposeRouterId plugin handles createRouter and DOES accept the /server subpath.
138
109
  if (result.router) {
139
110
  result.router =
140
111
  /import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router["']/.test(
@@ -153,11 +124,6 @@ export function detectImports(code: string): DetectedImports {
153
124
  return result;
154
125
  }
155
126
 
156
- /**
157
- * Skip past a string literal, template literal, or comment starting at pos.
158
- * Returns the index after the closing delimiter, or pos if not at a
159
- * string/comment start. Handles escape sequences and nested ${} in templates.
160
- */
161
127
  export function skipStringOrComment(code: string, pos: number): number {
162
128
  const ch = code[pos];
163
129
 
@@ -215,11 +181,6 @@ export function skipStringOrComment(code: string, pos: number): number {
215
181
  return pos;
216
182
  }
217
183
 
218
- /**
219
- * Find the matching closing paren starting after an already-opened paren.
220
- * Skips strings, template literals, and comments so parens inside them
221
- * don't affect depth tracking. Returns the index after the closing paren.
222
- */
223
184
  export function findMatchingParen(code: string, startPos: number): number {
224
185
  let depth = 1;
225
186
  let i = startPos;
@@ -236,10 +197,6 @@ export function findMatchingParen(code: string, startPos: number): number {
236
197
  return i;
237
198
  }
238
199
 
239
- /**
240
- * Count the number of top-level arguments in a function call.
241
- * Skips nested parens, brackets, braces, strings, and comments.
242
- */
243
200
  export function countArgs(
244
201
  code: string,
245
202
  startPos: number,
@@ -275,10 +232,6 @@ export function countArgs(
275
232
  return hasContent ? argCount + 1 : 0;
276
233
  }
277
234
 
278
- /**
279
- * Find the end of a statement: skip whitespace and optional semicolon after
280
- * a closing paren position.
281
- */
282
235
  export function findStatementEnd(code: string, pos: number): number {
283
236
  let i = pos;
284
237
  while (i < code.length && /\s/.test(code[i])) {
@@ -290,10 +243,6 @@ export function findStatementEnd(code: string, pos: number): number {
290
243
  return i;
291
244
  }
292
245
 
293
- /**
294
- * Escape special regex characters in a string so it can be safely
295
- * interpolated into a RegExp pattern.
296
- */
297
246
  export function escapeRegExp(input: string): string {
298
247
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
299
248
  }
@@ -6,14 +6,9 @@ import {
6
6
  buildExportMap,
7
7
  escapeRegExp,
8
8
  } from "../expose-id-utils.js";
9
+ import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
9
10
  import type { CreateExportBinding } from "./types.js";
10
11
 
11
- /**
12
- * Check whether every non-type export in `code` is accounted for by the given
13
- * bindings. Returns false if any export exists that is not one of the known
14
- * create* call locals/exports, allowing callers to bail out for mixed-export
15
- * files.
16
- */
17
12
  export function isExportOnlyFile(
18
13
  code: string,
19
14
  bindings: CreateExportBinding[],
@@ -27,10 +22,8 @@ export function isExportOnlyFile(
27
22
  for (const e of b.exportNames) knownExports.add(e);
28
23
  }
29
24
 
30
- // Bail on star re-exports (unknown exports)
31
25
  if (/export\s*\*/.test(code)) return false;
32
26
 
33
- // Check `export const/let/var/function/class/default X` declarations
34
27
  const declExportPattern =
35
28
  /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
36
29
  let match: RegExpExecArray | null;
@@ -38,8 +31,6 @@ export function isExportOnlyFile(
38
31
  if (!knownExports.has(match[2])) return false;
39
32
  }
40
33
 
41
- // Check `export { X }` and `export { X as Y }` specifiers: the local name
42
- // must reference a known create* binding.
43
34
  const specExportPattern = /export\s*\{([^}]+)\}/g;
44
35
  while ((match = specExportPattern.exec(code)) !== null) {
45
36
  const specifiers = match[1]
@@ -59,19 +50,45 @@ export function isExportOnlyFile(
59
50
  return true;
60
51
  }
61
52
 
62
- // NOTE: This regex may over-count when the fn name appears inside strings or
63
- // comments, but it's only used for the warning heuristic (totalCalls >
64
- // supportedBindings) and the inline-extraction pre-check, so over-counting
65
- // triggers a harmless extra AST parse rather than affecting correctness.
53
+ function createCallPattern(fnNames: string[]): RegExp {
54
+ return new RegExp(
55
+ `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
56
+ "g",
57
+ );
58
+ }
59
+
66
60
  export function countCreateCallsForNames(
67
61
  code: string,
68
62
  fnNames: string[],
69
63
  ): number {
70
- const pattern = new RegExp(
71
- `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
72
- "g",
73
- );
74
- return (code.match(pattern) || []).length;
64
+ return codeMatchIndices(code, createCallPattern(fnNames)).length;
65
+ }
66
+
67
+ export function offsetToLineColumn(
68
+ code: string,
69
+ index: number,
70
+ ): { line: number; column: number } {
71
+ let line = 1;
72
+ let lineStart = 0;
73
+ const end = Math.min(index, code.length);
74
+ for (let i = 0; i < end; i++) {
75
+ if (code[i] === "\n") {
76
+ line++;
77
+ lineStart = i + 1;
78
+ }
79
+ }
80
+ return { line, column: index - lineStart + 1 };
81
+ }
82
+
83
+ export function findUnsupportedCreateCallSites(
84
+ code: string,
85
+ fnNames: string[],
86
+ supportedBindings: CreateExportBinding[],
87
+ ): Array<{ line: number; column: number }> {
88
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
89
+ return codeMatchIndices(code, createCallPattern(fnNames))
90
+ .filter((index) => !supported.has(index))
91
+ .map((index) => offsetToLineColumn(code, index));
75
92
  }
76
93
 
77
94
  export function getImportedFnNames(
@@ -119,6 +136,18 @@ export function getCalledIdentifierFromCall(callExpr: any): string | null {
119
136
  return null;
120
137
  }
121
138
 
139
+ function unwrapSignatureWrappedCall(init: any, fnNameSet: Set<string>): any {
140
+ if (init?.type !== "CallExpression") return init;
141
+ const directId = getCalledIdentifierFromCall(init);
142
+ if (directId && fnNameSet.has(directId)) return init;
143
+ const firstArg = init.arguments?.[0];
144
+ if (firstArg?.type === "CallExpression") {
145
+ const innerId = getCalledIdentifierFromCall(firstArg);
146
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
147
+ }
148
+ return init;
149
+ }
150
+
122
151
  export function collectCreateExportBindingsFallback(
123
152
  code: string,
124
153
  fnNames: string[],
@@ -196,7 +225,7 @@ export function collectCreateExportBindings(
196
225
  ): CreateExportBinding[] {
197
226
  if (!program) {
198
227
  try {
199
- program = parseAst(code, { jsx: true });
228
+ program = parseAst(code, { lang: "tsx" });
200
229
  } catch {
201
230
  return collectCreateExportBindingsFallback(code, fnNames);
202
231
  }
@@ -212,10 +241,13 @@ export function collectCreateExportBindings(
212
241
  }
213
242
 
214
243
  for (const decl of varDecl.declarations ?? []) {
215
- const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
244
+ // Unwrap a Fast Refresh signature wrapper (`_s(createLoader(...), ...)`)
245
+ // so injection targets the inner create* call. Falls back to decl.init.
246
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
247
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
216
248
  if (
217
249
  decl?.id?.type !== "Identifier" ||
218
- decl?.init?.type !== "CallExpression" ||
250
+ callExpr?.type !== "CallExpression" ||
219
251
  !calledIdentifier ||
220
252
  !fnNameSet.has(calledIdentifier)
221
253
  ) {
@@ -226,9 +258,8 @@ export function collectCreateExportBindings(
226
258
  const exportNames = exportMap.get(localName) ?? [];
227
259
  if (exportNames.length === 0) continue;
228
260
 
229
- const callStart = decl.init.start as number;
230
- const callEnd = decl.init.end as number;
231
- const calleeEnd = decl.init.callee.end as number;
261
+ const callEnd = callExpr.end as number;
262
+ const calleeEnd = callExpr.callee.end as number;
232
263
 
233
264
  let openParenPos = -1;
234
265
  for (let i = calleeEnd; i < callEnd; i++) {
@@ -245,10 +276,10 @@ export function collectCreateExportBindings(
245
276
  bindings.push({
246
277
  localName,
247
278
  exportNames,
248
- callExprStart: decl.init.start as number,
279
+ callExprStart: callExpr.start as number,
249
280
  callOpenParenPos: openParenPos,
250
281
  callCloseParenPos: closeParenPos,
251
- argCount: decl.init.arguments?.length ?? 0,
282
+ argCount: callExpr.arguments?.length ?? 0,
252
283
  statementEnd,
253
284
  });
254
285
  }
@@ -268,10 +299,6 @@ export function collectCreateExportBindings(
268
299
  }
269
300
  }
270
301
 
271
- // When the JS parser misidentifies TypeScript generics (e.g.,
272
- // createLocationState<{ text: string }>({...})) as binary expressions,
273
- // the AST path finds 0 bindings even though calls exist. Fall back to
274
- // regex-based detection which handles generics via <[^>]*> matching.
275
302
  if (bindings.length === 0) {
276
303
  return collectCreateExportBindingsFallback(code, fnNames);
277
304
  }
@@ -282,9 +309,23 @@ export function collectCreateExportBindings(
282
309
  export function buildUnsupportedShapeWarning(
283
310
  filePath: string,
284
311
  fnName: string,
312
+ sites: Array<{ line: number; column: number }> = [],
285
313
  ): string {
286
- return [
287
- `[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
314
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
315
+
316
+ if (sites.length === 1) {
317
+ const s = sites[0];
318
+ lines.push(
319
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected — it is not in a supported shape.`,
320
+ );
321
+ } else if (sites.length > 1) {
322
+ lines.push(
323
+ `These ${fnName}(...) calls have no stable $$id injected — they are not in a supported shape:`,
324
+ );
325
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
326
+ }
327
+
328
+ lines.push(
288
329
  `Supported shapes are:`,
289
330
  ` - export const X = ${fnName}(...)`,
290
331
  ` - const X = ${fnName}(...); export { X }`,
@@ -292,5 +333,6 @@ export function buildUnsupportedShapeWarning(
292
333
  `Potentially unsupported forms include:`,
293
334
  ` - export let/var X = ${fnName}(...)`,
294
335
  ` - inline ${fnName}(...) calls`,
295
- ].join("\n");
336
+ );
337
+ return lines.join("\n");
296
338
  }
@@ -1,5 +1,5 @@
1
1
  import MagicString from "magic-string";
2
- import { hashId } from "../expose-id-utils.js";
2
+ import { makeStubId } from "../expose-id-utils.js";
3
3
  import type { HandlerTransformConfig, CreateExportBinding } from "./types.js";
4
4
  import { isExportOnlyFile } from "./export-analysis.js";
5
5
 
@@ -28,9 +28,7 @@ export function transformHandles(
28
28
  binding.callCloseParenPos,
29
29
  );
30
30
 
31
- const handleId = isBuild
32
- ? hashId(filePath, exportName)
33
- : `${filePath}#${exportName}`;
31
+ const handleId = makeStubId(filePath, exportName, isBuild);
34
32
 
35
33
  let paramInjection: string;
36
34
  if (!args.hasArgs) {
@@ -58,9 +56,7 @@ export function transformLocationState(
58
56
  for (const binding of bindings) {
59
57
  const exportName = binding.exportNames[0];
60
58
 
61
- const stateKey = isBuild
62
- ? hashId(filePath, exportName)
63
- : `${filePath}#${exportName}`;
59
+ const stateKey = makeStubId(filePath, exportName, isBuild);
64
60
 
65
61
  // Key is injected as a property assignment (not as a function argument).
66
62
  // This allows createLocationState to accept options like { flash: true }
@@ -73,10 +69,6 @@ export function transformLocationState(
73
69
  return hasChanges;
74
70
  }
75
71
 
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
72
  export function generateWholeFileStubs(
81
73
  cfg: HandlerTransformConfig,
82
74
  bindings: CreateExportBinding[],
@@ -88,62 +80,13 @@ export function generateWholeFileStubs(
88
80
 
89
81
  const exportNames = bindings.flatMap((b) => b.exportNames);
90
82
  const stubs = exportNames.map((name) => {
91
- const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
83
+ const handlerId = makeStubId(filePath, name, isBuild);
92
84
  return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
93
85
  });
94
86
 
95
87
  return { code: stubs.join("\n") + "\n", map: null };
96
88
  }
97
89
 
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
90
  export function stubHandlerExprs(
148
91
  cfg: HandlerTransformConfig,
149
92
  bindings: CreateExportBinding[],
@@ -154,9 +97,7 @@ export function stubHandlerExprs(
154
97
  let hasChanges = false;
155
98
  for (const binding of bindings) {
156
99
  const exportName = binding.exportNames[0];
157
- const handlerId = isBuild
158
- ? hashId(filePath, exportName)
159
- : `${filePath}#${exportName}`;
100
+ const handlerId = makeStubId(filePath, exportName, isBuild);
160
101
 
161
102
  s.overwrite(
162
103
  binding.callExprStart,
@@ -168,9 +109,6 @@ export function stubHandlerExprs(
168
109
  return hasChanges;
169
110
  }
170
111
 
171
- /**
172
- * Inject $$id into export const handler calls in RSC environments.
173
- */
174
112
  export function transformHandlerIds(
175
113
  cfg: HandlerTransformConfig,
176
114
  bindings: CreateExportBinding[],
@@ -182,14 +120,8 @@ export function transformHandlerIds(
182
120
  for (const binding of bindings) {
183
121
  const exportName = binding.exportNames[0];
184
122
 
185
- const handlerId = isBuild
186
- ? hashId(filePath, exportName)
187
- : `${filePath}#${exportName}`;
123
+ const handlerId = makeStubId(filePath, exportName, isBuild);
188
124
 
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
125
  let paramInjection: string;
194
126
  if (binding.argCount === 0) {
195
127
  paramInjection = `undefined, "${handlerId}"`;
@@ -1,5 +1,5 @@
1
1
  import type MagicString from "magic-string";
2
- import { hashId } from "../expose-id-utils.js";
2
+ import { makeStubId } from "../expose-id-utils.js";
3
3
  import type { CreateExportBinding } from "./types.js";
4
4
  import { isExportOnlyFile } from "./export-analysis.js";
5
5
 
@@ -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,
@@ -33,7 +21,7 @@ export function generateClientLoaderStubs(
33
21
 
34
22
  for (const binding of bindings) {
35
23
  for (const name of binding.exportNames) {
36
- const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
24
+ const loaderId = makeStubId(filePath, name, isBuild);
37
25
  lines.push(
38
26
  `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`,
39
27
  );
@@ -54,13 +42,8 @@ export function transformLoaders(
54
42
  for (const binding of bindings) {
55
43
  const exportName = binding.exportNames[0];
56
44
 
57
- const loaderId = isBuild
58
- ? hashId(filePath, exportName)
59
- : `${filePath}#${exportName}`;
45
+ const loaderId = makeStubId(filePath, exportName, isBuild);
60
46
 
61
- // Inject $$id as hidden third parameter.
62
- // createLoader(fn) -> createLoader(fn, undefined, "id")
63
- // createLoader(fn, true) -> createLoader(fn, true, "id")
64
47
  const paramInjection =
65
48
  binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
66
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,