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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4951 -930
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +92 -31
  18. package/skills/loader/SKILL.md +404 -44
  19. package/skills/middleware/SKILL.md +173 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +204 -1
  22. package/skills/prerender/SKILL.md +685 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +257 -14
  26. package/skills/router-setup/SKILL.md +210 -32
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +328 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/app-version.ts +14 -0
  36. package/src/browser/event-controller.ts +92 -64
  37. package/src/browser/history-state.ts +80 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +24 -4
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +20 -12
  42. package/src/browser/navigation-bridge.ts +296 -558
  43. package/src/browser/navigation-client.ts +179 -69
  44. package/src/browser/navigation-store.ts +73 -55
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +328 -313
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +150 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +160 -0
  53. package/src/browser/prefetch/resource-ready.ts +77 -0
  54. package/src/browser/rango-state.ts +112 -0
  55. package/src/browser/react/Link.tsx +230 -74
  56. package/src/browser/react/NavigationProvider.tsx +87 -11
  57. package/src/browser/react/context.ts +11 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +12 -12
  60. package/src/browser/react/location-state-shared.ts +95 -53
  61. package/src/browser/react/location-state.ts +60 -15
  62. package/src/browser/react/mount-context.ts +6 -1
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +29 -51
  66. package/src/browser/react/use-client-cache.ts +5 -3
  67. package/src/browser/react/use-handle.ts +30 -126
  68. package/src/browser/react/use-href.tsx +2 -2
  69. package/src/browser/react/use-link-status.ts +6 -5
  70. package/src/browser/react/use-navigation.ts +22 -63
  71. package/src/browser/react/use-params.ts +65 -0
  72. package/src/browser/react/use-pathname.ts +47 -0
  73. package/src/browser/react/use-router.ts +76 -0
  74. package/src/browser/react/use-search-params.ts +56 -0
  75. package/src/browser/react/use-segments.ts +80 -97
  76. package/src/browser/response-adapter.ts +73 -0
  77. package/src/browser/rsc-router.tsx +214 -58
  78. package/src/browser/scroll-restoration.ts +127 -52
  79. package/src/browser/segment-reconciler.ts +221 -0
  80. package/src/browser/segment-structure-assert.ts +16 -0
  81. package/src/browser/server-action-bridge.ts +510 -603
  82. package/src/browser/shallow.ts +6 -1
  83. package/src/browser/types.ts +141 -48
  84. package/src/browser/validate-redirect-origin.ts +29 -0
  85. package/src/build/generate-manifest.ts +235 -24
  86. package/src/build/generate-route-types.ts +39 -0
  87. package/src/build/index.ts +13 -0
  88. package/src/build/route-trie.ts +265 -0
  89. package/src/build/route-types/ast-helpers.ts +25 -0
  90. package/src/build/route-types/ast-route-extraction.ts +98 -0
  91. package/src/build/route-types/codegen.ts +102 -0
  92. package/src/build/route-types/include-resolution.ts +418 -0
  93. package/src/build/route-types/param-extraction.ts +48 -0
  94. package/src/build/route-types/per-module-writer.ts +128 -0
  95. package/src/build/route-types/router-processing.ts +618 -0
  96. package/src/build/route-types/scan-filter.ts +85 -0
  97. package/src/build/runtime-discovery.ts +231 -0
  98. package/src/cache/background-task.ts +34 -0
  99. package/src/cache/cache-key-utils.ts +44 -0
  100. package/src/cache/cache-policy.ts +125 -0
  101. package/src/cache/cache-runtime.ts +342 -0
  102. package/src/cache/cache-scope.ts +167 -309
  103. package/src/cache/cf/cf-cache-store.ts +571 -17
  104. package/src/cache/cf/index.ts +13 -3
  105. package/src/cache/document-cache.ts +116 -77
  106. package/src/cache/handle-capture.ts +81 -0
  107. package/src/cache/handle-snapshot.ts +41 -0
  108. package/src/cache/index.ts +1 -15
  109. package/src/cache/memory-segment-store.ts +191 -13
  110. package/src/cache/profile-registry.ts +73 -0
  111. package/src/cache/read-through-swr.ts +134 -0
  112. package/src/cache/segment-codec.ts +256 -0
  113. package/src/cache/taint.ts +153 -0
  114. package/src/cache/types.ts +72 -122
  115. package/src/client.rsc.tsx +3 -1
  116. package/src/client.tsx +105 -179
  117. package/src/component-utils.ts +4 -4
  118. package/src/components/DefaultDocument.tsx +5 -1
  119. package/src/context-var.ts +156 -0
  120. package/src/debug.ts +19 -9
  121. package/src/errors.ts +108 -2
  122. package/src/handle.ts +55 -29
  123. package/src/handles/MetaTags.tsx +73 -20
  124. package/src/handles/breadcrumbs.ts +66 -0
  125. package/src/handles/index.ts +1 -0
  126. package/src/handles/meta.ts +30 -13
  127. package/src/host/cookie-handler.ts +21 -15
  128. package/src/host/errors.ts +8 -8
  129. package/src/host/index.ts +4 -7
  130. package/src/host/pattern-matcher.ts +27 -27
  131. package/src/host/router.ts +61 -39
  132. package/src/host/testing.ts +8 -8
  133. package/src/host/types.ts +15 -7
  134. package/src/host/utils.ts +1 -1
  135. package/src/href-client.ts +119 -29
  136. package/src/index.rsc.ts +155 -19
  137. package/src/index.ts +223 -30
  138. package/src/internal-debug.ts +11 -0
  139. package/src/loader.rsc.ts +26 -157
  140. package/src/loader.ts +27 -10
  141. package/src/network-error-thrower.tsx +3 -1
  142. package/src/outlet-provider.tsx +45 -0
  143. package/src/prerender/param-hash.ts +37 -0
  144. package/src/prerender/store.ts +186 -0
  145. package/src/prerender.ts +524 -0
  146. package/src/reverse.ts +351 -0
  147. package/src/root-error-boundary.tsx +41 -29
  148. package/src/route-content-wrapper.tsx +7 -4
  149. package/src/route-definition/dsl-helpers.ts +982 -0
  150. package/src/route-definition/helper-factories.ts +200 -0
  151. package/src/route-definition/helpers-types.ts +434 -0
  152. package/src/route-definition/index.ts +55 -0
  153. package/src/route-definition/redirect.ts +101 -0
  154. package/src/route-definition/resolve-handler-use.ts +149 -0
  155. package/src/route-definition.ts +1 -1428
  156. package/src/route-map-builder.ts +217 -123
  157. package/src/route-name.ts +53 -0
  158. package/src/route-types.ts +70 -8
  159. package/src/router/content-negotiation.ts +215 -0
  160. package/src/router/debug-manifest.ts +72 -0
  161. package/src/router/error-handling.ts +9 -9
  162. package/src/router/find-match.ts +160 -0
  163. package/src/router/handler-context.ts +435 -86
  164. package/src/router/intercept-resolution.ts +402 -0
  165. package/src/router/lazy-includes.ts +237 -0
  166. package/src/router/loader-resolution.ts +356 -128
  167. package/src/router/logging.ts +251 -0
  168. package/src/router/manifest.ts +154 -35
  169. package/src/router/match-api.ts +555 -0
  170. package/src/router/match-context.ts +5 -3
  171. package/src/router/match-handlers.ts +440 -0
  172. package/src/router/match-middleware/background-revalidation.ts +108 -93
  173. package/src/router/match-middleware/cache-lookup.ts +459 -10
  174. package/src/router/match-middleware/cache-store.ts +98 -26
  175. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  176. package/src/router/match-middleware/segment-resolution.ts +80 -6
  177. package/src/router/match-pipelines.ts +10 -45
  178. package/src/router/match-result.ts +135 -35
  179. package/src/router/metrics.ts +240 -15
  180. package/src/router/middleware-cookies.ts +55 -0
  181. package/src/router/middleware-types.ts +220 -0
  182. package/src/router/middleware.ts +324 -369
  183. package/src/router/navigation-snapshot.ts +182 -0
  184. package/src/router/pattern-matching.ts +211 -43
  185. package/src/router/prerender-match.ts +502 -0
  186. package/src/router/preview-match.ts +98 -0
  187. package/src/router/request-classification.ts +310 -0
  188. package/src/router/revalidation.ts +137 -38
  189. package/src/router/route-snapshot.ts +245 -0
  190. package/src/router/router-context.ts +41 -21
  191. package/src/router/router-interfaces.ts +484 -0
  192. package/src/router/router-options.ts +618 -0
  193. package/src/router/router-registry.ts +24 -0
  194. package/src/router/segment-resolution/fresh.ts +748 -0
  195. package/src/router/segment-resolution/helpers.ts +268 -0
  196. package/src/router/segment-resolution/loader-cache.ts +199 -0
  197. package/src/router/segment-resolution/revalidation.ts +1379 -0
  198. package/src/router/segment-resolution/static-store.ts +67 -0
  199. package/src/router/segment-resolution.ts +21 -0
  200. package/src/router/segment-wrappers.ts +291 -0
  201. package/src/router/telemetry-otel.ts +299 -0
  202. package/src/router/telemetry.ts +300 -0
  203. package/src/router/timeout.ts +148 -0
  204. package/src/router/trie-matching.ts +239 -0
  205. package/src/router/types.ts +78 -3
  206. package/src/router.ts +740 -4252
  207. package/src/rsc/handler-context.ts +45 -0
  208. package/src/rsc/handler.ts +907 -797
  209. package/src/rsc/helpers.ts +140 -6
  210. package/src/rsc/index.ts +0 -20
  211. package/src/rsc/loader-fetch.ts +229 -0
  212. package/src/rsc/manifest-init.ts +90 -0
  213. package/src/rsc/nonce.ts +14 -0
  214. package/src/rsc/origin-guard.ts +141 -0
  215. package/src/rsc/progressive-enhancement.ts +391 -0
  216. package/src/rsc/response-error.ts +37 -0
  217. package/src/rsc/response-route-handler.ts +347 -0
  218. package/src/rsc/rsc-rendering.ts +246 -0
  219. package/src/rsc/runtime-warnings.ts +42 -0
  220. package/src/rsc/server-action.ts +356 -0
  221. package/src/rsc/ssr-setup.ts +128 -0
  222. package/src/rsc/types.ts +46 -11
  223. package/src/search-params.ts +230 -0
  224. package/src/segment-system.tsx +165 -17
  225. package/src/server/context.ts +315 -58
  226. package/src/server/cookie-store.ts +190 -0
  227. package/src/server/fetchable-loader-store.ts +37 -0
  228. package/src/server/handle-store.ts +113 -15
  229. package/src/server/loader-registry.ts +24 -64
  230. package/src/server/request-context.ts +607 -81
  231. package/src/server.ts +35 -130
  232. package/src/ssr/index.tsx +103 -30
  233. package/src/static-handler.ts +126 -0
  234. package/src/theme/ThemeProvider.tsx +21 -15
  235. package/src/theme/ThemeScript.tsx +5 -5
  236. package/src/theme/constants.ts +5 -2
  237. package/src/theme/index.ts +4 -14
  238. package/src/theme/theme-context.ts +4 -30
  239. package/src/theme/theme-script.ts +21 -18
  240. package/src/types/boundaries.ts +158 -0
  241. package/src/types/cache-types.ts +198 -0
  242. package/src/types/error-types.ts +192 -0
  243. package/src/types/global-namespace.ts +100 -0
  244. package/src/types/handler-context.ts +791 -0
  245. package/src/types/index.ts +88 -0
  246. package/src/types/loader-types.ts +210 -0
  247. package/src/types/route-config.ts +170 -0
  248. package/src/types/route-entry.ts +109 -0
  249. package/src/types/segments.ts +151 -0
  250. package/src/types.ts +1 -1623
  251. package/src/urls/include-helper.ts +197 -0
  252. package/src/urls/index.ts +53 -0
  253. package/src/urls/path-helper-types.ts +346 -0
  254. package/src/urls/path-helper.ts +364 -0
  255. package/src/urls/pattern-types.ts +107 -0
  256. package/src/urls/response-types.ts +116 -0
  257. package/src/urls/type-extraction.ts +372 -0
  258. package/src/urls/urls-function.ts +98 -0
  259. package/src/urls.ts +1 -802
  260. package/src/use-loader.tsx +161 -81
  261. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  262. package/src/vite/discovery/discover-routers.ts +348 -0
  263. package/src/vite/discovery/prerender-collection.ts +439 -0
  264. package/src/vite/discovery/route-types-writer.ts +258 -0
  265. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  266. package/src/vite/discovery/state.ts +117 -0
  267. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  268. package/src/vite/index.ts +15 -1129
  269. package/src/vite/plugin-types.ts +103 -0
  270. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  271. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  272. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  273. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  274. package/src/vite/plugins/expose-id-utils.ts +299 -0
  275. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  276. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  277. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  278. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  279. package/src/vite/plugins/expose-ids/types.ts +45 -0
  280. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  281. package/src/vite/plugins/performance-tracks.ts +88 -0
  282. package/src/vite/plugins/refresh-cmd.ts +127 -0
  283. package/src/vite/plugins/use-cache-transform.ts +323 -0
  284. package/src/vite/plugins/version-injector.ts +83 -0
  285. package/src/vite/plugins/version-plugin.ts +266 -0
  286. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  287. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  288. package/src/vite/rango.ts +462 -0
  289. package/src/vite/router-discovery.ts +918 -0
  290. package/src/vite/utils/ast-handler-extract.ts +517 -0
  291. package/src/vite/utils/banner.ts +36 -0
  292. package/src/vite/utils/bundle-analysis.ts +137 -0
  293. package/src/vite/utils/manifest-utils.ts +70 -0
  294. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  295. package/src/vite/utils/prerender-utils.ts +207 -0
  296. package/src/vite/utils/shared-utils.ts +170 -0
  297. package/CLAUDE.md +0 -43
  298. package/src/browser/lru-cache.ts +0 -69
  299. package/src/browser/request-controller.ts +0 -164
  300. package/src/cache/memory-store.ts +0 -253
  301. package/src/href-context.ts +0 -33
  302. package/src/href.ts +0 -255
  303. package/src/server/route-manifest-cache.ts +0 -173
  304. package/src/vite/expose-handle-id.ts +0 -209
  305. package/src/vite/expose-loader-id.ts +0 -426
  306. package/src/vite/expose-location-state-id.ts +0 -177
  307. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -1,426 +0,0 @@
1
- import type { Plugin, ResolvedConfig } from "vite";
2
- import MagicString from "magic-string";
3
- import path from "node:path";
4
- import crypto from "node:crypto";
5
-
6
- /**
7
- * Normalize path to forward slashes
8
- */
9
- function normalizePath(p: string): string {
10
- return p.split(path.sep).join("/");
11
- }
12
-
13
- /**
14
- * Generate a short hash for a loader ID
15
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short
16
- * Appends export name for easier debugging in production: "abc123#CartLoader"
17
- */
18
- function hashLoaderId(filePath: string, exportName: string): string {
19
- const input = `${filePath}#${exportName}`;
20
- const hash = crypto.createHash("sha256").update(input).digest("hex");
21
- return `${hash.slice(0, 8)}#${exportName}`;
22
- }
23
-
24
- /**
25
- * Check if file imports createLoader from rsc-router
26
- */
27
- function hasCreateLoaderImport(code: string): boolean {
28
- // Match: import { createLoader } from "@rangojs/router" or "@rangojs/router/server"
29
- // Must be exact - no aliasing support
30
- const pattern =
31
- /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/;
32
- return pattern.test(code);
33
- }
34
-
35
- /**
36
- * Count the number of arguments in a createLoader call
37
- * Returns the count of top-level arguments (not counting nested commas)
38
- */
39
- function countCreateLoaderArgs(code: string, startPos: number, endPos: number): number {
40
- let depth = 0;
41
- let argCount = 0;
42
- let hasContent = false;
43
-
44
- for (let i = startPos; i < endPos; i++) {
45
- const char = code[i];
46
-
47
- // Track nested structures
48
- if (char === "(" || char === "[" || char === "{") {
49
- depth++;
50
- hasContent = true;
51
- } else if (char === ")" || char === "]" || char === "}") {
52
- depth--;
53
- } else if (char === "," && depth === 0) {
54
- // Top-level comma means another argument
55
- argCount++;
56
- } else if (!/\s/.test(char)) {
57
- hasContent = true;
58
- }
59
- }
60
-
61
- // If there's content, we have at least one argument
62
- return hasContent ? argCount + 1 : 0;
63
- }
64
-
65
- /**
66
- * Find all export const X = createLoader(...) patterns and inject $$id
67
- * In production, IDs are hashed to avoid exposing file paths.
68
- * In dev, IDs use filePath#exportName for easier debugging.
69
- *
70
- * The ID is injected in two ways:
71
- * 1. As a hidden third parameter to createLoader() for registry registration
72
- * 2. As a property assignment X.$$id = "..." for external access
73
- *
74
- * IMPORTANT: The $$id must always be the THIRD parameter to createLoader.
75
- * createLoader(fn, fetchable?, __injectedId?)
76
- * If the user only provides fn, we inject: undefined, "id"
77
- * If the user provides fn and fetchable, we inject: , "id"
78
- */
79
- /**
80
- * Generate lightweight client stubs for loader files.
81
- *
82
- * When a loader file is imported from a client component (e.g., for useLoader()),
83
- * the client only needs { __brand: "loader", $$id: "..." } objects.
84
- * This function replaces the entire file contents with just those stub exports,
85
- * preventing server-only data (constants, DB queries, etc.) from leaking into
86
- * the client bundle.
87
- *
88
- * Only applies when ALL named exports are createLoader() calls (plus type exports
89
- * which are erased at compile time). Files with mixed exports are left untouched.
90
- */
91
- function generateClientLoaderStubs(
92
- code: string,
93
- filePath: string,
94
- isBuild: boolean
95
- ): { code: string; map?: undefined } | null {
96
- const loaderPattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
97
- const loaders: string[] = [];
98
- let match: RegExpExecArray | null;
99
-
100
- while ((match = loaderPattern.exec(code)) !== null) {
101
- loaders.push(match[1]);
102
- }
103
-
104
- if (loaders.length === 0) {
105
- return null;
106
- }
107
-
108
- // Check that every non-type export is a createLoader call.
109
- // If the file exports other values, we can't safely replace it.
110
- const allExports = /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
111
- let exportMatch: RegExpExecArray | null;
112
- const nonLoaderExports: string[] = [];
113
-
114
- while ((exportMatch = allExports.exec(code)) !== null) {
115
- const name = exportMatch[2];
116
- if (!loaders.includes(name)) {
117
- nonLoaderExports.push(name);
118
- }
119
- }
120
-
121
- if (nonLoaderExports.length > 0) {
122
- // Mixed exports - fall back to normal transform (inject $$id only)
123
- return null;
124
- }
125
-
126
- // Generate stub file: only $$id references, no server code
127
- const stubs = loaders.map((name) => {
128
- const loaderId = isBuild
129
- ? hashLoaderId(filePath, name)
130
- : `${filePath}#${name}`;
131
- return `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`;
132
- });
133
-
134
- return {
135
- code: stubs.join("\n") + "\n",
136
- };
137
- }
138
-
139
- function transformLoaderExports(
140
- code: string,
141
- filePath: string,
142
- sourceId?: string,
143
- isBuild: boolean = false
144
- ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
145
- // Quick bail-out
146
- if (!code.includes("createLoader")) {
147
- return null;
148
- }
149
-
150
- // Must have direct import from rsc-router
151
- if (!hasCreateLoaderImport(code)) {
152
- return null;
153
- }
154
-
155
- // Match: export const X = createLoader(
156
- // Captures the export name (X)
157
- const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
158
-
159
- const s = new MagicString(code);
160
- let hasChanges = false;
161
- let match: RegExpExecArray | null;
162
-
163
- while ((match = pattern.exec(code)) !== null) {
164
- const exportName = match[1];
165
- const matchEnd = match.index + match[0].length;
166
-
167
- // Find the end of the createLoader(...) call
168
- // Need to count parentheses to find matching close
169
- let parenDepth = 1;
170
- let i = matchEnd;
171
- while (i < code.length && parenDepth > 0) {
172
- if (code[i] === "(") parenDepth++;
173
- if (code[i] === ")") parenDepth--;
174
- i++;
175
- }
176
-
177
- // i now points just after the closing )
178
- const closeParenPos = i - 1;
179
-
180
- // Count existing arguments
181
- const argCount = countCreateLoaderArgs(code, matchEnd, closeParenPos);
182
-
183
- // Find the semicolon or end of statement
184
- let statementEnd = i;
185
- while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
186
- statementEnd++;
187
- }
188
- if (code[statementEnd] === ";") {
189
- statementEnd++;
190
- }
191
-
192
- // In production: hash ID to avoid exposing file paths
193
- // In dev: use readable format for easier debugging
194
- const loaderId = isBuild
195
- ? hashLoaderId(filePath, exportName)
196
- : `${filePath}#${exportName}`;
197
-
198
- // Inject $$id as hidden third parameter before the closing paren
199
- // If user only has 1 arg (fn), we need to add undefined for fetchable
200
- // createLoader(fn) -> createLoader(fn, undefined, "id")
201
- // createLoader(fn, true) -> createLoader(fn, true, "id")
202
- const paramInjection = argCount === 1
203
- ? `, undefined, "${loaderId}"`
204
- : `, "${loaderId}"`;
205
- s.appendLeft(closeParenPos, paramInjection);
206
-
207
- // Also set $$id property for external access (useLoader, useFetchLoader)
208
- const propInjection = `\n${exportName}.$$id = "${loaderId}";`;
209
- s.appendRight(statementEnd, propInjection);
210
- hasChanges = true;
211
- }
212
-
213
- if (!hasChanges) {
214
- return null;
215
- }
216
-
217
- return {
218
- code: s.toString(),
219
- map: s.generateMap({ source: sourceId, includeContent: true }),
220
- };
221
- }
222
-
223
- const VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
224
- const RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
225
-
226
- // Store for deferred manifest generation - populated during transform, used after build
227
- let manifestGenerated = false;
228
-
229
- /**
230
- * Vite plugin that exposes $$id on createLoader calls and generates a loader manifest.
231
- *
232
- * When users create loaders with createLoader(), this plugin:
233
- * 1. Injects a $$id property containing the file path and export name
234
- * 2. Tracks all loaders and generates a virtual manifest module
235
- *
236
- * The manifest can be imported by the RSC handler to get all loaders.
237
- *
238
- * Requirements:
239
- * - Must use direct import: import { createLoader } from "@rangojs/router"
240
- * - No aliasing support (import { createLoader as cl } won't work)
241
- * - Must use named export: export const MyLoader = createLoader(...)
242
- */
243
- export function exposeLoaderId(): Plugin {
244
- let config: ResolvedConfig;
245
- let isBuild = false;
246
-
247
- // Track discovered loaders: hashedId -> { filePath, exportName }
248
- const loaderRegistry = new Map<
249
- string,
250
- { filePath: string; exportName: string }
251
- >();
252
-
253
- // For build mode: pre-scan for loaders during buildStart
254
- const pendingLoaderScans = new Map<string, Promise<void>>();
255
-
256
- return {
257
- name: "@rangojs/router:expose-loader-id",
258
- enforce: "post",
259
-
260
- configResolved(resolvedConfig) {
261
- config = resolvedConfig;
262
- isBuild = config.command === "build";
263
- },
264
-
265
- async buildStart() {
266
- if (!isBuild) return;
267
-
268
- // Pre-scan for loader files to populate registry before manifest is loaded
269
- // This runs before module resolution, so manifest will have access to all loaders
270
- const fs = await import("node:fs/promises");
271
-
272
- async function scanDir(dir: string): Promise<string[]> {
273
- const results: string[] = [];
274
- try {
275
- const entries = await fs.readdir(dir, { withFileTypes: true });
276
- for (const entry of entries) {
277
- const fullPath = path.join(dir, entry.name);
278
- if (entry.isDirectory()) {
279
- if (entry.name !== "node_modules") {
280
- results.push(...(await scanDir(fullPath)));
281
- }
282
- } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
283
- results.push(fullPath);
284
- }
285
- }
286
- } catch {
287
- // Directory doesn't exist or not readable
288
- }
289
- return results;
290
- }
291
-
292
- try {
293
- const srcDir = path.join(config.root, "src");
294
- const files = await scanDir(srcDir);
295
-
296
- for (const filePath of files) {
297
- const content = await fs.readFile(filePath, "utf-8");
298
-
299
- // Quick check for createLoader
300
- if (!content.includes("createLoader")) continue;
301
- if (!hasCreateLoaderImport(content)) continue;
302
-
303
- // Extract loader exports
304
- const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
305
- const relativePath = normalizePath(
306
- path.relative(config.root, filePath)
307
- );
308
- let match: RegExpExecArray | null;
309
-
310
- while ((match = pattern.exec(content)) !== null) {
311
- const exportName = match[1];
312
- const hashedId = hashLoaderId(relativePath, exportName);
313
- loaderRegistry.set(hashedId, {
314
- filePath: relativePath,
315
- exportName,
316
- });
317
- }
318
- }
319
- } catch (error) {
320
- // Fall back to transform-time discovery
321
- console.warn("[exposeLoaderId] Pre-scan failed:", error);
322
- }
323
- },
324
-
325
- resolveId(id) {
326
- if (id === VIRTUAL_LOADER_MANIFEST) {
327
- return RESOLVED_VIRTUAL_LOADER_MANIFEST;
328
- }
329
- },
330
-
331
- load(id) {
332
- if (id === RESOLVED_VIRTUAL_LOADER_MANIFEST) {
333
- // Generate a lazy import map for on-demand loader loading
334
- // This avoids importing all loader modules at startup
335
-
336
- if (!isBuild) {
337
- // Dev mode: empty map - use fallback path parsing in loader registry
338
- // IDs in dev mode are "filePath#exportName" format for easier debugging
339
- return `import { setLoaderImports } from "@rangojs/router/server";
340
-
341
- // Dev mode: empty map, loaders are resolved dynamically via path parsing
342
- setLoaderImports({});
343
- `;
344
- }
345
-
346
- // Build mode: generate lazy import map
347
- // Each loader is only imported when first requested
348
- // Keys are hashed IDs to avoid exposing file paths
349
- const lazyImports: string[] = [];
350
-
351
- for (const [hashedId, { filePath, exportName }] of loaderRegistry) {
352
- // Create a lazy import function for each loader
353
- lazyImports.push(
354
- ` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`
355
- );
356
- }
357
-
358
- // If no loaders discovered, set empty map
359
- if (lazyImports.length === 0) {
360
- return `import { setLoaderImports } from "@rangojs/router/server";
361
-
362
- // No fetchable loaders discovered during build
363
- setLoaderImports({});
364
- `;
365
- }
366
-
367
- const code = `import { setLoaderImports } from "@rangojs/router/server";
368
-
369
- // Lazy import map - loaders are loaded on-demand when first requested
370
- setLoaderImports({
371
- ${lazyImports.join(",\n")}
372
- });
373
- `;
374
- return code;
375
- }
376
- },
377
-
378
- transform(code, id) {
379
- // Skip node_modules
380
- if (id.includes("/node_modules/")) {
381
- return;
382
- }
383
-
384
- // Quick bail-out
385
- if (!code.includes("createLoader")) {
386
- return;
387
- }
388
-
389
- // Must have direct import from rsc-router
390
- if (!hasCreateLoaderImport(code)) {
391
- return;
392
- }
393
-
394
- // Check if we're in RSC environment (server-side)
395
- const envName = this.environment?.name;
396
- const isRscEnv = envName === "rsc";
397
-
398
- // Get relative path for the ID
399
- const relativePath = normalizePath(path.relative(config.root, id));
400
-
401
- // Track loaders for manifest (only in RSC env to avoid duplicate entries)
402
- if (isRscEnv) {
403
- const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
404
- let match: RegExpExecArray | null;
405
- while ((match = pattern.exec(code)) !== null) {
406
- const exportName = match[1];
407
- const hashedId = hashLoaderId(relativePath, exportName);
408
- loaderRegistry.set(hashedId, { filePath: relativePath, exportName });
409
- }
410
- }
411
-
412
- // In client/ssr environments, replace loader files with lightweight stubs.
413
- // This prevents server-only data (product lists, DB queries, etc.) from
414
- // leaking into the client bundle. The client only needs { $$id } references.
415
- if (!isRscEnv) {
416
- const stubResult = generateClientLoaderStubs(code, relativePath, isBuild);
417
- if (stubResult) {
418
- return stubResult;
419
- }
420
- }
421
-
422
- // RSC environment: inject $$id into createLoader calls, keeping full implementation
423
- return transformLoaderExports(code, relativePath, id, isBuild);
424
- },
425
- };
426
- }
@@ -1,177 +0,0 @@
1
- import type { Plugin, ResolvedConfig } from "vite";
2
- import MagicString from "magic-string";
3
- import path from "node:path";
4
- import crypto from "node:crypto";
5
-
6
- /**
7
- * Normalize path to forward slashes
8
- */
9
- function normalizePath(p: string): string {
10
- return p.split(path.sep).join("/");
11
- }
12
-
13
- /**
14
- * Generate a short hash for a location state key
15
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping keys short
16
- * Appends export name for easier debugging: "abc123#ProductState"
17
- */
18
- function hashLocationStateKey(filePath: string, exportName: string): string {
19
- const input = `${filePath}#${exportName}`;
20
- const hash = crypto.createHash("sha256").update(input).digest("hex");
21
- return `${hash.slice(0, 8)}#${exportName}`;
22
- }
23
-
24
- /**
25
- * Check if file imports createLocationState from rsc-router
26
- */
27
- function hasCreateLocationStateImport(code: string): boolean {
28
- // Match: import { createLocationState } from "@rangojs/router" or "@rangojs/router/client"
29
- const pattern =
30
- /import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
31
- return pattern.test(code);
32
- }
33
-
34
- /**
35
- * Transform export const X = createLocationState<...>() patterns to inject key
36
- *
37
- * The key is injected as the first parameter if not present:
38
- * - createLocationState() -> createLocationState("id")
39
- * - createLocationState<T>() -> createLocationState<T>("id")
40
- */
41
- function transformLocationStateExports(
42
- code: string,
43
- filePath: string,
44
- sourceId?: string,
45
- isBuild: boolean = false
46
- ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
47
- // Quick bail-out
48
- if (!code.includes("createLocationState")) {
49
- return null;
50
- }
51
-
52
- // Must have direct import from rsc-router
53
- if (!hasCreateLocationStateImport(code)) {
54
- return null;
55
- }
56
-
57
- // Match: export const X = createLocationState<...>(
58
- // Captures the export name (X)
59
- const pattern = /export\s+const\s+(\w+)\s*=\s*createLocationState\s*(?:<[^>]*>)?\s*\(/g;
60
-
61
- const s = new MagicString(code);
62
- let hasChanges = false;
63
- let match: RegExpExecArray | null;
64
-
65
- while ((match = pattern.exec(code)) !== null) {
66
- const exportName = match[1];
67
- const matchEnd = match.index + match[0].length;
68
-
69
- // Find the end of the createLocationState(...) call
70
- let parenDepth = 1;
71
- let i = matchEnd;
72
- while (i < code.length && parenDepth > 0) {
73
- if (code[i] === "(") parenDepth++;
74
- if (code[i] === ")") parenDepth--;
75
- i++;
76
- }
77
-
78
- // i now points just after the closing )
79
- const closeParenPos = i - 1;
80
-
81
- // Check if there are any arguments (content between open and close paren)
82
- const content = code.slice(matchEnd, closeParenPos).trim();
83
- const hasArgs = content.length > 0;
84
-
85
- // Find the semicolon or end of statement
86
- let statementEnd = i;
87
- while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
88
- statementEnd++;
89
- }
90
- if (code[statementEnd] === ";") {
91
- statementEnd++;
92
- }
93
-
94
- // Generate key: hashed in production, readable in dev
95
- const stateKey = isBuild
96
- ? hashLocationStateKey(filePath, exportName)
97
- : `${filePath}#${exportName}`;
98
-
99
- // Inject key as the first (and only) parameter
100
- // createLocationState() -> createLocationState("id")
101
- if (!hasArgs) {
102
- s.appendLeft(closeParenPos, `"${stateKey}"`);
103
- } else {
104
- // Already has a key, skip (shouldn't happen with new API, but be safe)
105
- continue;
106
- }
107
-
108
- // Also set __rsc_ls_key property for verification
109
- const propInjection = `\n${exportName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
110
- s.appendRight(statementEnd, propInjection);
111
- hasChanges = true;
112
- }
113
-
114
- if (!hasChanges) {
115
- return null;
116
- }
117
-
118
- return {
119
- code: s.toString(),
120
- map: s.generateMap({ source: sourceId, includeContent: true }),
121
- };
122
- }
123
-
124
- /**
125
- * Vite plugin that exposes location state keys on createLocationState calls.
126
- *
127
- * When users create location states with createLocationState(), this plugin:
128
- * 1. Injects an auto-generated key as the first parameter
129
- * 2. Sets __rsc_ls_key property for verification
130
- *
131
- * This allows location states to be created without explicit keys:
132
- * - Before: export const ProductState = createLocationState<Product>("product")
133
- * - After: export const ProductState = createLocationState<Product>()
134
- *
135
- * The key is auto-generated from file path + export name.
136
- *
137
- * Requirements:
138
- * - Must use direct import: import { createLocationState } from "@rangojs/router"
139
- * - Must use named export: export const MyState = createLocationState(...)
140
- */
141
- export function exposeLocationStateId(): Plugin {
142
- let config: ResolvedConfig;
143
- let isBuild = false;
144
-
145
- return {
146
- name: "@rangojs/router:expose-location-state-id",
147
- enforce: "post",
148
-
149
- configResolved(resolvedConfig) {
150
- config = resolvedConfig;
151
- isBuild = config.command === "build";
152
- },
153
-
154
- transform(code, id) {
155
- // Skip node_modules
156
- if (id.includes("/node_modules/")) {
157
- return;
158
- }
159
-
160
- // Quick bail-out
161
- if (!code.includes("createLocationState")) {
162
- return;
163
- }
164
-
165
- // Must have direct import from rsc-router
166
- if (!hasCreateLocationStateImport(code)) {
167
- return;
168
- }
169
-
170
- // Get relative path for the key
171
- const relativePath = normalizePath(path.relative(config.root, id));
172
-
173
- // Transform: inject key
174
- return transformLocationStateExports(code, relativePath, id, isBuild);
175
- },
176
- };
177
- }
File without changes