@rangojs/router 0.0.0-experimental.002d056c

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 +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -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 +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -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 +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -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 +547 -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 +479 -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 +982 -0
  105. package/src/cache/cf/index.ts +29 -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 +44 -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 +281 -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 +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -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 +193 -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 +749 -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 +320 -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 +1242 -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 +291 -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 +1006 -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 +237 -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 +920 -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 +109 -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 +108 -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 +48 -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 +363 -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 +266 -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 +445 -0
  298. package/src/vite/router-discovery.ts +777 -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,569 @@
1
+ import type { Plugin, ResolvedConfig } from "vite";
2
+ import { parseAst } from "vite";
3
+ import MagicString from "magic-string";
4
+ import path from "node:path";
5
+ import { normalizePath, hashId, detectImports } from "./expose-id-utils.js";
6
+ import {
7
+ transformInlineHandlers,
8
+ type VirtualHandlerEntry,
9
+ } from "../utils/ast-handler-extract.js";
10
+
11
+ import type {
12
+ HandlerTransformConfig,
13
+ CreateExportBinding,
14
+ } from "./expose-ids/types.js";
15
+ import {
16
+ PRERENDER_CONFIG,
17
+ STATIC_CONFIG,
18
+ STRICT_CREATE_CONFIGS,
19
+ type ExposeInternalIdsApi,
20
+ } from "./expose-ids/types.js";
21
+ import {
22
+ countCreateCallsForNames,
23
+ getImportedFnNames,
24
+ collectCreateExportBindings,
25
+ buildUnsupportedShapeWarning,
26
+ } from "./expose-ids/export-analysis.js";
27
+ import {
28
+ hasCreateLoaderImport,
29
+ generateClientLoaderStubs,
30
+ transformLoaders,
31
+ } from "./expose-ids/loader-transform.js";
32
+ import {
33
+ transformHandles,
34
+ transformLocationState,
35
+ generateWholeFileStubs,
36
+ generateExprStubs,
37
+ transformHandlerIds,
38
+ } from "./expose-ids/handler-transform.js";
39
+
40
+ // Re-exports consumed by other packages/plugins
41
+ export { exposeRouterId } from "./expose-ids/router-transform.js";
42
+ export type { ExposeInternalIdsApi } from "./expose-ids/types.js";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Virtual module for loader manifest
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
49
+ const RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Virtual module prefix for extracted inline handlers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const VIRTUAL_HANDLER_PREFIX = "virtual:handler-extract:";
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Consolidated plugin
59
+ // ---------------------------------------------------------------------------
60
+
61
+ export function exposeInternalIds(options?: { forceBuild?: boolean }): Plugin {
62
+ let config: ResolvedConfig;
63
+ let isBuild = false;
64
+ let projectRoot = "";
65
+
66
+ // Loader registry: hashedId -> { filePath, exportName }
67
+ const loaderRegistry = new Map<
68
+ string,
69
+ { filePath: string; exportName: string }
70
+ >();
71
+
72
+ // Prerender handler module tracking (consumed via plugin API)
73
+ const prerenderHandlerModules: Map<string, string[]> = new Map();
74
+
75
+ // Static handler module tracking (consumed via plugin API)
76
+ const staticHandlerModules: Map<string, string[]> = new Map();
77
+
78
+ // Virtual module registry for inline handler extraction (both types)
79
+ const virtualHandlers = new Map<string, VirtualHandlerEntry>();
80
+ // De-duplicate unsupported shape warnings across repeated transforms.
81
+ const unsupportedShapeWarnings = new Set<string>();
82
+
83
+ return {
84
+ name: "@rangojs/router:expose-internal-ids",
85
+ enforce: "post",
86
+
87
+ api: {
88
+ prerenderHandlerModules,
89
+ staticHandlerModules,
90
+ } satisfies ExposeInternalIdsApi,
91
+
92
+ configResolved(resolved) {
93
+ config = resolved;
94
+ isBuild = options?.forceBuild || config.command === "build";
95
+ projectRoot = config.root;
96
+ },
97
+
98
+ // --------------- Virtual module support ---------------
99
+
100
+ resolveId(id, importer) {
101
+ if (id === VIRTUAL_LOADER_MANIFEST) {
102
+ return RESOLVED_VIRTUAL_LOADER_MANIFEST;
103
+ }
104
+ if (id.startsWith(VIRTUAL_HANDLER_PREFIX)) {
105
+ return "\0" + id;
106
+ }
107
+ // Resolve imports FROM virtual modules against the original file
108
+ if (importer?.startsWith("\0" + VIRTUAL_HANDLER_PREFIX)) {
109
+ const entry = virtualHandlers.get(importer);
110
+ if (entry) {
111
+ return this.resolve(id, entry.originalModuleId, { skipSelf: true });
112
+ }
113
+ }
114
+ },
115
+
116
+ load(id) {
117
+ // Virtual handler modules (both prerender and static)
118
+ if (id.startsWith("\0" + VIRTUAL_HANDLER_PREFIX)) {
119
+ const entry = virtualHandlers.get(id);
120
+ if (!entry) return null;
121
+ return (
122
+ [
123
+ ...entry.imports,
124
+ ...entry.declarations,
125
+ `export const ${entry.exportName} = ${entry.handlerCode};`,
126
+ ].join("\n") + "\n"
127
+ );
128
+ }
129
+
130
+ if (id !== RESOLVED_VIRTUAL_LOADER_MANIFEST) return;
131
+
132
+ if (!isBuild) {
133
+ return `import { setLoaderImports } from "@rangojs/router/server";
134
+
135
+ // Dev mode: empty map, loaders are resolved dynamically via path parsing
136
+ setLoaderImports({});
137
+ `;
138
+ }
139
+
140
+ // Build mode: generate lazy import map
141
+ const lazyImports: string[] = [];
142
+
143
+ for (const [hashedId, { filePath, exportName }] of loaderRegistry) {
144
+ lazyImports.push(
145
+ ` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`,
146
+ );
147
+ }
148
+
149
+ if (lazyImports.length === 0) {
150
+ return `import { setLoaderImports } from "@rangojs/router/server";
151
+
152
+ // No fetchable loaders discovered during build
153
+ setLoaderImports({});
154
+ `;
155
+ }
156
+
157
+ return `import { setLoaderImports } from "@rangojs/router/server";
158
+
159
+ // Lazy import map - loaders are loaded on-demand when first requested
160
+ setLoaderImports({
161
+ ${lazyImports.join(",\n")}
162
+ });
163
+ `;
164
+ },
165
+
166
+ // --------------- Loader pre-scan (build mode) ---------------
167
+
168
+ async buildStart() {
169
+ if (!isBuild) return;
170
+
171
+ const fs = await import("node:fs/promises");
172
+
173
+ const SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
174
+
175
+ async function scanDir(dir: string): Promise<string[]> {
176
+ const results: string[] = [];
177
+ try {
178
+ const entries = await fs.readdir(dir, { withFileTypes: true });
179
+ for (const entry of entries) {
180
+ const fullPath = path.join(dir, entry.name);
181
+ if (entry.isDirectory()) {
182
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
183
+ results.push(...(await scanDir(fullPath)));
184
+ }
185
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
186
+ results.push(fullPath);
187
+ }
188
+ }
189
+ } catch {
190
+ // Directory doesn't exist or not readable
191
+ }
192
+ return results;
193
+ }
194
+
195
+ try {
196
+ const files = await scanDir(projectRoot);
197
+
198
+ for (const filePath of files) {
199
+ const content = await fs.readFile(filePath, "utf-8");
200
+
201
+ if (!content.includes("createLoader")) continue;
202
+ if (!hasCreateLoaderImport(content)) continue;
203
+
204
+ const fnNames = getImportedFnNames(content, "createLoader");
205
+ const relativePath = normalizePath(
206
+ path.relative(projectRoot, filePath),
207
+ );
208
+ const bindings = collectCreateExportBindings(content, fnNames);
209
+
210
+ for (const binding of bindings) {
211
+ const exportName = binding.exportNames[0];
212
+ const hashedId = hashId(relativePath, exportName);
213
+ loaderRegistry.set(hashedId, {
214
+ filePath: relativePath,
215
+ exportName,
216
+ });
217
+ }
218
+ }
219
+ } catch (error) {
220
+ console.warn("[exposeInternalIds] Loader pre-scan failed:", error);
221
+ }
222
+ },
223
+
224
+ // --------------- Unified transform ---------------
225
+
226
+ transform(code, id) {
227
+ if (id.includes("/node_modules/")) return;
228
+
229
+ const filePath = normalizePath(path.relative(projectRoot, id));
230
+ const isRscEnv = this.environment?.name === "rsc";
231
+
232
+ // Warn if named-routes.gen is imported in a client component.
233
+ // NamedRoutes is server-only data and would bloat the client bundle.
234
+ if (
235
+ id.includes(".named-routes.gen.") &&
236
+ !isRscEnv &&
237
+ this.environment?.name === "client"
238
+ ) {
239
+ this.warn(
240
+ `\n` +
241
+ `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n` +
242
+ `!! !!\n` +
243
+ `!! WARNING: NamedRoutes imported in a CLIENT component! !!\n` +
244
+ `!! !!\n` +
245
+ `!! File: ${filePath.padEnd(53)}!!\n` +
246
+ `!! !!\n` +
247
+ `!! NamedRoutes contains your entire route structure — every !!\n` +
248
+ `!! route name and URL pattern in your application. Shipping !!\n` +
249
+ `!! this to the browser exposes your full routing topology to !!\n` +
250
+ `!! the client, which is a security concern (internal/admin !!\n` +
251
+ `!! routes, API endpoints, hidden paths become visible). !!\n` +
252
+ `!! !!\n` +
253
+ `!! It also bloats the client bundle — this map contains all !!\n` +
254
+ `!! named routes in your application. !!\n` +
255
+ `!! !!\n` +
256
+ `!! Fix: remove the import or move it to a server component. !!\n` +
257
+ `!! !!\n` +
258
+ `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n`,
259
+ );
260
+ }
261
+
262
+ // Fast exit: if the file doesn't import from @rangojs/router at all,
263
+ // skip all create* analysis and transforms.
264
+ if (!code.includes("@rangojs/router")) return;
265
+
266
+ // Detect all relevant imports in one pass
267
+ const has = detectImports(code);
268
+
269
+ // Quick bail-out: also check for raw create* identifiers.
270
+ // This is safe even with aliases (e.g., `import { createLoader as cl }`)
271
+ // because the import statement itself always contains the canonical name
272
+ // "createLoader", so code.includes("createLoader") will still match.
273
+ const hasLoaderCode = has.loader && code.includes("createLoader");
274
+ const hasHandleCode = has.handle && code.includes("createHandle");
275
+ const hasLocationStateCode =
276
+ has.locationState && code.includes("createLocationState");
277
+ const hasPrerenderHandlerCode =
278
+ has.prerenderHandler && code.includes("Prerender");
279
+ const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
280
+ if (
281
+ !hasLoaderCode &&
282
+ !hasHandleCode &&
283
+ !hasLocationStateCode &&
284
+ !hasPrerenderHandlerCode &&
285
+ !hasStaticHandlerCode
286
+ ) {
287
+ return;
288
+ }
289
+
290
+ // Per-invocation caches to avoid redundant AST parsing.
291
+ // getImportedFnNames is cached by canonical name (imports never change).
292
+ // collectCreateExportBindings is cached by fnNames key; the cache is
293
+ // cleared when `code` changes (e.g., after inline handler extraction).
294
+ const _fnNamesCache = new Map<string, string[]>();
295
+ const _bindingsCache = new Map<string, CreateExportBinding[]>();
296
+ let _cachedAst: any;
297
+ let _astParseFailed = false;
298
+ let _astCodeRef = code;
299
+
300
+ const getFnNames = (canonicalName: string): string[] => {
301
+ let result = _fnNamesCache.get(canonicalName);
302
+ if (!result) {
303
+ result = getImportedFnNames(code, canonicalName);
304
+ _fnNamesCache.set(canonicalName, result);
305
+ }
306
+ return result;
307
+ };
308
+
309
+ // Lazy AST parse: parsed once and shared across all
310
+ // collectCreateExportBindings calls for the same code string.
311
+ const lazyAst = (): any | undefined => {
312
+ if (code !== _astCodeRef) {
313
+ _cachedAst = undefined;
314
+ _astParseFailed = false;
315
+ _astCodeRef = code;
316
+ }
317
+ if (_cachedAst !== undefined || _astParseFailed) return _cachedAst;
318
+ try {
319
+ _cachedAst = parseAst(code, { jsx: true });
320
+ } catch {
321
+ _astParseFailed = true;
322
+ }
323
+ return _cachedAst;
324
+ };
325
+
326
+ const getBindings = (
327
+ currentCode: string,
328
+ fnNames: string[],
329
+ ): CreateExportBinding[] => {
330
+ const key = fnNames.join("\0");
331
+ let result = _bindingsCache.get(key);
332
+ if (!result) {
333
+ result = collectCreateExportBindings(currentCode, fnNames, lazyAst());
334
+ _bindingsCache.set(key, result);
335
+ }
336
+ return result;
337
+ };
338
+
339
+ // Warn on create* declaration shapes that are currently unsupported by
340
+ // non-AST transforms (loader/handle/locationState only).
341
+ for (const cfg of STRICT_CREATE_CONFIGS) {
342
+ const hasCode =
343
+ cfg.fnName === "createLoader"
344
+ ? hasLoaderCode
345
+ : cfg.fnName === "createHandle"
346
+ ? hasHandleCode
347
+ : hasLocationStateCode;
348
+ if (!hasCode) continue;
349
+
350
+ const fnNames = getFnNames(cfg.fnName);
351
+ const totalCalls = countCreateCallsForNames(code, fnNames);
352
+ const supportedBindings = getBindings(code, fnNames).length;
353
+ if (totalCalls <= supportedBindings) continue;
354
+
355
+ const warnKey = `${id}::${cfg.fnName}`;
356
+ if (unsupportedShapeWarnings.has(warnKey)) continue;
357
+ unsupportedShapeWarnings.add(warnKey);
358
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
359
+ }
360
+
361
+ // --- Loader: track for manifest (RSC env only) ---
362
+ if (hasLoaderCode && isRscEnv) {
363
+ const fnNames = getFnNames("createLoader");
364
+ const bindings = getBindings(code, fnNames);
365
+ for (const binding of bindings) {
366
+ const exportName = binding.exportNames[0];
367
+ const hashedId = hashId(filePath, exportName);
368
+ loaderRegistry.set(hashedId, {
369
+ filePath,
370
+ exportName,
371
+ });
372
+ }
373
+ }
374
+
375
+ // --- Loader: client stubs for non-RSC environments ---
376
+ if (hasLoaderCode && !isRscEnv) {
377
+ const fnNames = getFnNames("createLoader");
378
+ const bindings = getBindings(code, fnNames);
379
+ const stubResult = generateClientLoaderStubs(
380
+ bindings,
381
+ code,
382
+ filePath,
383
+ isBuild,
384
+ );
385
+ if (stubResult) return stubResult;
386
+ }
387
+
388
+ // --- PrerenderHandler: non-RSC stub replacement ---
389
+ if (hasPrerenderHandlerCode && !isRscEnv) {
390
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
391
+ const bindings = getBindings(code, fnNames);
392
+ const wholeFile = generateWholeFileStubs(
393
+ PRERENDER_CONFIG,
394
+ bindings,
395
+ code,
396
+ filePath,
397
+ isBuild,
398
+ );
399
+ if (wholeFile) return wholeFile;
400
+
401
+ const exprStubs = generateExprStubs(
402
+ PRERENDER_CONFIG,
403
+ bindings,
404
+ code,
405
+ filePath,
406
+ id,
407
+ isBuild,
408
+ );
409
+ if (exprStubs) return exprStubs;
410
+ }
411
+
412
+ // --- PrerenderHandler: RSC build module tracking ---
413
+ if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
414
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
415
+ const exportNames = getBindings(code, fnNames).map(
416
+ (b) => b.exportNames[0],
417
+ );
418
+ if (exportNames.length > 0) {
419
+ prerenderHandlerModules.set(id, exportNames);
420
+ }
421
+ }
422
+
423
+ // --- Inline handler extraction to virtual modules ---
424
+ // Runs before stubs/tracking so inline calls become imports, then
425
+ // the existing regex fast path handles both the original file's
426
+ // export const patterns and the virtual modules independently.
427
+ //
428
+ // Cheap pre-check: count total fnName( occurrences vs export const
429
+ // patterns. If they match, every call is a named export and the
430
+ // regex fast path handles them -- skip the AST parse entirely.
431
+ //
432
+ // Each iteration creates a fresh MagicString so that AST positions
433
+ // from findHandlerCalls always match the string they were parsed from.
434
+ let changed = false;
435
+
436
+ const handlerConfigs = [
437
+ hasStaticHandlerCode && STATIC_CONFIG,
438
+ hasPrerenderHandlerCode && PRERENDER_CONFIG,
439
+ ]
440
+ .filter((c): c is HandlerTransformConfig => !!c)
441
+ .map((cfg) => {
442
+ const fnNames = getFnNames(cfg.fnName);
443
+ return { cfg, fnNames };
444
+ });
445
+
446
+ for (const { cfg, fnNames } of handlerConfigs) {
447
+ const totalCalls = countCreateCallsForNames(code, fnNames);
448
+ const supportedBindings = getBindings(code, fnNames).length;
449
+
450
+ if (totalCalls > supportedBindings) {
451
+ const iterS = new MagicString(code);
452
+ const result = transformInlineHandlers(
453
+ cfg.fnName,
454
+ VIRTUAL_HANDLER_PREFIX,
455
+ iterS,
456
+ code,
457
+ filePath,
458
+ virtualHandlers,
459
+ id,
460
+ parseAst,
461
+ );
462
+ if (result) {
463
+ changed = true;
464
+ code = iterS.toString();
465
+ _bindingsCache.clear();
466
+ }
467
+ }
468
+ }
469
+
470
+ // --- StaticHandler: non-RSC stub replacement ---
471
+ if (hasStaticHandlerCode && !isRscEnv) {
472
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
473
+ const bindings = getBindings(code, fnNames);
474
+ const wholeFile = generateWholeFileStubs(
475
+ STATIC_CONFIG,
476
+ bindings,
477
+ code,
478
+ filePath,
479
+ isBuild,
480
+ );
481
+ if (wholeFile) return wholeFile;
482
+
483
+ const exprStubs = generateExprStubs(
484
+ STATIC_CONFIG,
485
+ bindings,
486
+ code,
487
+ filePath,
488
+ id,
489
+ isBuild,
490
+ );
491
+ if (exprStubs) return exprStubs;
492
+ }
493
+
494
+ // --- StaticHandler: RSC build module tracking ---
495
+ if (hasStaticHandlerCode && isRscEnv && isBuild) {
496
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
497
+ const exportNames = getBindings(code, fnNames).map(
498
+ (b) => b.exportNames[0],
499
+ );
500
+ if (exportNames.length > 0) {
501
+ staticHandlerModules.set(id, exportNames);
502
+ }
503
+ }
504
+
505
+ // --- Unified MagicString transforms ---
506
+ // Single pipeline for all downstream transforms (loaders, handles,
507
+ // locationState, handler IDs). Uses the post-extraction code so
508
+ // positions are always consistent.
509
+ const s = new MagicString(code);
510
+
511
+ if (hasLoaderCode) {
512
+ const fnNames = getFnNames("createLoader");
513
+ changed =
514
+ transformLoaders(getBindings(code, fnNames), s, filePath, isBuild) ||
515
+ changed;
516
+ }
517
+ if (hasHandleCode) {
518
+ const fnNames = getFnNames("createHandle");
519
+ changed =
520
+ transformHandles(
521
+ getBindings(code, fnNames),
522
+ s,
523
+ code,
524
+ filePath,
525
+ isBuild,
526
+ ) || changed;
527
+ }
528
+ if (hasLocationStateCode) {
529
+ const fnNames = getFnNames("createLocationState");
530
+ changed =
531
+ transformLocationState(
532
+ getBindings(code, fnNames),
533
+ s,
534
+ filePath,
535
+ isBuild,
536
+ ) || changed;
537
+ }
538
+ if (hasPrerenderHandlerCode && isRscEnv) {
539
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
540
+ changed =
541
+ transformHandlerIds(
542
+ PRERENDER_CONFIG,
543
+ getBindings(code, fnNames),
544
+ s,
545
+ filePath,
546
+ isBuild,
547
+ ) || changed;
548
+ }
549
+ if (hasStaticHandlerCode && isRscEnv) {
550
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
551
+ changed =
552
+ transformHandlerIds(
553
+ STATIC_CONFIG,
554
+ getBindings(code, fnNames),
555
+ s,
556
+ filePath,
557
+ isBuild,
558
+ ) || changed;
559
+ }
560
+
561
+ if (!changed) return;
562
+
563
+ return {
564
+ code: s.toString(),
565
+ map: s.generateMap({ source: id, includeContent: true }),
566
+ };
567
+ },
568
+ };
569
+ }
@@ -0,0 +1,65 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ /**
4
+ * Vite plugin that triggers a full browser reload when Ctrl+R is pressed
5
+ * in the terminal running the dev server.
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * import { poke } from "@rangojs/router/vite";
10
+ *
11
+ * export default defineConfig({
12
+ * plugins: [rango(), poke()],
13
+ * });
14
+ * ```
15
+ */
16
+ export function poke(): Plugin {
17
+ return {
18
+ name: "vite-plugin-poke",
19
+ apply: "serve",
20
+
21
+ configureServer(server) {
22
+ const stdin = process.stdin;
23
+
24
+ // Raw mode delivers individual keystrokes as immediate single-byte
25
+ // events instead of waiting for Enter (cooked/line-buffered mode).
26
+ // Without it, Ctrl+R (0x12) is never delivered as a discrete byte.
27
+ // When stdin is a pipe (CI, spawned process) setRawMode is unavailable
28
+ // but data already arrives unbuffered, so the isTTY guard suffices.
29
+ const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
30
+ if (stdin.isTTY) {
31
+ stdin.setRawMode(true);
32
+ }
33
+
34
+ const onData = (data: Buffer) => {
35
+ if (data.length !== 1) return;
36
+
37
+ // Ctrl+C (0x03) — defensive fallback. This plugin enables raw mode
38
+ // before Vite's internal stdin handler is registered (user plugins
39
+ // run first), so there is a brief window where Ctrl+C would be
40
+ // swallowed. Re-emit SIGINT so the process exits as expected.
41
+ if (data[0] === 0x03) {
42
+ process.emit("SIGINT", "SIGINT");
43
+ return;
44
+ }
45
+
46
+ // Ctrl+R = 0x12 in raw mode
47
+ if (data[0] === 0x12) {
48
+ server.hot.send({ type: "full-reload", path: "*" });
49
+ server.config.logger.info(" browser reload (ctrl+r)", {
50
+ timestamp: true,
51
+ });
52
+ }
53
+ };
54
+
55
+ stdin.on("data", onData);
56
+
57
+ server.httpServer?.on("close", () => {
58
+ stdin.off("data", onData);
59
+ if (stdin.isTTY && previousRawMode !== null) {
60
+ stdin.setRawMode(previousRawMode);
61
+ }
62
+ });
63
+ },
64
+ };
65
+ }