@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847

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 (298) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -212
  4. package/dist/vite/index.js +3995 -2489
  5. package/package.json +57 -52
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -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 +6 -4
  13. package/skills/hooks/SKILL.md +328 -70
  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 +62 -15
  18. package/skills/loader/SKILL.md +368 -42
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +14 -10
  21. package/skills/parallel/SKILL.md +137 -1
  22. package/skills/prerender/SKILL.md +366 -28
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +136 -83
  25. package/skills/route/SKILL.md +195 -21
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +240 -102
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +92 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +11 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +266 -558
  41. package/src/browser/navigation-client.ts +132 -75
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +297 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +303 -309
  46. package/src/browser/prefetch/cache.ts +206 -0
  47. package/src/browser/prefetch/fetch.ts +144 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +128 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +190 -70
  53. package/src/browser/react/NavigationProvider.tsx +78 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +188 -57
  74. package/src/browser/scroll-restoration.ts +117 -44
  75. package/src/browser/segment-reconciler.ts +221 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +488 -606
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +116 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +63 -21
  82. package/src/build/generate-route-types.ts +36 -1038
  83. package/src/build/index.ts +2 -5
  84. package/src/build/route-trie.ts +38 -12
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +479 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +342 -0
  98. package/src/cache/cache-scope.ts +122 -303
  99. package/src/cache/cf/cf-cache-store.ts +571 -17
  100. package/src/cache/cf/index.ts +13 -3
  101. package/src/cache/document-cache.ts +116 -77
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +1 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +19 -9
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +12 -7
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +104 -40
  133. package/src/index.ts +122 -67
  134. package/src/internal-debug.ts +9 -3
  135. package/src/loader.rsc.ts +18 -93
  136. package/src/loader.ts +26 -9
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +121 -17
  141. package/src/prerender.ts +325 -20
  142. package/src/reverse.ts +144 -124
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +959 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1450
  151. package/src/route-map-builder.ts +87 -133
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +41 -6
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +160 -0
  158. package/src/router/handler-context.ts +324 -116
  159. package/src/router/intercept-resolution.ts +11 -4
  160. package/src/router/lazy-includes.ts +237 -0
  161. package/src/router/loader-resolution.ts +179 -133
  162. package/src/router/logging.ts +112 -6
  163. package/src/router/manifest.ts +58 -19
  164. package/src/router/match-api.ts +89 -88
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +86 -89
  168. package/src/router/match-middleware/cache-lookup.ts +295 -49
  169. package/src/router/match-middleware/cache-store.ts +56 -13
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -22
  171. package/src/router/match-middleware/segment-resolution.ts +20 -9
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +44 -21
  174. package/src/router/metrics.ts +240 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +327 -369
  178. package/src/router/pattern-matching.ts +169 -31
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +105 -14
  182. package/src/router/router-context.ts +40 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +677 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +199 -0
  189. package/src/router/segment-resolution/revalidation.ts +1296 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1354
  192. package/src/router/segment-wrappers.ts +291 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +15 -9
  198. package/src/router.ts +642 -2366
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +639 -1027
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +66 -54
  216. package/src/segment-system.tsx +165 -17
  217. package/src/server/context.ts +237 -54
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +11 -6
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +438 -71
  223. package/src/server.ts +26 -164
  224. package/src/ssr/index.tsx +101 -31
  225. package/src/static-handler.ts +22 -4
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +773 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +109 -0
  241. package/src/types/segments.ts +150 -0
  242. package/src/types.ts +1 -1795
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1323
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +108 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -2259
  261. package/src/vite/plugin-types.ts +48 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -47
  266. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +266 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +445 -0
  280. package/src/vite/router-discovery.ts +777 -0
  281. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/dist/vite/index.named-routes.gen.ts +0 -103
  290. package/src/browser/lru-cache.ts +0 -69
  291. package/src/browser/request-controller.ts +0 -164
  292. package/src/cache/memory-store.ts +0 -253
  293. package/src/href-context.ts +0 -33
  294. package/src/router.gen.ts +0 -6
  295. package/src/static-handler.gen.ts +0 -5
  296. package/src/urls.gen.ts +0 -8
  297. package/src/vite/expose-internal-ids.ts +0 -1167
  298. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,189 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ copyFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ rmSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { resolve } from "node:path";
11
+
12
+ /**
13
+ * Escape special RegExp characters in a string for safe interpolation
14
+ * into new RegExp() patterns.
15
+ */
16
+ export function escapeRegExp(str: string): string {
17
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+
20
+ /**
21
+ * Encode route param values for path interpolation while preserving path
22
+ * separators for wildcard params (splat-style values can include `/`).
23
+ */
24
+ export function encodePathParam(value: unknown): string {
25
+ return String(value)
26
+ .split("/")
27
+ .map((segment) => encodeURIComponent(segment))
28
+ .join("/");
29
+ }
30
+
31
+ /**
32
+ * Substitute route params into a pattern, stripping constraint and optional
33
+ * syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
34
+ */
35
+ export function substituteRouteParams(
36
+ pattern: string,
37
+ params: Record<string, string>,
38
+ encode: (value: string) => string = encodeURIComponent,
39
+ ): string {
40
+ let result = pattern;
41
+ for (const [key, value] of Object.entries(params)) {
42
+ const escaped = escapeRegExp(key);
43
+ result = result.replace(
44
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
45
+ encode(value),
46
+ );
47
+ result = result.replace(`*${key}`, encode(value));
48
+ }
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Run an async function over items with bounded concurrency.
54
+ * Errors propagate immediately and abort remaining work.
55
+ */
56
+ export async function runWithConcurrency<T>(
57
+ items: T[],
58
+ concurrency: number,
59
+ fn: (item: T) => Promise<void>,
60
+ ): Promise<void> {
61
+ const limit = Math.max(1, Math.min(concurrency, items.length));
62
+ if (limit <= 1) {
63
+ for (const item of items) await fn(item);
64
+ return;
65
+ }
66
+ let nextIndex = 0;
67
+ async function worker() {
68
+ while (nextIndex < items.length) {
69
+ const idx = nextIndex++;
70
+ await fn(items[idx]);
71
+ }
72
+ }
73
+ await Promise.all(Array.from({ length: limit }, () => worker()));
74
+ }
75
+
76
+ /**
77
+ * Group prerender entries by their concurrency setting so each group
78
+ * can be rendered with the appropriate parallelism.
79
+ */
80
+ export function groupByConcurrency<T extends { concurrency: number }>(
81
+ entries: T[],
82
+ ): { concurrency: number; entries: T[] }[] {
83
+ const map = new Map<number, T[]>();
84
+ for (const entry of entries) {
85
+ const key = entry.concurrency;
86
+ let group = map.get(key);
87
+ if (!group) {
88
+ group = [];
89
+ map.set(key, group);
90
+ }
91
+ group.push(entry);
92
+ }
93
+ return Array.from(map.entries(), ([concurrency, items]) => ({
94
+ concurrency,
95
+ entries: items,
96
+ }));
97
+ }
98
+
99
+ /**
100
+ * Notify all routers' onError callbacks about a build-time error.
101
+ * Uses a synthetic request since there is no real request during build.
102
+ */
103
+ export function notifyOnError(
104
+ registry: Map<string, any>,
105
+ error: unknown,
106
+ phase: "prerender" | "static",
107
+ routeKey?: string,
108
+ pathname?: string,
109
+ skipped?: boolean,
110
+ ): void {
111
+ for (const [, routerInstance] of registry) {
112
+ const onError = routerInstance.onError;
113
+ if (!onError) continue;
114
+
115
+ const errorObj = error instanceof Error ? error : new Error(String(error));
116
+ const syntheticUrl = new URL("http://prerender" + (pathname || "/"));
117
+ const context = {
118
+ error: errorObj,
119
+ phase,
120
+ request: new Request(syntheticUrl),
121
+ url: syntheticUrl,
122
+ pathname: syntheticUrl.pathname,
123
+ method: "GET",
124
+ routeKey,
125
+ metadata: skipped ? { skipped: true } : undefined,
126
+ };
127
+
128
+ try {
129
+ const result = onError(context);
130
+ if (result instanceof Promise) {
131
+ result.catch((cbErr: unknown) => {
132
+ console.error(`[Build.onError] Callback error:`, cbErr);
133
+ });
134
+ }
135
+ } catch (cbErr) {
136
+ console.error(`[Build.onError] Callback error:`, cbErr);
137
+ }
138
+ break; // Only notify the first router with onError
139
+ }
140
+ }
141
+
142
+ function getStagedAssetDir(projectRoot: string): string {
143
+ return resolve(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
144
+ }
145
+
146
+ export function resetStagedBuildAssets(projectRoot: string): void {
147
+ rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
148
+ }
149
+
150
+ export function stageBuildAssetModule(
151
+ projectRoot: string,
152
+ prefix: "__pr" | "__st",
153
+ exportValue: string,
154
+ ): string {
155
+ const stagedDir = getStagedAssetDir(projectRoot);
156
+ mkdirSync(stagedDir, { recursive: true });
157
+
158
+ const contentHash = createHash("sha256")
159
+ .update(exportValue)
160
+ .digest("hex")
161
+ .slice(0, 8);
162
+ const fileName = `${prefix}-${contentHash}.js`;
163
+ const filePath = resolve(stagedDir, fileName);
164
+
165
+ if (!existsSync(filePath)) {
166
+ writeFileSync(filePath, `export default ${exportValue};\n`);
167
+ }
168
+
169
+ return fileName;
170
+ }
171
+
172
+ export function copyStagedBuildAssets(
173
+ projectRoot: string,
174
+ fileNames: Iterable<string>,
175
+ ): number {
176
+ const stagedDir = getStagedAssetDir(projectRoot);
177
+ const distAssetsDir = resolve(projectRoot, "dist/rsc/assets");
178
+ mkdirSync(distAssetsDir, { recursive: true });
179
+
180
+ let totalBytes = 0;
181
+ for (const fileName of new Set(fileNames)) {
182
+ const stagedPath = resolve(stagedDir, fileName);
183
+ const distPath = resolve(distAssetsDir, fileName);
184
+ copyFileSync(stagedPath, distPath);
185
+ totalBytes += statSync(stagedPath).size;
186
+ }
187
+
188
+ return totalBytes;
189
+ }
@@ -0,0 +1,169 @@
1
+ import type { Plugin } from "vite";
2
+ import * as Vite from "vite";
3
+ import { getPublishedPackageName } from "./package-resolution.js";
4
+ import {
5
+ VIRTUAL_ENTRY_BROWSER,
6
+ VIRTUAL_ENTRY_SSR,
7
+ getVirtualEntryRSC,
8
+ VIRTUAL_IDS,
9
+ } from "../plugins/virtual-entries.js";
10
+
11
+ /**
12
+ * esbuild plugin to provide rsc-router:version virtual module during optimization.
13
+ * This is needed because esbuild runs during Vite's dependency optimization phase,
14
+ * before Vite's plugin system can handle virtual modules.
15
+ */
16
+ const versionEsbuildPlugin = {
17
+ name: "@rangojs/router-version",
18
+ setup(build: any): void {
19
+ build.onResolve({ filter: /^rsc-router:version$/ }, (args: any) => ({
20
+ path: args.path,
21
+ namespace: "@rangojs/router-virtual",
22
+ }));
23
+ build.onLoad(
24
+ { filter: /.*/, namespace: "@rangojs/router-virtual" },
25
+ () => ({
26
+ contents: `export const VERSION = "dev";`,
27
+ loader: "js",
28
+ }),
29
+ );
30
+ },
31
+ };
32
+
33
+ /**
34
+ * Shared esbuild options for dependency optimization.
35
+ * Includes the version stub plugin for all environments.
36
+ */
37
+ export const sharedEsbuildOptions: {
38
+ plugins: (typeof versionEsbuildPlugin)[];
39
+ } = {
40
+ plugins: [versionEsbuildPlugin],
41
+ };
42
+
43
+ /**
44
+ * Create a virtual modules plugin for default entry files.
45
+ * Provides virtual module content when entries use VIRTUAL_IDS (no custom entry configured).
46
+ */
47
+ export function createVirtualEntriesPlugin(
48
+ entries: { client: string; ssr: string; rsc?: string },
49
+ routerPathRef?: { path?: string },
50
+ ): Plugin {
51
+ // Build virtual modules map based on which entries use virtual IDs
52
+ const virtualModules: Record<string, string> = {};
53
+
54
+ if (entries.client === VIRTUAL_IDS.browser) {
55
+ virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
56
+ }
57
+ if (entries.ssr === VIRTUAL_IDS.ssr) {
58
+ virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
59
+ }
60
+
61
+ // RSC entry is resolved lazily in load() because routerPath may be
62
+ // set after plugin creation (e.g. by the auto-discover config() hook).
63
+ // Track all known virtual IDs for resolveId (content is separate).
64
+ const knownIds = new Set(Object.keys(virtualModules));
65
+ if (entries.rsc === VIRTUAL_IDS.rsc) {
66
+ knownIds.add(VIRTUAL_IDS.rsc);
67
+ }
68
+
69
+ return {
70
+ name: "@rangojs/router:virtual-entries",
71
+ enforce: "pre",
72
+
73
+ resolveId(id) {
74
+ if (knownIds.has(id)) {
75
+ return "\0" + id;
76
+ }
77
+ // Handle if the id already has the null prefix (RSC plugin wrapper imports)
78
+ if (id.startsWith("\0") && knownIds.has(id.slice(1))) {
79
+ return id;
80
+ }
81
+ return null;
82
+ },
83
+
84
+ load(id) {
85
+ if (id.startsWith("\0virtual:rsc-router/")) {
86
+ const virtualId = id.slice(1);
87
+ if (virtualId in virtualModules) {
88
+ return virtualModules[virtualId];
89
+ }
90
+ // Lazy RSC entry: routerPath may have been set by a config() hook
91
+ if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
92
+ const raw = routerPathRef.path.startsWith(".")
93
+ ? "/" + routerPathRef.path.slice(2) // ./src/router.tsx -> /src/router.tsx
94
+ : routerPathRef.path;
95
+ // Normalize backslashes for Windows (path.join/slice preserve native separators)
96
+ const absoluteRouterPath = raw.replaceAll("\\", "/");
97
+ return getVirtualEntryRSC(absoluteRouterPath);
98
+ }
99
+ }
100
+ return null;
101
+ },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Rollup onwarn handler that suppresses known harmless warnings:
107
+ * - "use client" directives: handled by the RSC plugin, not relevant to Rollup
108
+ * - sourcemap errors: caused by "use client" directive at line 1:0 confusing sourcemap resolution
109
+ * - sourcemap incomplete: plugins that transform without generating sourcemaps (router + RSC plugin)
110
+ * - dynamic/static mixed imports: expected for router internals (e.g. request-context, cache-scope)
111
+ * - empty bundle: @vitejs/plugin-rsc scan build (step 1/5) produces an empty "index" chunk
112
+ * because the RSC entry is fully externalized during client-reference analysis
113
+ */
114
+ export function onwarn(
115
+ warning: Vite.Rollup.RollupLog,
116
+ defaultHandler: (warning: Vite.Rollup.RollupLog) => void,
117
+ ): void {
118
+ if (
119
+ warning.code === "MODULE_LEVEL_DIRECTIVE" ||
120
+ warning.code === "SOURCEMAP_ERROR" ||
121
+ warning.code === "EMPTY_BUNDLE"
122
+ ) {
123
+ return;
124
+ }
125
+ // @vitejs/plugin-rsc@0.5.14: rsc:virtual:vite-rsc/assets-manifest renderChunk
126
+ // returns { code } without map, causing Rollup to warn about incorrect sourcemaps.
127
+ // This is harmless (simple string replacement). Remove this suppression if a
128
+ // future version of @vitejs/plugin-rsc fixes the missing sourcemap.
129
+ if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
130
+ return;
131
+ }
132
+ if (
133
+ warning.plugin === "vite:reporter" &&
134
+ warning.message?.includes(
135
+ "dynamic import will not move module into another chunk",
136
+ )
137
+ ) {
138
+ return;
139
+ }
140
+ defaultHandler(warning);
141
+ }
142
+
143
+ /**
144
+ * Manual chunks configuration for client build.
145
+ * Splits React and router packages into separate chunks for better caching.
146
+ */
147
+ export function getManualChunks(id: string): string | undefined {
148
+ const normalized = Vite.normalizePath(id);
149
+
150
+ if (
151
+ normalized.includes("node_modules/react/") ||
152
+ normalized.includes("node_modules/react-dom/") ||
153
+ normalized.includes("node_modules/react-server-dom-webpack/") ||
154
+ normalized.includes("node_modules/@vitejs/plugin-rsc/")
155
+ ) {
156
+ return "react";
157
+ }
158
+ // Use dynamic package name from package.json
159
+ // Check both npm install path and workspace symlink resolved path
160
+ const packageName = getPublishedPackageName();
161
+ if (
162
+ normalized.includes(`node_modules/${packageName}/`) ||
163
+ normalized.includes("packages/rsc-router/") ||
164
+ normalized.includes("packages/rangojs-router/")
165
+ ) {
166
+ return "router";
167
+ }
168
+ return undefined;
169
+ }
package/CLAUDE.md DELETED
@@ -1,43 +0,0 @@
1
- # @rangojs/router
2
-
3
- Run `/rango` first to understand the API. Skills are in `node_modules/@rangojs/router/skills/`.
4
-
5
- ## Tree-Structure-Critical Files (DO NOT MODIFY without understanding)
6
-
7
- The following files control the React tree structure. Changing the tree structure
8
- (element types, nesting depth, or keys at any position) between SSR, navigation,
9
- and action renders will cause React to remount components, destroying client state
10
- like `useActionState`, refs, and local state. This is extremely hard to debug.
11
-
12
- **Protected files:**
13
-
14
- - `src/segment-system.tsx` - `renderSegments()` builds the React tree from segments.
15
- The `loading` property determines tree structure:
16
- - `undefined` / `null` -> OutletProvider directly (no boundary)
17
- - `false` -> LoaderBoundary + OutletProvider (boundary, no RouteContentWrapper)
18
- - truthy (ReactNode) -> LoaderBoundary + OutletProvider + RouteContentWrapper
19
-
20
- - `src/route-content-wrapper.tsx` - `LoaderBoundary` and `RouteContentWrapper`.
21
- These add structural depth (Suspense boundaries) to the React tree.
22
-
23
- - `src/browser/server-action-bridge.ts` - Merges server action segments with
24
- cached segments. Must preserve cached `loading` values to prevent tree drift.
25
-
26
- - `src/browser/partial-update.ts` - Merges navigation segments with cached segments.
27
-
28
- **Rules:**
29
-
30
- 1. Never change the conditional logic in `renderSegments()` that decides between
31
- LoaderBoundary/RouteContentWrapper/OutletProvider without verifying all three
32
- render paths (SSR, navigation, action) produce identical tree structures.
33
-
34
- 2. Never add or remove wrapper elements (Suspense, div, Fragment) around segment
35
- content without checking that the same wrappers exist in ALL render paths.
36
-
37
- 3. When merging segments (action bridge, partial update), always preserve the
38
- cached `loading` value if it differs from the server value. The server may
39
- return different `loading` values based on `isSSR` context.
40
-
41
- 4. Run `pnpm --filter @rangojs/router exec playwright test loader-behavior` after
42
- any changes to these files. The skipSSR action tests specifically catch tree
43
- structure regressions.
@@ -1,103 +0,0 @@
1
- // Auto-generated by @rangojs/router - do not edit
2
- export {};
3
-
4
- declare global {
5
- namespace RSCRouter {
6
- interface GeneratedRouteMap {
7
- "apiShop.cart": "/api/shop/cart";
8
- "apiShop.cartItem": "/api/shop/cart/:itemId";
9
- "apiShop.catalog": "/api/shop/catalog";
10
- "apiShop.health": "/api/shop/health";
11
- "apiShop.product": "/api/shop/catalog/:productId";
12
- "blog.index": "/blog";
13
- "blog.post": "/blog/:postId";
14
- "cacheStatus.notFound": "/cache-status/not-found";
15
- "cacheStatus.redirect": "/cache-status/redirect";
16
- "cacheStatus.redirectTarget": "/cache-status/redirect-target";
17
- "cacheStatus.serverError": "/cache-status/server-error";
18
- "cacheStatus.success": "/cache-status/success";
19
- "cacheTest.cachedLoader": "/cache-test/cached-loader";
20
- "cacheTest.interceptDetail": "/cache-test/intercept/:itemId";
21
- "cacheTest.interceptIndex": "/cache-test/intercept";
22
- "cacheTest.nonCachedLoader": "/cache-test/non-cached-loader";
23
- "cacheTest.useLoaderDetail": "/cache-test/useloader/:itemId";
24
- "cacheTest.useLoaderIndex": "/cache-test/useloader";
25
- "changelog": "/changelog";
26
- "docs": "/docs";
27
- "docs.article": "/docs/:slug";
28
- "errors.clientError": "/errors/client-error";
29
- "errors.index": "/errors";
30
- "errors.serverError": "/errors/server-error";
31
- "errors.streamingError": "/errors/streaming-error";
32
- "fetchLoader": "/fetch-loader";
33
- "handlePassthrough": "/handle-passthrough";
34
- "handlePassthroughAsync": "/handle-passthrough-async";
35
- "hookTests.formAction": "/hook-tests/form-action";
36
- "hookTests.index": "/hook-tests";
37
- "hookTests.noLoader": "/hook-tests/no-loader";
38
- "hookTests.routeA": "/hook-tests/route-a";
39
- "hookTests.routeB": "/hook-tests/route-b";
40
- "href.detail": "/href/:id";
41
- "href.index": "/href";
42
- "href.nested.index": "/href/nested";
43
- "hydrationTest": "/hydration-test";
44
- "index": "/";
45
- "inlineAction": "/inline-action";
46
- "loaderComposition": "/loader-composition";
47
- "metaMerge.child": "/meta-merge/child";
48
- "metaMerge.deep": "/meta-merge/deep/nested";
49
- "metaMerge.index": "/meta-merge";
50
- "metaTemplate.absolute": "/meta-template/absolute";
51
- "metaTemplate.child": "/meta-template/child";
52
- "metaTemplate.index": "/meta-template";
53
- "metaTemplate.nested": "/meta-template/nested";
54
- "metaTemplate.nestedChild": "/meta-template/nested/child";
55
- "metaUnset.child": "/meta-unset/child";
56
- "metaUnset.index": "/meta-unset";
57
- "metaUnset.unsetThenSet": "/meta-unset/unset-then-set";
58
- "middlewareTest.cookies": "/middleware-test/cookies";
59
- "middlewareTest.errorHandler": "/middleware-test/error-handler/trigger";
60
- "middlewareTest.index": "/middleware-test";
61
- "middlewareTest.params": "/middleware-test/params/:paramId";
62
- "middlewareTest.protected": "/middleware-test/protected";
63
- "middlewareTest.protectedDashboard": "/middleware-test/protected/dashboard";
64
- "middlewareTest.routeLevel": "/middleware-test/route-level";
65
- "middlewareTest.routeLevelWithParams": "/middleware-test/route-level/:routeId";
66
- "middlewareTest.sharedVars": "/middleware-test/shared-vars";
67
- "negotiateJsonFirst": "/negotiate-test-json-first";
68
- "negotiateJsonFirstRsc": "/negotiate-test-json-first";
69
- "negotiateTest": "/negotiate-test";
70
- "negotiateTestJson": "/negotiate-test";
71
- "negotiateTestMd": "/negotiate-test";
72
- "proactiveCache.index": "/proactive-cache";
73
- "proactiveCache.itemA": "/proactive-cache/item-a";
74
- "proactiveCache.itemB": "/proactive-cache/item-b";
75
- "product.detail": "/product/:productId";
76
- "progressiveEnhancement": "/progressive-enhancement";
77
- "refTest.bothProps": "/ref-test/both-props";
78
- "refTest.handleProp": "/ref-test/handle-prop";
79
- "refTest.loaderProp": "/ref-test/loader-prop";
80
- "responseInLayout": "/response-in-layout";
81
- "responseInLayoutMd": "/response-in-layout-md";
82
- "responseMwMd": "/response-mw/md-with-mw";
83
- "responseMwNested": "/response-mw/nested";
84
- "responseWrapAuto": "/response-wrap/auto";
85
- "responseWrapCustom": "/response-wrap/custom-response";
86
- "responseWrapHtml": "/response-wrap/html";
87
- "responseWrapJsonHeaders": "/response-wrap/json-headers";
88
- "responseWrapText": "/response-wrap/text";
89
- "responseWrapWithHeaders": "/response-wrap/with-headers";
90
- "responseWrapXml": "/response-wrap/xml";
91
- "shopPlayground": "/shop-playground";
92
- "slow": "/slow";
93
- "slowProduct.detail": "/slow-product/:productId";
94
- "slowStreaming": "/slow-streaming";
95
- "slowStreamingSkipSsr": "/slow-streaming-skip-ssr";
96
- "theme.index": "/theme";
97
- "theme.toggle": "/theme/toggle";
98
- "trailingSlash.always": "/ts-always";
99
- "trailingSlash.ignore": "/ts-ignore";
100
- "trailingSlash.never": "/ts-never";
101
- }
102
- }
103
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * Simple LRU (Least Recently Used) cache implementation
3
- * Used for caching navigation segments to enable instant back/forward
4
- */
5
- export class LRUCache<K, V> {
6
- private cache = new Map<K, V>();
7
- private maxSize: number;
8
-
9
- constructor(maxSize: number) {
10
- this.maxSize = maxSize;
11
- }
12
-
13
- get(key: K): V | undefined {
14
- if (!this.cache.has(key)) {
15
- return undefined;
16
- }
17
-
18
- // Move to end (most recently used)
19
- const value = this.cache.get(key)!;
20
- this.cache.delete(key);
21
- this.cache.set(key, value);
22
- return value;
23
- }
24
-
25
- set(key: K, value: V): void {
26
- // If key exists, delete it first to update position
27
- if (this.cache.has(key)) {
28
- this.cache.delete(key);
29
- }
30
-
31
- this.cache.set(key, value);
32
-
33
- // Evict oldest entries if over capacity
34
- while (this.cache.size > this.maxSize) {
35
- const oldestKey = this.cache.keys().next().value;
36
- if (oldestKey !== undefined) {
37
- this.cache.delete(oldestKey);
38
- }
39
- }
40
- }
41
-
42
- has(key: K): boolean {
43
- if (!this.cache.has(key)) {
44
- return false;
45
- }
46
-
47
- // Move to end (most recently used) - same as get()
48
- const value = this.cache.get(key)!;
49
- this.cache.delete(key);
50
- this.cache.set(key, value);
51
- return true;
52
- }
53
-
54
- delete(key: K): boolean {
55
- return this.cache.delete(key);
56
- }
57
-
58
- clear(): void {
59
- this.cache.clear();
60
- }
61
-
62
- keys(): IterableIterator<K> {
63
- return this.cache.keys();
64
- }
65
-
66
- get size(): number {
67
- return this.cache.size;
68
- }
69
- }