@rangojs/router 0.0.0-experimental.0f44aca1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,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,510 @@
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 {
17
+ createScanFilter,
18
+ findRouterFiles,
19
+ } from "../build/generate-route-types.js";
20
+ import { createVersionPlugin } from "./plugins/version-plugin.js";
21
+ import {
22
+ sharedEsbuildOptions,
23
+ createVirtualEntriesPlugin,
24
+ onwarn,
25
+ getManualChunks,
26
+ } from "./utils/shared-utils.js";
27
+ import type {
28
+ RangoOptions,
29
+ RangoNodeOptions,
30
+ RscPluginOptions,
31
+ } from "./plugin-types.js";
32
+ import { printBanner, rangoVersion } from "./utils/banner.js";
33
+ import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
34
+ import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
35
+ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
36
+
37
+ /**
38
+ * Vite plugin for @rangojs/router.
39
+ *
40
+ * Includes @vitejs/plugin-rsc and all necessary transforms for the router
41
+ * to function correctly with React Server Components.
42
+ *
43
+ * @example Node.js (default)
44
+ * ```ts
45
+ * export default defineConfig({
46
+ * plugins: [react(), rango({ router: './src/router.tsx' })],
47
+ * });
48
+ * ```
49
+ *
50
+ * @example Cloudflare Workers
51
+ * ```ts
52
+ * export default defineConfig({
53
+ * plugins: [
54
+ * react(),
55
+ * rango({ preset: 'cloudflare' }),
56
+ * cloudflare({ viteEnvironment: { name: 'rsc' } }),
57
+ * ],
58
+ * });
59
+ * ```
60
+ */
61
+ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
62
+ const resolvedOptions: RangoOptions = options ?? { preset: "node" };
63
+ const preset = resolvedOptions.preset ?? "node";
64
+ const showBanner = resolvedOptions.banner ?? true;
65
+
66
+ const plugins: PluginOption[] = [];
67
+
68
+ // Get package resolution info (workspace vs npm install)
69
+ const rangoAliases = getPackageAliases();
70
+ const excludeDeps = getExcludeDeps();
71
+
72
+ // Track RSC entry path for version injection
73
+ let rscEntryPath: string | null = null;
74
+
75
+ // Mutable ref for router path (node preset only).
76
+ // Set immediately when user-specified, or populated by the auto-discover
77
+ // config() hook using Vite's resolved root.
78
+ const routerRef: { path: string | undefined } = { path: undefined };
79
+
80
+ // Build-time prerendering is enabled for both presets.
81
+ // Collection runs in-process via the RSC dev environment runner during discoverRouters().
82
+ const prerenderEnabled = true;
83
+
84
+ if (preset === "cloudflare") {
85
+ // Cloudflare preset: configure entries for cloudflare worker setup
86
+ // Router is not needed here - worker.rsc.tsx imports it directly
87
+
88
+ // Dynamically import @vitejs/plugin-rsc
89
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
90
+
91
+ // Only client and ssr entries - rsc entry is handled by cloudflare plugin
92
+ // Always use virtual modules for cloudflare preset
93
+ const finalEntries: { client: string; ssr: string } = {
94
+ client: VIRTUAL_IDS.browser,
95
+ ssr: VIRTUAL_IDS.ssr,
96
+ };
97
+
98
+ plugins.push({
99
+ name: "@rangojs/router:cloudflare-integration",
100
+ enforce: "pre",
101
+
102
+ config() {
103
+ // Configure environments for cloudflare deployment
104
+ return {
105
+ // Exclude rsc-router modules from optimization to prevent module duplication
106
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
107
+ optimizeDeps: {
108
+ exclude: excludeDeps,
109
+ esbuildOptions: sharedEsbuildOptions,
110
+ },
111
+ resolve: {
112
+ alias: rangoAliases,
113
+ },
114
+ build: {
115
+ rollupOptions: { onwarn },
116
+ },
117
+ environments: {
118
+ client: {
119
+ build: {
120
+ rollupOptions: {
121
+ output: {
122
+ manualChunks: getManualChunks,
123
+ },
124
+ },
125
+ },
126
+ // Pre-bundle rsc-html-stream to prevent discovery during first request
127
+ // Exclude rsc-router modules to ensure same Context instance
128
+ optimizeDeps: {
129
+ include: ["rsc-html-stream/client"],
130
+ exclude: excludeDeps,
131
+ esbuildOptions: sharedEsbuildOptions,
132
+ },
133
+ },
134
+ ssr: {
135
+ // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
136
+ build: {
137
+ outDir: "./dist/rsc/ssr",
138
+ },
139
+ resolve: {
140
+ // Ensure single React instance in SSR child environment
141
+ dedupe: ["react", "react-dom"],
142
+ },
143
+ // Pre-bundle SSR entry and React for proper module linking with childEnvironments
144
+ // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
145
+ optimizeDeps: {
146
+ entries: [finalEntries.ssr],
147
+ include: [
148
+ "react",
149
+ "react-dom",
150
+ "react-dom/server.edge",
151
+ "react-dom/static.edge",
152
+ "react/jsx-runtime",
153
+ "react/jsx-dev-runtime",
154
+ "rsc-html-stream/server",
155
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
156
+ ],
157
+ exclude: excludeDeps,
158
+ esbuildOptions: sharedEsbuildOptions,
159
+ },
160
+ },
161
+ rsc: {
162
+ // RSC environment needs exclude list and esbuild options
163
+ // Exclude rsc-router modules to prevent createContext in RSC environment
164
+ optimizeDeps: {
165
+ // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
166
+ include: [
167
+ "react",
168
+ "react/jsx-runtime",
169
+ "react/jsx-dev-runtime",
170
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
171
+ ],
172
+ exclude: excludeDeps,
173
+ esbuildOptions: sharedEsbuildOptions,
174
+ },
175
+ },
176
+ },
177
+ };
178
+ },
179
+
180
+ configResolved(config) {
181
+ if (showBanner) {
182
+ const mode =
183
+ config.command === "serve"
184
+ ? process.argv.includes("preview")
185
+ ? "preview"
186
+ : "dev"
187
+ : "build";
188
+ printBanner(mode, "cloudflare", rangoVersion);
189
+ }
190
+ },
191
+ });
192
+
193
+ plugins.push(createVirtualEntriesPlugin(finalEntries));
194
+
195
+ // Add RSC plugin with cloudflare-specific options
196
+ // Note: loadModuleDevProxy should NOT be used with childEnvironments
197
+ // since SSR runs in workerd alongside RSC
198
+ plugins.push(
199
+ rsc({
200
+ entries: finalEntries,
201
+ serverHandler: false,
202
+ }) as PluginOption,
203
+ );
204
+
205
+ // Deduplicate client references from third-party packages in dev mode.
206
+ // Prevents module duplication when server components import "use client"
207
+ // packages that are also imported directly by client components.
208
+ plugins.push(clientRefDedup());
209
+ } else {
210
+ // Node preset: full RSC plugin integration
211
+ const nodeOptions = resolvedOptions as RangoNodeOptions;
212
+
213
+ routerRef.path = nodeOptions.router;
214
+
215
+ // Auto-discover router using Vite's resolved root (not process.cwd())
216
+ if (!routerRef.path) {
217
+ plugins.push({
218
+ name: "@rangojs/router:auto-discover",
219
+ config(userConfig) {
220
+ if (routerRef.path) return;
221
+ const root = userConfig.root
222
+ ? resolve(process.cwd(), userConfig.root)
223
+ : process.cwd();
224
+ const filter = createScanFilter(root, {
225
+ include: resolvedOptions.include,
226
+ exclude: resolvedOptions.exclude,
227
+ });
228
+ const candidates = findRouterFiles(root, filter);
229
+ if (candidates.length === 1) {
230
+ const abs = candidates[0];
231
+ routerRef.path = (
232
+ abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
233
+ ).replaceAll("\\", "/");
234
+ } else if (candidates.length > 1) {
235
+ const list = candidates
236
+ .map(
237
+ (f) =>
238
+ " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
239
+ )
240
+ .join("\n");
241
+ throw new Error(
242
+ `[rsc-router] Multiple routers found. Specify \`router\` to choose one:\n${list}`,
243
+ );
244
+ }
245
+ // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
246
+ },
247
+ });
248
+ }
249
+
250
+ const rscOption = nodeOptions.rsc ?? true;
251
+
252
+ // Add RSC plugin by default (can be disabled with rsc: false)
253
+ if (rscOption !== false) {
254
+ // Dynamically import @vitejs/plugin-rsc
255
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
256
+
257
+ // Resolve entry paths: use explicit config or virtual modules
258
+ const userEntries =
259
+ typeof rscOption === "boolean" ? {} : rscOption.entries || {};
260
+ const finalEntries = {
261
+ client: userEntries.client ?? VIRTUAL_IDS.browser,
262
+ ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
263
+ rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc,
264
+ };
265
+
266
+ // Track RSC entry for version injection (only if custom entry provided)
267
+ rscEntryPath = userEntries.rsc ?? null;
268
+
269
+ // Create wrapper plugin that checks for duplicates
270
+ let hasWarnedDuplicate = false;
271
+
272
+ plugins.push({
273
+ name: "@rangojs/router:rsc-integration",
274
+ enforce: "pre",
275
+
276
+ config() {
277
+ // Configure environments for RSC
278
+ // When using virtual entries, we need to explicitly configure optimizeDeps
279
+ // so Vite pre-bundles React before processing the virtual modules.
280
+ // Without this, the dep optimizer may run multiple times with different hashes,
281
+ // causing React instance mismatches.
282
+ const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
283
+ const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
284
+ const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
285
+
286
+ return {
287
+ // Exclude rsc-router modules from optimization to prevent module duplication
288
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
289
+ optimizeDeps: {
290
+ exclude: excludeDeps,
291
+ esbuildOptions: sharedEsbuildOptions,
292
+ },
293
+ build: {
294
+ rollupOptions: { onwarn },
295
+ },
296
+ resolve: {
297
+ alias: rangoAliases,
298
+ },
299
+ environments: {
300
+ client: {
301
+ build: {
302
+ rollupOptions: {
303
+ output: {
304
+ manualChunks: getManualChunks,
305
+ },
306
+ },
307
+ },
308
+ // Always exclude rsc-router modules, conditionally add virtual entry
309
+ optimizeDeps: {
310
+ // Pre-bundle React and rsc-html-stream to prevent late discovery
311
+ // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
312
+ include: [
313
+ "react",
314
+ "react-dom",
315
+ "react/jsx-runtime",
316
+ "react/jsx-dev-runtime",
317
+ "rsc-html-stream/client",
318
+ ],
319
+ exclude: excludeDeps,
320
+ esbuildOptions: sharedEsbuildOptions,
321
+ ...(useVirtualClient && {
322
+ // Tell Vite to scan the virtual entry for dependencies
323
+ entries: [VIRTUAL_IDS.browser],
324
+ }),
325
+ },
326
+ },
327
+ ...(useVirtualSSR && {
328
+ ssr: {
329
+ optimizeDeps: {
330
+ entries: [VIRTUAL_IDS.ssr],
331
+ // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
332
+ include: [
333
+ "react",
334
+ "react-dom",
335
+ "react-dom/server.edge",
336
+ "react-dom/static.edge",
337
+ "react/jsx-runtime",
338
+ "react/jsx-dev-runtime",
339
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
340
+ ],
341
+ exclude: excludeDeps,
342
+ esbuildOptions: sharedEsbuildOptions,
343
+ },
344
+ },
345
+ }),
346
+ ...(useVirtualRSC && {
347
+ rsc: {
348
+ optimizeDeps: {
349
+ entries: [VIRTUAL_IDS.rsc],
350
+ // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
351
+ include: [
352
+ "react",
353
+ "react/jsx-runtime",
354
+ "react/jsx-dev-runtime",
355
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
356
+ ],
357
+ esbuildOptions: sharedEsbuildOptions,
358
+ },
359
+ },
360
+ }),
361
+ },
362
+ };
363
+ },
364
+
365
+ configResolved(config) {
366
+ if (showBanner) {
367
+ const mode =
368
+ config.command === "serve"
369
+ ? process.argv.includes("preview")
370
+ ? "preview"
371
+ : "dev"
372
+ : "build";
373
+ printBanner(mode, "node", rangoVersion);
374
+ }
375
+
376
+ // Count how many RSC base plugins there are (rsc:minimal is the main one)
377
+ const rscMinimalCount = config.plugins.filter(
378
+ (p) => p.name === "rsc:minimal",
379
+ ).length;
380
+
381
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
382
+ hasWarnedDuplicate = true;
383
+ console.warn(
384
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
385
+ "Remove rsc() from your config or use rango({ rsc: false }) for manual configuration.",
386
+ );
387
+ }
388
+ },
389
+ });
390
+
391
+ // Add virtual entries plugin (RSC entry generated lazily from routerRef)
392
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
393
+
394
+ // Add the RSC plugin directly
395
+ // Cast to PluginOption to handle type differences between bundled vite types
396
+ plugins.push(
397
+ rsc({
398
+ entries: finalEntries,
399
+ }) as PluginOption,
400
+ );
401
+ }
402
+
403
+ // Deduplicate client references from third-party packages in dev mode.
404
+ // Prevents module duplication when server components import "use client"
405
+ // packages that are also imported directly by client components.
406
+ plugins.push(clientRefDedup());
407
+ }
408
+
409
+ // Fix HMR for "use client" components.
410
+ //
411
+ // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
412
+ // in the RSC environment. Vite then tries to propagate through the RSC
413
+ // module graph, but the proxy module has no import.meta.hot.accept()
414
+ // boundary, causing a full page reload. The client env would handle it
415
+ // fine via React Refresh, but the RSC env's full-reload arrives first.
416
+ //
417
+ // Fix: in the RSC env, return [] for "use client" files to signal
418
+ // "handled, nothing to propagate". The client env is left alone so
419
+ // React Refresh processes the update normally.
420
+ plugins.push({
421
+ name: "@rangojs/router:client-component-hmr",
422
+ hotUpdate(ctx) {
423
+ const envName = this.environment?.name;
424
+ if (envName !== "rsc" && envName !== "ssr") return;
425
+
426
+ // Check if the changed file is a "use client" module
427
+ const file = ctx.file;
428
+ if (
429
+ !file.endsWith(".tsx") &&
430
+ !file.endsWith(".ts") &&
431
+ !file.endsWith(".jsx") &&
432
+ !file.endsWith(".js")
433
+ )
434
+ return;
435
+
436
+ try {
437
+ const source = readFileSync(file, "utf-8");
438
+ const trimmed = source.trimStart();
439
+ if (
440
+ trimmed.startsWith('"use client"') ||
441
+ trimmed.startsWith("'use client'")
442
+ ) {
443
+ // Consume the update in RSC/SSR envs. The proxy module was already
444
+ // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
445
+ // tries to propagate through the RSC/SSR module graph where the proxy
446
+ // has no import.meta.hot.accept() boundary, triggering a full reload.
447
+ // The actual component update is handled by React Refresh in the
448
+ // client environment.
449
+ return [];
450
+ }
451
+ } catch {
452
+ // File deleted/moved during HMR, let default handling proceed
453
+ }
454
+ },
455
+ });
456
+
457
+ plugins.push(exposeActionId());
458
+
459
+ // "use cache" directive transform (enforce: "post"):
460
+ // Wraps exports with registerCachedFunction() for function-level caching.
461
+ plugins.push(useCacheTransform());
462
+
463
+ // Consolidated plugin for create* ID injection (enforce: "post"):
464
+ // loaders, handles, location state, and prerender handlers.
465
+ plugins.push(exposeInternalIds());
466
+
467
+ // Router ID injection runs at normal priority (no enforce) to avoid
468
+ // changing Vite's dep optimization timing.
469
+ plugins.push(exposeRouterId());
470
+
471
+ // Add version virtual module plugin for cache invalidation
472
+ plugins.push(createVersionPlugin());
473
+
474
+ // Entry path for discovery: user-specified value (if any) or undefined.
475
+ // Auto-discovered path is passed separately via routerRef.
476
+ // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
477
+ const discoveryEntryPath =
478
+ preset !== "cloudflare" ? routerRef.path : undefined;
479
+ // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
480
+ const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
481
+
482
+ // Version injector: auto-injects VERSION and routes-manifest into custom entry.rsc files.
483
+ // Only applies when there's an explicit rscEntryPath or for cloudflare preset (resolved
484
+ // lazily in configResolved). For node preset without a custom entry, the router file
485
+ // must NOT be transformed — injecting routes-manifest there creates a circular dependency.
486
+ const injectorEntryPath =
487
+ rscEntryPath ?? (preset === "cloudflare" ? undefined : null);
488
+ if (injectorEntryPath !== null) {
489
+ plugins.push(createVersionInjectorPlugin(injectorEntryPath));
490
+ }
491
+
492
+ // Transform CJS vendor files to ESM for browser compatibility
493
+ // optimizeDeps.include doesn't work because the file is loaded after initial optimization
494
+ plugins.push(createCjsToEsmPlugin());
495
+
496
+ // Router discovery plugin for build-time manifest generation.
497
+ // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
498
+ // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
499
+ plugins.push(
500
+ createRouterDiscoveryPlugin(discoveryEntryPath, {
501
+ routerPathRef: discoveryRouterRef,
502
+ enableBuildPrerender: prerenderEnabled,
503
+ staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
504
+ include: resolvedOptions.include,
505
+ exclude: resolvedOptions.exclude,
506
+ }),
507
+ );
508
+
509
+ return plugins;
510
+ }