@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.204030a9

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 (293) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +242 -55
  3. package/dist/bin/rango.js +277 -99
  4. package/dist/vite/index.js +2929 -1132
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +68 -21
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +159 -10
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +262 -51
  16. package/skills/host-router/SKILL.md +243 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +291 -31
  22. package/skills/middleware/SKILL.md +49 -12
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +197 -6
  28. package/skills/prerender/SKILL.md +125 -102
  29. package/skills/rango/SKILL.md +242 -23
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +91 -8
  33. package/skills/router-setup/SKILL.md +98 -8
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +511 -188
  37. package/skills/typesafety/SKILL.md +354 -50
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +117 -0
  42. package/src/__internal.ts +92 -0
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/link-interceptor.ts +4 -0
  50. package/src/browser/navigation-bridge.ts +183 -18
  51. package/src/browser/navigation-client.ts +187 -57
  52. package/src/browser/navigation-store.ts +75 -17
  53. package/src/browser/navigation-transaction.ts +21 -37
  54. package/src/browser/partial-update.ts +143 -40
  55. package/src/browser/prefetch/cache.ts +275 -28
  56. package/src/browser/prefetch/fetch.ts +191 -46
  57. package/src/browser/prefetch/policy.ts +6 -0
  58. package/src/browser/prefetch/queue.ts +123 -20
  59. package/src/browser/prefetch/resource-ready.ts +77 -0
  60. package/src/browser/rango-state.ts +53 -13
  61. package/src/browser/react/Link.tsx +98 -14
  62. package/src/browser/react/NavigationProvider.tsx +110 -33
  63. package/src/browser/react/context.ts +7 -2
  64. package/src/browser/react/filter-segment-order.ts +51 -7
  65. package/src/browser/react/index.ts +3 -0
  66. package/src/browser/react/location-state-shared.ts +175 -4
  67. package/src/browser/react/location-state.ts +39 -13
  68. package/src/browser/react/use-handle.ts +23 -64
  69. package/src/browser/react/use-navigation.ts +22 -2
  70. package/src/browser/react/use-params.ts +20 -8
  71. package/src/browser/react/use-reverse.ts +106 -0
  72. package/src/browser/react/use-router.ts +43 -10
  73. package/src/browser/react/use-segments.ts +11 -8
  74. package/src/browser/response-adapter.ts +25 -0
  75. package/src/browser/rsc-router.tsx +200 -75
  76. package/src/browser/scroll-restoration.ts +46 -39
  77. package/src/browser/segment-reconciler.ts +36 -9
  78. package/src/browser/segment-structure-assert.ts +2 -2
  79. package/src/browser/server-action-bridge.ts +31 -36
  80. package/src/browser/types.ts +81 -5
  81. package/src/build/collect-fallback-refs.ts +107 -0
  82. package/src/build/generate-manifest.ts +65 -40
  83. package/src/build/generate-route-types.ts +5 -0
  84. package/src/build/index.ts +2 -0
  85. package/src/build/route-trie.ts +69 -26
  86. package/src/build/route-types/codegen.ts +4 -4
  87. package/src/build/route-types/include-resolution.ts +9 -2
  88. package/src/build/route-types/per-module-writer.ts +7 -4
  89. package/src/build/route-types/router-processing.ts +278 -88
  90. package/src/build/route-types/scan-filter.ts +9 -2
  91. package/src/build/route-types/source-scan.ts +118 -0
  92. package/src/build/runtime-discovery.ts +9 -20
  93. package/src/cache/cache-runtime.ts +15 -11
  94. package/src/cache/cache-scope.ts +76 -49
  95. package/src/cache/cf/cf-cache-store.ts +501 -18
  96. package/src/cache/cf/index.ts +5 -1
  97. package/src/cache/document-cache.ts +17 -7
  98. package/src/cache/index.ts +1 -0
  99. package/src/cache/taint.ts +55 -0
  100. package/src/client.rsc.tsx +5 -1
  101. package/src/client.tsx +95 -284
  102. package/src/context-var.ts +72 -2
  103. package/src/debug.ts +2 -2
  104. package/src/decode-loader-results.ts +36 -0
  105. package/src/errors.ts +30 -1
  106. package/src/handle.ts +65 -12
  107. package/src/handles/breadcrumbs.ts +66 -0
  108. package/src/handles/index.ts +1 -0
  109. package/src/host/index.ts +2 -5
  110. package/src/host/router.ts +129 -57
  111. package/src/host/types.ts +31 -2
  112. package/src/host/utils.ts +1 -1
  113. package/src/href-client.ts +140 -20
  114. package/src/index.rsc.ts +15 -40
  115. package/src/index.ts +92 -76
  116. package/src/loader-store.ts +500 -0
  117. package/src/loader.rsc.ts +2 -5
  118. package/src/loader.ts +3 -10
  119. package/src/missing-id-error.ts +68 -0
  120. package/src/outlet-context.ts +1 -1
  121. package/src/prerender/store.ts +57 -15
  122. package/src/prerender.ts +141 -80
  123. package/src/response-utils.ts +37 -0
  124. package/src/reverse.ts +65 -15
  125. package/src/route-content-wrapper.tsx +6 -28
  126. package/src/route-definition/dsl-helpers.ts +435 -260
  127. package/src/route-definition/helper-factories.ts +29 -139
  128. package/src/route-definition/helpers-types.ts +110 -34
  129. package/src/route-definition/index.ts +3 -3
  130. package/src/route-definition/redirect.ts +11 -3
  131. package/src/route-definition/resolve-handler-use.ts +155 -0
  132. package/src/route-definition/use-item-types.ts +32 -0
  133. package/src/route-map-builder.ts +7 -1
  134. package/src/route-types.ts +37 -41
  135. package/src/router/basename.ts +14 -0
  136. package/src/router/content-negotiation.ts +113 -1
  137. package/src/router/error-handling.ts +1 -1
  138. package/src/router/find-match.ts +4 -2
  139. package/src/router/handler-context.ts +105 -39
  140. package/src/router/intercept-resolution.ts +15 -22
  141. package/src/router/lazy-includes.ts +12 -9
  142. package/src/router/loader-resolution.ts +175 -23
  143. package/src/router/logging.ts +5 -2
  144. package/src/router/manifest.ts +31 -16
  145. package/src/router/match-api.ts +129 -193
  146. package/src/router/match-handlers.ts +63 -20
  147. package/src/router/match-middleware/background-revalidation.ts +30 -2
  148. package/src/router/match-middleware/cache-lookup.ts +136 -106
  149. package/src/router/match-middleware/cache-store.ts +54 -10
  150. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  151. package/src/router/match-middleware/segment-resolution.ts +61 -5
  152. package/src/router/match-result.ts +124 -18
  153. package/src/router/metrics.ts +239 -14
  154. package/src/router/middleware-types.ts +61 -31
  155. package/src/router/middleware.ts +226 -124
  156. package/src/router/navigation-snapshot.ts +182 -0
  157. package/src/router/pattern-matching.ts +118 -19
  158. package/src/router/prerender-match.ts +114 -10
  159. package/src/router/preview-match.ts +32 -102
  160. package/src/router/request-classification.ts +286 -0
  161. package/src/router/revalidation.ts +85 -9
  162. package/src/router/route-snapshot.ts +245 -0
  163. package/src/router/router-context.ts +6 -1
  164. package/src/router/router-interfaces.ts +91 -29
  165. package/src/router/router-options.ts +89 -19
  166. package/src/router/router-registry.ts +2 -5
  167. package/src/router/segment-resolution/fresh.ts +240 -23
  168. package/src/router/segment-resolution/helpers.ts +30 -25
  169. package/src/router/segment-resolution/loader-cache.ts +1 -0
  170. package/src/router/segment-resolution/revalidation.ts +483 -289
  171. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  172. package/src/router/segment-wrappers.ts +2 -0
  173. package/src/router/substitute-pattern-params.ts +56 -0
  174. package/src/router/telemetry.ts +99 -0
  175. package/src/router/trie-matching.ts +38 -15
  176. package/src/router/types.ts +9 -0
  177. package/src/router/url-params.ts +49 -0
  178. package/src/router.ts +120 -32
  179. package/src/rsc/handler-context.ts +2 -2
  180. package/src/rsc/handler.ts +524 -370
  181. package/src/rsc/helpers.ts +91 -43
  182. package/src/rsc/index.ts +1 -21
  183. package/src/rsc/loader-fetch.ts +23 -3
  184. package/src/rsc/manifest-init.ts +5 -1
  185. package/src/rsc/origin-guard.ts +28 -10
  186. package/src/rsc/progressive-enhancement.ts +39 -10
  187. package/src/rsc/response-route-handler.ts +46 -53
  188. package/src/rsc/rsc-rendering.ts +69 -89
  189. package/src/rsc/runtime-warnings.ts +9 -10
  190. package/src/rsc/server-action.ts +39 -47
  191. package/src/rsc/ssr-setup.ts +144 -0
  192. package/src/rsc/types.ts +19 -3
  193. package/src/search-params.ts +20 -17
  194. package/src/segment-content-promise.ts +67 -0
  195. package/src/segment-loader-promise.ts +122 -0
  196. package/src/segment-system.tsx +219 -67
  197. package/src/serialize.ts +243 -0
  198. package/src/server/context.ts +285 -63
  199. package/src/server/cookie-store.ts +28 -4
  200. package/src/server/handle-store.ts +19 -0
  201. package/src/server/loader-registry.ts +9 -8
  202. package/src/server/request-context.ts +228 -65
  203. package/src/server.ts +6 -0
  204. package/src/ssr/index.tsx +9 -1
  205. package/src/static-handler.ts +19 -7
  206. package/src/testing/cache-status.ts +166 -0
  207. package/src/testing/collect-handle.ts +63 -0
  208. package/src/testing/dispatch.ts +440 -0
  209. package/src/testing/dom.entry.ts +22 -0
  210. package/src/testing/e2e/fixture.ts +154 -0
  211. package/src/testing/e2e/index.ts +149 -0
  212. package/src/testing/e2e/matchers.ts +51 -0
  213. package/src/testing/e2e/page-helpers.ts +272 -0
  214. package/src/testing/e2e/parity.ts +306 -0
  215. package/src/testing/e2e/server.ts +183 -0
  216. package/src/testing/flight-matchers.ts +104 -0
  217. package/src/testing/flight-runtime.d.ts +21 -0
  218. package/src/testing/flight.entry.ts +22 -0
  219. package/src/testing/flight.ts +182 -0
  220. package/src/testing/generated-routes.ts +223 -0
  221. package/src/testing/index.ts +98 -0
  222. package/src/testing/internal/context.ts +151 -0
  223. package/src/testing/render-route.tsx +536 -0
  224. package/src/testing/run-loader.ts +296 -0
  225. package/src/testing/run-middleware.ts +170 -0
  226. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  227. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  228. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  229. package/src/testing/vitest-stubs/version.ts +5 -0
  230. package/src/testing/vitest.ts +112 -0
  231. package/src/theme/index.ts +4 -13
  232. package/src/types/cache-types.ts +4 -4
  233. package/src/types/global-namespace.ts +39 -26
  234. package/src/types/handler-context.ts +197 -79
  235. package/src/types/index.ts +1 -0
  236. package/src/types/loader-types.ts +41 -15
  237. package/src/types/request-scope.ts +126 -0
  238. package/src/types/route-config.ts +17 -8
  239. package/src/types/route-entry.ts +19 -1
  240. package/src/types/segments.ts +37 -6
  241. package/src/urls/include-helper.ts +34 -67
  242. package/src/urls/index.ts +0 -3
  243. package/src/urls/path-helper-types.ts +50 -9
  244. package/src/urls/path-helper.ts +63 -63
  245. package/src/urls/pattern-types.ts +48 -19
  246. package/src/urls/response-types.ts +25 -22
  247. package/src/urls/type-extraction.ts +26 -116
  248. package/src/urls/urls-function.ts +1 -5
  249. package/src/use-loader.tsx +487 -44
  250. package/src/vite/debug.ts +185 -0
  251. package/src/vite/discovery/bundle-postprocess.ts +63 -91
  252. package/src/vite/discovery/discover-routers.ts +106 -53
  253. package/src/vite/discovery/discovery-errors.ts +194 -0
  254. package/src/vite/discovery/gate-state.ts +171 -0
  255. package/src/vite/discovery/prerender-collection.ts +222 -107
  256. package/src/vite/discovery/route-types-writer.ts +40 -84
  257. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  258. package/src/vite/discovery/state.ts +50 -13
  259. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  260. package/src/vite/index.ts +10 -3
  261. package/src/vite/plugin-types.ts +111 -72
  262. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  263. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  265. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  266. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  267. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  268. package/src/vite/plugins/expose-action-id.ts +55 -33
  269. package/src/vite/plugins/expose-id-utils.ts +24 -8
  270. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  271. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  272. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  273. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  274. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  275. package/src/vite/plugins/performance-tracks.ts +92 -0
  276. package/src/vite/plugins/refresh-cmd.ts +127 -0
  277. package/src/vite/plugins/use-cache-transform.ts +65 -50
  278. package/src/vite/plugins/version-injector.ts +39 -23
  279. package/src/vite/plugins/version-plugin.ts +72 -3
  280. package/src/vite/plugins/virtual-entries.ts +2 -2
  281. package/src/vite/rango.ts +265 -226
  282. package/src/vite/router-discovery.ts +924 -137
  283. package/src/vite/utils/ast-handler-extract.ts +15 -15
  284. package/src/vite/utils/banner.ts +4 -4
  285. package/src/vite/utils/bundle-analysis.ts +4 -2
  286. package/src/vite/utils/client-chunks.ts +190 -0
  287. package/src/vite/utils/forward-user-plugins.ts +193 -0
  288. package/src/vite/utils/manifest-utils.ts +21 -5
  289. package/src/vite/utils/package-resolution.ts +41 -1
  290. package/src/vite/utils/prerender-utils.ts +98 -5
  291. package/src/vite/utils/shared-utils.ts +109 -27
  292. package/src/browser/action-response-classifier.ts +0 -99
  293. package/src/route-definition/route-function.ts +0 -119
@@ -1,3 +1,14 @@
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
+
1
12
  /**
2
13
  * Escape special RegExp characters in a string for safe interpolation
3
14
  * into new RegExp() patterns.
@@ -20,6 +31,7 @@ export function encodePathParam(value: unknown): string {
20
31
  /**
21
32
  * Substitute route params into a pattern, stripping constraint and optional
22
33
  * syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
34
+ * Optional params not present in `params` are removed from the output.
23
35
  */
24
36
  export function substituteRouteParams(
25
37
  pattern: string,
@@ -27,14 +39,46 @@ export function substituteRouteParams(
27
39
  encode: (value: string) => string = encodeURIComponent,
28
40
  ): string {
29
41
  let result = pattern;
42
+ let hadOmittedOptional = false;
43
+
44
+ // First pass: substitute provided params.
45
+ // Empty string on an optional placeholder is treated as omitted —
46
+ // caller-supplied params or `getParams()` shapes may pass `""` for an
47
+ // absent optional, so letting the second pass strip them keeps slash
48
+ // cleanup consistent. Empty string on required `:key` or wildcard
49
+ // `*key` still substitutes, matching prior behaviour.
30
50
  for (const [key, value] of Object.entries(params)) {
31
51
  const escaped = escapeRegExp(key);
32
- result = result.replace(
33
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
34
- encode(value),
35
- );
36
- result = result.replace(`*${key}`, encode(value));
52
+ if (value === "") {
53
+ // Only replace required placeholders (negative lookahead for `?`);
54
+ // leave `:key?` for the second pass.
55
+ result = result.replace(
56
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
57
+ "",
58
+ );
59
+ result = result.replace(`*${key}`, "");
60
+ } else {
61
+ result = result.replace(
62
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
63
+ encode(value),
64
+ );
65
+ result = result.replace(`*${key}`, encode(value));
66
+ }
67
+ }
68
+
69
+ // Second pass: strip remaining optional param placeholders not in params
70
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
71
+ hadOmittedOptional = true;
72
+ return "";
73
+ });
74
+
75
+ // Clean up slashes from omitted optional segments
76
+ if (hadOmittedOptional) {
77
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
78
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
79
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
37
80
  }
81
+
38
82
  return result;
39
83
  }
40
84
 
@@ -127,3 +171,52 @@ export function notifyOnError(
127
171
  break; // Only notify the first router with onError
128
172
  }
129
173
  }
174
+
175
+ function getStagedAssetDir(projectRoot: string): string {
176
+ return resolve(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
177
+ }
178
+
179
+ export function resetStagedBuildAssets(projectRoot: string): void {
180
+ rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
181
+ }
182
+
183
+ export function stageBuildAssetModule(
184
+ projectRoot: string,
185
+ prefix: "__pr" | "__st",
186
+ exportValue: string,
187
+ ): string {
188
+ const stagedDir = getStagedAssetDir(projectRoot);
189
+ mkdirSync(stagedDir, { recursive: true });
190
+
191
+ const contentHash = createHash("sha256")
192
+ .update(exportValue)
193
+ .digest("hex")
194
+ .slice(0, 8);
195
+ const fileName = `${prefix}-${contentHash}.js`;
196
+ const filePath = resolve(stagedDir, fileName);
197
+
198
+ if (!existsSync(filePath)) {
199
+ writeFileSync(filePath, `export default ${exportValue};\n`);
200
+ }
201
+
202
+ return fileName;
203
+ }
204
+
205
+ export function copyStagedBuildAssets(
206
+ projectRoot: string,
207
+ fileNames: Iterable<string>,
208
+ ): number {
209
+ const stagedDir = getStagedAssetDir(projectRoot);
210
+ const distAssetsDir = resolve(projectRoot, "dist/rsc/assets");
211
+ mkdirSync(distAssetsDir, { recursive: true });
212
+
213
+ let totalBytes = 0;
214
+ for (const fileName of new Set(fileNames)) {
215
+ const stagedPath = resolve(stagedDir, fileName);
216
+ const distPath = resolve(distAssetsDir, fileName);
217
+ copyFileSync(stagedPath, distPath);
218
+ totalBytes += statSync(stagedPath).size;
219
+ }
220
+
221
+ return totalBytes;
222
+ }
@@ -1,43 +1,57 @@
1
- import type { Plugin } from "vite";
1
+ import type { Plugin, ResolvedConfig } from "vite";
2
2
  import * as Vite from "vite";
3
3
  import { getPublishedPackageName } from "./package-resolution.js";
4
+ import { performanceTracksOptimizeDepsPlugin } from "../plugins/performance-tracks.js";
4
5
  import {
5
6
  VIRTUAL_ENTRY_BROWSER,
6
7
  VIRTUAL_ENTRY_SSR,
7
8
  getVirtualEntryRSC,
9
+ getVirtualVersionContent,
8
10
  VIRTUAL_IDS,
9
11
  } from "../plugins/virtual-entries.js";
10
12
 
13
+ // Cloudflare preset: @cloudflare/vite-plugin sets optimizeDeps.entries (string
14
+ // or array) on the rsc environment. Single source for both the discovery plugin
15
+ // and the version injector so they target the same entry.
16
+ export function resolveRscEntryFromConfig(
17
+ config: ResolvedConfig,
18
+ ): string | undefined {
19
+ const entries = (config.environments as any)?.["rsc"]?.optimizeDeps?.entries;
20
+ if (typeof entries === "string") return entries;
21
+ if (Array.isArray(entries) && entries.length > 0) return entries[0];
22
+ return undefined;
23
+ }
24
+
11
25
  /**
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.
26
+ * Rolldown plugin to provide the version virtual module during dependency
27
+ * optimization. Vite 8 optimizes deps with Rolldown (a Rollup-style plugin
28
+ * pipeline that is separate from the main plugin set), so this is a
29
+ * resolveId/load plugin under optimizeDeps.rolldownOptions. Any dep pulled into
30
+ * optimization that imports the version virtual module gets a "dev" stub here;
31
+ * the real VERSION is injected into runtime modules by the version plugin.
15
32
  */
16
- const versionEsbuildPlugin = {
33
+ const versionRolldownPlugin = {
17
34
  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
- );
35
+ resolveId(id: string): string | undefined {
36
+ if (id === VIRTUAL_IDS.version) return "\0" + VIRTUAL_IDS.version;
37
+ return undefined;
38
+ },
39
+ load(id: string): string | undefined {
40
+ if (id === "\0" + VIRTUAL_IDS.version) {
41
+ return getVirtualVersionContent("dev");
42
+ }
43
+ return undefined;
30
44
  },
31
45
  };
32
46
 
33
47
  /**
34
- * Shared esbuild options for dependency optimization.
35
- * Includes the version stub plugin for all environments.
48
+ * Shared Rolldown options for dependency optimization (Vite 8).
49
+ * Includes the version stub plugin and the performance-tracks RSDW patch.
36
50
  */
37
- export const sharedEsbuildOptions: {
38
- plugins: (typeof versionEsbuildPlugin)[];
51
+ export const sharedRolldownOptions: {
52
+ plugins: any[];
39
53
  } = {
40
- plugins: [versionEsbuildPlugin],
54
+ plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()],
41
55
  };
42
56
 
43
57
  /**
@@ -102,14 +116,68 @@ export function createVirtualEntriesPlugin(
102
116
  };
103
117
  }
104
118
 
119
+ // Matches rollup's FILE_NAME_CONFLICT message and reports whether the colliding
120
+ // file is a content-hashed asset, e.g.
121
+ // The emitted file "assets/index-DlGNrvnU.css" overwrites a previously ...
122
+ // The emitted file "assets/inter-latin-Dx4kXJAl.woff2" overwrites a ...
123
+ // The match is UNANCHORED on purpose: by the time the warning reaches this user
124
+ // onwarn handler, Vite's logger has wrapped rollup's raw message with an ANSI
125
+ // color sequence and a "[CODE] " label, e.g.
126
+ // "[FILE_NAME_CONFLICT] The emitted file \"...\" overwrites ..."
127
+ // A "^The emitted file" anchor sits behind that prefix and never matches; and
128
+ // Vite also strips the JSON.stringify quotes rollup puts around the filename, so
129
+ // the match is UNANCHORED and quote-OPTIONAL ("?...?"?). The non-whitespace
130
+ // capture stops at the space before "overwrites" (Vite's unquoted display form)
131
+ // or the closing quote (raw rollup form); either way it carries no ANSI.
132
+ // A content-hashed name ends with a "-" separator + a Vite content hash. The
133
+ // hash is a FIXED-LENGTH base64url run ([A-Za-z0-9_-], default 8), so it can
134
+ // itself contain "-"/"_": it CANNOT be located by splitting on the last "-"
135
+ // (that lands inside the hash whenever it carries a dash, e.g. "...-Cabi7G8-" ->
136
+ // "" or "...-CkhJZR-_" -> "_", which let those conflicts leak). Instead take the
137
+ // trailing HASH_LEN chars and require the "-" separator right before them. The
138
+ // hash must hold an uppercase letter or digit (a real hash is never an
139
+ // all-lowercase word), so stable names like "assets/manifest.json" or
140
+ // "assets/loading-skeleton.css" still surface as potential genuine overwrites.
141
+ function isContentHashedAssetConflict(message: string | undefined): boolean {
142
+ if (!message) return false;
143
+ const match =
144
+ /The emitted file "?([^"\s]+)"? overwrites a previously emitted file/.exec(
145
+ message,
146
+ );
147
+ if (!match) return false;
148
+ const fileName = match[1];
149
+ const base = fileName.slice(fileName.lastIndexOf("/") + 1);
150
+ const dot = base.lastIndexOf(".");
151
+ if (dot <= 0) return false;
152
+ const stem = base.slice(0, dot);
153
+ // HASH_LEN tracks Vite's default [hash] width; bump it if an app sets a custom
154
+ // assetFileNames hash length.
155
+ const HASH_LEN = 8;
156
+ if (stem.length < HASH_LEN + 1 || stem[stem.length - HASH_LEN - 1] !== "-") {
157
+ return false;
158
+ }
159
+ const hash = stem.slice(-HASH_LEN);
160
+ return /^[A-Za-z0-9_-]+$/.test(hash) && /[A-Z0-9]/.test(hash);
161
+ }
162
+
105
163
  /**
106
164
  * Rollup onwarn handler that suppresses known harmless warnings:
107
165
  * - "use client" directives: handled by the RSC plugin, not relevant to Rollup
108
166
  * - sourcemap errors: caused by "use client" directive at line 1:0 confusing sourcemap resolution
109
167
  * - 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)
168
+ * - dynamic/static mixed imports: expected for router internals (e.g. request-context, cache-scope).
169
+ * Under Rolldown (Vite 8) this surfaces as the INEFFECTIVE_DYNAMIC_IMPORT code emitted directly
170
+ * by the bundler, rather than the vite:reporter message handled below (Rollup/Vite 7 shape).
111
171
  * - empty bundle: @vitejs/plugin-rsc scan build (step 1/5) produces an empty "index" chunk
112
172
  * because the RSC entry is fully externalized during client-reference analysis
173
+ * - file name conflicts on content-hashed assets: @vitejs/plugin-rsc copies the rsc
174
+ * environment's imported CSS/assets into the client bundle (its assets-manifest
175
+ * generateBundle re-emits each via emitFile with an explicit content-hashed
176
+ * fileName). When the client bundle already produced that identical asset,
177
+ * rollup raises FILE_NAME_CONFLICT even though the bytes are identical (a
178
+ * content hash collision IS a content match). Only these are suppressed; a
179
+ * collision on a stable name still surfaces. No upstream fix as of
180
+ * @vitejs/plugin-rsc@0.5.27; remove when it skips the redundant emit.
113
181
  */
114
182
  export function onwarn(
115
183
  warning: Vite.Rollup.RollupLog,
@@ -118,7 +186,14 @@ export function onwarn(
118
186
  if (
119
187
  warning.code === "MODULE_LEVEL_DIRECTIVE" ||
120
188
  warning.code === "SOURCEMAP_ERROR" ||
121
- warning.code === "EMPTY_BUNDLE"
189
+ warning.code === "EMPTY_BUNDLE" ||
190
+ warning.code === "INEFFECTIVE_DYNAMIC_IMPORT"
191
+ ) {
192
+ return;
193
+ }
194
+ if (
195
+ warning.code === "FILE_NAME_CONFLICT" &&
196
+ isContentHashedAssetConflict(warning.message)
122
197
  ) {
123
198
  return;
124
199
  }
@@ -156,12 +231,19 @@ export function getManualChunks(id: string): string | undefined {
156
231
  return "react";
157
232
  }
158
233
  // Use dynamic package name from package.json
159
- // Check both npm install path and workspace symlink resolved path
234
+ // Check both npm install path and workspace symlink resolved path.
235
+ //
236
+ // The workspace patterns are anchored to the package's own `src`/`dist` so
237
+ // they match the router runtime but NOT consumer apps that merely live under a
238
+ // `packages/rangojs-router/` ancestor (the in-repo e2e apps at
239
+ // `packages/rangojs-router/e2e/<app>/src/...`). Without the anchor those apps'
240
+ // own client components were force-merged into the shared "router" chunk,
241
+ // which both misrepresented real-consumer bundles and blocked `clientChunks`
242
+ // splitting from relocating them.
160
243
  const packageName = getPublishedPackageName();
161
244
  if (
162
245
  normalized.includes(`node_modules/${packageName}/`) ||
163
- normalized.includes("packages/rsc-router/") ||
164
- normalized.includes("packages/rangojs-router/")
246
+ /\/packages\/(rsc-router|rangojs-router)\/(src|dist)\//.test(normalized)
165
247
  ) {
166
248
  return "router";
167
249
  }
@@ -1,99 +0,0 @@
1
- /**
2
- * Discriminated union of post-reconciliation action response scenarios.
3
- *
4
- * Error and full-update-unsupported are handled inline in the bridge
5
- * before reconciliation. This classifier only runs for partial responses
6
- * that have been successfully reconciled.
7
- */
8
- export type ActionScenario =
9
- | {
10
- type: "navigated-away";
11
- historyKeyChanged: boolean;
12
- onInterceptRoute: boolean;
13
- }
14
- | { type: "hmr-missing" }
15
- | { type: "consolidation-needed"; segmentIds: string[] }
16
- | { type: "concurrent-skip"; otherFetchingCount: number }
17
- | { type: "normal" };
18
-
19
- /**
20
- * Pure data inputs for classifying a partial action response.
21
- * All values come from the bridge but no browser APIs or side effects.
22
- */
23
- export interface ClassifierInput {
24
- /** window.location.pathname captured at action start */
25
- actionStartPathname: string;
26
- /** window.location.pathname at classification time */
27
- currentPathname: string;
28
- /** window.history.state?.key captured at action start */
29
- actionStartLocationKey: string | undefined;
30
- /** window.history.state?.key at classification time */
31
- currentLocationKey: string | undefined;
32
- /** Number of segments after reconciliation */
33
- reconciledSegmentCount: number;
34
- /** Number of matched segment IDs from server */
35
- matchedCount: number;
36
- /** Segment IDs needing consolidation (from concurrent action tracking) */
37
- consolidationSegments: string[] | null;
38
- /** Number of other actions still in "fetching" phase */
39
- otherFetchingActionCount: number;
40
- /** Current intercept source URL (null when not on intercept route) */
41
- currentInterceptSource: string | null;
42
- }
43
-
44
- /**
45
- * Classify a partial action response into one of 5 post-reconciliation
46
- * scenarios.
47
- *
48
- * Called after error and full-update cases are handled inline by the bridge.
49
- * The classification order matches the priority chain:
50
- * 1. User navigated away during action
51
- * 2. HMR missing segments (fewer reconciled than matched)
52
- * 3. Consolidation needed (concurrent actions finished)
53
- * 4. Concurrent skip (other actions still fetching)
54
- * 5. Normal (single action, no issues)
55
- *
56
- * This is a pure function with no side effects - the bridge handles
57
- * all UI updates, store mutations, and network requests based on the
58
- * returned scenario.
59
- */
60
- export function classifyActionResponse(input: ClassifierInput): ActionScenario {
61
- // Check if user navigated away during the action
62
- const userNavigatedAway =
63
- input.currentPathname !== input.actionStartPathname ||
64
- input.currentLocationKey !== input.actionStartLocationKey;
65
-
66
- if (userNavigatedAway) {
67
- const historyKeyChanged =
68
- input.currentLocationKey !== input.actionStartLocationKey;
69
- return {
70
- type: "navigated-away",
71
- historyKeyChanged,
72
- onInterceptRoute: input.currentInterceptSource !== null,
73
- };
74
- }
75
-
76
- // HMR resilience: segments missing after reconciliation
77
- if (input.reconciledSegmentCount < input.matchedCount) {
78
- return { type: "hmr-missing" };
79
- }
80
-
81
- // Consolidation needed for concurrent actions
82
- if (input.consolidationSegments && input.consolidationSegments.length > 0) {
83
- return {
84
- type: "consolidation-needed",
85
- segmentIds: input.consolidationSegments,
86
- };
87
- }
88
-
89
- // Other actions still fetching - skip UI update
90
- if (input.otherFetchingActionCount > 0) {
91
- return {
92
- type: "concurrent-skip",
93
- otherFetchingCount: input.otherFetchingActionCount,
94
- };
95
- }
96
-
97
- // Normal single-action completion
98
- return { type: "normal" };
99
- }
@@ -1,119 +0,0 @@
1
- import type {
2
- ResolvedRouteMap,
3
- RouteConfig,
4
- RouteDefinition,
5
- RouteDefinitionOptions,
6
- TrailingSlashMode,
7
- } from "../types.js";
8
-
9
- /**
10
- * Result of route() function with paths and trailing slash config
11
- */
12
- export interface RouteDefinitionResult<T extends RouteDefinition> {
13
- routes: ResolvedRouteMap<T>;
14
- trailingSlash: Record<string, TrailingSlashMode>;
15
- }
16
-
17
- /**
18
- * Check if a value is a RouteConfig object
19
- */
20
- function isRouteConfig(value: unknown): value is RouteConfig {
21
- return (
22
- typeof value === "object" &&
23
- value !== null &&
24
- "path" in value &&
25
- typeof (value as RouteConfig).path === "string"
26
- );
27
- }
28
-
29
- /**
30
- * Define routes with optional trailing slash configuration
31
- *
32
- * @example
33
- * ```typescript
34
- * // Simple string paths
35
- * const routes = route({
36
- * blog: "/blog",
37
- * post: "/blog/:id",
38
- * });
39
- *
40
- * // With trailing slash config
41
- * const routes = route({
42
- * blog: "/blog",
43
- * api: { path: "/api", trailingSlash: "ignore" },
44
- * }, { trailingSlash: "never" }); // global default
45
- * ```
46
- */
47
- export function route<const T extends RouteDefinition>(
48
- input: T,
49
- options?: RouteDefinitionOptions,
50
- ): ResolvedRouteMap<T> & {
51
- __trailingSlash?: Record<string, TrailingSlashMode>;
52
- } {
53
- const trailingSlash: Record<string, TrailingSlashMode> = {};
54
- const routes = flattenRoutes(
55
- input as RouteDefinition,
56
- "",
57
- trailingSlash,
58
- options?.trailingSlash,
59
- );
60
-
61
- // Attach trailing slash config as a non-enumerable property
62
- // This keeps backwards compatibility while passing the config through
63
- const result = routes as ResolvedRouteMap<T> & {
64
- __trailingSlash?: Record<string, TrailingSlashMode>;
65
- };
66
- if (Object.keys(trailingSlash).length > 0) {
67
- Object.defineProperty(result, "__trailingSlash", {
68
- value: trailingSlash,
69
- enumerable: false,
70
- writable: false,
71
- });
72
- }
73
-
74
- return result;
75
- }
76
-
77
- /**
78
- * Flatten nested route definitions
79
- */
80
- function flattenRoutes(
81
- routes: RouteDefinition,
82
- prefix: string,
83
- trailingSlashConfig: Record<string, TrailingSlashMode>,
84
- defaultTrailingSlash?: TrailingSlashMode,
85
- ): Record<string, string> {
86
- const flattened: Record<string, string> = {};
87
-
88
- for (const [key, value] of Object.entries(routes)) {
89
- const fullKey = prefix + key;
90
-
91
- if (typeof value === "string") {
92
- // Direct route pattern - include prefix
93
- flattened[fullKey] = value;
94
- // Apply default trailing slash if set
95
- if (defaultTrailingSlash) {
96
- trailingSlashConfig[fullKey] = defaultTrailingSlash;
97
- }
98
- } else if (isRouteConfig(value)) {
99
- // Route config object with path and optional trailingSlash
100
- flattened[fullKey] = value.path;
101
- // Use route-specific config or fall back to default
102
- const mode = value.trailingSlash ?? defaultTrailingSlash;
103
- if (mode) {
104
- trailingSlashConfig[fullKey] = mode;
105
- }
106
- } else {
107
- // Nested routes - flatten recursively
108
- const nested = flattenRoutes(
109
- value,
110
- `${fullKey}.`,
111
- trailingSlashConfig,
112
- defaultTrailingSlash,
113
- );
114
- Object.assign(flattened, nested);
115
- }
116
- }
117
-
118
- return flattened;
119
- }