@rangojs/router 0.0.0-experimental.7 → 0.0.0-experimental.70

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 (307) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4951 -930
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +92 -31
  18. package/skills/loader/SKILL.md +404 -44
  19. package/skills/middleware/SKILL.md +173 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +204 -1
  22. package/skills/prerender/SKILL.md +685 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +257 -14
  26. package/skills/router-setup/SKILL.md +210 -32
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +328 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  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/app-version.ts +14 -0
  36. package/src/browser/event-controller.ts +92 -64
  37. package/src/browser/history-state.ts +80 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +24 -4
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +20 -12
  42. package/src/browser/navigation-bridge.ts +296 -558
  43. package/src/browser/navigation-client.ts +179 -69
  44. package/src/browser/navigation-store.ts +73 -55
  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 +328 -313
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +150 -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 +160 -0
  53. package/src/browser/prefetch/resource-ready.ts +77 -0
  54. package/src/browser/rango-state.ts +112 -0
  55. package/src/browser/react/Link.tsx +230 -74
  56. package/src/browser/react/NavigationProvider.tsx +87 -11
  57. package/src/browser/react/context.ts +11 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +12 -12
  60. package/src/browser/react/location-state-shared.ts +95 -53
  61. package/src/browser/react/location-state.ts +60 -15
  62. package/src/browser/react/mount-context.ts +6 -1
  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 +29 -51
  66. package/src/browser/react/use-client-cache.ts +5 -3
  67. package/src/browser/react/use-handle.ts +30 -126
  68. package/src/browser/react/use-href.tsx +2 -2
  69. package/src/browser/react/use-link-status.ts +6 -5
  70. package/src/browser/react/use-navigation.ts +22 -63
  71. package/src/browser/react/use-params.ts +65 -0
  72. package/src/browser/react/use-pathname.ts +47 -0
  73. package/src/browser/react/use-router.ts +76 -0
  74. package/src/browser/react/use-search-params.ts +56 -0
  75. package/src/browser/react/use-segments.ts +80 -97
  76. package/src/browser/response-adapter.ts +73 -0
  77. package/src/browser/rsc-router.tsx +214 -58
  78. package/src/browser/scroll-restoration.ts +127 -52
  79. package/src/browser/segment-reconciler.ts +221 -0
  80. package/src/browser/segment-structure-assert.ts +16 -0
  81. package/src/browser/server-action-bridge.ts +510 -603
  82. package/src/browser/shallow.ts +6 -1
  83. package/src/browser/types.ts +141 -48
  84. package/src/browser/validate-redirect-origin.ts +29 -0
  85. package/src/build/generate-manifest.ts +235 -24
  86. package/src/build/generate-route-types.ts +39 -0
  87. package/src/build/index.ts +13 -0
  88. package/src/build/route-trie.ts +265 -0
  89. package/src/build/route-types/ast-helpers.ts +25 -0
  90. package/src/build/route-types/ast-route-extraction.ts +98 -0
  91. package/src/build/route-types/codegen.ts +102 -0
  92. package/src/build/route-types/include-resolution.ts +418 -0
  93. package/src/build/route-types/param-extraction.ts +48 -0
  94. package/src/build/route-types/per-module-writer.ts +128 -0
  95. package/src/build/route-types/router-processing.ts +618 -0
  96. package/src/build/route-types/scan-filter.ts +85 -0
  97. package/src/build/runtime-discovery.ts +231 -0
  98. package/src/cache/background-task.ts +34 -0
  99. package/src/cache/cache-key-utils.ts +44 -0
  100. package/src/cache/cache-policy.ts +125 -0
  101. package/src/cache/cache-runtime.ts +342 -0
  102. package/src/cache/cache-scope.ts +167 -309
  103. package/src/cache/cf/cf-cache-store.ts +571 -17
  104. package/src/cache/cf/index.ts +13 -3
  105. package/src/cache/document-cache.ts +116 -77
  106. package/src/cache/handle-capture.ts +81 -0
  107. package/src/cache/handle-snapshot.ts +41 -0
  108. package/src/cache/index.ts +1 -15
  109. package/src/cache/memory-segment-store.ts +191 -13
  110. package/src/cache/profile-registry.ts +73 -0
  111. package/src/cache/read-through-swr.ts +134 -0
  112. package/src/cache/segment-codec.ts +256 -0
  113. package/src/cache/taint.ts +153 -0
  114. package/src/cache/types.ts +72 -122
  115. package/src/client.rsc.tsx +3 -1
  116. package/src/client.tsx +105 -179
  117. package/src/component-utils.ts +4 -4
  118. package/src/components/DefaultDocument.tsx +5 -1
  119. package/src/context-var.ts +156 -0
  120. package/src/debug.ts +19 -9
  121. package/src/errors.ts +108 -2
  122. package/src/handle.ts +55 -29
  123. package/src/handles/MetaTags.tsx +73 -20
  124. package/src/handles/breadcrumbs.ts +66 -0
  125. package/src/handles/index.ts +1 -0
  126. package/src/handles/meta.ts +30 -13
  127. package/src/host/cookie-handler.ts +21 -15
  128. package/src/host/errors.ts +8 -8
  129. package/src/host/index.ts +4 -7
  130. package/src/host/pattern-matcher.ts +27 -27
  131. package/src/host/router.ts +61 -39
  132. package/src/host/testing.ts +8 -8
  133. package/src/host/types.ts +15 -7
  134. package/src/host/utils.ts +1 -1
  135. package/src/href-client.ts +119 -29
  136. package/src/index.rsc.ts +155 -19
  137. package/src/index.ts +223 -30
  138. package/src/internal-debug.ts +11 -0
  139. package/src/loader.rsc.ts +26 -157
  140. package/src/loader.ts +27 -10
  141. package/src/network-error-thrower.tsx +3 -1
  142. package/src/outlet-provider.tsx +45 -0
  143. package/src/prerender/param-hash.ts +37 -0
  144. package/src/prerender/store.ts +186 -0
  145. package/src/prerender.ts +524 -0
  146. package/src/reverse.ts +351 -0
  147. package/src/root-error-boundary.tsx +41 -29
  148. package/src/route-content-wrapper.tsx +7 -4
  149. package/src/route-definition/dsl-helpers.ts +982 -0
  150. package/src/route-definition/helper-factories.ts +200 -0
  151. package/src/route-definition/helpers-types.ts +434 -0
  152. package/src/route-definition/index.ts +55 -0
  153. package/src/route-definition/redirect.ts +101 -0
  154. package/src/route-definition/resolve-handler-use.ts +149 -0
  155. package/src/route-definition.ts +1 -1428
  156. package/src/route-map-builder.ts +217 -123
  157. package/src/route-name.ts +53 -0
  158. package/src/route-types.ts +70 -8
  159. package/src/router/content-negotiation.ts +215 -0
  160. package/src/router/debug-manifest.ts +72 -0
  161. package/src/router/error-handling.ts +9 -9
  162. package/src/router/find-match.ts +160 -0
  163. package/src/router/handler-context.ts +435 -86
  164. package/src/router/intercept-resolution.ts +402 -0
  165. package/src/router/lazy-includes.ts +237 -0
  166. package/src/router/loader-resolution.ts +356 -128
  167. package/src/router/logging.ts +251 -0
  168. package/src/router/manifest.ts +154 -35
  169. package/src/router/match-api.ts +555 -0
  170. package/src/router/match-context.ts +5 -3
  171. package/src/router/match-handlers.ts +440 -0
  172. package/src/router/match-middleware/background-revalidation.ts +108 -93
  173. package/src/router/match-middleware/cache-lookup.ts +459 -10
  174. package/src/router/match-middleware/cache-store.ts +98 -26
  175. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  176. package/src/router/match-middleware/segment-resolution.ts +80 -6
  177. package/src/router/match-pipelines.ts +10 -45
  178. package/src/router/match-result.ts +55 -33
  179. package/src/router/metrics.ts +240 -15
  180. package/src/router/middleware-cookies.ts +55 -0
  181. package/src/router/middleware-types.ts +220 -0
  182. package/src/router/middleware.ts +324 -369
  183. package/src/router/navigation-snapshot.ts +182 -0
  184. package/src/router/pattern-matching.ts +211 -43
  185. package/src/router/prerender-match.ts +502 -0
  186. package/src/router/preview-match.ts +98 -0
  187. package/src/router/request-classification.ts +310 -0
  188. package/src/router/revalidation.ts +137 -38
  189. package/src/router/route-snapshot.ts +245 -0
  190. package/src/router/router-context.ts +41 -21
  191. package/src/router/router-interfaces.ts +484 -0
  192. package/src/router/router-options.ts +618 -0
  193. package/src/router/router-registry.ts +24 -0
  194. package/src/router/segment-resolution/fresh.ts +743 -0
  195. package/src/router/segment-resolution/helpers.ts +268 -0
  196. package/src/router/segment-resolution/loader-cache.ts +199 -0
  197. package/src/router/segment-resolution/revalidation.ts +1373 -0
  198. package/src/router/segment-resolution/static-store.ts +67 -0
  199. package/src/router/segment-resolution.ts +21 -0
  200. package/src/router/segment-wrappers.ts +291 -0
  201. package/src/router/telemetry-otel.ts +299 -0
  202. package/src/router/telemetry.ts +300 -0
  203. package/src/router/timeout.ts +148 -0
  204. package/src/router/trie-matching.ts +239 -0
  205. package/src/router/types.ts +78 -3
  206. package/src/router.ts +740 -4252
  207. package/src/rsc/handler-context.ts +45 -0
  208. package/src/rsc/handler.ts +907 -797
  209. package/src/rsc/helpers.ts +140 -6
  210. package/src/rsc/index.ts +0 -20
  211. package/src/rsc/loader-fetch.ts +229 -0
  212. package/src/rsc/manifest-init.ts +90 -0
  213. package/src/rsc/nonce.ts +14 -0
  214. package/src/rsc/origin-guard.ts +141 -0
  215. package/src/rsc/progressive-enhancement.ts +391 -0
  216. package/src/rsc/response-error.ts +37 -0
  217. package/src/rsc/response-route-handler.ts +347 -0
  218. package/src/rsc/rsc-rendering.ts +246 -0
  219. package/src/rsc/runtime-warnings.ts +42 -0
  220. package/src/rsc/server-action.ts +356 -0
  221. package/src/rsc/ssr-setup.ts +128 -0
  222. package/src/rsc/types.ts +46 -11
  223. package/src/search-params.ts +230 -0
  224. package/src/segment-system.tsx +165 -17
  225. package/src/server/context.ts +315 -58
  226. package/src/server/cookie-store.ts +190 -0
  227. package/src/server/fetchable-loader-store.ts +37 -0
  228. package/src/server/handle-store.ts +113 -15
  229. package/src/server/loader-registry.ts +24 -64
  230. package/src/server/request-context.ts +607 -81
  231. package/src/server.ts +35 -130
  232. package/src/ssr/index.tsx +103 -30
  233. package/src/static-handler.ts +126 -0
  234. package/src/theme/ThemeProvider.tsx +21 -15
  235. package/src/theme/ThemeScript.tsx +5 -5
  236. package/src/theme/constants.ts +5 -2
  237. package/src/theme/index.ts +4 -14
  238. package/src/theme/theme-context.ts +4 -30
  239. package/src/theme/theme-script.ts +21 -18
  240. package/src/types/boundaries.ts +158 -0
  241. package/src/types/cache-types.ts +198 -0
  242. package/src/types/error-types.ts +192 -0
  243. package/src/types/global-namespace.ts +100 -0
  244. package/src/types/handler-context.ts +791 -0
  245. package/src/types/index.ts +88 -0
  246. package/src/types/loader-types.ts +210 -0
  247. package/src/types/route-config.ts +170 -0
  248. package/src/types/route-entry.ts +109 -0
  249. package/src/types/segments.ts +150 -0
  250. package/src/types.ts +1 -1623
  251. package/src/urls/include-helper.ts +197 -0
  252. package/src/urls/index.ts +53 -0
  253. package/src/urls/path-helper-types.ts +346 -0
  254. package/src/urls/path-helper.ts +364 -0
  255. package/src/urls/pattern-types.ts +107 -0
  256. package/src/urls/response-types.ts +116 -0
  257. package/src/urls/type-extraction.ts +372 -0
  258. package/src/urls/urls-function.ts +98 -0
  259. package/src/urls.ts +1 -802
  260. package/src/use-loader.tsx +161 -81
  261. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  262. package/src/vite/discovery/discover-routers.ts +348 -0
  263. package/src/vite/discovery/prerender-collection.ts +439 -0
  264. package/src/vite/discovery/route-types-writer.ts +258 -0
  265. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  266. package/src/vite/discovery/state.ts +117 -0
  267. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  268. package/src/vite/index.ts +15 -1129
  269. package/src/vite/plugin-types.ts +103 -0
  270. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  271. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  272. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  273. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  274. package/src/vite/plugins/expose-id-utils.ts +299 -0
  275. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  276. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  277. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  278. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  279. package/src/vite/plugins/expose-ids/types.ts +45 -0
  280. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  281. package/src/vite/plugins/performance-tracks.ts +88 -0
  282. package/src/vite/plugins/refresh-cmd.ts +127 -0
  283. package/src/vite/plugins/use-cache-transform.ts +323 -0
  284. package/src/vite/plugins/version-injector.ts +83 -0
  285. package/src/vite/plugins/version-plugin.ts +266 -0
  286. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  287. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  288. package/src/vite/rango.ts +462 -0
  289. package/src/vite/router-discovery.ts +918 -0
  290. package/src/vite/utils/ast-handler-extract.ts +517 -0
  291. package/src/vite/utils/banner.ts +36 -0
  292. package/src/vite/utils/bundle-analysis.ts +137 -0
  293. package/src/vite/utils/manifest-utils.ts +70 -0
  294. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  295. package/src/vite/utils/prerender-utils.ts +207 -0
  296. package/src/vite/utils/shared-utils.ts +170 -0
  297. package/CLAUDE.md +0 -43
  298. package/src/browser/lru-cache.ts +0 -69
  299. package/src/browser/request-controller.ts +0 -164
  300. package/src/cache/memory-store.ts +0 -253
  301. package/src/href-context.ts +0 -33
  302. package/src/href.ts +0 -255
  303. package/src/server/route-manifest-cache.ts +0 -173
  304. package/src/vite/expose-handle-id.ts +0 -209
  305. package/src/vite/expose-loader-id.ts +0 -426
  306. package/src/vite/expose-location-state-id.ts +0 -177
  307. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Self-Generated File Tracking
3
+ *
4
+ * Tracks gen files recently written by the discovery plugin so the
5
+ * file watcher can distinguish self-triggered change events from
6
+ * manual edits.
7
+ */
8
+
9
+ import { createHash } from "node:crypto";
10
+ import { readFileSync } from "node:fs";
11
+ import type { DiscoveryState } from "./state.js";
12
+
13
+ export function markSelfGenWrite(
14
+ state: DiscoveryState,
15
+ filePath: string,
16
+ content: string,
17
+ ): void {
18
+ const hash = createHash("sha256").update(content).digest("hex");
19
+ state.selfWrittenGenFiles.set(filePath, { at: Date.now(), hash });
20
+ }
21
+
22
+ export function consumeSelfGenWrite(
23
+ state: DiscoveryState,
24
+ filePath: string,
25
+ ): boolean {
26
+ const info = state.selfWrittenGenFiles.get(filePath);
27
+ if (!info) return false;
28
+ if (Date.now() - info.at > state.SELF_WRITE_WINDOW_MS) {
29
+ state.selfWrittenGenFiles.delete(filePath);
30
+ return false;
31
+ }
32
+ try {
33
+ const current = readFileSync(filePath, "utf-8");
34
+ const currentHash = createHash("sha256").update(current).digest("hex");
35
+ if (currentHash === info.hash) {
36
+ state.selfWrittenGenFiles.delete(filePath);
37
+ return true;
38
+ }
39
+ // Hash mismatch: file was changed externally. Keep the entry so a
40
+ // subsequent watcher event from our own write can still be consumed
41
+ // (e.g. when multiple Vite servers watch the same directory).
42
+ return false;
43
+ } catch {
44
+ state.selfWrittenGenFiles.delete(filePath);
45
+ return false;
46
+ }
47
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Discovery State
3
+ *
4
+ * Shared mutable state for the router discovery plugin.
5
+ * Created once by createRouterDiscoveryPlugin() and passed
6
+ * to all extracted helper functions.
7
+ */
8
+
9
+ import type { ScanFilter } from "../../build/generate-route-types.js";
10
+
11
+ export const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
12
+
13
+ export interface PluginOptions {
14
+ enableBuildPrerender?: boolean;
15
+ staticRouteTypesGeneration?: boolean;
16
+ // Mutable ref for deferred auto-discovery (node preset).
17
+ // The auto-discover config() hook populates this before configResolved.
18
+ routerPathRef?: { path?: string };
19
+ /** Build-time env option from rango() config. */
20
+ buildEnv?: import("../plugin-types.js").BuildEnvOption;
21
+ /** Deployment preset (needed for buildEnv "auto" resolution). */
22
+ preset?: "node" | "cloudflare";
23
+ }
24
+
25
+ export interface PrecomputedEntry {
26
+ staticPrefix: string;
27
+ routes: Record<string, string>;
28
+ }
29
+
30
+ export interface ChunkInfo {
31
+ fileName: string;
32
+ exports: Array<{ name: string; handlerId: string; passthrough: boolean }>;
33
+ }
34
+
35
+ export interface PerRouterManifestEntry {
36
+ id: string;
37
+ routeManifest: Record<string, string>;
38
+ routeSearchSchemas?: Record<string, Record<string, string>>;
39
+ sourceFile?: string;
40
+ factoryOnlyPrefixes?: Set<string>;
41
+ }
42
+
43
+ export interface DiscoveryState {
44
+ resolvedEntryPath: string | undefined;
45
+ projectRoot: string;
46
+ isBuildMode: boolean;
47
+ userResolveAlias: any;
48
+ scanFilter: ScanFilter | undefined;
49
+ cachedRouterFiles: string[] | undefined;
50
+ opts: PluginOptions | undefined;
51
+
52
+ mergedRouteManifest: Record<string, string> | null;
53
+ perRouterManifests: PerRouterManifestEntry[];
54
+ mergedPrecomputedEntries: PrecomputedEntry[] | null;
55
+ mergedRouteTrie: any;
56
+
57
+ perRouterTrieMap: Map<string, any>;
58
+ perRouterPrecomputedMap: Map<string, PrecomputedEntry[]>;
59
+ perRouterManifestDataMap: Map<string, Record<string, string>>;
60
+
61
+ prerenderManifestEntries: Record<string, string> | null;
62
+ staticManifestEntries: Record<string, string> | null;
63
+ handlerChunkInfoMap: Map<string, ChunkInfo>;
64
+ staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
65
+ rscEntryFileName: string | null;
66
+ resolvedPrerenderModules: Map<string, string[]> | undefined;
67
+ resolvedStaticModules: Map<string, string[]> | undefined;
68
+
69
+ discoveryDone: Promise<void> | null;
70
+ devServerOrigin: string | null;
71
+ devServer: any;
72
+ selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
73
+ SELF_WRITE_WINDOW_MS: number;
74
+
75
+ /** Resolved build-time env bindings (set during buildStart/configureServer). */
76
+ resolvedBuildEnv?: Record<string, unknown>;
77
+ /** Cleanup function for build-time env resources (e.g., miniflare). */
78
+ buildEnvDispose?: (() => Promise<void> | void) | null;
79
+ }
80
+
81
+ export function createDiscoveryState(
82
+ entryPath: string | undefined,
83
+ opts: PluginOptions | undefined,
84
+ ): DiscoveryState {
85
+ return {
86
+ resolvedEntryPath: entryPath,
87
+ projectRoot: "",
88
+ isBuildMode: false,
89
+ userResolveAlias: undefined,
90
+ scanFilter: undefined,
91
+ cachedRouterFiles: undefined,
92
+ opts,
93
+
94
+ mergedRouteManifest: null,
95
+ perRouterManifests: [],
96
+ mergedPrecomputedEntries: null,
97
+ mergedRouteTrie: null,
98
+
99
+ perRouterTrieMap: new Map(),
100
+ perRouterPrecomputedMap: new Map(),
101
+ perRouterManifestDataMap: new Map(),
102
+
103
+ prerenderManifestEntries: null,
104
+ staticManifestEntries: null,
105
+ handlerChunkInfoMap: new Map(),
106
+ staticHandlerChunkInfoMap: new Map(),
107
+ rscEntryFileName: null,
108
+ resolvedPrerenderModules: undefined,
109
+ resolvedStaticModules: undefined,
110
+
111
+ discoveryDone: null,
112
+ devServerOrigin: null,
113
+ devServer: null,
114
+ selfWrittenGenFiles: new Map(),
115
+ SELF_WRITE_WINDOW_MS: 5_000,
116
+ };
117
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Virtual Module Code Generation
3
+ *
4
+ * Generates the code for virtual:rsc-router/routes-manifest and
5
+ * per-router virtual modules used by the load() hook.
6
+ */
7
+
8
+ import { dirname, basename, join } from "node:path";
9
+ import { jsonParseExpression } from "../utils/manifest-utils.js";
10
+ import { VIRTUAL_ROUTES_MANIFEST_ID } from "./state.js";
11
+ import type { DiscoveryState } from "./state.js";
12
+
13
+ /**
14
+ * Generate the code for the main virtual:rsc-router/routes-manifest module.
15
+ */
16
+ export function generateRoutesManifestModule(state: DiscoveryState): string {
17
+ const hasManifest =
18
+ state.mergedRouteManifest &&
19
+ Object.keys(state.mergedRouteManifest).length > 0;
20
+
21
+ if (hasManifest) {
22
+ // Build gen file import statements for each router with a sourceFile.
23
+ // This creates a dependency in Vite's module graph: when the gen file
24
+ // changes (e.g. after HMR route edits), Vite invalidates this virtual
25
+ // module and re-evaluates it on the next request, calling
26
+ // setCachedManifest() with fresh data. No manual sync needed.
27
+ const genFileImports: string[] = [];
28
+ const genFileVars: string[] = [];
29
+ const routersWithoutGenFile: Array<{
30
+ id: string;
31
+ manifest: Record<string, string>;
32
+ }> = [];
33
+ let varIdx = 0;
34
+
35
+ for (const entry of state.perRouterManifests) {
36
+ if (entry.sourceFile) {
37
+ const routerDir = dirname(entry.sourceFile);
38
+ const routerBasename = basename(entry.sourceFile).replace(
39
+ /\.(tsx?|jsx?)$/,
40
+ "",
41
+ );
42
+ const genPath = join(
43
+ routerDir,
44
+ `${routerBasename}.named-routes.gen.js`,
45
+ ).replaceAll("\\", "/");
46
+ const varName = `_r${varIdx++}`;
47
+ genFileImports.push(
48
+ `import { NamedRoutes as ${varName} } from ${JSON.stringify(genPath)};`,
49
+ );
50
+ genFileVars.push(varName);
51
+ } else {
52
+ // Routers without sourceFile: inline their manifest data directly
53
+ routersWithoutGenFile.push({
54
+ id: entry.id,
55
+ manifest: entry.routeManifest,
56
+ });
57
+ }
58
+ }
59
+
60
+ const lines = [
61
+ `import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
62
+ ...genFileImports,
63
+ // Clear stale per-router cached data (manifest, trie, precomputed entries)
64
+ // before re-populating. In Cloudflare dev mode, program reloads re-evaluate
65
+ // this virtual module but the route-map-builder singleton retains old data
66
+ // because it's not in the HMR invalidation chain. Without this clear, the
67
+ // handler finds stale trie data and never rebuilds from updated urlpatterns.
68
+ `clearAllRouterData();`,
69
+ ];
70
+
71
+ // Flatten NamedRoutes entries: search schema objects -> plain string paths
72
+ if (genFileVars.length > 0) {
73
+ lines.push(
74
+ `function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`,
75
+ );
76
+ }
77
+
78
+ // Build the merged manifest from gen file imports + inlined data
79
+ if (genFileVars.length === 1 && routersWithoutGenFile.length === 0) {
80
+ lines.push(`setCachedManifest(__flat(${genFileVars[0]}));`);
81
+ } else {
82
+ const parts: string[] = [];
83
+ for (const v of genFileVars) parts.push(`...__flat(${v})`);
84
+ for (const { manifest } of routersWithoutGenFile)
85
+ parts.push(`...${jsonParseExpression(manifest)}`);
86
+ lines.push(`setCachedManifest({ ${parts.join(", ")} });`);
87
+ }
88
+
89
+ // Set per-router manifests
90
+ let genVarIdx = 0;
91
+ for (const entry of state.perRouterManifests) {
92
+ if (entry.sourceFile) {
93
+ const varName = genFileVars[genVarIdx++];
94
+ lines.push(
95
+ `setRouterManifest(${JSON.stringify(entry.id)}, __flat(${varName}));`,
96
+ );
97
+ } else {
98
+ lines.push(
99
+ `setRouterManifest(${JSON.stringify(entry.id)}, ${jsonParseExpression(entry.routeManifest)});`,
100
+ );
101
+ }
102
+ }
103
+
104
+ // In dev mode, skip trie and precomputed entries injection. These are
105
+ // computed once during initial discovery and become stale after route
106
+ // changes. A stale trie would incorrectly match removed routes. The
107
+ // handler falls back to Phase 2 regex matching against the live
108
+ // router.urlpatterns, which is always correct after a program reload.
109
+ // In build mode, the trie is always fresh (built from the final route
110
+ // tree) so it's safe to inject.
111
+ if (state.isBuildMode) {
112
+ if (
113
+ state.mergedPrecomputedEntries &&
114
+ state.mergedPrecomputedEntries.length > 0
115
+ ) {
116
+ lines.push(
117
+ `setPrecomputedEntries(${jsonParseExpression(state.mergedPrecomputedEntries)});`,
118
+ );
119
+ }
120
+ if (state.mergedRouteTrie) {
121
+ lines.push(
122
+ `setRouteTrie(${jsonParseExpression(state.mergedRouteTrie)});`,
123
+ );
124
+ }
125
+ }
126
+
127
+ // Register lazy loaders for per-router manifest modules.
128
+ // Each import() uses a static string literal so Rollup creates separate chunks.
129
+ for (const routerId of state.perRouterManifestDataMap.keys()) {
130
+ lines.push(
131
+ `registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`,
132
+ );
133
+ }
134
+ if (!state.isBuildMode && state.devServerOrigin) {
135
+ lines.push(
136
+ `globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(state.devServerOrigin)};`,
137
+ );
138
+ }
139
+ return lines.join("\n");
140
+ }
141
+
142
+ // No manifest: either discovery hasn't completed or no runner (Cloudflare dev).
143
+ // Still inject __PRERENDER_DEV_URL so the prerender store can fetch on-demand.
144
+ // Re-resolve origin now since the server is listening by module load time.
145
+ if (!state.isBuildMode) {
146
+ const origin =
147
+ state.devServerOrigin ||
148
+ state.devServer?.resolvedUrls?.local?.[0]?.replace(/\/$/, "") ||
149
+ (state.devServer &&
150
+ `http://localhost:${state.devServer.config.server.port || 5173}`);
151
+ if (origin) {
152
+ state.devServerOrigin = origin;
153
+ return `globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(origin)};`;
154
+ }
155
+ }
156
+ return `// Route manifest will be populated at runtime`;
157
+ }
158
+
159
+ /**
160
+ * Generate the code for a per-router virtual module.
161
+ */
162
+ export function generatePerRouterModule(
163
+ state: DiscoveryState,
164
+ routerId: string,
165
+ ): string {
166
+ // Find the per-router entry to get the gen file path
167
+ const routerEntry = state.perRouterManifests.find((e) => e.id === routerId);
168
+ const trie = state.perRouterTrieMap.get(routerId);
169
+ const entries = state.perRouterPrecomputedMap.get(routerId);
170
+ const lines: string[] = [];
171
+
172
+ if (routerEntry?.sourceFile) {
173
+ // Import manifest from the gen file so HMR auto-propagates
174
+ const routerDir = dirname(routerEntry.sourceFile);
175
+ const routerBasename = basename(routerEntry.sourceFile).replace(
176
+ /\.(tsx?|jsx?)$/,
177
+ "",
178
+ );
179
+ const genPath = join(
180
+ routerDir,
181
+ `${routerBasename}.named-routes.gen.js`,
182
+ ).replaceAll("\\", "/");
183
+ lines.push(`import { NamedRoutes as _r } from ${JSON.stringify(genPath)};`);
184
+ lines.push(
185
+ `function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`,
186
+ );
187
+ lines.push(`export const manifest = __flat(_r);`);
188
+ } else {
189
+ const manifest = state.perRouterManifestDataMap.get(routerId);
190
+ if (manifest) {
191
+ lines.push(`export const manifest = ${jsonParseExpression(manifest)};`);
192
+ }
193
+ }
194
+ if (trie) {
195
+ lines.push(`export const trie = ${jsonParseExpression(trie)};`);
196
+ }
197
+ if (entries && entries.length > 0) {
198
+ lines.push(
199
+ `export const precomputedEntries = ${jsonParseExpression(entries)};`,
200
+ );
201
+ }
202
+ return lines.join("\n") || "// empty router manifest";
203
+ }