@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -22,6 +22,61 @@ export interface UnresolvableInclude {
22
22
  detail: string;
23
23
  }
24
24
 
25
+ // ---------------------------------------------------------------------------
26
+ // Per-scan memo
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Per-scan memo shared across the include-resolution recursion. Keys are
31
+ * absolute file paths; values cache the readFileSync result and the
32
+ * ts.SourceFile parsed from the FULL file source. A separate blockSources map
33
+ * caches parses of extracted sub-blocks (urls() call text), keyed by the exact
34
+ * block string, so the two extractors (routes + includes) for the same block
35
+ * share a single parse.
36
+ *
37
+ * The memo is purely a performance accelerator: same input -> same parse, so
38
+ * generated output is identical to the un-memoized path. A fresh memo is
39
+ * created per top-level scan entry, so stale file edits between scans are never
40
+ * served.
41
+ */
42
+ export interface ScanMemo {
43
+ files: Map<string, string>;
44
+ blockSourceFiles: Map<string, ts.SourceFile>;
45
+ }
46
+
47
+ export function createScanMemo(): ScanMemo {
48
+ return { files: new Map(), blockSourceFiles: new Map() };
49
+ }
50
+
51
+ function parseBlock(memo: ScanMemo | undefined, block: string): ts.SourceFile {
52
+ if (memo) {
53
+ const cached = memo.blockSourceFiles.get(block);
54
+ if (cached) return cached;
55
+ }
56
+ const sf = ts.createSourceFile(
57
+ "input.tsx",
58
+ block,
59
+ ts.ScriptTarget.Latest,
60
+ true,
61
+ ts.ScriptKind.TSX,
62
+ );
63
+ if (memo) memo.blockSourceFiles.set(block, sf);
64
+ return sf;
65
+ }
66
+
67
+ function readSourceMemoized(
68
+ memo: ScanMemo | undefined,
69
+ realPath: string,
70
+ ): string {
71
+ if (memo) {
72
+ const cached = memo.files.get(realPath);
73
+ if (cached !== undefined) return cached;
74
+ }
75
+ const source = readFileSync(realPath, "utf-8");
76
+ if (memo) memo.files.set(realPath, source);
77
+ return source;
78
+ }
79
+
25
80
  // ---------------------------------------------------------------------------
26
81
  // AST-based include() parsing
27
82
  // ---------------------------------------------------------------------------
@@ -47,7 +102,10 @@ function extractNamePrefixFromInclude(node: ts.CallExpression): string | null {
47
102
  * Returns both resolved includes (identifier second args) and unresolvable
48
103
  * includes (factory calls, etc.) with reasons.
49
104
  */
50
- export function extractIncludesWithDiagnostics(code: string): {
105
+ export function extractIncludesWithDiagnostics(
106
+ code: string,
107
+ sourceFileArg?: ts.SourceFile,
108
+ ): {
51
109
  resolved: Array<{
52
110
  pathPrefix: string;
53
111
  variableName: string;
@@ -60,13 +118,16 @@ export function extractIncludesWithDiagnostics(code: string): {
60
118
  detail: string;
61
119
  }>;
62
120
  } {
63
- const sourceFile = ts.createSourceFile(
64
- "input.tsx",
65
- code,
66
- ts.ScriptTarget.Latest,
67
- true,
68
- ts.ScriptKind.TSX,
69
- );
121
+ // Reuse a caller-provided SourceFile (parsed once per scan) when given.
122
+ const sourceFile =
123
+ sourceFileArg ??
124
+ ts.createSourceFile(
125
+ "input.tsx",
126
+ code,
127
+ ts.ScriptTarget.Latest,
128
+ true,
129
+ ts.ScriptKind.TSX,
130
+ );
70
131
  const resolved: Array<{
71
132
  pathPrefix: string;
72
133
  variableName: string;
@@ -140,7 +201,13 @@ export function resolveImportedVariable(
140
201
  code: string,
141
202
  localName: string,
142
203
  ): { specifier: string; exportedName: string } | null {
143
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
204
+ // Allow an optional leading default binding before the named-import brace so
205
+ // a combined `import Foo, { bar } from "..."` is matched (the named members
206
+ // are the only part we resolve; the default binding is skipped). Without the
207
+ // optional `(?:[\w$]+\s*,\s*)?` segment, the `Foo, ` prefix breaks the match
208
+ // and a legitimate static named import surfaces as `unresolvable-import`.
209
+ const importRegex =
210
+ /import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
144
211
  let match;
145
212
 
146
213
  while ((match = importRegex.exec(code)) !== null) {
@@ -206,14 +273,18 @@ export function resolveImportPath(
206
273
  function extractUrlsBlockForVariable(
207
274
  code: string,
208
275
  varName: string,
276
+ sourceFileArg?: ts.SourceFile,
209
277
  ): string | null {
210
- const sourceFile = ts.createSourceFile(
211
- "input.tsx",
212
- code,
213
- ts.ScriptTarget.Latest,
214
- true,
215
- ts.ScriptKind.TSX,
216
- );
278
+ // Reuse a caller-provided full-source SourceFile (parsed once per scan).
279
+ const sourceFile =
280
+ sourceFileArg ??
281
+ ts.createSourceFile(
282
+ "input.tsx",
283
+ code,
284
+ ts.ScriptTarget.Latest,
285
+ true,
286
+ ts.ScriptKind.TSX,
287
+ );
217
288
  let result: string | null = null;
218
289
 
219
290
  function visit(node: ts.Node) {
@@ -249,11 +320,15 @@ function buildRouteMapFromBlock(
249
320
  visited: Set<string>,
250
321
  searchSchemasOut?: Record<string, Record<string, string>>,
251
322
  diagnosticsOut?: UnresolvableInclude[],
323
+ memo?: ScanMemo,
252
324
  ): Record<string, string> {
253
325
  const routeMap: Record<string, string> = {};
254
326
 
327
+ // Parse the block once and share the SourceFile between both extractors.
328
+ const blockSourceFile = parseBlock(memo, block);
329
+
255
330
  // Extract local path() routes
256
- const localRoutes = extractRoutesFromSource(block);
331
+ const localRoutes = extractRoutesFromSource(block, blockSourceFile);
257
332
  for (const { name, pattern, search } of localRoutes) {
258
333
  routeMap[name] = pattern;
259
334
  if (search && searchSchemasOut) {
@@ -262,8 +337,10 @@ function buildRouteMapFromBlock(
262
337
  }
263
338
 
264
339
  // Extract include() calls with diagnostics for unresolvable ones
265
- const { resolved: includes, unresolvable } =
266
- extractIncludesWithDiagnostics(block);
340
+ const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(
341
+ block,
342
+ blockSourceFile,
343
+ );
267
344
 
268
345
  if (diagnosticsOut) {
269
346
  for (const entry of unresolvable) {
@@ -298,12 +375,17 @@ function buildRouteMapFromBlock(
298
375
  imported.exportedName,
299
376
  visited,
300
377
  diagnosticsOut,
378
+ undefined,
379
+ memo,
301
380
  );
302
381
  } else {
303
- // Check if variable exists as a same-file urls() definition
382
+ // Check if variable exists as a same-file urls() definition. Share the
383
+ // full-source SourceFile parse via the memo (block === fullSource hits the
384
+ // same cache entry already used above).
304
385
  const sameFileBlock = extractUrlsBlockForVariable(
305
386
  fullSource,
306
387
  variableName,
388
+ parseBlock(memo, fullSource),
307
389
  );
308
390
  if (!sameFileBlock) {
309
391
  if (diagnosticsOut) {
@@ -322,6 +404,8 @@ function buildRouteMapFromBlock(
322
404
  variableName,
323
405
  visited,
324
406
  diagnosticsOut,
407
+ undefined,
408
+ memo,
325
409
  );
326
410
  }
327
411
 
@@ -357,35 +441,51 @@ function buildRouteMapFromBlock(
357
441
  /**
358
442
  * Build route map and search schemas together.
359
443
  * Internal helper used by the include resolution path.
444
+ *
445
+ * @param inlineBlock - Optional pre-extracted code block (e.g. from an inline
446
+ * builder function). When provided, variableName is ignored and the block
447
+ * is parsed directly for path()/include() calls.
448
+ * @param memo - Per-scan readFileSync/parse memo. A fresh one is created at the
449
+ * top-level entry and threaded through the recursion so each file is read and
450
+ * parsed once per scan. Behavior-preserving (same input -> same output).
360
451
  */
361
452
  export function buildCombinedRouteMapWithSearch(
362
453
  filePath: string,
363
454
  variableName?: string,
364
455
  visited?: Set<string>,
365
456
  diagnosticsOut?: UnresolvableInclude[],
457
+ inlineBlock?: string,
458
+ memo?: ScanMemo,
366
459
  ): {
367
460
  routes: Record<string, string>;
368
461
  searchSchemas: Record<string, Record<string, string>>;
369
462
  } {
370
463
  visited = visited ?? new Set();
464
+ memo = memo ?? createScanMemo();
371
465
  const realPath = resolve(filePath);
372
466
  const key = variableName ? `${realPath}:${variableName}` : realPath;
373
467
  if (visited.has(key)) {
374
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
468
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
375
469
  return { routes: {}, searchSchemas: {} };
376
470
  }
377
471
  visited.add(key);
378
472
 
379
473
  let source: string;
380
474
  try {
381
- source = readFileSync(realPath, "utf-8");
475
+ source = readSourceMemoized(memo, realPath);
382
476
  } catch {
383
477
  return { routes: {}, searchSchemas: {} };
384
478
  }
385
479
 
386
480
  let block: string;
387
- if (variableName) {
388
- const extracted = extractUrlsBlockForVariable(source, variableName);
481
+ if (inlineBlock) {
482
+ block = inlineBlock;
483
+ } else if (variableName) {
484
+ const extracted = extractUrlsBlockForVariable(
485
+ source,
486
+ variableName,
487
+ parseBlock(memo, source),
488
+ );
389
489
  if (!extracted) return { routes: {}, searchSchemas: {} };
390
490
  block = extracted;
391
491
  } else {
@@ -400,6 +500,7 @@ export function buildCombinedRouteMapWithSearch(
400
500
  visited,
401
501
  searchSchemas,
402
502
  diagnosticsOut,
503
+ memo,
403
504
  );
404
505
 
405
506
  // Remove from visited so sibling branches can include the same variable
@@ -37,12 +37,15 @@ export function formatRouteEntry(
37
37
  ): string {
38
38
  const hasSearch = search && Object.keys(search).length > 0;
39
39
 
40
+ // JSON.stringify the pattern and search values so backslashes and quotes in a
41
+ // route pattern (e.g. a custom regex constraint) survive interpolation into
42
+ // both the type-level string and the runtime NamedRoutes value.
40
43
  if (!hasSearch) {
41
- return ` ${key}: "${pattern}",`;
44
+ return ` ${key}: ${JSON.stringify(pattern)},`;
42
45
  }
43
46
 
44
47
  const searchBody = Object.entries(search!)
45
- .map(([k, v]) => `${k}: "${v}"`)
48
+ .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
46
49
  .join(", ");
47
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
50
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
48
51
  }
@@ -3,7 +3,10 @@ import ts from "typescript";
3
3
  import { extractParamsFromPattern } from "./param-extraction.js";
4
4
  import { extractRoutesFromSource } from "./ast-route-extraction.js";
5
5
  import { generatePerModuleTypesSource } from "./codegen.js";
6
- import { buildCombinedRouteMapWithSearch } from "./include-resolution.js";
6
+ import {
7
+ buildCombinedRouteMapWithSearch,
8
+ createScanMemo,
9
+ } from "./include-resolution.js";
7
10
  import type { ScanFilter } from "./scan-filter.js";
8
11
  import { findTsFiles } from "./scan-filter.js";
9
12
 
@@ -78,10 +81,20 @@ export function writePerModuleRouteTypesForFile(filePath: string): void {
78
81
  if (varNames.length > 0) {
79
82
  // Follow includes recursively via the combined route map builder.
80
83
  // The visited set in buildCombinedRouteMapWithSearch prevents infinite loops.
84
+ // Share one per-file scan memo across all urls() variables so a shared
85
+ // include target is read+parsed once for this file, not once per variable.
81
86
  routes = [];
87
+ const memo = createScanMemo();
82
88
  for (const varName of varNames) {
83
89
  const { routes: routeMap, searchSchemas } =
84
- buildCombinedRouteMapWithSearch(filePath, varName);
90
+ buildCombinedRouteMapWithSearch(
91
+ filePath,
92
+ varName,
93
+ undefined,
94
+ undefined,
95
+ undefined,
96
+ memo,
97
+ );
85
98
  for (const [name, pattern] of Object.entries(routeMap)) {
86
99
  const params = extractParamsFromPattern(pattern);
87
100
  routes.push({
@@ -97,7 +110,10 @@ export function writePerModuleRouteTypesForFile(filePath: string): void {
97
110
  routes = extractRoutesFromSource(source);
98
111
  }
99
112
 
100
- const genPath = filePath.replace(/\.(tsx?)$/, ".gen.ts");
113
+ // Match .ts/.tsx/.js/.jsx (same as router-processing.ts / router-transform.ts).
114
+ // Without the jsx? branch a .jsx/.js source produced genPath === filePath,
115
+ // overwriting the source file instead of writing a sibling .gen.ts.
116
+ const genPath = filePath.replace(/\.(tsx?|jsx?)$/, ".gen.ts");
101
117
 
102
118
  // When a urls() variable was found but static resolution yields zero
103
119
  // routes, write an empty placeholder so generated imports stay
@@ -106,7 +122,7 @@ export function writePerModuleRouteTypesForFile(filePath: string): void {
106
122
  if (varNames.length > 0 && !existsSync(genPath)) {
107
123
  writeFileSync(genPath, generatePerModuleTypesSource([]));
108
124
  console.log(
109
- `[rsc-router] Generated route types (placeholder) -> ${genPath}`,
125
+ `[rango] Generated route types (placeholder) -> ${genPath}`,
110
126
  );
111
127
  }
112
128
  return;
@@ -118,11 +134,11 @@ export function writePerModuleRouteTypesForFile(filePath: string): void {
118
134
  : null;
119
135
  if (existing !== genSource) {
120
136
  writeFileSync(genPath, genSource);
121
- console.log(`[rsc-router] Generated route types -> ${genPath}`);
137
+ console.log(`[rango] Generated route types -> ${genPath}`);
122
138
  }
123
139
  } catch (err) {
124
140
  console.warn(
125
- `[rsc-router] Failed to generate route types for ${filePath}: ${(err as Error).message}`,
141
+ `[rango] Failed to generate route types for ${filePath}: ${(err as Error).message}`,
126
142
  );
127
143
  }
128
144
  }