@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,305 @@
1
+ /**
2
+ * @rangojs/router/testing/vitest
3
+ *
4
+ * Vitest setup helper for the UNIT + INTEGRATION + DOM test project of a
5
+ * @rangojs/router consumer app. It returns the `resolve.alias` entries that make
6
+ * a real app's router / loaders / middleware importable in a bare Vitest process.
7
+ *
8
+ * Why this is needed (the documented "vi.mock(plugin-rsc) + import router"
9
+ * recipe is not sufficient for a real app):
10
+ *
11
+ * - `@rangojs/router` resolves to SERVER-ONLY STUBS outside the `react-server`
12
+ * condition — `urls()`, `createRouter()`, `cookies()`, `getRequestContext()`
13
+ * throw "only available in a react-server environment". Importing the app's own
14
+ * router/loaders/middleware then fails immediately. Vitest does NOT apply the
15
+ * `react-server` condition to bare-package exports resolution, and enabling it
16
+ * globally flips React to its server build (no `createContext`), crashing the
17
+ * router's client-boundary imports. The surgical fix is to alias ONLY the bare
18
+ * `@rangojs/router` specifier to its react-server entry (real impls) while
19
+ * leaving React as the client build — which is exactly what this helper does.
20
+ * - The build-only `@rangojs/router:version` virtual and `@vitejs/plugin-rsc/rsc`
21
+ * (whose real body imports unresolvable Vite virtuals) are stubbed.
22
+ * - Cloudflare apps additionally import the `cloudflare:workers` /
23
+ * `cloudflare:email` runtime virtuals; pass `{ preset: "cloudflare" }` to stub them.
24
+ *
25
+ * Usage (recommended one-call form — see {@link rangoTestConfig}):
26
+ *
27
+ * ```ts
28
+ * // vitest.config.ts
29
+ * import { defineConfig } from "vitest/config";
30
+ * import { rangoTestConfig } from "@rangojs/router/testing/vitest";
31
+ *
32
+ * export default defineConfig({
33
+ * test: {
34
+ * globals: true,
35
+ * include: ["test/**\/*.test.{ts,tsx}"],
36
+ * environment: "node",
37
+ * ...rangoTestConfig({ preset: "cloudflare" }),
38
+ * },
39
+ * });
40
+ * ```
41
+ *
42
+ * `rangoTestConfig` bundles the resolve aliases ({@link rangoTestAliases}) with
43
+ * the `server.deps.inline` contract ({@link rangoInlineDeps}) an installed
44
+ * consumer needs — @rangojs/router ships as TS source, and without `deps.inline`
45
+ * Vitest hands those `.ts` files to Node, which on Node >= 23 throws
46
+ * `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. Use the lower-level
47
+ * `rangoTestAliases` directly only if you wire `deps.inline` yourself.
48
+ *
49
+ * Notes:
50
+ * - The Flight project (real RSC rendering via `@rangojs/router/testing/flight`)
51
+ * uses the `react-server` condition AND needs this same alias whenever a
52
+ * rendered handler/component imports a server API (`getRequestContext`,
53
+ * `cookies`) from the bare `@rangojs/router` — without it that import resolves
54
+ * to the throwing out-of-react-server stub (`resolve.conditions` alone is not
55
+ * reliably applied to bare-package export resolution). The alias points at
56
+ * `index.rsc.ts` (the real react-server build) and leaves React itself
57
+ * untouched, so it does NOT crash the server React build. The router's OWN
58
+ * Flight tests omit it only because they import via RELATIVE paths, not the
59
+ * bare specifier; a consumer importing the bare specifier must include it. See
60
+ * the testing skill (`skills/testing/setup.md`, shipped in the package) for
61
+ * the complete Flight config.
62
+ * - `renderRoute` (`@rangojs/router/testing/dom`) tests run in this same project
63
+ * under a DOM environment (`happy-dom`/`jsdom`); the alias does not affect them.
64
+ * - A router using `Prerender()` / `createLoader()` / `Static()` now CONSTRUCTS in
65
+ * a bare test: each assigns a process-stable runtime fallback `$$id` ONLY under
66
+ * a test runner (`process.env.VITEST`), so `createRouter().routes(...)` builds
67
+ * without the "missing `$$id`" throw (for `dispatch` / `assertGeneratedRoutesMatch`).
68
+ * Outside a test runner (a real build) a missing id still THROWS — so an
69
+ * unsupported handler shape the plugin skipped (e.g. `export let`) fails loud
70
+ * rather than getting a silent synthetic id. (The plugin always injects for
71
+ * supported `export const` shapes, and the static manifest keys on that id.)
72
+ * - Importing your app's whole router *file* can still fail for app-specific
73
+ * reasons (page modules pulling their own deps, or plugin `virtual:` modules
74
+ * that need the rango plugin) — build whole-router `dispatch`/drift checks from
75
+ * a focused include, or use e2e.
76
+ */
77
+
78
+ import { fileURLToPath } from "node:url";
79
+
80
+ /** A single Vite/Vitest resolve alias entry. Structurally a Vite `Alias`. */
81
+ export interface TestAlias {
82
+ find: string | RegExp;
83
+ replacement: string;
84
+ }
85
+
86
+ /** Options for {@link rangoTestAliases}. */
87
+ export interface RangoTestAliasOptions {
88
+ /**
89
+ * Deployment preset, matching `rango({ preset })` in the Vite plugin. With
90
+ * `"cloudflare"` the helper additionally stubs the Cloudflare Workers runtime
91
+ * virtuals (`cloudflare:workers` / `cloudflare:email`) a CF app's route tree
92
+ * imports. A string (not a boolean) so more presets can be added without an
93
+ * API change. Default: `"node"`.
94
+ */
95
+ preset?: "node" | "cloudflare";
96
+ }
97
+
98
+ /**
99
+ * Resolve a path relative to this module. Anchored at the PACKAGE ROOT
100
+ * (`../../` from both `src/testing/vitest.ts` and the shipped
101
+ * `dist/testing/vitest.js` — each is two levels below the root), so the alias
102
+ * targets always point at the `src/*.ts` files Vite transpiles at test time,
103
+ * regardless of whether this helper is loaded as source (in-repo) or as the
104
+ * compiled `dist` entry (an installed consumer).
105
+ */
106
+ function here(relativeFromRoot: string): string {
107
+ return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
108
+ }
109
+
110
+ /**
111
+ * Build the `resolve.alias` entries a consumer's node/DOM Vitest project needs to
112
+ * import a real @rangojs/router app's router/loaders/middleware. Spread into a
113
+ * Vitest config: `resolve: { alias: rangoTestAliases(...) }` (concat your own
114
+ * aliases as needed).
115
+ */
116
+ export function rangoTestAliases(
117
+ opts: RangoTestAliasOptions = {},
118
+ ): TestAlias[] {
119
+ const aliases: TestAlias[] = [
120
+ // Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
121
+ // subpaths (/testing, /client, /cache, ...) are untouched. React stays the
122
+ // client build, so createContext and "use client" modules work.
123
+ { find: /^@rangojs\/router$/, replacement: here("src/index.rsc.ts") },
124
+ {
125
+ find: "@rangojs/router:version",
126
+ replacement: here("src/testing/vitest-stubs/version.ts"),
127
+ },
128
+ {
129
+ find: /^@vitejs\/plugin-rsc\/rsc$/,
130
+ replacement: here("src/testing/vitest-stubs/plugin-rsc.ts"),
131
+ },
132
+ ];
133
+
134
+ if (opts.preset === "cloudflare") {
135
+ aliases.push(
136
+ {
137
+ find: "cloudflare:workers",
138
+ replacement: here("src/testing/vitest-stubs/cloudflare-workers.ts"),
139
+ },
140
+ {
141
+ find: "cloudflare:email",
142
+ replacement: here("src/testing/vitest-stubs/cloudflare-email.ts"),
143
+ },
144
+ );
145
+ }
146
+
147
+ return aliases;
148
+ }
149
+
150
+ /**
151
+ * Vitest `server.deps.inline` patterns that force Vite (not Node) to transpile
152
+ * @rangojs/router's TypeScript source under test.
153
+ *
154
+ * REQUIRED for an installed (node_modules) consumer: @rangojs/router ships as TS
155
+ * source, and Vitest externalizes node_modules by default — so without this Node
156
+ * loads the `.ts` files directly and, on Node >= 23, throws
157
+ * `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. In this monorepo it is a no-op
158
+ * (the workspace symlink resolves to a realpath outside node_modules, which Vite
159
+ * already transpiles), which is precisely why an in-repo dogfood never surfaces
160
+ * the need and the contract has to be shipped explicitly.
161
+ */
162
+ export const rangoInlineDeps: RegExp[] = [/@rangojs[/\\]router/];
163
+
164
+ /** The Vitest `test`-block fragment {@link rangoTestConfig} returns. */
165
+ export interface RangoTestConfig {
166
+ alias: TestAlias[];
167
+ server: { deps: { inline: RegExp[] } };
168
+ }
169
+
170
+ /**
171
+ * The complete Vitest `test`-block fragment a consumer needs: the resolve
172
+ * aliases ({@link rangoTestAliases}) AND the `server.deps.inline` contract
173
+ * ({@link rangoInlineDeps}). Spread it into your `test` block so both land in
174
+ * one place and a consumer cannot forget the `deps.inline` half (omitting it
175
+ * loads rango's TS source through Node and breaks on Node >= 23):
176
+ *
177
+ * ```ts
178
+ * // vitest.config.ts
179
+ * import { defineConfig } from "vitest/config";
180
+ * import { rangoTestConfig } from "@rangojs/router/testing/vitest";
181
+ *
182
+ * export default defineConfig({
183
+ * test: {
184
+ * globals: true,
185
+ * include: ["test/**\/*.test.{ts,tsx}"],
186
+ * environment: "node",
187
+ * ...rangoTestConfig({ preset: "cloudflare" }),
188
+ * },
189
+ * });
190
+ * ```
191
+ */
192
+ export function rangoTestConfig(
193
+ opts: RangoTestAliasOptions = {},
194
+ ): RangoTestConfig {
195
+ return {
196
+ alias: rangoTestAliases(opts),
197
+ // fresh copy so the shared rangoInlineDeps const is never aliased into (or
198
+ // mutated through) a consumer's resolved config
199
+ server: { deps: { inline: [...rangoInlineDeps] } },
200
+ };
201
+ }
202
+
203
+ /** A minimal Vite plugin shape (avoids a hard dependency on Vite's types). */
204
+ interface FlightTransformPlugin {
205
+ name: string;
206
+ transform(
207
+ code: string,
208
+ id: string,
209
+ ): Promise<{ code: string; map: unknown } | undefined>;
210
+ }
211
+
212
+ /**
213
+ * A Vite plugin for the FLIGHT (react-server) Vitest project that applies the
214
+ * `"use client"` transform to a consumer's client modules — the same transform a
215
+ * real build applies. With it, `renderServerTree` (`@rangojs/router/testing/flight`)
216
+ * resolves client islands AUTOMATICALLY from the server tree's own imports: no
217
+ * `clientComponents` to pass, no filename convention. Without it, a `"use client"`
218
+ * module is imported as a plain (unmarked) function and would render server-side,
219
+ * so you must list islands via `renderServerTree(..., { clientComponents })`.
220
+ *
221
+ * Add it to your react-server Vitest project. This is the COMPLETE config — the
222
+ * alias, `server.deps.inline`, and `NODE_ENV` are load-bearing, not optional (see
223
+ * the inline notes). The testing skill (`skills/testing/setup.md`, shipped in the
224
+ * package) has the annotated walkthrough.
225
+ *
226
+ * ```ts
227
+ * // vitest.rsc.config.ts
228
+ * import { defineConfig } from "vitest/config";
229
+ * import {
230
+ * rangoUseClientTransform,
231
+ * rangoTestAliases,
232
+ * rangoInlineDeps,
233
+ * } from "@rangojs/router/testing/vitest";
234
+ *
235
+ * // Flight serialization needs React's production build; the dev build's jsxDEV
236
+ * // crashes / yields unstable snapshots.
237
+ * process.env.NODE_ENV = "production";
238
+ *
239
+ * export default defineConfig({
240
+ * plugins: [rangoUseClientTransform()],
241
+ * resolve: {
242
+ * conditions: ["react-server"],
243
+ * // Bare `@rangojs/router` -> its react-server build, so a handler/component
244
+ * // reading getRequestContext()/cookies() resolves the real impl, not the
245
+ * // throwing stub. Pass { preset: "cloudflare" } for a CF app.
246
+ * alias: rangoTestAliases(),
247
+ * },
248
+ * test: {
249
+ * include: ["test/**\/*.rsc-test.{ts,tsx}"],
250
+ * pool: "forks",
251
+ * execArgv: ["--conditions=react-server"],
252
+ * // Required for an INSTALLED consumer on Node >= 23 (rango ships TS source).
253
+ * server: { deps: { inline: rangoInlineDeps } },
254
+ * },
255
+ * });
256
+ * ```
257
+ *
258
+ * Each `"use client"` module's exports are replaced with client references keyed
259
+ * by the module's absolute path (the boundary id), the export name becoming the
260
+ * boundary name. Modules without the directive (server components) are untouched,
261
+ * so `renderToFlightString` of pure leaf trees is unaffected.
262
+ */
263
+ export function rangoUseClientTransform(): FlightTransformPlugin {
264
+ return {
265
+ name: "rango:testing-use-client",
266
+ async transform(code, id) {
267
+ if (id.includes("/node_modules/")) return undefined;
268
+ // Fast path: only parse modules that mention the directive.
269
+ if (!code.includes("use client")) return undefined;
270
+ const { parseAstAsync } = await import("vite");
271
+ const { hasDirective, transformDirectiveProxyExport } =
272
+ await import("@vitejs/plugin-rsc/transforms");
273
+ // vite's parser and the transforms ship structurally-compatible but
274
+ // distinctly-typed ASTs (oxc vs estree); cast through the transform's own
275
+ // parameter type, exactly as plugin-rsc does at runtime.
276
+ type TransformAst = Parameters<typeof transformDirectiveProxyExport>[0];
277
+ let ast: TransformAst;
278
+ try {
279
+ ast = (await parseAstAsync(code)) as unknown as TransformAst;
280
+ } catch {
281
+ return undefined;
282
+ }
283
+ if (!hasDirective(ast.body, "use client")) return undefined;
284
+ const result = transformDirectiveProxyExport(ast, {
285
+ directive: "use client",
286
+ code,
287
+ runtime: (name: string) =>
288
+ `$$RangoRSD.registerClientReference(` +
289
+ `() => { throw new Error("client reference " + ${JSON.stringify(name)} + " is not callable on the server"); }, ` +
290
+ `${JSON.stringify(id)}, ${JSON.stringify(name)})`,
291
+ });
292
+ if (!result) return undefined;
293
+ const { output } = result;
294
+ // The vendored server serializer is the one renderToFlightString uses;
295
+ // resolvable here under the react-server condition.
296
+ output.prepend(
297
+ `import * as $$RangoRSD from "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";\n`,
298
+ );
299
+ return {
300
+ code: output.toString(),
301
+ map: output.generateMap({ hires: true }),
302
+ };
303
+ },
304
+ };
305
+ }
@@ -28,9 +28,6 @@ import type {
28
28
  } from "./types.js";
29
29
  import { THEME_COOKIE } from "./constants.js";
30
30
 
31
- /**
32
- * Get system preference for color scheme
33
- */
34
31
  function getSystemTheme(): ResolvedTheme {
35
32
  if (typeof window !== "undefined" && window.matchMedia) {
36
33
  return window.matchMedia("(prefers-color-scheme: dark)").matches
@@ -40,9 +37,6 @@ function getSystemTheme(): ResolvedTheme {
40
37
  return "light";
41
38
  }
42
39
 
43
- /**
44
- * Read theme from cookie
45
- */
46
40
  function readThemeFromCookie(storageKey: string): string | null {
47
41
  if (typeof document === "undefined") return null;
48
42
 
@@ -61,9 +55,6 @@ function readThemeFromCookie(storageKey: string): string | null {
61
55
  return null;
62
56
  }
63
57
 
64
- /**
65
- * Read theme from localStorage
66
- */
67
58
  function readThemeFromStorage(storageKey: string): string | null {
68
59
  if (typeof localStorage === "undefined") return null;
69
60
 
@@ -74,9 +65,6 @@ function readThemeFromStorage(storageKey: string): string | null {
74
65
  }
75
66
  }
76
67
 
77
- /**
78
- * Write theme to cookie
79
- */
80
68
  function writeThemeToCookie(storageKey: string, theme: Theme): void {
81
69
  if (typeof document === "undefined") return;
82
70
 
@@ -85,9 +73,6 @@ function writeThemeToCookie(storageKey: string, theme: Theme): void {
85
73
  document.cookie = cookie;
86
74
  }
87
75
 
88
- /**
89
- * Write theme to localStorage
90
- */
91
76
  function writeThemeToStorage(storageKey: string, theme: Theme): void {
92
77
  if (typeof localStorage === "undefined") return;
93
78
 
@@ -98,9 +83,6 @@ function writeThemeToStorage(storageKey: string, theme: Theme): void {
98
83
  }
99
84
  }
100
85
 
101
- /**
102
- * Apply theme to HTML element
103
- */
104
86
  function applyThemeToDocument(theme: Theme, config: ResolvedThemeConfig): void {
105
87
  if (typeof document === "undefined") return;
106
88
 
@@ -112,40 +94,30 @@ function applyThemeToDocument(theme: Theme, config: ResolvedThemeConfig): void {
112
94
  const value = config.value[resolved] || resolved;
113
95
  const el = document.documentElement;
114
96
 
115
- // Apply attribute
116
97
  if (config.attribute === "class") {
117
- // Remove all theme classes
118
98
  for (const t of config.themes) {
119
99
  const v = config.value[t] || t;
120
100
  el.classList.remove(v);
121
101
  }
122
- // Add current theme class
123
102
  el.classList.add(value);
124
103
  } else {
125
104
  el.setAttribute(config.attribute, value);
126
105
  }
127
106
 
128
- // Set color-scheme for native dark mode support
129
107
  if (config.enableColorScheme) {
130
108
  el.style.colorScheme = resolved;
131
109
  }
132
110
  }
133
111
 
134
- /**
135
- * Get the resolved stored theme (validated against available themes)
136
- */
137
112
  function getStoredTheme(config: ResolvedThemeConfig): Theme {
138
113
  const { storageKey, themes, defaultTheme, enableSystem } = config;
139
114
 
140
- // Try cookie first (for SSR consistency)
141
115
  let stored = readThemeFromCookie(storageKey);
142
116
 
143
- // Fall back to localStorage
144
117
  if (!stored) {
145
118
  stored = readThemeFromStorage(storageKey);
146
119
  }
147
120
 
148
- // Validate stored value
149
121
  if (stored) {
150
122
  if (stored === "system" && enableSystem) {
151
123
  return "system";
@@ -158,38 +130,26 @@ function getStoredTheme(config: ResolvedThemeConfig): Theme {
158
130
  return defaultTheme;
159
131
  }
160
132
 
161
- /**
162
- * ThemeProvider component
163
- *
164
- * Provides theme state to the component tree via context.
165
- * Handles theme persistence, system preference detection, and cross-tab sync.
166
- */
167
133
  export function ThemeProvider({
168
134
  config,
169
135
  initialTheme,
170
136
  children,
171
137
  }: ThemeProviderProps): React.ReactNode {
172
- // Track mount state to avoid hydration mismatches
173
- // During SSR and initial hydration, mounted is false
174
138
  const [mounted, setMounted] = useState(false);
175
139
 
176
- // Initialize theme from prop, storage, or default
177
140
  const [theme, setThemeState] = useState<Theme>(() => {
178
141
  if (initialTheme) return initialTheme;
179
142
  if (typeof window === "undefined") return config.defaultTheme;
180
143
  return getStoredTheme(config);
181
144
  });
182
145
 
183
- // Track system preference - use stable default during SSR
184
146
  const [systemTheme, setSystemTheme] = useState<ResolvedTheme>("light");
185
147
 
186
- // Set mounted after hydration and detect actual system theme
187
148
  useEffect(() => {
188
149
  setMounted(true);
189
150
  setSystemTheme(getSystemTheme());
190
151
  }, []);
191
152
 
192
- // Set theme and persist to storage
193
153
  const setTheme = useCallback(
194
154
  (newTheme: Theme) => {
195
155
  setThemeState(newTheme);
@@ -200,7 +160,6 @@ export function ThemeProvider({
200
160
  [config],
201
161
  );
202
162
 
203
- // Listen for system preference changes
204
163
  useEffect(() => {
205
164
  if (!config.enableSystem) return;
206
165
  if (typeof window === "undefined" || !window.matchMedia) return;
@@ -211,13 +170,11 @@ export function ThemeProvider({
211
170
  const newSystemTheme = e.matches ? "dark" : "light";
212
171
  setSystemTheme(newSystemTheme);
213
172
 
214
- // If current theme is "system", re-apply to update document
215
173
  if (theme === "system") {
216
174
  applyThemeToDocument("system", config);
217
175
  }
218
176
  };
219
177
 
220
- // Modern browsers
221
178
  mediaQuery.addEventListener("change", handleChange);
222
179
 
223
180
  return () => {
@@ -225,7 +182,6 @@ export function ThemeProvider({
225
182
  };
226
183
  }, [config, theme]);
227
184
 
228
- // Cross-tab synchronization via localStorage storage event
229
185
  useEffect(() => {
230
186
  if (typeof window === "undefined") return;
231
187
 
@@ -249,12 +205,8 @@ export function ThemeProvider({
249
205
  };
250
206
  }, [config]);
251
207
 
252
- // Compute resolved theme
253
- // During SSR (not mounted), use the initial theme or default to avoid hydration mismatch
254
208
  const resolvedTheme: ResolvedTheme = useMemo(() => {
255
209
  if (!mounted) {
256
- // During SSR, return the initial theme if it's not "system", otherwise "light"
257
- // The inline script will apply the correct class before hydration
258
210
  if (initialTheme && initialTheme !== "system") {
259
211
  return initialTheme as ResolvedTheme;
260
212
  }
@@ -266,7 +218,6 @@ export function ThemeProvider({
266
218
  return theme as ResolvedTheme;
267
219
  }, [theme, systemTheme, config.enableSystem, mounted, initialTheme]);
268
220
 
269
- // Build themes list (include "system" if enabled)
270
221
  const themes = useMemo(() => {
271
222
  if (config.enableSystem) {
272
223
  return ["system", ...config.themes.filter((t) => t !== "system")];
@@ -274,14 +225,11 @@ export function ThemeProvider({
274
225
  return config.themes;
275
226
  }, [config.themes, config.enableSystem]);
276
227
 
277
- // Context value
278
- // During SSR (not mounted), return stable values to avoid hydration mismatch
279
228
  const contextValue: ThemeContextValue = useMemo(
280
229
  () => ({
281
230
  theme,
282
231
  setTheme,
283
232
  resolvedTheme,
284
- // Return stable "light" for systemTheme during SSR - actual value updates after mount
285
233
  systemTheme: mounted ? systemTheme : "light",
286
234
  themes,
287
235
  config,
@@ -43,12 +43,6 @@ export interface ThemeScriptProps {
43
43
  nonce?: string;
44
44
  }
45
45
 
46
- /**
47
- * Server component that renders the theme initialization script.
48
- *
49
- * This renders a synchronous inline script that applies the theme
50
- * to the HTML element before React hydration, preventing FOUC.
51
- */
52
46
  export function ThemeScript({
53
47
  config,
54
48
  nonce,
@@ -4,9 +4,6 @@
4
4
 
5
5
  import type { ResolvedThemeConfig, ThemeConfig } from "./types.js";
6
6
 
7
- /**
8
- * Default theme configuration values
9
- */
10
7
  export const THEME_DEFAULTS = {
11
8
  defaultTheme: "system",
12
9
  themes: ["light", "dark"],
@@ -16,9 +13,6 @@ export const THEME_DEFAULTS = {
16
13
  enableColorScheme: true,
17
14
  } as const;
18
15
 
19
- /**
20
- * Cookie configuration for theme persistence
21
- */
22
16
  export const THEME_COOKIE: {
23
17
  readonly maxAge: number;
24
18
  readonly path: string;
@@ -29,21 +23,15 @@ export const THEME_COOKIE: {
29
23
  sameSite: "lax",
30
24
  };
31
25
 
32
- /**
33
- * Resolve theme config by applying defaults.
34
- * Accepts `true` to enable with all defaults, or a config object.
35
- */
36
26
  export function resolveThemeConfig(
37
27
  config: ThemeConfig | true,
38
28
  ): ResolvedThemeConfig {
39
- // Handle `theme: true` shorthand
40
29
  if (config === true) {
41
30
  config = {};
42
31
  }
43
32
 
44
33
  const themes = config.themes ?? [...THEME_DEFAULTS.themes];
45
34
 
46
- // Build value mapping - default to identity mapping
47
35
  const value: Record<string, string> = {};
48
36
  for (const theme of themes) {
49
37
  value[theme] = config.value?.[theme] ?? theme;
@@ -23,16 +23,10 @@
23
23
  * ```
24
24
  */
25
25
 
26
- // Main hook for accessing theme
27
26
  export { useTheme } from "./use-theme.js";
28
-
29
- // Provider (typically auto-included via NavigationProvider when theme is enabled)
30
27
  export { ThemeProvider } from "./ThemeProvider.js";
31
-
32
- // Script component for FOUC prevention (use in document head)
33
28
  export { ThemeScript, type ThemeScriptProps } from "./ThemeScript.js";
34
29
 
35
- // Types
36
30
  export type {
37
31
  Theme,
38
32
  ResolvedTheme,
@@ -44,5 +38,4 @@ export type {
44
38
  ThemeContextValue,
45
39
  } from "./types.js";
46
40
 
47
- // Constants
48
41
  export { THEME_DEFAULTS, THEME_COOKIE } from "./constants.js";
@@ -19,17 +19,13 @@ import type { ThemeContextValue } from "./types.js";
19
19
  export const ThemeContext: Context<ThemeContextValue | null> =
20
20
  createContext<ThemeContextValue | null>(null);
21
21
 
22
- /**
23
- * Get theme context (internal use)
24
- * Returns null if theme is not enabled
25
- */
26
22
  export function useThemeContext(): ThemeContextValue | null {
27
23
  return useContext(ThemeContext);
28
24
  }
29
25
 
30
26
  /**
31
27
  * Get theme context, throwing if not available
32
- * Use this in useTheme hook
28
+ * Used by useTheme hook
33
29
  */
34
30
  export function requireThemeContext(): ThemeContextValue {
35
31
  const ctx = useContext(ThemeContext);
@@ -22,7 +22,6 @@ import type { ResolvedThemeConfig } from "./types.js";
22
22
  * - Handle all edge cases (no localStorage, no cookie, etc.)
23
23
  */
24
24
  export function generateThemeScript(config: ResolvedThemeConfig): string {
25
- // Build the script as a string, then minify
26
25
  const script = `
27
26
  (function() {
28
27
  var storageKey = ${JSON.stringify(config.storageKey)};
@@ -33,9 +32,7 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
33
32
  var valueMap = ${JSON.stringify(config.value)};
34
33
  var themes = ${JSON.stringify(config.themes)};
35
34
 
36
- // Read theme from cookie or localStorage
37
35
  function getStoredTheme() {
38
- // Try cookie first (for SSR consistency)
39
36
  var cookies = document.cookie.split(';');
40
37
  for (var i = 0; i < cookies.length; i++) {
41
38
  var cookie = cookies[i].trim();
@@ -44,7 +41,6 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
44
41
  catch (e) { return cookie.substring(storageKey.length + 1); }
45
42
  }
46
43
  }
47
- // Fall back to localStorage
48
44
  try {
49
45
  return localStorage.getItem(storageKey);
50
46
  } catch (e) {
@@ -52,7 +48,6 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
52
48
  }
53
49
  }
54
50
 
55
- // Get system preference
56
51
  function getSystemTheme() {
57
52
  if (typeof window !== 'undefined' && window.matchMedia) {
58
53
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
@@ -60,7 +55,6 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
60
55
  return 'light';
61
56
  }
62
57
 
63
- // Resolve "system" to actual theme
64
58
  function resolveTheme(theme) {
65
59
  if (theme === 'system' && enableSystem) {
66
60
  return getSystemTheme();
@@ -68,15 +62,12 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
68
62
  return theme;
69
63
  }
70
64
 
71
- // Apply theme to HTML element
72
65
  function applyTheme(theme) {
73
66
  var resolved = resolveTheme(theme);
74
67
  var value = valueMap[resolved] || resolved;
75
68
  var el = document.documentElement;
76
69
 
77
- // Apply attribute
78
70
  if (attribute === 'class') {
79
- // Remove all theme classes, then add current
80
71
  for (var i = 0; i < themes.length; i++) {
81
72
  var v = valueMap[themes[i]] || themes[i];
82
73
  el.classList.remove(v);
@@ -86,22 +77,18 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
86
77
  el.setAttribute(attribute, value);
87
78
  }
88
79
 
89
- // Set color-scheme for native dark mode support
90
80
  if (enableColorScheme) {
91
81
  el.style.colorScheme = resolved;
92
82
  }
93
83
  }
94
84
 
95
- // Get stored theme or use default
96
85
  var stored = getStoredTheme();
97
86
  var theme = stored && (stored === 'system' || themes.indexOf(stored) !== -1)
98
87
  ? stored
99
88
  : defaultTheme;
100
89
 
101
- // Apply immediately
102
90
  applyTheme(theme);
103
91
 
104
- // Listen for system preference changes (for "system" theme)
105
92
  if (enableSystem && typeof window !== 'undefined' && window.matchMedia) {
106
93
  try {
107
94
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() {
@@ -117,7 +104,6 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
117
104
  })();
118
105
  `;
119
106
 
120
- // Minify by removing comments, extra whitespace, and newlines
121
107
  return minifyScript(script);
122
108
  }
123
109