@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,123 @@
1
+ /**
2
+ * Default virtual entry file contents for rsc-router.
3
+ * These are used when users don't provide their own entry files.
4
+ */
5
+
6
+ export const VIRTUAL_ENTRY_BROWSER: string = `
7
+ import {
8
+ createFromReadableStream,
9
+ createFromFetch,
10
+ setServerCallback,
11
+ encodeReply,
12
+ createTemporaryReferenceSet,
13
+ } from "@rangojs/router/internal/deps/browser";
14
+ import { createElement, StrictMode } from "react";
15
+ import { hydrateRoot } from "react-dom/client";
16
+ import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
17
+ import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
18
+
19
+ async function initializeApp() {
20
+ const deps = {
21
+ createFromFetch,
22
+ createFromReadableStream,
23
+ encodeReply,
24
+ setServerCallback,
25
+ createTemporaryReferenceSet,
26
+ };
27
+
28
+ await initBrowserApp({ rscStream, deps });
29
+
30
+ hydrateRoot(
31
+ document,
32
+ createElement(StrictMode, null, createElement(RSCRouter))
33
+ );
34
+ }
35
+
36
+ initializeApp().catch(console.error);
37
+ `.trim();
38
+
39
+ export const VIRTUAL_ENTRY_SSR: string = `
40
+ import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
41
+ import { renderToReadableStream } from "react-dom/server.edge";
42
+ import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
43
+ import { createSSRHandler } from "@rangojs/router/ssr";
44
+
45
+ export const renderHTML = createSSRHandler({
46
+ createFromReadableStream,
47
+ renderToReadableStream,
48
+ injectRSCPayload,
49
+ loadBootstrapScriptContent: () =>
50
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
51
+ });
52
+ `.trim();
53
+
54
+ /**
55
+ * Generate the RSC entry content with the specified router path
56
+ */
57
+ export function getVirtualEntryRSC(routerPath: string): string {
58
+ return `
59
+ import {
60
+ renderToReadableStream,
61
+ decodeReply,
62
+ createTemporaryReferenceSet,
63
+ loadServerAction,
64
+ decodeAction,
65
+ decodeFormState,
66
+ } from "@rangojs/router/internal/deps/rsc";
67
+ import { router } from "${routerPath}";
68
+ import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
69
+ import { VERSION } from "@rangojs/router:version";
70
+
71
+ // Import loader manifest to ensure all fetchable loaders are registered at startup
72
+ // This is critical for serverless/multi-process deployments where the loader module
73
+ // might not be imported before a GET request arrives
74
+ import "virtual:rsc-router/loader-manifest";
75
+
76
+ // Import pre-generated route manifest so href() works immediately on cold start.
77
+ // In build mode, this contains the full route map generated at build time.
78
+ // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
79
+ import "virtual:rsc-router/routes-manifest";
80
+
81
+ // Lazily create the handler on first request so that ESM live bindings
82
+ // have resolved by the time we read \`router\`. During HMR the module may
83
+ // re-evaluate before router.tsx finishes, leaving the import undefined.
84
+ let _handler;
85
+ export default function handler(request, env) {
86
+ if (!_handler) {
87
+ _handler = createRSCHandler({
88
+ router,
89
+ version: VERSION,
90
+ deps: {
91
+ renderToReadableStream,
92
+ decodeReply,
93
+ createTemporaryReferenceSet,
94
+ loadServerAction,
95
+ decodeAction,
96
+ decodeFormState,
97
+ },
98
+ loadSSRModule: () =>
99
+ import.meta.viteRsc.loadModule("ssr", "index"),
100
+ });
101
+ }
102
+ return _handler(request, env);
103
+ }
104
+ `.trim();
105
+ }
106
+
107
+ /**
108
+ * Virtual module IDs
109
+ */
110
+ export const VIRTUAL_IDS = {
111
+ browser: "virtual:rsc-router/entry.browser.js",
112
+ ssr: "virtual:rsc-router/entry.ssr.js",
113
+ rsc: "virtual:rsc-router/entry.rsc.js",
114
+ version: "@rangojs/router:version",
115
+ } as const;
116
+
117
+ /**
118
+ * Virtual module content for version.
119
+ * Exports VERSION - a timestamp that changes on server restart (dev) or at build time (production).
120
+ */
121
+ export function getVirtualVersionContent(version: string): string {
122
+ return `export const VERSION = ${JSON.stringify(version)};`;
123
+ }
@@ -0,0 +1,29 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ /**
4
+ * Stub plugin that resolves and provides empty exports for virtual modules
5
+ * that the RSC entry may import but aren't needed for route discovery.
6
+ * @internal
7
+ */
8
+ export function createVirtualStubPlugin(): Plugin {
9
+ const STUB_PREFIXES = [
10
+ "virtual:rsc-router/",
11
+ "virtual:entry-",
12
+ "virtual:vite-rsc/",
13
+ ];
14
+ return {
15
+ name: "@rangojs/router:virtual-stubs",
16
+ resolveId(id) {
17
+ if (STUB_PREFIXES.some((p) => id.startsWith(p))) {
18
+ return "\0stub:" + id;
19
+ }
20
+ return null;
21
+ },
22
+ load(id) {
23
+ if (id.startsWith("\0stub:")) {
24
+ return "export default {}";
25
+ }
26
+ return null;
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,445 @@
1
+ import type { PluginOption } from "vite";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { exposeActionId } from "./plugins/expose-action-id.js";
5
+ import {
6
+ exposeInternalIds,
7
+ exposeRouterId,
8
+ } from "./plugins/expose-internal-ids.js";
9
+ import { useCacheTransform } from "./plugins/use-cache-transform.js";
10
+ import { clientRefDedup } from "./plugins/client-ref-dedup.js";
11
+ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
+ import {
13
+ getExcludeDeps,
14
+ getPackageAliases,
15
+ } from "./utils/package-resolution.js";
16
+ import { findRouterFiles } from "../build/generate-route-types.js";
17
+ import { createVersionPlugin } from "./plugins/version-plugin.js";
18
+ import {
19
+ sharedEsbuildOptions,
20
+ createVirtualEntriesPlugin,
21
+ onwarn,
22
+ getManualChunks,
23
+ } from "./utils/shared-utils.js";
24
+ import type { RangoOptions } from "./plugin-types.js";
25
+ import { printBanner, rangoVersion } from "./utils/banner.js";
26
+ import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
27
+ import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
28
+ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
+
30
+ /**
31
+ * Vite plugin for @rangojs/router.
32
+ *
33
+ * Includes @vitejs/plugin-rsc and all necessary transforms for the router
34
+ * to function correctly with React Server Components.
35
+ *
36
+ * @example Node.js (default)
37
+ * ```ts
38
+ * export default defineConfig({
39
+ * plugins: [react(), rango()],
40
+ * });
41
+ * ```
42
+ *
43
+ * @example Cloudflare Workers
44
+ * ```ts
45
+ * export default defineConfig({
46
+ * plugins: [
47
+ * react(),
48
+ * rango({ preset: 'cloudflare' }),
49
+ * cloudflare({ viteEnvironment: { name: 'rsc' } }),
50
+ * ],
51
+ * });
52
+ * ```
53
+ */
54
+ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
55
+ const resolvedOptions: RangoOptions = options ?? { preset: "node" };
56
+ const preset = resolvedOptions.preset ?? "node";
57
+ const showBanner = resolvedOptions.banner ?? true;
58
+
59
+ const plugins: PluginOption[] = [];
60
+
61
+ // Get package resolution info (workspace vs npm install)
62
+ const rangoAliases = getPackageAliases();
63
+ const excludeDeps = getExcludeDeps();
64
+
65
+ // Mutable ref for router path (node preset only).
66
+ // Set immediately when user-specified, or populated by the auto-discover
67
+ // config() hook using Vite's resolved root.
68
+ const routerRef: { path: string | undefined } = { path: undefined };
69
+
70
+ // Build-time prerendering is enabled for both presets.
71
+ // Collection runs in-process via the RSC dev environment runner during discoverRouters().
72
+ const prerenderEnabled = true;
73
+
74
+ if (preset === "cloudflare") {
75
+ // Cloudflare preset: configure entries for cloudflare worker setup
76
+ // Router is not needed here - worker.rsc.tsx imports it directly
77
+
78
+ // Dynamically import @vitejs/plugin-rsc
79
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
80
+
81
+ // Only client and ssr entries - rsc entry is handled by cloudflare plugin
82
+ // Always use virtual modules for cloudflare preset
83
+ const finalEntries: { client: string; ssr: string } = {
84
+ client: VIRTUAL_IDS.browser,
85
+ ssr: VIRTUAL_IDS.ssr,
86
+ };
87
+
88
+ plugins.push({
89
+ name: "@rangojs/router:cloudflare-integration",
90
+ enforce: "pre",
91
+
92
+ config() {
93
+ // Configure environments for cloudflare deployment
94
+ return {
95
+ // Exclude rsc-router modules from optimization to prevent module duplication
96
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
97
+ optimizeDeps: {
98
+ exclude: excludeDeps,
99
+ esbuildOptions: sharedEsbuildOptions,
100
+ },
101
+ resolve: {
102
+ alias: rangoAliases,
103
+ },
104
+ build: {
105
+ rollupOptions: { onwarn },
106
+ },
107
+ environments: {
108
+ client: {
109
+ build: {
110
+ rollupOptions: {
111
+ output: {
112
+ manualChunks: getManualChunks,
113
+ },
114
+ },
115
+ },
116
+ // Pre-bundle rsc-html-stream to prevent discovery during first request
117
+ // Exclude rsc-router modules to ensure same Context instance
118
+ optimizeDeps: {
119
+ include: ["rsc-html-stream/client"],
120
+ exclude: excludeDeps,
121
+ esbuildOptions: sharedEsbuildOptions,
122
+ },
123
+ },
124
+ ssr: {
125
+ // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
126
+ build: {
127
+ outDir: "./dist/rsc/ssr",
128
+ },
129
+ resolve: {
130
+ // Ensure single React instance in SSR child environment
131
+ dedupe: ["react", "react-dom"],
132
+ },
133
+ // Pre-bundle SSR entry and React for proper module linking with childEnvironments
134
+ // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
135
+ optimizeDeps: {
136
+ entries: [finalEntries.ssr],
137
+ include: [
138
+ "react",
139
+ "react-dom",
140
+ "react-dom/server.edge",
141
+ "react-dom/static.edge",
142
+ "react/jsx-runtime",
143
+ "react/jsx-dev-runtime",
144
+ "rsc-html-stream/server",
145
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
146
+ ],
147
+ exclude: excludeDeps,
148
+ esbuildOptions: sharedEsbuildOptions,
149
+ },
150
+ },
151
+ rsc: {
152
+ // RSC environment needs exclude list and esbuild options
153
+ // Exclude rsc-router modules to prevent createContext in RSC environment
154
+ optimizeDeps: {
155
+ // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
156
+ include: [
157
+ "react",
158
+ "react/jsx-runtime",
159
+ "react/jsx-dev-runtime",
160
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
161
+ ],
162
+ exclude: excludeDeps,
163
+ esbuildOptions: sharedEsbuildOptions,
164
+ },
165
+ },
166
+ },
167
+ };
168
+ },
169
+
170
+ configResolved(config) {
171
+ if (showBanner) {
172
+ const mode =
173
+ config.command === "serve"
174
+ ? process.argv.includes("preview")
175
+ ? "preview"
176
+ : "dev"
177
+ : "build";
178
+ printBanner(mode, "cloudflare", rangoVersion);
179
+ }
180
+ },
181
+ });
182
+
183
+ plugins.push(createVirtualEntriesPlugin(finalEntries));
184
+
185
+ // Add RSC plugin with cloudflare-specific options
186
+ // Note: loadModuleDevProxy should NOT be used with childEnvironments
187
+ // since SSR runs in workerd alongside RSC
188
+ plugins.push(
189
+ rsc({
190
+ entries: finalEntries,
191
+ serverHandler: false,
192
+ }) as PluginOption,
193
+ );
194
+
195
+ // Deduplicate client references from third-party packages in dev mode.
196
+ // Prevents module duplication when server components import "use client"
197
+ // packages that are also imported directly by client components.
198
+ plugins.push(clientRefDedup());
199
+ } else {
200
+ // Auto-discover router using Vite's resolved root (not process.cwd())
201
+ plugins.push({
202
+ name: "@rangojs/router:auto-discover",
203
+ config(userConfig) {
204
+ if (routerRef.path) return;
205
+ const root = userConfig.root
206
+ ? resolve(process.cwd(), userConfig.root)
207
+ : process.cwd();
208
+ const candidates = findRouterFiles(root);
209
+ if (candidates.length === 1) {
210
+ const abs = candidates[0];
211
+ routerRef.path = (
212
+ abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
213
+ ).replaceAll("\\", "/");
214
+ } else if (candidates.length > 1) {
215
+ const list = candidates
216
+ .map(
217
+ (f) =>
218
+ " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
219
+ )
220
+ .join("\n");
221
+ throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
222
+ }
223
+ // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
224
+ },
225
+ });
226
+
227
+ // Always use virtual entries for client, ssr, and rsc
228
+ const finalEntries = {
229
+ client: VIRTUAL_IDS.browser,
230
+ ssr: VIRTUAL_IDS.ssr,
231
+ rsc: VIRTUAL_IDS.rsc,
232
+ };
233
+
234
+ // Dynamically import @vitejs/plugin-rsc
235
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
236
+
237
+ let hasWarnedDuplicate = false;
238
+
239
+ plugins.push({
240
+ name: "@rangojs/router:rsc-integration",
241
+ enforce: "pre",
242
+
243
+ config() {
244
+ return {
245
+ optimizeDeps: {
246
+ exclude: excludeDeps,
247
+ esbuildOptions: sharedEsbuildOptions,
248
+ },
249
+ build: {
250
+ rollupOptions: { onwarn },
251
+ },
252
+ resolve: {
253
+ alias: rangoAliases,
254
+ },
255
+ environments: {
256
+ client: {
257
+ build: {
258
+ rollupOptions: {
259
+ output: {
260
+ manualChunks: getManualChunks,
261
+ },
262
+ },
263
+ },
264
+ optimizeDeps: {
265
+ include: [
266
+ "react",
267
+ "react-dom",
268
+ "react/jsx-runtime",
269
+ "react/jsx-dev-runtime",
270
+ "rsc-html-stream/client",
271
+ ],
272
+ exclude: excludeDeps,
273
+ esbuildOptions: sharedEsbuildOptions,
274
+ entries: [VIRTUAL_IDS.browser],
275
+ },
276
+ },
277
+ ssr: {
278
+ optimizeDeps: {
279
+ entries: [VIRTUAL_IDS.ssr],
280
+ include: [
281
+ "react",
282
+ "react-dom",
283
+ "react-dom/server.edge",
284
+ "react-dom/static.edge",
285
+ "react/jsx-runtime",
286
+ "react/jsx-dev-runtime",
287
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
288
+ ],
289
+ exclude: excludeDeps,
290
+ esbuildOptions: sharedEsbuildOptions,
291
+ },
292
+ },
293
+ rsc: {
294
+ optimizeDeps: {
295
+ entries: [VIRTUAL_IDS.rsc],
296
+ include: [
297
+ "react",
298
+ "react/jsx-runtime",
299
+ "react/jsx-dev-runtime",
300
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
301
+ ],
302
+ esbuildOptions: sharedEsbuildOptions,
303
+ },
304
+ },
305
+ },
306
+ };
307
+ },
308
+
309
+ configResolved(config) {
310
+ if (showBanner) {
311
+ const mode =
312
+ config.command === "serve"
313
+ ? process.argv.includes("preview")
314
+ ? "preview"
315
+ : "dev"
316
+ : "build";
317
+ printBanner(mode, "node", rangoVersion);
318
+ }
319
+
320
+ const rscMinimalCount = config.plugins.filter(
321
+ (p) => p.name === "rsc:minimal",
322
+ ).length;
323
+
324
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
325
+ hasWarnedDuplicate = true;
326
+ console.warn(
327
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
328
+ "Remove rsc() from your vite config — rango() includes it automatically.",
329
+ );
330
+ }
331
+ },
332
+ });
333
+
334
+ // Add virtual entries plugin (RSC entry generated lazily from routerRef)
335
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
336
+
337
+ plugins.push(
338
+ rsc({
339
+ entries: finalEntries,
340
+ }) as PluginOption,
341
+ );
342
+
343
+ // Deduplicate client references from third-party packages in dev mode.
344
+ // Prevents module duplication when server components import "use client"
345
+ // packages that are also imported directly by client components.
346
+ plugins.push(clientRefDedup());
347
+ }
348
+
349
+ // Fix HMR for "use client" components.
350
+ //
351
+ // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
352
+ // in the RSC environment. Vite then tries to propagate through the RSC
353
+ // module graph, but the proxy module has no import.meta.hot.accept()
354
+ // boundary, causing a full page reload. The client env would handle it
355
+ // fine via React Refresh, but the RSC env's full-reload arrives first.
356
+ //
357
+ // Fix: in the RSC env, return [] for "use client" files to signal
358
+ // "handled, nothing to propagate". The client env is left alone so
359
+ // React Refresh processes the update normally.
360
+ plugins.push({
361
+ name: "@rangojs/router:client-component-hmr",
362
+ hotUpdate(ctx) {
363
+ const envName = this.environment?.name;
364
+ if (envName !== "rsc" && envName !== "ssr") return;
365
+
366
+ // Check if the changed file is a "use client" module
367
+ const file = ctx.file;
368
+ if (
369
+ !file.endsWith(".tsx") &&
370
+ !file.endsWith(".ts") &&
371
+ !file.endsWith(".jsx") &&
372
+ !file.endsWith(".js")
373
+ )
374
+ return;
375
+
376
+ try {
377
+ const source = readFileSync(file, "utf-8");
378
+ const trimmed = source.trimStart();
379
+ if (
380
+ trimmed.startsWith('"use client"') ||
381
+ trimmed.startsWith("'use client'")
382
+ ) {
383
+ // Consume the update in RSC/SSR envs. The proxy module was already
384
+ // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
385
+ // tries to propagate through the RSC/SSR module graph where the proxy
386
+ // has no import.meta.hot.accept() boundary, triggering a full reload.
387
+ // The actual component update is handled by React Refresh in the
388
+ // client environment.
389
+ return [];
390
+ }
391
+ } catch {
392
+ // File deleted/moved during HMR, let default handling proceed
393
+ }
394
+ },
395
+ });
396
+
397
+ plugins.push(exposeActionId());
398
+
399
+ // "use cache" directive transform (enforce: "post"):
400
+ // Wraps exports with registerCachedFunction() for function-level caching.
401
+ plugins.push(useCacheTransform());
402
+
403
+ // Consolidated plugin for create* ID injection (enforce: "post"):
404
+ // loaders, handles, location state, and prerender handlers.
405
+ plugins.push(exposeInternalIds());
406
+
407
+ // Router ID injection runs at normal priority (no enforce) to avoid
408
+ // changing Vite's dep optimization timing.
409
+ plugins.push(exposeRouterId());
410
+
411
+ // Add version virtual module plugin for cache invalidation
412
+ plugins.push(createVersionPlugin());
413
+
414
+ // Entry path for discovery: user-specified value (if any) or undefined.
415
+ // Auto-discovered path is passed separately via routerRef.
416
+ // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
417
+ const discoveryEntryPath =
418
+ preset !== "cloudflare" ? routerRef.path : undefined;
419
+ // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
420
+ const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
421
+
422
+ // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
423
+ // For cloudflare preset, the entry is resolved lazily in configResolved.
424
+ // For node preset, the virtual entry already includes these imports.
425
+ if (preset === "cloudflare") {
426
+ plugins.push(createVersionInjectorPlugin(undefined));
427
+ }
428
+
429
+ // Transform CJS vendor files to ESM for browser compatibility
430
+ // optimizeDeps.include doesn't work because the file is loaded after initial optimization
431
+ plugins.push(createCjsToEsmPlugin());
432
+
433
+ // Router discovery plugin for build-time manifest generation.
434
+ // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
435
+ // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
436
+ plugins.push(
437
+ createRouterDiscoveryPlugin(discoveryEntryPath, {
438
+ routerPathRef: discoveryRouterRef,
439
+ enableBuildPrerender: prerenderEnabled,
440
+ staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
441
+ }),
442
+ );
443
+
444
+ return plugins;
445
+ }