@rangojs/router 0.0.0-experimental.10

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 (172) hide show
  1. package/CLAUDE.md +43 -0
  2. package/README.md +19 -0
  3. package/dist/bin/rango.js +227 -0
  4. package/dist/vite/index.js +3039 -0
  5. package/package.json +171 -0
  6. package/skills/caching/SKILL.md +191 -0
  7. package/skills/debug-manifest/SKILL.md +108 -0
  8. package/skills/document-cache/SKILL.md +180 -0
  9. package/skills/fonts/SKILL.md +165 -0
  10. package/skills/hooks/SKILL.md +442 -0
  11. package/skills/intercept/SKILL.md +190 -0
  12. package/skills/layout/SKILL.md +213 -0
  13. package/skills/links/SKILL.md +180 -0
  14. package/skills/loader/SKILL.md +246 -0
  15. package/skills/middleware/SKILL.md +202 -0
  16. package/skills/mime-routes/SKILL.md +124 -0
  17. package/skills/parallel/SKILL.md +228 -0
  18. package/skills/prerender/SKILL.md +283 -0
  19. package/skills/rango/SKILL.md +54 -0
  20. package/skills/response-routes/SKILL.md +358 -0
  21. package/skills/route/SKILL.md +173 -0
  22. package/skills/router-setup/SKILL.md +346 -0
  23. package/skills/tailwind/SKILL.md +129 -0
  24. package/skills/theme/SKILL.md +78 -0
  25. package/skills/typesafety/SKILL.md +394 -0
  26. package/src/__internal.ts +175 -0
  27. package/src/bin/rango.ts +24 -0
  28. package/src/browser/event-controller.ts +876 -0
  29. package/src/browser/index.ts +18 -0
  30. package/src/browser/link-interceptor.ts +121 -0
  31. package/src/browser/lru-cache.ts +69 -0
  32. package/src/browser/merge-segment-loaders.ts +126 -0
  33. package/src/browser/navigation-bridge.ts +913 -0
  34. package/src/browser/navigation-client.ts +165 -0
  35. package/src/browser/navigation-store.ts +823 -0
  36. package/src/browser/partial-update.ts +600 -0
  37. package/src/browser/react/Link.tsx +248 -0
  38. package/src/browser/react/NavigationProvider.tsx +346 -0
  39. package/src/browser/react/ScrollRestoration.tsx +94 -0
  40. package/src/browser/react/context.ts +53 -0
  41. package/src/browser/react/index.ts +52 -0
  42. package/src/browser/react/location-state-shared.ts +120 -0
  43. package/src/browser/react/location-state.ts +62 -0
  44. package/src/browser/react/mount-context.ts +32 -0
  45. package/src/browser/react/use-action.ts +240 -0
  46. package/src/browser/react/use-client-cache.ts +56 -0
  47. package/src/browser/react/use-handle.ts +203 -0
  48. package/src/browser/react/use-href.tsx +40 -0
  49. package/src/browser/react/use-link-status.ts +134 -0
  50. package/src/browser/react/use-mount.ts +31 -0
  51. package/src/browser/react/use-navigation.ts +140 -0
  52. package/src/browser/react/use-segments.ts +188 -0
  53. package/src/browser/request-controller.ts +164 -0
  54. package/src/browser/rsc-router.tsx +352 -0
  55. package/src/browser/scroll-restoration.ts +324 -0
  56. package/src/browser/segment-structure-assert.ts +67 -0
  57. package/src/browser/server-action-bridge.ts +762 -0
  58. package/src/browser/shallow.ts +35 -0
  59. package/src/browser/types.ts +478 -0
  60. package/src/build/generate-manifest.ts +377 -0
  61. package/src/build/generate-route-types.ts +828 -0
  62. package/src/build/index.ts +36 -0
  63. package/src/build/route-trie.ts +239 -0
  64. package/src/cache/cache-scope.ts +563 -0
  65. package/src/cache/cf/cf-cache-store.ts +428 -0
  66. package/src/cache/cf/index.ts +19 -0
  67. package/src/cache/document-cache.ts +340 -0
  68. package/src/cache/index.ts +58 -0
  69. package/src/cache/memory-segment-store.ts +150 -0
  70. package/src/cache/memory-store.ts +253 -0
  71. package/src/cache/types.ts +392 -0
  72. package/src/client.rsc.tsx +83 -0
  73. package/src/client.tsx +643 -0
  74. package/src/component-utils.ts +76 -0
  75. package/src/components/DefaultDocument.tsx +23 -0
  76. package/src/debug.ts +233 -0
  77. package/src/default-error-boundary.tsx +88 -0
  78. package/src/deps/browser.ts +8 -0
  79. package/src/deps/html-stream-client.ts +2 -0
  80. package/src/deps/html-stream-server.ts +2 -0
  81. package/src/deps/rsc.ts +10 -0
  82. package/src/deps/ssr.ts +2 -0
  83. package/src/errors.ts +295 -0
  84. package/src/handle.ts +130 -0
  85. package/src/handles/MetaTags.tsx +193 -0
  86. package/src/handles/index.ts +6 -0
  87. package/src/handles/meta.ts +247 -0
  88. package/src/host/cookie-handler.ts +159 -0
  89. package/src/host/errors.ts +97 -0
  90. package/src/host/index.ts +56 -0
  91. package/src/host/pattern-matcher.ts +214 -0
  92. package/src/host/router.ts +330 -0
  93. package/src/host/testing.ts +79 -0
  94. package/src/host/types.ts +138 -0
  95. package/src/host/utils.ts +25 -0
  96. package/src/href-client.ts +202 -0
  97. package/src/href-context.ts +33 -0
  98. package/src/index.rsc.ts +121 -0
  99. package/src/index.ts +165 -0
  100. package/src/loader.rsc.ts +207 -0
  101. package/src/loader.ts +47 -0
  102. package/src/network-error-thrower.tsx +21 -0
  103. package/src/outlet-context.ts +15 -0
  104. package/src/prerender/param-hash.ts +35 -0
  105. package/src/prerender/store.ts +40 -0
  106. package/src/prerender.ts +156 -0
  107. package/src/reverse.ts +267 -0
  108. package/src/root-error-boundary.tsx +277 -0
  109. package/src/route-content-wrapper.tsx +193 -0
  110. package/src/route-definition.ts +1431 -0
  111. package/src/route-map-builder.ts +242 -0
  112. package/src/route-types.ts +220 -0
  113. package/src/router/error-handling.ts +287 -0
  114. package/src/router/handler-context.ts +158 -0
  115. package/src/router/intercept-resolution.ts +387 -0
  116. package/src/router/loader-resolution.ts +327 -0
  117. package/src/router/manifest.ts +216 -0
  118. package/src/router/match-api.ts +621 -0
  119. package/src/router/match-context.ts +264 -0
  120. package/src/router/match-middleware/background-revalidation.ts +236 -0
  121. package/src/router/match-middleware/cache-lookup.ts +382 -0
  122. package/src/router/match-middleware/cache-store.ts +276 -0
  123. package/src/router/match-middleware/index.ts +81 -0
  124. package/src/router/match-middleware/intercept-resolution.ts +281 -0
  125. package/src/router/match-middleware/segment-resolution.ts +184 -0
  126. package/src/router/match-pipelines.ts +214 -0
  127. package/src/router/match-result.ts +213 -0
  128. package/src/router/metrics.ts +62 -0
  129. package/src/router/middleware.ts +791 -0
  130. package/src/router/pattern-matching.ts +407 -0
  131. package/src/router/revalidation.ts +190 -0
  132. package/src/router/router-context.ts +301 -0
  133. package/src/router/segment-resolution.ts +1315 -0
  134. package/src/router/trie-matching.ts +172 -0
  135. package/src/router/types.ts +163 -0
  136. package/src/router.gen.ts +6 -0
  137. package/src/router.ts +2423 -0
  138. package/src/rsc/handler.ts +1443 -0
  139. package/src/rsc/helpers.ts +64 -0
  140. package/src/rsc/index.ts +56 -0
  141. package/src/rsc/nonce.ts +18 -0
  142. package/src/rsc/types.ts +236 -0
  143. package/src/segment-system.tsx +442 -0
  144. package/src/server/context.ts +466 -0
  145. package/src/server/handle-store.ts +229 -0
  146. package/src/server/loader-registry.ts +174 -0
  147. package/src/server/request-context.ts +554 -0
  148. package/src/server/root-layout.tsx +10 -0
  149. package/src/server/tsconfig.json +14 -0
  150. package/src/server.ts +171 -0
  151. package/src/ssr/index.tsx +296 -0
  152. package/src/theme/ThemeProvider.tsx +291 -0
  153. package/src/theme/ThemeScript.tsx +61 -0
  154. package/src/theme/constants.ts +59 -0
  155. package/src/theme/index.ts +58 -0
  156. package/src/theme/theme-context.ts +70 -0
  157. package/src/theme/theme-script.ts +152 -0
  158. package/src/theme/types.ts +182 -0
  159. package/src/theme/use-theme.ts +44 -0
  160. package/src/types.ts +1757 -0
  161. package/src/urls.gen.ts +8 -0
  162. package/src/urls.ts +1282 -0
  163. package/src/use-loader.tsx +346 -0
  164. package/src/vite/expose-action-id.ts +344 -0
  165. package/src/vite/expose-handle-id.ts +209 -0
  166. package/src/vite/expose-loader-id.ts +426 -0
  167. package/src/vite/expose-location-state-id.ts +177 -0
  168. package/src/vite/expose-prerender-handler-id.ts +429 -0
  169. package/src/vite/index.ts +2068 -0
  170. package/src/vite/package-resolution.ts +125 -0
  171. package/src/vite/version.d.ts +12 -0
  172. package/src/vite/virtual-entries.ts +114 -0
@@ -0,0 +1,3039 @@
1
+ // src/vite/index.ts
2
+ import { createServer as createViteServer } from "vite";
3
+ import * as Vite from "vite";
4
+ import { resolve as resolve3, join as join2, dirname as dirname2, basename } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ import { createRequire } from "node:module";
7
+ import { mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "node:fs";
8
+
9
+ // src/build/generate-route-types.ts
10
+ import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync } from "node:fs";
11
+ import { join, dirname, resolve, relative, basename as pathBasename } from "node:path";
12
+ import picomatch from "picomatch";
13
+ function extractRoutesFromSource(code) {
14
+ const routes = [];
15
+ const regex = /\bpath(?:\.(?:json|text|html|xml|image|stream|any))?\s*\(/g;
16
+ let match;
17
+ while ((match = regex.exec(code)) !== null) {
18
+ const result = parsePathCall(code, match.index + match[0].length);
19
+ if (result) routes.push(result);
20
+ }
21
+ return routes;
22
+ }
23
+ function generatePerModuleTypesSource(routes) {
24
+ const valid = routes.filter(({ name }) => {
25
+ if (!name || /["'\\`\n\r]/.test(name)) {
26
+ console.warn(`[rsc-router] Skipping route with invalid name: ${JSON.stringify(name)}`);
27
+ return false;
28
+ }
29
+ return true;
30
+ });
31
+ const deduped = /* @__PURE__ */ new Map();
32
+ for (const { name, pattern } of valid) {
33
+ deduped.set(name, pattern);
34
+ }
35
+ const sorted = [...deduped.entries()].sort(([a], [b]) => a.localeCompare(b));
36
+ const body = sorted.map(([name, pattern]) => {
37
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
38
+ return ` ${key}: "${pattern}",`;
39
+ }).join("\n");
40
+ return `// Auto-generated by @rangojs/router - do not edit
41
+ export const routes = {
42
+ ${body}
43
+ } as const;
44
+ export type routes = typeof routes;
45
+ `;
46
+ }
47
+ function isWhitespace(ch) {
48
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r";
49
+ }
50
+ function readString(code, pos) {
51
+ const quote = code[pos];
52
+ if (quote !== '"' && quote !== "'") return null;
53
+ let value = "";
54
+ pos++;
55
+ while (pos < code.length) {
56
+ if (code[pos] === "\\") {
57
+ pos++;
58
+ if (pos < code.length) {
59
+ value += code[pos];
60
+ pos++;
61
+ }
62
+ continue;
63
+ }
64
+ if (code[pos] === quote) {
65
+ return { value, end: pos + 1 };
66
+ }
67
+ value += code[pos];
68
+ pos++;
69
+ }
70
+ return null;
71
+ }
72
+ function skipStringLiteral(code, pos) {
73
+ const quote = code[pos];
74
+ if (quote === "`") {
75
+ pos++;
76
+ while (pos < code.length) {
77
+ if (code[pos] === "\\") {
78
+ pos += 2;
79
+ continue;
80
+ }
81
+ if (code[pos] === "`") return pos + 1;
82
+ if (code[pos] === "$" && pos + 1 < code.length && code[pos + 1] === "{") {
83
+ pos += 2;
84
+ let braceDepth = 1;
85
+ while (pos < code.length && braceDepth > 0) {
86
+ if (code[pos] === "{") braceDepth++;
87
+ else if (code[pos] === "}") braceDepth--;
88
+ else if (code[pos] === "\\") pos++;
89
+ else if (code[pos] === '"' || code[pos] === "'" || code[pos] === "`") {
90
+ pos = skipStringLiteral(code, pos);
91
+ continue;
92
+ }
93
+ if (braceDepth > 0) pos++;
94
+ }
95
+ continue;
96
+ }
97
+ pos++;
98
+ }
99
+ return pos;
100
+ }
101
+ pos++;
102
+ while (pos < code.length) {
103
+ if (code[pos] === "\\") {
104
+ pos += 2;
105
+ continue;
106
+ }
107
+ if (code[pos] === quote) return pos + 1;
108
+ pos++;
109
+ }
110
+ return pos;
111
+ }
112
+ function matchesNameColon(code, pos) {
113
+ if (code.slice(pos, pos + 4) !== "name") return false;
114
+ if (pos > 0 && /\w/.test(code[pos - 1])) return false;
115
+ const afterName = pos + 4;
116
+ if (afterName < code.length && /\w/.test(code[afterName])) return false;
117
+ let checkPos = afterName;
118
+ while (checkPos < code.length && isWhitespace(code[checkPos])) checkPos++;
119
+ return code[checkPos] === ":";
120
+ }
121
+ function extractNameValue(code, pos) {
122
+ pos += 4;
123
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
124
+ pos++;
125
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
126
+ return readString(code, pos);
127
+ }
128
+ function parsePathCall(code, pos) {
129
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
130
+ const patternStr = readString(code, pos);
131
+ if (!patternStr) return null;
132
+ const pattern = patternStr.value;
133
+ pos = patternStr.end;
134
+ let depth = 1;
135
+ let name = null;
136
+ while (pos < code.length && depth > 0) {
137
+ const ch = code[pos];
138
+ if (isWhitespace(ch)) {
139
+ pos++;
140
+ continue;
141
+ }
142
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "/") {
143
+ pos += 2;
144
+ while (pos < code.length && code[pos] !== "\n") pos++;
145
+ continue;
146
+ }
147
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "*") {
148
+ pos += 2;
149
+ while (pos < code.length - 1 && !(code[pos] === "*" && code[pos + 1] === "/"))
150
+ pos++;
151
+ pos += 2;
152
+ continue;
153
+ }
154
+ if (depth === 2 && ch === "n" && matchesNameColon(code, pos)) {
155
+ const nameResult = extractNameValue(code, pos);
156
+ if (nameResult) {
157
+ name = nameResult.value;
158
+ pos = nameResult.end;
159
+ continue;
160
+ }
161
+ }
162
+ if (ch === '"' || ch === "`" || ch === "'" && (pos === 0 || !/\w/.test(code[pos - 1]))) {
163
+ pos = skipStringLiteral(code, pos);
164
+ continue;
165
+ }
166
+ if (ch === "(" || ch === "{" || ch === "[") depth++;
167
+ else if (ch === ")" || ch === "}" || ch === "]") depth--;
168
+ pos++;
169
+ }
170
+ if (name === null) return null;
171
+ return { name, pattern };
172
+ }
173
+ function generateRouteTypesSource(routeManifest) {
174
+ const entries = Object.entries(routeManifest).sort(
175
+ ([a], [b]) => a.localeCompare(b)
176
+ );
177
+ const interfaceBody = entries.map(([name, pattern]) => ` "${name}": "${pattern}";`).join("\n");
178
+ return `// Auto-generated by @rangojs/router - do not edit
179
+ export {};
180
+
181
+ declare global {
182
+ namespace RSCRouter {
183
+ interface GeneratedRouteMap {
184
+ ${interfaceBody}
185
+ }
186
+ }
187
+ }
188
+ `;
189
+ }
190
+ var DEFAULT_EXCLUDE_PATTERNS = [
191
+ "**/__tests__/**",
192
+ "**/__mocks__/**",
193
+ "**/dist/**",
194
+ "**/coverage/**",
195
+ "**/*.test.{ts,tsx}",
196
+ "**/*.spec.{ts,tsx}"
197
+ ];
198
+ function createScanFilter(root, opts) {
199
+ const { include, exclude } = opts;
200
+ const hasInclude = include && include.length > 0;
201
+ const hasCustomExclude = exclude !== void 0;
202
+ if (!hasInclude && !hasCustomExclude) return void 0;
203
+ const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
204
+ const includeMatcher = hasInclude ? picomatch(include) : null;
205
+ const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
206
+ return (absolutePath) => {
207
+ const rel = relative(root, absolutePath);
208
+ if (excludeMatcher && excludeMatcher(rel)) return false;
209
+ if (includeMatcher) return includeMatcher(rel);
210
+ return true;
211
+ };
212
+ }
213
+ function findTsFiles(dir, filter) {
214
+ const results = [];
215
+ let entries;
216
+ try {
217
+ entries = readdirSync(dir, { withFileTypes: true });
218
+ } catch (err) {
219
+ console.warn(`[rsc-router] Failed to scan directory ${dir}: ${err.message}`);
220
+ return results;
221
+ }
222
+ for (const entry of entries) {
223
+ const fullPath = join(dir, entry.name);
224
+ if (entry.isDirectory()) {
225
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
226
+ results.push(...findTsFiles(fullPath, filter));
227
+ } else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.includes(".gen.")) {
228
+ if (filter && !filter(fullPath)) continue;
229
+ results.push(fullPath);
230
+ }
231
+ }
232
+ return results;
233
+ }
234
+ function writePerModuleRouteTypes(root, filter) {
235
+ const files = findTsFiles(root, filter);
236
+ for (const filePath of files) {
237
+ writePerModuleRouteTypesForFile(filePath);
238
+ }
239
+ }
240
+ function writePerModuleRouteTypesForFile(filePath) {
241
+ try {
242
+ const source = readFileSync(filePath, "utf-8");
243
+ if (!source.includes("urls(")) return;
244
+ const routes = extractRoutesFromSource(source);
245
+ if (routes.length === 0) return;
246
+ const genPath = filePath.replace(/\.(tsx?)$/, ".gen.ts");
247
+ const genSource = generatePerModuleTypesSource(routes);
248
+ const existing = existsSync(genPath) ? readFileSync(genPath, "utf-8") : null;
249
+ if (existing !== genSource) {
250
+ writeFileSync(genPath, genSource);
251
+ console.log(`[rsc-router] Generated route types -> ${genPath}`);
252
+ }
253
+ } catch (err) {
254
+ console.warn(`[rsc-router] Failed to generate route types for ${filePath}: ${err.message}`);
255
+ }
256
+ }
257
+ function extractIncludesFromSource(code) {
258
+ const results = [];
259
+ const regex = /\binclude\s*\(/g;
260
+ let match;
261
+ while ((match = regex.exec(code)) !== null) {
262
+ const result = parseIncludeCall(code, match.index + match[0].length);
263
+ if (result) results.push(result);
264
+ }
265
+ return results;
266
+ }
267
+ function parseIncludeCall(code, pos) {
268
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
269
+ const prefixStr = readString(code, pos);
270
+ if (!prefixStr) return null;
271
+ const pathPrefix = prefixStr.value;
272
+ pos = prefixStr.end;
273
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
274
+ if (pos >= code.length || code[pos] !== ",") return null;
275
+ pos++;
276
+ while (pos < code.length && isWhitespace(code[pos])) pos++;
277
+ const varStart = pos;
278
+ while (pos < code.length && /[\w$]/.test(code[pos])) pos++;
279
+ if (pos === varStart) return null;
280
+ const variableName = code.slice(varStart, pos);
281
+ let namePrefix = null;
282
+ let depth = 1;
283
+ while (pos < code.length && depth > 0) {
284
+ const ch = code[pos];
285
+ if (isWhitespace(ch)) {
286
+ pos++;
287
+ continue;
288
+ }
289
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "/") {
290
+ pos += 2;
291
+ while (pos < code.length && code[pos] !== "\n") pos++;
292
+ continue;
293
+ }
294
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "*") {
295
+ pos += 2;
296
+ while (pos < code.length - 1 && !(code[pos] === "*" && code[pos + 1] === "/"))
297
+ pos++;
298
+ pos += 2;
299
+ continue;
300
+ }
301
+ if (depth === 2 && ch === "n" && matchesNameColon(code, pos)) {
302
+ const nameResult = extractNameValue(code, pos);
303
+ if (nameResult) {
304
+ namePrefix = nameResult.value;
305
+ pos = nameResult.end;
306
+ continue;
307
+ }
308
+ }
309
+ if (ch === '"' || ch === "`" || ch === "'" && (pos === 0 || !/\w/.test(code[pos - 1]))) {
310
+ pos = skipStringLiteral(code, pos);
311
+ continue;
312
+ }
313
+ if (ch === "(" || ch === "{" || ch === "[") depth++;
314
+ else if (ch === ")" || ch === "}" || ch === "]") depth--;
315
+ pos++;
316
+ }
317
+ return { pathPrefix, variableName, namePrefix };
318
+ }
319
+ function resolveImportedVariable(code, localName) {
320
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
321
+ let match;
322
+ while ((match = importRegex.exec(code)) !== null) {
323
+ const imports = match[1];
324
+ const specifier = match[2];
325
+ const parts = imports.split(",").map((s) => s.trim()).filter(Boolean);
326
+ for (const part of parts) {
327
+ const asMatch = part.match(/^(\w+)\s+as\s+(\w+)$/);
328
+ if (asMatch && asMatch[2] === localName)
329
+ return { specifier, exportedName: asMatch[1] };
330
+ if (part === localName) return { specifier, exportedName: localName };
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+ function resolveImportPath(importSpec, fromFile) {
336
+ if (!importSpec.startsWith(".")) return null;
337
+ const dir = dirname(fromFile);
338
+ let base = importSpec;
339
+ if (base.endsWith(".js")) base = base.slice(0, -3);
340
+ else if (base.endsWith(".mjs")) base = base.slice(0, -4);
341
+ const candidates = [
342
+ resolve(dir, base + ".ts"),
343
+ resolve(dir, base + ".tsx"),
344
+ resolve(dir, base + "/index.ts"),
345
+ resolve(dir, base + "/index.tsx")
346
+ ];
347
+ for (const candidate of candidates) {
348
+ if (existsSync(candidate)) return candidate;
349
+ }
350
+ return null;
351
+ }
352
+ function escapeRegExp(s) {
353
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
354
+ }
355
+ function extractUrlsBlockForVariable(code, varName) {
356
+ const pattern = new RegExp(
357
+ `(?:export\\s+)?(?:const|let|var)\\s+${escapeRegExp(varName)}\\s*=\\s*urls\\s*\\(`
358
+ );
359
+ const match = pattern.exec(code);
360
+ if (!match) return null;
361
+ const openParen = match.index + match[0].length - 1;
362
+ let depth = 1;
363
+ let pos = openParen + 1;
364
+ while (pos < code.length && depth > 0) {
365
+ const ch = code[pos];
366
+ if (ch === '"' || ch === "`" || ch === "'" && (pos === 0 || !/\w/.test(code[pos - 1]))) {
367
+ pos = skipStringLiteral(code, pos);
368
+ continue;
369
+ }
370
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "/") {
371
+ pos += 2;
372
+ while (pos < code.length && code[pos] !== "\n") pos++;
373
+ continue;
374
+ }
375
+ if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "*") {
376
+ pos += 2;
377
+ while (pos < code.length - 1 && !(code[pos] === "*" && code[pos + 1] === "/"))
378
+ pos++;
379
+ pos += 2;
380
+ continue;
381
+ }
382
+ if (ch === "(" || ch === "{" || ch === "[") depth++;
383
+ else if (ch === ")" || ch === "}" || ch === "]") depth--;
384
+ pos++;
385
+ }
386
+ return code.slice(openParen, pos);
387
+ }
388
+ function buildCombinedRouteMap(filePath, variableName, visited) {
389
+ visited = visited ?? /* @__PURE__ */ new Set();
390
+ const realPath = resolve(filePath);
391
+ const key = variableName ? `${realPath}:${variableName}` : realPath;
392
+ if (visited.has(key)) return {};
393
+ visited.add(key);
394
+ let source;
395
+ try {
396
+ source = readFileSync(realPath, "utf-8");
397
+ } catch {
398
+ return {};
399
+ }
400
+ let block;
401
+ if (variableName) {
402
+ const extracted = extractUrlsBlockForVariable(source, variableName);
403
+ if (!extracted) return {};
404
+ block = extracted;
405
+ } else {
406
+ block = source;
407
+ }
408
+ return buildRouteMapFromBlock(block, source, realPath, visited);
409
+ }
410
+ function buildRouteMapFromBlock(block, fullSource, filePath, visited) {
411
+ const routeMap = {};
412
+ const localRoutes = extractRoutesFromSource(block);
413
+ for (const { name, pattern } of localRoutes) {
414
+ routeMap[name] = pattern;
415
+ }
416
+ const includes = extractIncludesFromSource(block);
417
+ for (const { pathPrefix, variableName, namePrefix } of includes) {
418
+ let childRoutes;
419
+ const imported = resolveImportedVariable(fullSource, variableName);
420
+ if (imported) {
421
+ const targetFile = resolveImportPath(imported.specifier, filePath);
422
+ if (!targetFile) continue;
423
+ childRoutes = buildCombinedRouteMap(
424
+ targetFile,
425
+ imported.exportedName,
426
+ visited
427
+ );
428
+ } else {
429
+ childRoutes = buildCombinedRouteMap(filePath, variableName, visited);
430
+ }
431
+ for (const [name, pattern] of Object.entries(childRoutes)) {
432
+ const prefixedName = namePrefix ? `${namePrefix}.${name}` : name;
433
+ const prefixedPattern = pattern === "/" ? pathPrefix || "/" : pathPrefix + pattern;
434
+ routeMap[prefixedName] = prefixedPattern;
435
+ }
436
+ }
437
+ return routeMap;
438
+ }
439
+ function extractUrlsVariableFromRouter(code) {
440
+ const routesCallMatch = code.match(/\.routes\s*\(\s*([a-zA-Z_$][\w$]*)\s*\)/);
441
+ if (routesCallMatch) return routesCallMatch[1];
442
+ const urlsOptionMatch = code.match(/urls\s*:\s*([a-zA-Z_$][\w$]*)/);
443
+ if (urlsOptionMatch) return urlsOptionMatch[1];
444
+ return null;
445
+ }
446
+ function findRouterFiles(root, filter) {
447
+ const files = findTsFiles(root, filter);
448
+ const result = [];
449
+ for (const filePath of files) {
450
+ if (filePath.includes(".gen.")) continue;
451
+ try {
452
+ const source = readFileSync(filePath, "utf-8");
453
+ if (/\bcreateRouter\s*[<(]/.test(source)) {
454
+ result.push(filePath);
455
+ }
456
+ } catch {
457
+ continue;
458
+ }
459
+ }
460
+ return result;
461
+ }
462
+ function writeCombinedRouteTypes(root, knownRouterFiles) {
463
+ try {
464
+ const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
465
+ if (existsSync(oldCombinedPath)) {
466
+ unlinkSync(oldCombinedPath);
467
+ console.log(`[rsc-router] Removed stale combined route types: ${oldCombinedPath}`);
468
+ }
469
+ } catch {
470
+ }
471
+ const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
472
+ if (routerFilePaths.length === 0) return;
473
+ for (const routerFilePath of routerFilePaths) {
474
+ let routerSource;
475
+ try {
476
+ routerSource = readFileSync(routerFilePath, "utf-8");
477
+ } catch {
478
+ continue;
479
+ }
480
+ const urlsVarName = extractUrlsVariableFromRouter(routerSource);
481
+ if (!urlsVarName) continue;
482
+ let routeMap;
483
+ const imported = resolveImportedVariable(routerSource, urlsVarName);
484
+ if (imported) {
485
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
486
+ if (!targetFile) continue;
487
+ routeMap = buildCombinedRouteMap(targetFile, imported.exportedName);
488
+ } else {
489
+ routeMap = buildCombinedRouteMap(routerFilePath, urlsVarName);
490
+ }
491
+ if (Object.keys(routeMap).length === 0) continue;
492
+ const routerBasename = pathBasename(routerFilePath).replace(/\.(tsx?|jsx?)$/, "");
493
+ const outPath = join(dirname(routerFilePath), `${routerBasename}.named-routes.gen.ts`);
494
+ const source = generateRouteTypesSource(routeMap);
495
+ const existing = existsSync(outPath) ? readFileSync(outPath, "utf-8") : null;
496
+ if (existing !== source) {
497
+ writeFileSync(outPath, source);
498
+ console.log(`[rsc-router] Generated route types (${Object.keys(routeMap).length} routes) -> ${outPath}`);
499
+ }
500
+ }
501
+ }
502
+
503
+ // src/vite/expose-action-id.ts
504
+ import MagicString from "magic-string";
505
+ import path from "node:path";
506
+ import fs from "node:fs";
507
+ function getRscPluginApi(config) {
508
+ let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
509
+ if (!plugin) {
510
+ plugin = config.plugins.find(
511
+ (p) => p.api?.manager?.serverReferenceMetaMap !== void 0
512
+ );
513
+ if (plugin) {
514
+ console.warn(
515
+ `[rsc-router:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
516
+ );
517
+ }
518
+ }
519
+ return plugin?.api;
520
+ }
521
+ function normalizePath(p) {
522
+ return p.split(path.sep).join("/");
523
+ }
524
+ function isUseServerModule(filePath) {
525
+ try {
526
+ const content = fs.readFileSync(filePath, "utf-8");
527
+ const trimmed = content.replace(/^\s*\/\/[^\n]*\n/gm, "").replace(/^\s*\/\*[\s\S]*?\*\/\s*/gm, "").trimStart();
528
+ return trimmed.startsWith('"use server"') || trimmed.startsWith("'use server'");
529
+ } catch {
530
+ return false;
531
+ }
532
+ }
533
+ function transformServerReferences(code, sourceId, hashToFileMap) {
534
+ if (!code.includes("createServerReference(")) {
535
+ return null;
536
+ }
537
+ const pattern = /((?:\$\$\w+\.)?createServerReference)\(("[^"]+#[^"]+")([^)]*)\)/g;
538
+ const s = new MagicString(code);
539
+ let hasChanges = false;
540
+ let match;
541
+ while ((match = pattern.exec(code)) !== null) {
542
+ hasChanges = true;
543
+ const [fullMatch, fnCall, idArg, rest] = match;
544
+ const start = match.index;
545
+ const end = start + fullMatch.length;
546
+ let finalIdArg = idArg;
547
+ if (hashToFileMap) {
548
+ const idValue = idArg.slice(1, -1);
549
+ const hashMatch = idValue.match(/^([^#]+)#(.+)$/);
550
+ if (hashMatch) {
551
+ const [, hash, actionName] = hashMatch;
552
+ const filePath = hashToFileMap.get(hash);
553
+ if (filePath) {
554
+ finalIdArg = `"${filePath}#${actionName}"`;
555
+ }
556
+ }
557
+ }
558
+ const replacement = `(function(fn) { fn.$$id = ${finalIdArg}; return fn; })(${fnCall}(${idArg}${rest}))`;
559
+ s.overwrite(start, end, replacement);
560
+ }
561
+ if (!hasChanges) {
562
+ return null;
563
+ }
564
+ return {
565
+ code: s.toString(),
566
+ map: s.generateMap({ source: sourceId, includeContent: true })
567
+ };
568
+ }
569
+ function transformRegisterServerReference(code, sourceId, hashToFileMap) {
570
+ if (!hashToFileMap || !code.includes("registerServerReference(")) {
571
+ return null;
572
+ }
573
+ const pattern = /registerServerReference\(([^,]+),\s*"([^"]+)",\s*"([^"]+)"\)/g;
574
+ const s = new MagicString(code);
575
+ let hasChanges = false;
576
+ let match;
577
+ while ((match = pattern.exec(code)) !== null) {
578
+ const [fullMatch, fnArg, hash, exportName] = match;
579
+ const start = match.index;
580
+ const end = start + fullMatch.length;
581
+ const filePath = hashToFileMap.get(hash);
582
+ if (filePath) {
583
+ hasChanges = true;
584
+ const filePathId = `${filePath}#${exportName}`;
585
+ const replacement = `(function(fn) { fn.$id = "${filePathId}"; return fn; })(registerServerReference(${fnArg}, "${hash}", "${exportName}"))`;
586
+ s.overwrite(start, end, replacement);
587
+ }
588
+ }
589
+ if (!hasChanges) {
590
+ return null;
591
+ }
592
+ return {
593
+ code: s.toString(),
594
+ map: s.generateMap({ source: sourceId, includeContent: true })
595
+ };
596
+ }
597
+ function exposeActionId() {
598
+ let config;
599
+ let isBuild = false;
600
+ let hashToFileMap;
601
+ let rscPluginApi;
602
+ return {
603
+ name: "@rangojs/router:expose-action-id",
604
+ // Run after all other plugins (including RSC plugin's transforms)
605
+ enforce: "post",
606
+ configResolved(resolvedConfig) {
607
+ config = resolvedConfig;
608
+ isBuild = config.command === "build";
609
+ rscPluginApi = getRscPluginApi(config);
610
+ },
611
+ buildStart() {
612
+ if (!rscPluginApi) {
613
+ rscPluginApi = getRscPluginApi(config);
614
+ }
615
+ if (!rscPluginApi) {
616
+ throw new Error(
617
+ "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin.\nThe RSC plugin should be included automatically. If you disabled it with\nrango({ rsc: false }), add rsc() before rango() in your config."
618
+ );
619
+ }
620
+ if (!isBuild) return;
621
+ hashToFileMap = /* @__PURE__ */ new Map();
622
+ const { serverReferenceMetaMap } = rscPluginApi.manager;
623
+ for (const [absolutePath, meta] of Object.entries(
624
+ serverReferenceMetaMap
625
+ )) {
626
+ if (!isUseServerModule(absolutePath)) {
627
+ continue;
628
+ }
629
+ const relativePath = normalizePath(
630
+ path.relative(config.root, absolutePath)
631
+ );
632
+ hashToFileMap.set(meta.referenceKey, relativePath);
633
+ }
634
+ },
635
+ // Dev mode only: transform hook runs after RSC plugin creates server references
636
+ // In dev mode, IDs already contain file paths, not hashes
637
+ transform(code, id) {
638
+ if (isBuild) {
639
+ return;
640
+ }
641
+ if (!code.includes("createServerReference(")) {
642
+ return;
643
+ }
644
+ if (id.includes("/node_modules/")) {
645
+ return;
646
+ }
647
+ return transformServerReferences(code, id);
648
+ },
649
+ // Build mode: renderChunk runs after all transforms and bundling complete
650
+ renderChunk(code, chunk) {
651
+ const isRscEnv = this.environment?.name === "rsc";
652
+ const effectiveMap = isRscEnv ? hashToFileMap : void 0;
653
+ const result = transformServerReferences(
654
+ code,
655
+ chunk.fileName,
656
+ effectiveMap
657
+ );
658
+ if (isRscEnv && hashToFileMap) {
659
+ const codeToTransform = result ? result.code : code;
660
+ const registerResult = transformRegisterServerReference(
661
+ codeToTransform,
662
+ chunk.fileName,
663
+ hashToFileMap
664
+ );
665
+ if (registerResult) {
666
+ return { code: registerResult.code, map: registerResult.map };
667
+ }
668
+ }
669
+ if (result) {
670
+ return { code: result.code, map: result.map };
671
+ }
672
+ return null;
673
+ }
674
+ };
675
+ }
676
+
677
+ // src/vite/expose-loader-id.ts
678
+ import MagicString2 from "magic-string";
679
+ import path2 from "node:path";
680
+ import crypto from "node:crypto";
681
+ function normalizePath2(p) {
682
+ return p.split(path2.sep).join("/");
683
+ }
684
+ function hashLoaderId(filePath, exportName) {
685
+ const input = `${filePath}#${exportName}`;
686
+ const hash = crypto.createHash("sha256").update(input).digest("hex");
687
+ return `${hash.slice(0, 8)}#${exportName}`;
688
+ }
689
+ function hasCreateLoaderImport(code) {
690
+ const pattern = /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/;
691
+ return pattern.test(code);
692
+ }
693
+ function countCreateLoaderArgs(code, startPos, endPos) {
694
+ let depth = 0;
695
+ let argCount = 0;
696
+ let hasContent = false;
697
+ for (let i = startPos; i < endPos; i++) {
698
+ const char = code[i];
699
+ if (char === "(" || char === "[" || char === "{") {
700
+ depth++;
701
+ hasContent = true;
702
+ } else if (char === ")" || char === "]" || char === "}") {
703
+ depth--;
704
+ } else if (char === "," && depth === 0) {
705
+ argCount++;
706
+ } else if (!/\s/.test(char)) {
707
+ hasContent = true;
708
+ }
709
+ }
710
+ return hasContent ? argCount + 1 : 0;
711
+ }
712
+ function generateClientLoaderStubs(code, filePath, isBuild) {
713
+ const loaderPattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
714
+ const loaders = [];
715
+ let match;
716
+ while ((match = loaderPattern.exec(code)) !== null) {
717
+ loaders.push(match[1]);
718
+ }
719
+ if (loaders.length === 0) {
720
+ return null;
721
+ }
722
+ const allExports = /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
723
+ let exportMatch;
724
+ const nonLoaderExports = [];
725
+ while ((exportMatch = allExports.exec(code)) !== null) {
726
+ const name = exportMatch[2];
727
+ if (!loaders.includes(name)) {
728
+ nonLoaderExports.push(name);
729
+ }
730
+ }
731
+ if (nonLoaderExports.length > 0) {
732
+ return null;
733
+ }
734
+ const stubs = loaders.map((name) => {
735
+ const loaderId = isBuild ? hashLoaderId(filePath, name) : `${filePath}#${name}`;
736
+ return `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`;
737
+ });
738
+ return {
739
+ code: stubs.join("\n") + "\n"
740
+ };
741
+ }
742
+ function transformLoaderExports(code, filePath, sourceId, isBuild = false) {
743
+ if (!code.includes("createLoader")) {
744
+ return null;
745
+ }
746
+ if (!hasCreateLoaderImport(code)) {
747
+ return null;
748
+ }
749
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
750
+ const s = new MagicString2(code);
751
+ let hasChanges = false;
752
+ let match;
753
+ while ((match = pattern.exec(code)) !== null) {
754
+ const exportName = match[1];
755
+ const matchEnd = match.index + match[0].length;
756
+ let parenDepth = 1;
757
+ let i = matchEnd;
758
+ while (i < code.length && parenDepth > 0) {
759
+ if (code[i] === "(") parenDepth++;
760
+ if (code[i] === ")") parenDepth--;
761
+ i++;
762
+ }
763
+ const closeParenPos = i - 1;
764
+ const argCount = countCreateLoaderArgs(code, matchEnd, closeParenPos);
765
+ let statementEnd = i;
766
+ while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
767
+ statementEnd++;
768
+ }
769
+ if (code[statementEnd] === ";") {
770
+ statementEnd++;
771
+ }
772
+ const loaderId = isBuild ? hashLoaderId(filePath, exportName) : `${filePath}#${exportName}`;
773
+ const paramInjection = argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
774
+ s.appendLeft(closeParenPos, paramInjection);
775
+ const propInjection = `
776
+ ${exportName}.$$id = "${loaderId}";`;
777
+ s.appendRight(statementEnd, propInjection);
778
+ hasChanges = true;
779
+ }
780
+ if (!hasChanges) {
781
+ return null;
782
+ }
783
+ return {
784
+ code: s.toString(),
785
+ map: s.generateMap({ source: sourceId, includeContent: true })
786
+ };
787
+ }
788
+ var VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
789
+ var RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
790
+ function exposeLoaderId() {
791
+ let config;
792
+ let isBuild = false;
793
+ const loaderRegistry = /* @__PURE__ */ new Map();
794
+ const pendingLoaderScans = /* @__PURE__ */ new Map();
795
+ return {
796
+ name: "@rangojs/router:expose-loader-id",
797
+ enforce: "post",
798
+ configResolved(resolvedConfig) {
799
+ config = resolvedConfig;
800
+ isBuild = config.command === "build";
801
+ },
802
+ async buildStart() {
803
+ if (!isBuild) return;
804
+ const fs2 = await import("node:fs/promises");
805
+ async function scanDir(dir) {
806
+ const results = [];
807
+ try {
808
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
809
+ for (const entry of entries) {
810
+ const fullPath = path2.join(dir, entry.name);
811
+ if (entry.isDirectory()) {
812
+ if (entry.name !== "node_modules") {
813
+ results.push(...await scanDir(fullPath));
814
+ }
815
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
816
+ results.push(fullPath);
817
+ }
818
+ }
819
+ } catch {
820
+ }
821
+ return results;
822
+ }
823
+ try {
824
+ const srcDir = path2.join(config.root, "src");
825
+ const files = await scanDir(srcDir);
826
+ for (const filePath of files) {
827
+ const content = await fs2.readFile(filePath, "utf-8");
828
+ if (!content.includes("createLoader")) continue;
829
+ if (!hasCreateLoaderImport(content)) continue;
830
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
831
+ const relativePath = normalizePath2(
832
+ path2.relative(config.root, filePath)
833
+ );
834
+ let match;
835
+ while ((match = pattern.exec(content)) !== null) {
836
+ const exportName = match[1];
837
+ const hashedId = hashLoaderId(relativePath, exportName);
838
+ loaderRegistry.set(hashedId, {
839
+ filePath: relativePath,
840
+ exportName
841
+ });
842
+ }
843
+ }
844
+ } catch (error) {
845
+ console.warn("[exposeLoaderId] Pre-scan failed:", error);
846
+ }
847
+ },
848
+ resolveId(id) {
849
+ if (id === VIRTUAL_LOADER_MANIFEST) {
850
+ return RESOLVED_VIRTUAL_LOADER_MANIFEST;
851
+ }
852
+ },
853
+ load(id) {
854
+ if (id === RESOLVED_VIRTUAL_LOADER_MANIFEST) {
855
+ if (!isBuild) {
856
+ return `import { setLoaderImports } from "@rangojs/router/server";
857
+
858
+ // Dev mode: empty map, loaders are resolved dynamically via path parsing
859
+ setLoaderImports({});
860
+ `;
861
+ }
862
+ const lazyImports = [];
863
+ for (const [hashedId, { filePath, exportName }] of loaderRegistry) {
864
+ lazyImports.push(
865
+ ` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`
866
+ );
867
+ }
868
+ if (lazyImports.length === 0) {
869
+ return `import { setLoaderImports } from "@rangojs/router/server";
870
+
871
+ // No fetchable loaders discovered during build
872
+ setLoaderImports({});
873
+ `;
874
+ }
875
+ const code = `import { setLoaderImports } from "@rangojs/router/server";
876
+
877
+ // Lazy import map - loaders are loaded on-demand when first requested
878
+ setLoaderImports({
879
+ ${lazyImports.join(",\n")}
880
+ });
881
+ `;
882
+ return code;
883
+ }
884
+ },
885
+ transform(code, id) {
886
+ if (id.includes("/node_modules/")) {
887
+ return;
888
+ }
889
+ if (!code.includes("createLoader")) {
890
+ return;
891
+ }
892
+ if (!hasCreateLoaderImport(code)) {
893
+ return;
894
+ }
895
+ const envName = this.environment?.name;
896
+ const isRscEnv = envName === "rsc";
897
+ const relativePath = normalizePath2(path2.relative(config.root, id));
898
+ if (isRscEnv) {
899
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createLoader\s*\(/g;
900
+ let match;
901
+ while ((match = pattern.exec(code)) !== null) {
902
+ const exportName = match[1];
903
+ const hashedId = hashLoaderId(relativePath, exportName);
904
+ loaderRegistry.set(hashedId, { filePath: relativePath, exportName });
905
+ }
906
+ }
907
+ if (!isRscEnv) {
908
+ const stubResult = generateClientLoaderStubs(code, relativePath, isBuild);
909
+ if (stubResult) {
910
+ return stubResult;
911
+ }
912
+ }
913
+ return transformLoaderExports(code, relativePath, id, isBuild);
914
+ }
915
+ };
916
+ }
917
+
918
+ // src/vite/expose-handle-id.ts
919
+ import MagicString3 from "magic-string";
920
+ import path3 from "node:path";
921
+ import crypto2 from "node:crypto";
922
+ function normalizePath3(p) {
923
+ return p.split(path3.sep).join("/");
924
+ }
925
+ function hashHandleId(filePath, exportName) {
926
+ const input = `${filePath}#${exportName}`;
927
+ const hash = crypto2.createHash("sha256").update(input).digest("hex");
928
+ return `${hash.slice(0, 8)}#${exportName}`;
929
+ }
930
+ function hasCreateHandleImport(code) {
931
+ const pattern = /import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
932
+ return pattern.test(code);
933
+ }
934
+ function analyzeCreateHandleArgs(code, startPos, endPos) {
935
+ const content = code.slice(startPos, endPos).trim();
936
+ if (!content) {
937
+ return { hasArgs: false, firstArgIsString: false, firstArgIsFunction: false };
938
+ }
939
+ const firstArgIsString = /^["']/.test(content);
940
+ const firstArgIsFunction = content.startsWith("(") || content.startsWith("function") || // Check for identifier that could be a collect function reference
941
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*(?:,|$)/.test(content);
942
+ return { hasArgs: true, firstArgIsString, firstArgIsFunction };
943
+ }
944
+ function transformHandleExports(code, filePath, sourceId, isBuild = false) {
945
+ if (!code.includes("createHandle")) {
946
+ return null;
947
+ }
948
+ if (!hasCreateHandleImport(code)) {
949
+ return null;
950
+ }
951
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createHandle\s*(?:<[^>]*>)?\s*\(/g;
952
+ const s = new MagicString3(code);
953
+ let hasChanges = false;
954
+ let match;
955
+ while ((match = pattern.exec(code)) !== null) {
956
+ const exportName = match[1];
957
+ const matchEnd = match.index + match[0].length;
958
+ let parenDepth = 1;
959
+ let i = matchEnd;
960
+ while (i < code.length && parenDepth > 0) {
961
+ if (code[i] === "(") parenDepth++;
962
+ if (code[i] === ")") parenDepth--;
963
+ i++;
964
+ }
965
+ const closeParenPos = i - 1;
966
+ const args = analyzeCreateHandleArgs(code, matchEnd, closeParenPos);
967
+ let statementEnd = i;
968
+ while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
969
+ statementEnd++;
970
+ }
971
+ if (code[statementEnd] === ";") {
972
+ statementEnd++;
973
+ }
974
+ const handleId = isBuild ? hashHandleId(filePath, exportName) : `${filePath}#${exportName}`;
975
+ let paramInjection;
976
+ if (!args.hasArgs) {
977
+ paramInjection = `undefined, "${handleId}"`;
978
+ } else {
979
+ paramInjection = `, "${handleId}"`;
980
+ }
981
+ s.appendLeft(closeParenPos, paramInjection);
982
+ const propInjection = `
983
+ ${exportName}.$$id = "${handleId}";`;
984
+ s.appendRight(statementEnd, propInjection);
985
+ hasChanges = true;
986
+ }
987
+ if (!hasChanges) {
988
+ return null;
989
+ }
990
+ return {
991
+ code: s.toString(),
992
+ map: s.generateMap({ source: sourceId, includeContent: true })
993
+ };
994
+ }
995
+ function exposeHandleId() {
996
+ let config;
997
+ let isBuild = false;
998
+ return {
999
+ name: "@rangojs/router:expose-handle-id",
1000
+ enforce: "post",
1001
+ configResolved(resolvedConfig) {
1002
+ config = resolvedConfig;
1003
+ isBuild = config.command === "build";
1004
+ },
1005
+ transform(code, id) {
1006
+ if (id.includes("/node_modules/")) {
1007
+ return;
1008
+ }
1009
+ if (!code.includes("createHandle")) {
1010
+ return;
1011
+ }
1012
+ if (!hasCreateHandleImport(code)) {
1013
+ return;
1014
+ }
1015
+ const relativePath = normalizePath3(path3.relative(config.root, id));
1016
+ return transformHandleExports(code, relativePath, id, isBuild);
1017
+ }
1018
+ };
1019
+ }
1020
+
1021
+ // src/vite/expose-location-state-id.ts
1022
+ import MagicString4 from "magic-string";
1023
+ import path4 from "node:path";
1024
+ import crypto3 from "node:crypto";
1025
+ function normalizePath4(p) {
1026
+ return p.split(path4.sep).join("/");
1027
+ }
1028
+ function hashLocationStateKey(filePath, exportName) {
1029
+ const input = `${filePath}#${exportName}`;
1030
+ const hash = crypto3.createHash("sha256").update(input).digest("hex");
1031
+ return `${hash.slice(0, 8)}#${exportName}`;
1032
+ }
1033
+ function hasCreateLocationStateImport(code) {
1034
+ const pattern = /import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
1035
+ return pattern.test(code);
1036
+ }
1037
+ function transformLocationStateExports(code, filePath, sourceId, isBuild = false) {
1038
+ if (!code.includes("createLocationState")) {
1039
+ return null;
1040
+ }
1041
+ if (!hasCreateLocationStateImport(code)) {
1042
+ return null;
1043
+ }
1044
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createLocationState\s*(?:<[^>]*>)?\s*\(/g;
1045
+ const s = new MagicString4(code);
1046
+ let hasChanges = false;
1047
+ let match;
1048
+ while ((match = pattern.exec(code)) !== null) {
1049
+ const exportName = match[1];
1050
+ const matchEnd = match.index + match[0].length;
1051
+ let parenDepth = 1;
1052
+ let i = matchEnd;
1053
+ while (i < code.length && parenDepth > 0) {
1054
+ if (code[i] === "(") parenDepth++;
1055
+ if (code[i] === ")") parenDepth--;
1056
+ i++;
1057
+ }
1058
+ const closeParenPos = i - 1;
1059
+ const content = code.slice(matchEnd, closeParenPos).trim();
1060
+ const hasArgs = content.length > 0;
1061
+ let statementEnd = i;
1062
+ while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
1063
+ statementEnd++;
1064
+ }
1065
+ if (code[statementEnd] === ";") {
1066
+ statementEnd++;
1067
+ }
1068
+ const stateKey = isBuild ? hashLocationStateKey(filePath, exportName) : `${filePath}#${exportName}`;
1069
+ if (!hasArgs) {
1070
+ s.appendLeft(closeParenPos, `"${stateKey}"`);
1071
+ } else {
1072
+ continue;
1073
+ }
1074
+ const propInjection = `
1075
+ ${exportName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
1076
+ s.appendRight(statementEnd, propInjection);
1077
+ hasChanges = true;
1078
+ }
1079
+ if (!hasChanges) {
1080
+ return null;
1081
+ }
1082
+ return {
1083
+ code: s.toString(),
1084
+ map: s.generateMap({ source: sourceId, includeContent: true })
1085
+ };
1086
+ }
1087
+ function exposeLocationStateId() {
1088
+ let config;
1089
+ let isBuild = false;
1090
+ return {
1091
+ name: "@rangojs/router:expose-location-state-id",
1092
+ enforce: "post",
1093
+ configResolved(resolvedConfig) {
1094
+ config = resolvedConfig;
1095
+ isBuild = config.command === "build";
1096
+ },
1097
+ transform(code, id) {
1098
+ if (id.includes("/node_modules/")) {
1099
+ return;
1100
+ }
1101
+ if (!code.includes("createLocationState")) {
1102
+ return;
1103
+ }
1104
+ if (!hasCreateLocationStateImport(code)) {
1105
+ return;
1106
+ }
1107
+ const relativePath = normalizePath4(path4.relative(config.root, id));
1108
+ return transformLocationStateExports(code, relativePath, id, isBuild);
1109
+ }
1110
+ };
1111
+ }
1112
+
1113
+ // src/vite/expose-prerender-handler-id.ts
1114
+ import MagicString5 from "magic-string";
1115
+ import path5 from "node:path";
1116
+ import crypto4 from "node:crypto";
1117
+ function normalizePath5(p) {
1118
+ return p.split(path5.sep).join("/");
1119
+ }
1120
+ function hashPrerenderHandlerId(filePath, exportName) {
1121
+ const input = `${filePath}#${exportName}`;
1122
+ const hash = crypto4.createHash("sha256").update(input).digest("hex");
1123
+ return `${hash.slice(0, 8)}#${exportName}`;
1124
+ }
1125
+ function hasCreatePrerenderHandlerImport(code) {
1126
+ const pattern = /import\s*\{[^}]*\bcreatePrerenderHandler\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
1127
+ return pattern.test(code);
1128
+ }
1129
+ function skipStringOrComment(code, pos) {
1130
+ const ch = code[pos];
1131
+ if (ch === '"' || ch === "'") {
1132
+ for (let j = pos + 1; j < code.length; j++) {
1133
+ if (code[j] === "\\") {
1134
+ j++;
1135
+ continue;
1136
+ }
1137
+ if (code[j] === ch) return j + 1;
1138
+ }
1139
+ return code.length;
1140
+ }
1141
+ if (ch === "`") {
1142
+ let j = pos + 1;
1143
+ while (j < code.length) {
1144
+ if (code[j] === "\\") {
1145
+ j += 2;
1146
+ continue;
1147
+ }
1148
+ if (code[j] === "`") return j + 1;
1149
+ if (code[j] === "$" && j + 1 < code.length && code[j + 1] === "{") {
1150
+ j += 2;
1151
+ let braceDepth = 1;
1152
+ while (j < code.length && braceDepth > 0) {
1153
+ const inner = skipStringOrComment(code, j);
1154
+ if (inner > j) {
1155
+ j = inner;
1156
+ continue;
1157
+ }
1158
+ if (code[j] === "{") braceDepth++;
1159
+ else if (code[j] === "}") braceDepth--;
1160
+ if (braceDepth > 0) j++;
1161
+ }
1162
+ if (braceDepth === 0) j++;
1163
+ continue;
1164
+ }
1165
+ j++;
1166
+ }
1167
+ return j;
1168
+ }
1169
+ if (ch === "/" && pos + 1 < code.length) {
1170
+ if (code[pos + 1] === "/") {
1171
+ const eol = code.indexOf("\n", pos + 2);
1172
+ return eol === -1 ? code.length : eol + 1;
1173
+ }
1174
+ if (code[pos + 1] === "*") {
1175
+ const end = code.indexOf("*/", pos + 2);
1176
+ return end === -1 ? code.length : end + 2;
1177
+ }
1178
+ }
1179
+ return pos;
1180
+ }
1181
+ function findMatchingParen(code, startPos) {
1182
+ let depth = 1;
1183
+ let i = startPos;
1184
+ while (i < code.length && depth > 0) {
1185
+ const skipped = skipStringOrComment(code, i);
1186
+ if (skipped > i) {
1187
+ i = skipped;
1188
+ continue;
1189
+ }
1190
+ if (code[i] === "(") depth++;
1191
+ if (code[i] === ")") depth--;
1192
+ i++;
1193
+ }
1194
+ return i;
1195
+ }
1196
+ function countArgs(code, startPos, endPos) {
1197
+ let depth = 0;
1198
+ let argCount = 0;
1199
+ let hasContent = false;
1200
+ let i = startPos;
1201
+ while (i < endPos) {
1202
+ const skipped = skipStringOrComment(code, i);
1203
+ if (skipped > i) {
1204
+ hasContent = true;
1205
+ i = skipped;
1206
+ continue;
1207
+ }
1208
+ const char = code[i];
1209
+ if (char === "(" || char === "[" || char === "{") {
1210
+ depth++;
1211
+ hasContent = true;
1212
+ } else if (char === ")" || char === "]" || char === "}") {
1213
+ depth--;
1214
+ } else if (char === "," && depth === 0) {
1215
+ argCount++;
1216
+ } else if (!/\s/.test(char)) {
1217
+ hasContent = true;
1218
+ }
1219
+ i++;
1220
+ }
1221
+ return hasContent ? argCount + 1 : 0;
1222
+ }
1223
+ function transformPrerenderHandlerExports(code, filePath, sourceId, isBuild = false) {
1224
+ if (!code.includes("createPrerenderHandler")) {
1225
+ return null;
1226
+ }
1227
+ if (!hasCreatePrerenderHandlerImport(code)) {
1228
+ return null;
1229
+ }
1230
+ const pattern = /export\s+const\s+(\w+)\s*=\s*createPrerenderHandler\s*(?:<[^>]*>)?\s*\(/g;
1231
+ const s = new MagicString5(code);
1232
+ let hasChanges = false;
1233
+ let match;
1234
+ while ((match = pattern.exec(code)) !== null) {
1235
+ const exportName = match[1];
1236
+ const matchEnd = match.index + match[0].length;
1237
+ const afterClose = findMatchingParen(code, matchEnd);
1238
+ const closeParenPos = afterClose - 1;
1239
+ const argCount = countArgs(code, matchEnd, closeParenPos);
1240
+ let statementEnd = afterClose;
1241
+ while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
1242
+ statementEnd++;
1243
+ }
1244
+ if (code[statementEnd] === ";") {
1245
+ statementEnd++;
1246
+ }
1247
+ const handlerId = isBuild ? hashPrerenderHandlerId(filePath, exportName) : `${filePath}#${exportName}`;
1248
+ let paramInjection;
1249
+ if (argCount === 0) {
1250
+ paramInjection = `undefined, "${handlerId}"`;
1251
+ } else if (argCount === 1) {
1252
+ paramInjection = `, undefined, "${handlerId}"`;
1253
+ } else {
1254
+ paramInjection = `, "${handlerId}"`;
1255
+ }
1256
+ s.appendLeft(closeParenPos, paramInjection);
1257
+ const propInjection = `
1258
+ ${exportName}.$$id = "${handlerId}";`;
1259
+ s.appendRight(statementEnd, propInjection);
1260
+ hasChanges = true;
1261
+ }
1262
+ if (!hasChanges) {
1263
+ return null;
1264
+ }
1265
+ return {
1266
+ code: s.toString(),
1267
+ map: s.generateMap({ source: sourceId, includeContent: true, hires: "boundary" })
1268
+ };
1269
+ }
1270
+ function generateWholeFileHandlerStubs(code, filePath, isBuild) {
1271
+ const handlerPattern = /export\s+const\s+(\w+)\s*=\s*createPrerenderHandler\s*(?:<[^>]*>)?\s*\(/g;
1272
+ const handlers = [];
1273
+ let match;
1274
+ while ((match = handlerPattern.exec(code)) !== null) {
1275
+ handlers.push(match[1]);
1276
+ }
1277
+ if (handlers.length === 0) return null;
1278
+ if (/export\s*\{/.test(code) || /export\s*\*/.test(code)) {
1279
+ return null;
1280
+ }
1281
+ const allExports = /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
1282
+ let exportMatch;
1283
+ while ((exportMatch = allExports.exec(code)) !== null) {
1284
+ const name = exportMatch[2];
1285
+ if (!handlers.includes(name)) {
1286
+ return null;
1287
+ }
1288
+ }
1289
+ const stubs = handlers.map((name) => {
1290
+ const handlerId = isBuild ? hashPrerenderHandlerId(filePath, name) : `${filePath}#${name}`;
1291
+ return `export const ${name} = { __brand: "prerenderHandler", $$id: "${handlerId}" };`;
1292
+ });
1293
+ return { code: stubs.join("\n") + "\n", map: null };
1294
+ }
1295
+ function generatePrerenderHandlerStubs(code, filePath, sourceId, isBuild = false) {
1296
+ const pattern = /export\s+const\s+(\w+)\s*=\s*(createPrerenderHandler\s*(?:<[^>]*>)?\s*\()/g;
1297
+ const s = new MagicString5(code);
1298
+ let hasChanges = false;
1299
+ let match;
1300
+ while ((match = pattern.exec(code)) !== null) {
1301
+ const exportName = match[1];
1302
+ const callStart = match.index + match[0].length - match[2].length;
1303
+ const openParenPos = match.index + match[0].length;
1304
+ const afterCloseParen = findMatchingParen(code, openParenPos);
1305
+ const handlerId = isBuild ? hashPrerenderHandlerId(filePath, exportName) : `${filePath}#${exportName}`;
1306
+ s.overwrite(
1307
+ callStart,
1308
+ afterCloseParen,
1309
+ `{ __brand: "prerenderHandler", $$id: "${handlerId}" }`
1310
+ );
1311
+ hasChanges = true;
1312
+ }
1313
+ if (!hasChanges) return null;
1314
+ return {
1315
+ code: s.toString(),
1316
+ map: s.generateMap({ source: sourceId, includeContent: true, hires: "boundary" })
1317
+ };
1318
+ }
1319
+ function exposePrerenderHandlerId() {
1320
+ let config;
1321
+ let isBuild = false;
1322
+ const prerenderHandlerModules = /* @__PURE__ */ new Map();
1323
+ return {
1324
+ name: "@rangojs/router:expose-prerender-handler-id",
1325
+ enforce: "post",
1326
+ api: {
1327
+ prerenderHandlerModules
1328
+ },
1329
+ configResolved(resolvedConfig) {
1330
+ config = resolvedConfig;
1331
+ isBuild = config.command === "build";
1332
+ },
1333
+ transform(code, id) {
1334
+ if (id.includes("/node_modules/")) {
1335
+ return;
1336
+ }
1337
+ if (!code.includes("createPrerenderHandler")) {
1338
+ return;
1339
+ }
1340
+ if (!hasCreatePrerenderHandlerImport(code)) {
1341
+ return;
1342
+ }
1343
+ const relativePath = normalizePath5(path5.relative(config.root, id));
1344
+ const isRscEnv = this.environment?.name === "rsc";
1345
+ if (!isRscEnv) {
1346
+ return generateWholeFileHandlerStubs(code, relativePath, isBuild) ?? generatePrerenderHandlerStubs(code, relativePath, id, isBuild);
1347
+ }
1348
+ if (isBuild) {
1349
+ const handlerPattern = /export\s+const\s+(\w+)\s*=\s*createPrerenderHandler\s*(?:<[^>]*>)?\s*\(/g;
1350
+ const exportNames = [];
1351
+ let m;
1352
+ while ((m = handlerPattern.exec(code)) !== null) {
1353
+ exportNames.push(m[1]);
1354
+ }
1355
+ if (exportNames.length > 0) {
1356
+ prerenderHandlerModules.set(id, exportNames);
1357
+ }
1358
+ }
1359
+ return transformPrerenderHandlerExports(code, relativePath, id, isBuild);
1360
+ }
1361
+ };
1362
+ }
1363
+
1364
+ // src/vite/virtual-entries.ts
1365
+ var VIRTUAL_ENTRY_BROWSER = `
1366
+ import {
1367
+ createFromReadableStream,
1368
+ createFromFetch,
1369
+ setServerCallback,
1370
+ encodeReply,
1371
+ createTemporaryReferenceSet,
1372
+ } from "@rangojs/router/internal/deps/browser";
1373
+ import { createElement, StrictMode } from "react";
1374
+ import { hydrateRoot } from "react-dom/client";
1375
+ import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
1376
+ import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
1377
+
1378
+ async function initializeApp() {
1379
+ const deps = {
1380
+ createFromFetch,
1381
+ createFromReadableStream,
1382
+ encodeReply,
1383
+ setServerCallback,
1384
+ createTemporaryReferenceSet,
1385
+ };
1386
+
1387
+ await initBrowserApp({ rscStream, deps });
1388
+
1389
+ hydrateRoot(
1390
+ document,
1391
+ createElement(StrictMode, null, createElement(RSCRouter))
1392
+ );
1393
+ }
1394
+
1395
+ initializeApp().catch(console.error);
1396
+ `.trim();
1397
+ var VIRTUAL_ENTRY_SSR = `
1398
+ import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
1399
+ import { renderToReadableStream } from "react-dom/server.edge";
1400
+ import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
1401
+ import { createSSRHandler } from "@rangojs/router/ssr";
1402
+
1403
+ export const renderHTML = createSSRHandler({
1404
+ createFromReadableStream,
1405
+ renderToReadableStream,
1406
+ injectRSCPayload,
1407
+ loadBootstrapScriptContent: () =>
1408
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
1409
+ });
1410
+ `.trim();
1411
+ function getVirtualEntryRSC(routerPath) {
1412
+ return `
1413
+ import {
1414
+ renderToReadableStream,
1415
+ decodeReply,
1416
+ createTemporaryReferenceSet,
1417
+ loadServerAction,
1418
+ decodeAction,
1419
+ decodeFormState,
1420
+ } from "@rangojs/router/internal/deps/rsc";
1421
+ import { router } from "${routerPath}";
1422
+ import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
1423
+ import { VERSION } from "@rangojs/router:version";
1424
+
1425
+ // Import loader manifest to ensure all fetchable loaders are registered at startup
1426
+ // This is critical for serverless/multi-process deployments where the loader module
1427
+ // might not be imported before a GET request arrives
1428
+ import "virtual:rsc-router/loader-manifest";
1429
+
1430
+ // Import pre-generated route manifest so href() works immediately on cold start.
1431
+ // In build mode, this contains the full route map generated at build time.
1432
+ // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
1433
+ import "virtual:rsc-router/routes-manifest";
1434
+
1435
+ export default createRSCHandler({
1436
+ router,
1437
+ version: VERSION,
1438
+ deps: {
1439
+ renderToReadableStream,
1440
+ decodeReply,
1441
+ createTemporaryReferenceSet,
1442
+ loadServerAction,
1443
+ decodeAction,
1444
+ decodeFormState,
1445
+ },
1446
+ loadSSRModule: () =>
1447
+ import.meta.viteRsc.loadModule("ssr", "index"),
1448
+ });
1449
+ `.trim();
1450
+ }
1451
+ var VIRTUAL_IDS = {
1452
+ browser: "virtual:rsc-router/entry.browser.js",
1453
+ ssr: "virtual:rsc-router/entry.ssr.js",
1454
+ rsc: "virtual:rsc-router/entry.rsc.js",
1455
+ version: "@rangojs/router:version"
1456
+ };
1457
+ function getVirtualVersionContent(version) {
1458
+ return `export const VERSION = ${JSON.stringify(version)};`;
1459
+ }
1460
+
1461
+ // src/vite/package-resolution.ts
1462
+ import { existsSync as existsSync2 } from "node:fs";
1463
+ import { resolve as resolve2 } from "node:path";
1464
+
1465
+ // package.json
1466
+ var package_default = {
1467
+ name: "@rangojs/router",
1468
+ version: "0.0.0-experimental.10",
1469
+ type: "module",
1470
+ description: "Django-inspired RSC router with composable URL patterns",
1471
+ author: "Ivo Todorov",
1472
+ license: "MIT",
1473
+ repository: {
1474
+ type: "git",
1475
+ url: "git+https://github.com/ivogt/vite-rsc.git",
1476
+ directory: "packages/rangojs-router"
1477
+ },
1478
+ homepage: "https://github.com/ivogt/vite-rsc#readme",
1479
+ bugs: {
1480
+ url: "https://github.com/ivogt/vite-rsc/issues"
1481
+ },
1482
+ publishConfig: {
1483
+ access: "public",
1484
+ tag: "experimental"
1485
+ },
1486
+ keywords: [
1487
+ "react",
1488
+ "rsc",
1489
+ "react-server-components",
1490
+ "router",
1491
+ "vite"
1492
+ ],
1493
+ exports: {
1494
+ ".": {
1495
+ "react-server": "./src/index.rsc.ts",
1496
+ types: "./src/index.rsc.ts",
1497
+ default: "./src/index.ts"
1498
+ },
1499
+ "./server": {
1500
+ types: "./src/server.ts",
1501
+ import: "./src/server.ts"
1502
+ },
1503
+ "./client": {
1504
+ "react-server": "./src/client.rsc.tsx",
1505
+ types: "./src/client.tsx",
1506
+ default: "./src/client.tsx"
1507
+ },
1508
+ "./browser": {
1509
+ types: "./src/browser/index.ts",
1510
+ default: "./src/browser/index.ts"
1511
+ },
1512
+ "./ssr": {
1513
+ types: "./src/ssr/index.tsx",
1514
+ default: "./src/ssr/index.tsx"
1515
+ },
1516
+ "./rsc": {
1517
+ "react-server": "./src/rsc/index.ts",
1518
+ types: "./src/rsc/index.ts",
1519
+ default: "./src/rsc/index.ts"
1520
+ },
1521
+ "./vite": {
1522
+ types: "./src/vite/index.ts",
1523
+ import: "./dist/vite/index.js"
1524
+ },
1525
+ "./types": {
1526
+ types: "./src/vite/version.d.ts"
1527
+ },
1528
+ "./__internal": {
1529
+ types: "./src/__internal.ts",
1530
+ default: "./src/__internal.ts"
1531
+ },
1532
+ "./internal/deps/browser": {
1533
+ types: "./src/deps/browser.ts",
1534
+ default: "./src/deps/browser.ts"
1535
+ },
1536
+ "./internal/deps/ssr": {
1537
+ types: "./src/deps/ssr.ts",
1538
+ default: "./src/deps/ssr.ts"
1539
+ },
1540
+ "./internal/deps/rsc": {
1541
+ "react-server": "./src/deps/rsc.ts",
1542
+ types: "./src/deps/rsc.ts",
1543
+ default: "./src/deps/rsc.ts"
1544
+ },
1545
+ "./internal/deps/html-stream-client": {
1546
+ types: "./src/deps/html-stream-client.ts",
1547
+ default: "./src/deps/html-stream-client.ts"
1548
+ },
1549
+ "./internal/deps/html-stream-server": {
1550
+ types: "./src/deps/html-stream-server.ts",
1551
+ default: "./src/deps/html-stream-server.ts"
1552
+ },
1553
+ "./internal/rsc-handler": {
1554
+ "react-server": "./src/rsc/handler.ts",
1555
+ types: "./src/rsc/handler.ts",
1556
+ default: "./src/rsc/handler.ts"
1557
+ },
1558
+ "./cache": {
1559
+ "react-server": "./src/cache/index.ts",
1560
+ types: "./src/cache/index.ts",
1561
+ default: "./src/cache/index.ts"
1562
+ },
1563
+ "./theme": {
1564
+ types: "./src/theme/index.ts",
1565
+ default: "./src/theme/index.ts"
1566
+ },
1567
+ "./build": {
1568
+ types: "./src/build/index.ts",
1569
+ import: "./src/build/index.ts"
1570
+ },
1571
+ "./host": {
1572
+ "react-server": "./src/host/index.ts",
1573
+ types: "./src/host/index.ts",
1574
+ default: "./src/host/index.ts"
1575
+ },
1576
+ "./host/testing": {
1577
+ types: "./src/host/testing.ts",
1578
+ default: "./src/host/testing.ts"
1579
+ }
1580
+ },
1581
+ files: [
1582
+ "src",
1583
+ "!src/**/__tests__",
1584
+ "!src/**/__mocks__",
1585
+ "!src/**/*.test.ts",
1586
+ "!src/**/*.test.tsx",
1587
+ "dist",
1588
+ "skills",
1589
+ "CLAUDE.md",
1590
+ "README.md"
1591
+ ],
1592
+ bin: {
1593
+ rango: "./dist/bin/rango.js"
1594
+ },
1595
+ scripts: {
1596
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node'",
1597
+ prepublishOnly: "pnpm build",
1598
+ typecheck: "tsc --noEmit",
1599
+ test: "playwright test",
1600
+ "test:ui": "playwright test --ui",
1601
+ "test:unit": "vitest run",
1602
+ "test:unit:watch": "vitest"
1603
+ },
1604
+ peerDependencies: {
1605
+ "@cloudflare/vite-plugin": "^1.21.0",
1606
+ "@vitejs/plugin-rsc": "^0.5.14",
1607
+ react: "^18.0.0 || ^19.0.0",
1608
+ vite: "^7.3.0"
1609
+ },
1610
+ peerDependenciesMeta: {
1611
+ "@cloudflare/vite-plugin": {
1612
+ optional: true
1613
+ },
1614
+ vite: {
1615
+ optional: true
1616
+ }
1617
+ },
1618
+ dependencies: {
1619
+ "@vitejs/plugin-rsc": "^0.5.14",
1620
+ "magic-string": "^0.30.17",
1621
+ picomatch: "^4.0.3",
1622
+ "rsc-html-stream": "^0.0.7"
1623
+ },
1624
+ devDependencies: {
1625
+ "@playwright/test": "^1.49.1",
1626
+ "@types/node": "^24.10.1",
1627
+ "@types/react": "catalog:",
1628
+ "@types/react-dom": "catalog:",
1629
+ esbuild: "^0.27.0",
1630
+ jiti: "^2.6.1",
1631
+ react: "catalog:",
1632
+ "react-dom": "catalog:",
1633
+ tinyexec: "^0.3.2",
1634
+ typescript: "^5.3.0",
1635
+ vitest: "^4.0.0"
1636
+ }
1637
+ };
1638
+
1639
+ // src/vite/package-resolution.ts
1640
+ var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
1641
+ function getPublishedPackageName() {
1642
+ return package_default.name;
1643
+ }
1644
+ function isInstalledFromNpm() {
1645
+ const packageName = getPublishedPackageName();
1646
+ return existsSync2(resolve2(process.cwd(), "node_modules", packageName));
1647
+ }
1648
+ function isWorkspaceDevelopment() {
1649
+ return !isInstalledFromNpm();
1650
+ }
1651
+ var PACKAGE_SUBPATHS = [
1652
+ "",
1653
+ "/browser",
1654
+ "/client",
1655
+ "/server",
1656
+ "/rsc",
1657
+ "/ssr",
1658
+ "/internal/deps/browser",
1659
+ "/internal/deps/html-stream-client",
1660
+ "/internal/deps/ssr",
1661
+ "/internal/deps/rsc"
1662
+ ];
1663
+ function getExcludeDeps() {
1664
+ const packageName = getPublishedPackageName();
1665
+ const excludes = [];
1666
+ for (const subpath of PACKAGE_SUBPATHS) {
1667
+ excludes.push(`${packageName}${subpath}`);
1668
+ if (packageName !== VIRTUAL_PACKAGE_NAME) {
1669
+ excludes.push(`${VIRTUAL_PACKAGE_NAME}${subpath}`);
1670
+ }
1671
+ }
1672
+ return excludes;
1673
+ }
1674
+ var ALIAS_SUBPATHS = [
1675
+ "/internal/deps/browser",
1676
+ "/internal/deps/ssr",
1677
+ "/internal/deps/rsc",
1678
+ "/internal/deps/html-stream-client",
1679
+ "/internal/deps/html-stream-server",
1680
+ "/browser",
1681
+ "/client",
1682
+ "/server",
1683
+ "/rsc",
1684
+ "/ssr"
1685
+ ];
1686
+ function getPackageAliases() {
1687
+ if (isWorkspaceDevelopment()) {
1688
+ return {};
1689
+ }
1690
+ const packageName = getPublishedPackageName();
1691
+ const aliases = {};
1692
+ for (const subpath of ALIAS_SUBPATHS) {
1693
+ aliases[`${VIRTUAL_PACKAGE_NAME}${subpath}`] = `${packageName}${subpath}`;
1694
+ }
1695
+ return aliases;
1696
+ }
1697
+
1698
+ // src/vite/index.ts
1699
+ var versionEsbuildPlugin = {
1700
+ name: "@rangojs/router-version",
1701
+ setup(build) {
1702
+ build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
1703
+ path: args.path,
1704
+ namespace: "@rangojs/router-virtual"
1705
+ }));
1706
+ build.onLoad({ filter: /.*/, namespace: "@rangojs/router-virtual" }, () => ({
1707
+ contents: `export const VERSION = "dev";`,
1708
+ loader: "js"
1709
+ }));
1710
+ }
1711
+ };
1712
+ var sharedEsbuildOptions = {
1713
+ plugins: [versionEsbuildPlugin]
1714
+ };
1715
+ function createVirtualEntriesPlugin(entries, routerPath) {
1716
+ const virtualModules = {};
1717
+ if (entries.client === VIRTUAL_IDS.browser) {
1718
+ virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
1719
+ }
1720
+ if (entries.ssr === VIRTUAL_IDS.ssr) {
1721
+ virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
1722
+ }
1723
+ if (entries.rsc === VIRTUAL_IDS.rsc && routerPath) {
1724
+ const absoluteRouterPath = routerPath.startsWith(".") ? "/" + routerPath.slice(2) : routerPath;
1725
+ virtualModules[VIRTUAL_IDS.rsc] = getVirtualEntryRSC(absoluteRouterPath);
1726
+ }
1727
+ return {
1728
+ name: "@rangojs/router:virtual-entries",
1729
+ enforce: "pre",
1730
+ resolveId(id) {
1731
+ if (id in virtualModules) {
1732
+ return "\0" + id;
1733
+ }
1734
+ if (id.startsWith("\0") && id.slice(1) in virtualModules) {
1735
+ return id;
1736
+ }
1737
+ return null;
1738
+ },
1739
+ load(id) {
1740
+ if (id.startsWith("\0virtual:rsc-router/")) {
1741
+ const virtualId = id.slice(1);
1742
+ if (virtualId in virtualModules) {
1743
+ return virtualModules[virtualId];
1744
+ }
1745
+ }
1746
+ return null;
1747
+ }
1748
+ };
1749
+ }
1750
+ function getManualChunks(id) {
1751
+ const normalized = Vite.normalizePath(id);
1752
+ if (normalized.includes("node_modules/react/") || normalized.includes("node_modules/react-dom/") || normalized.includes("node_modules/react-server-dom-webpack/") || normalized.includes("node_modules/@vitejs/plugin-rsc/")) {
1753
+ return "react";
1754
+ }
1755
+ const packageName = getPublishedPackageName();
1756
+ if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
1757
+ return "router";
1758
+ }
1759
+ return void 0;
1760
+ }
1761
+ function createVersionPlugin() {
1762
+ const buildVersion = Date.now().toString(16);
1763
+ let currentVersion = buildVersion;
1764
+ let isDev = false;
1765
+ let server = null;
1766
+ return {
1767
+ name: "@rangojs/router:version",
1768
+ enforce: "pre",
1769
+ configResolved(config) {
1770
+ isDev = config.command === "serve";
1771
+ },
1772
+ configureServer(devServer) {
1773
+ server = devServer;
1774
+ },
1775
+ resolveId(id) {
1776
+ if (id === VIRTUAL_IDS.version) {
1777
+ return "\0" + id;
1778
+ }
1779
+ return null;
1780
+ },
1781
+ load(id) {
1782
+ if (id === "\0" + VIRTUAL_IDS.version) {
1783
+ return getVirtualVersionContent(currentVersion);
1784
+ }
1785
+ return null;
1786
+ },
1787
+ // Track RSC module changes and update version
1788
+ hotUpdate(ctx) {
1789
+ if (!isDev) return;
1790
+ const isRscModule = this.environment?.name === "rsc";
1791
+ if (isRscModule && ctx.modules.length > 0) {
1792
+ currentVersion = Date.now().toString(16);
1793
+ console.log(
1794
+ `[rsc-router] RSC module changed, version updated: ${currentVersion}`
1795
+ );
1796
+ if (server) {
1797
+ const rscEnv = server.environments?.rsc;
1798
+ if (rscEnv?.moduleGraph) {
1799
+ const versionMod = rscEnv.moduleGraph.getModuleById(
1800
+ "\0" + VIRTUAL_IDS.version
1801
+ );
1802
+ if (versionMod) {
1803
+ rscEnv.moduleGraph.invalidateModule(versionMod);
1804
+ }
1805
+ }
1806
+ }
1807
+ }
1808
+ }
1809
+ };
1810
+ }
1811
+ function flattenLeafEntries(prefixTree, routeManifest, result) {
1812
+ function visit(node) {
1813
+ const children = node.children || {};
1814
+ if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
1815
+ const routes = {};
1816
+ for (const name of node.routes) {
1817
+ if (name in routeManifest) {
1818
+ routes[name] = routeManifest[name];
1819
+ }
1820
+ }
1821
+ result.push({ staticPrefix: node.staticPrefix, routes });
1822
+ } else {
1823
+ for (const child of Object.values(children)) {
1824
+ visit(child);
1825
+ }
1826
+ }
1827
+ }
1828
+ for (const node of Object.values(prefixTree)) {
1829
+ visit(node);
1830
+ }
1831
+ }
1832
+ function buildRouteToStaticPrefix(prefixTree, result) {
1833
+ function visit(node) {
1834
+ const sp = node.staticPrefix || "";
1835
+ for (const name of node.routes || []) {
1836
+ result[name] = sp;
1837
+ }
1838
+ for (const child of Object.values(node.children || {})) {
1839
+ visit(child);
1840
+ }
1841
+ }
1842
+ for (const node of Object.values(prefixTree)) {
1843
+ visit(node);
1844
+ }
1845
+ }
1846
+ function createRouterDiscoveryPlugin(entryPath, opts) {
1847
+ let projectRoot = "";
1848
+ let isBuildMode = false;
1849
+ let userResolveAlias = void 0;
1850
+ let scanFilter;
1851
+ let cachedRouterFiles;
1852
+ let mergedRouteManifest = null;
1853
+ let perRouterManifests = [];
1854
+ let prerenderBuildUrls = null;
1855
+ let prerenderRouteHashMap = {};
1856
+ let rscPluginManager = null;
1857
+ let cfIntegrationApi = null;
1858
+ let discoveryDone = null;
1859
+ let mergedPrecomputedEntries = null;
1860
+ let mergedRouteTrie = null;
1861
+ async function discoverRouters(rscEnv) {
1862
+ await rscEnv.runner.import(entryPath);
1863
+ const serverMod = await rscEnv.runner.import("@rangojs/router/server");
1864
+ let registry = serverMod.RouterRegistry;
1865
+ if (!registry || registry.size === 0) {
1866
+ try {
1867
+ const hostMod = await rscEnv.runner.import("@rangojs/router/host");
1868
+ const hostRegistry = hostMod.HostRouterRegistry;
1869
+ if (hostRegistry && hostRegistry.size > 0) {
1870
+ console.log(
1871
+ `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
1872
+ );
1873
+ for (const [, entry] of hostRegistry) {
1874
+ for (const route of entry.routes) {
1875
+ if (typeof route.handler === "function") {
1876
+ try {
1877
+ await route.handler();
1878
+ } catch {
1879
+ }
1880
+ }
1881
+ }
1882
+ if (entry.fallback && typeof entry.fallback.handler === "function") {
1883
+ try {
1884
+ await entry.fallback.handler();
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ }
1889
+ const freshServerMod = await rscEnv.runner.import("@rangojs/router/server");
1890
+ const freshRegistry = freshServerMod.RouterRegistry;
1891
+ if (freshRegistry && freshRegistry.size > 0) {
1892
+ Object.assign(serverMod, freshServerMod);
1893
+ registry = freshRegistry;
1894
+ }
1895
+ }
1896
+ } catch {
1897
+ }
1898
+ if (!registry || registry.size === 0) {
1899
+ throw new Error(
1900
+ `[rsc-router] No routers found in registry after importing ${entryPath}`
1901
+ );
1902
+ }
1903
+ }
1904
+ const buildMod = await rscEnv.runner.import("@rangojs/router/build");
1905
+ const generateManifest = buildMod.generateManifest;
1906
+ mergedRouteManifest = {};
1907
+ mergedPrecomputedEntries = [];
1908
+ perRouterManifests = [];
1909
+ let mergedRouteAncestry = {};
1910
+ let mergedRouteTrailingSlash = {};
1911
+ let routerMountIndex = 0;
1912
+ const allManifests = [];
1913
+ for (const [id, router] of registry) {
1914
+ if (!router.urlpatterns || !generateManifest) {
1915
+ continue;
1916
+ }
1917
+ const manifest = generateManifest(router.urlpatterns, routerMountIndex);
1918
+ routerMountIndex++;
1919
+ allManifests.push({ id, manifest });
1920
+ const routeCount = Object.keys(manifest.routeManifest).length;
1921
+ const staticRoutes = Object.values(manifest.routeManifest).filter(
1922
+ (p) => !p.includes(":") && !p.includes("*")
1923
+ ).length;
1924
+ const dynamicRoutes = routeCount - staticRoutes;
1925
+ Object.assign(mergedRouteManifest, manifest.routeManifest);
1926
+ perRouterManifests.push({ id, routeManifest: manifest.routeManifest, sourceFile: router.__sourceFile });
1927
+ if (manifest._routeAncestry) {
1928
+ Object.assign(mergedRouteAncestry, manifest._routeAncestry);
1929
+ }
1930
+ if (manifest.routeTrailingSlash) {
1931
+ Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
1932
+ }
1933
+ flattenLeafEntries(manifest.prefixTree, manifest.routeManifest, mergedPrecomputedEntries);
1934
+ const hash = hashRouterId(id);
1935
+ const outDir = join2(projectRoot, "dist", "static", `__${hash}`);
1936
+ mkdirSync(outDir, { recursive: true });
1937
+ writeFileSync2(
1938
+ join2(outDir, "routes.json"),
1939
+ JSON.stringify(manifest.routeManifest, null, 2) + "\n"
1940
+ );
1941
+ writeFileSync2(
1942
+ join2(outDir, "prefixes.json"),
1943
+ JSON.stringify(manifest.prefixTree, null, 2) + "\n"
1944
+ );
1945
+ console.log(
1946
+ `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic) -> dist/static/__${hash}/`
1947
+ );
1948
+ }
1949
+ if (mergedRouteManifest && Object.keys(mergedRouteManifest).length > 0) {
1950
+ const buildRouteTrie = buildMod.buildRouteTrie;
1951
+ if (buildRouteTrie && mergedRouteAncestry) {
1952
+ const routeToStaticPrefix = {};
1953
+ for (const { manifest } of allManifests) {
1954
+ for (const name of Object.keys(manifest.routeManifest)) {
1955
+ if (!(name in routeToStaticPrefix)) {
1956
+ routeToStaticPrefix[name] = "";
1957
+ }
1958
+ }
1959
+ buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
1960
+ }
1961
+ const prerenderRouteNames = /* @__PURE__ */ new Set();
1962
+ const passthroughRouteNames = /* @__PURE__ */ new Set();
1963
+ const mergedResponseTypeRoutes = {};
1964
+ for (const { manifest } of allManifests) {
1965
+ if (manifest.prerenderRoutes) {
1966
+ for (const name of manifest.prerenderRoutes) {
1967
+ prerenderRouteNames.add(name);
1968
+ }
1969
+ }
1970
+ if (manifest.passthroughRoutes) {
1971
+ for (const name of manifest.passthroughRoutes) {
1972
+ passthroughRouteNames.add(name);
1973
+ }
1974
+ }
1975
+ if (manifest.responseTypeRoutes) {
1976
+ Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
1977
+ }
1978
+ }
1979
+ mergedRouteTrie = buildRouteTrie(
1980
+ mergedRouteManifest,
1981
+ mergedRouteAncestry,
1982
+ routeToStaticPrefix,
1983
+ Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
1984
+ prerenderRouteNames.size > 0 ? prerenderRouteNames : void 0,
1985
+ passthroughRouteNames.size > 0 ? passthroughRouteNames : void 0,
1986
+ Object.keys(mergedResponseTypeRoutes).length > 0 ? mergedResponseTypeRoutes : void 0
1987
+ );
1988
+ }
1989
+ }
1990
+ if (opts?.enableBuildPrerender) {
1991
+ const urls = [];
1992
+ const routeHashMap = {};
1993
+ for (const { id, manifest } of allManifests) {
1994
+ if (!manifest.prerenderRoutes) continue;
1995
+ const rHash = hashRouterId(id);
1996
+ const defs = manifest._prerenderDefs || {};
1997
+ for (const routeName of manifest.prerenderRoutes) {
1998
+ routeHashMap[routeName] = rHash;
1999
+ const pattern = manifest.routeManifest[routeName];
2000
+ if (!pattern) continue;
2001
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
2002
+ if (!hasDynamic) {
2003
+ urls.push(pattern.replace(/\/$/, "") || "/");
2004
+ } else {
2005
+ const def = defs[routeName];
2006
+ if (def?.getParams) {
2007
+ try {
2008
+ const paramsList = await def.getParams();
2009
+ for (const params of paramsList) {
2010
+ let url = pattern;
2011
+ for (const [key, value] of Object.entries(params)) {
2012
+ url = url.replace(`:${key}`, encodeURIComponent(String(value)));
2013
+ }
2014
+ urls.push(url.replace(/\/$/, "") || "/");
2015
+ }
2016
+ } catch (err) {
2017
+ console.warn(
2018
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
2019
+ );
2020
+ }
2021
+ } else {
2022
+ console.warn(
2023
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
2024
+ );
2025
+ }
2026
+ }
2027
+ }
2028
+ }
2029
+ if (urls.length > 0) {
2030
+ prerenderBuildUrls = urls;
2031
+ prerenderRouteHashMap = routeHashMap;
2032
+ console.log(
2033
+ `[rsc-router] Pre-render URLs: ${urls.join(", ")}`
2034
+ );
2035
+ }
2036
+ }
2037
+ return serverMod;
2038
+ }
2039
+ function writeRouteTypesFiles() {
2040
+ if (perRouterManifests.length === 0) return;
2041
+ try {
2042
+ const entryDir = dirname2(resolve3(projectRoot, entryPath));
2043
+ const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
2044
+ if (existsSync3(oldCombinedPath)) {
2045
+ unlinkSync2(oldCombinedPath);
2046
+ console.log(`[rsc-router] Removed stale combined route types: ${oldCombinedPath}`);
2047
+ }
2048
+ } catch {
2049
+ }
2050
+ for (const { routeManifest, sourceFile } of perRouterManifests) {
2051
+ if (!sourceFile) continue;
2052
+ try {
2053
+ const routerDir = dirname2(sourceFile);
2054
+ const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
2055
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
2056
+ const source = generateRouteTypesSource(routeManifest);
2057
+ const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2058
+ if (existing !== source) {
2059
+ writeFileSync2(outPath, source);
2060
+ console.log(`[rsc-router] Generated route types -> ${outPath}`);
2061
+ }
2062
+ } catch (err) {
2063
+ console.warn(`[rsc-router] Failed to write named-routes.gen.ts: ${err.message}`);
2064
+ }
2065
+ }
2066
+ }
2067
+ return {
2068
+ name: "@rangojs/router:discovery",
2069
+ configResolved(config) {
2070
+ projectRoot = config.root;
2071
+ isBuildMode = config.command === "build";
2072
+ userResolveAlias = config.resolve.alias;
2073
+ if (opts?.include || opts?.exclude) {
2074
+ scanFilter = createScanFilter(projectRoot, {
2075
+ include: opts.include,
2076
+ exclude: opts.exclude
2077
+ });
2078
+ }
2079
+ if (opts?.staticRouteTypesGeneration !== false) {
2080
+ writePerModuleRouteTypes(projectRoot, scanFilter);
2081
+ cachedRouterFiles = findRouterFiles(projectRoot, scanFilter);
2082
+ writeCombinedRouteTypes(projectRoot, cachedRouterFiles);
2083
+ }
2084
+ if (opts?.enableBuildPrerender) {
2085
+ const rscPlugin = config.plugins.find((p) => p.name === "rsc:minimal");
2086
+ if (rscPlugin?.api?.manager) {
2087
+ rscPluginManager = rscPlugin.api.manager;
2088
+ }
2089
+ const cfPlugin = config.plugins.find(
2090
+ (p) => p.name === "@rangojs/router:cloudflare-integration"
2091
+ );
2092
+ if (cfPlugin?.api) {
2093
+ cfIntegrationApi = cfPlugin.api;
2094
+ }
2095
+ }
2096
+ },
2097
+ // Dev mode: discover routers and populate manifest in memory.
2098
+ // Skipped in build mode (buildStart handles it).
2099
+ configureServer(server) {
2100
+ if (isBuildMode) return;
2101
+ if (globalThis.__rscRouterDiscoveryActive) return;
2102
+ let resolveDiscovery;
2103
+ const discoveryPromise = new Promise((resolve4) => {
2104
+ resolveDiscovery = resolve4;
2105
+ });
2106
+ const discover = async () => {
2107
+ const rscEnv = server.environments?.rsc;
2108
+ if (!rscEnv?.runner) {
2109
+ resolveDiscovery();
2110
+ return;
2111
+ }
2112
+ try {
2113
+ const serverMod = await rscEnv.runner.import("@rangojs/router/server");
2114
+ if (serverMod?.setManifestReadyPromise) {
2115
+ serverMod.setManifestReadyPromise(discoveryPromise);
2116
+ }
2117
+ await discoverRouters(rscEnv);
2118
+ if (opts?.staticRouteTypesGeneration === false) {
2119
+ writeRouteTypesFiles();
2120
+ }
2121
+ if (mergedRouteManifest && serverMod?.setCachedManifest) {
2122
+ serverMod.setCachedManifest(mergedRouteManifest);
2123
+ }
2124
+ if (mergedPrecomputedEntries && mergedPrecomputedEntries.length > 0 && serverMod?.setPrecomputedEntries) {
2125
+ serverMod.setPrecomputedEntries(mergedPrecomputedEntries);
2126
+ }
2127
+ if (mergedRouteTrie && serverMod?.setRouteTrie) {
2128
+ serverMod.setRouteTrie(mergedRouteTrie);
2129
+ }
2130
+ } catch (err) {
2131
+ console.warn(
2132
+ `[rsc-router] Router discovery failed: ${err.message}
2133
+ ${err.stack}`
2134
+ );
2135
+ } finally {
2136
+ resolveDiscovery();
2137
+ }
2138
+ };
2139
+ discoveryDone = new Promise((resolve4) => {
2140
+ setTimeout(() => discover().then(resolve4, resolve4), 0);
2141
+ });
2142
+ if (opts?.staticRouteTypesGeneration !== false) {
2143
+ server.watcher.on("change", (filePath) => {
2144
+ if (filePath.endsWith(".gen.ts")) return;
2145
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx")) return;
2146
+ if (scanFilter && !scanFilter(filePath)) return;
2147
+ try {
2148
+ const source = readFileSync2(filePath, "utf-8");
2149
+ const trimmed = source.trimStart();
2150
+ if (trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'")) return;
2151
+ const hasUrls = source.includes("urls(");
2152
+ const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
2153
+ if (!hasUrls && !hasCreateRouter) return;
2154
+ if (hasUrls) {
2155
+ writePerModuleRouteTypesForFile(filePath);
2156
+ }
2157
+ if (hasCreateRouter) {
2158
+ cachedRouterFiles = void 0;
2159
+ }
2160
+ writeCombinedRouteTypes(projectRoot, cachedRouterFiles);
2161
+ } catch {
2162
+ }
2163
+ });
2164
+ }
2165
+ },
2166
+ // Build mode: create a temporary Vite dev server to access the RSC
2167
+ // environment's module runner, then discover routers and generate manifests.
2168
+ // The manifest data is stored for the virtual module's load hook.
2169
+ async buildStart() {
2170
+ if (!isBuildMode) return;
2171
+ if (mergedRouteManifest !== null) return;
2172
+ let tempServer = null;
2173
+ try {
2174
+ globalThis.__rscRouterDiscoveryActive = true;
2175
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
2176
+ tempServer = await createViteServer({
2177
+ root: projectRoot,
2178
+ configFile: false,
2179
+ server: { middlewareMode: true },
2180
+ appType: "custom",
2181
+ logLevel: "silent",
2182
+ // Use the resolved aliases from the real config (includes user's path aliases
2183
+ // like @/ -> src/ AND package aliases from rsc-router)
2184
+ resolve: { alias: userResolveAlias },
2185
+ // Enable automatic JSX runtime so .tsx files don't need `import React`.
2186
+ // Without this, esbuild defaults to classic mode (React.createElement)
2187
+ // which fails when lazy host-router handlers load sub-app modules with JSX.
2188
+ esbuild: { jsx: "automatic", jsxImportSource: "react" },
2189
+ plugins: [
2190
+ rsc({ entries: { client: "virtual:entry-client", ssr: "virtual:entry-ssr", rsc: entryPath } }),
2191
+ createVersionPlugin(),
2192
+ // Stub virtual modules that the RSC entry may import
2193
+ // (e.g., virtual:rsc-router/routes-manifest, virtual:rsc-router/loader-manifest)
2194
+ createVirtualStubPlugin()
2195
+ ]
2196
+ });
2197
+ const rscEnv = tempServer.environments?.rsc;
2198
+ if (!rscEnv?.runner) {
2199
+ console.warn(
2200
+ "[rsc-router] RSC environment runner not available during build, skipping manifest generation"
2201
+ );
2202
+ return;
2203
+ }
2204
+ await discoverRouters(rscEnv);
2205
+ writeRouteTypesFiles();
2206
+ } catch (err) {
2207
+ delete globalThis.__rscRouterDiscoveryActive;
2208
+ if (tempServer) {
2209
+ await tempServer.close();
2210
+ }
2211
+ throw new Error(
2212
+ `[rsc-router] Build-time router discovery failed: ${err.message}`
2213
+ );
2214
+ } finally {
2215
+ delete globalThis.__rscRouterDiscoveryActive;
2216
+ if (tempServer) {
2217
+ await tempServer.close();
2218
+ }
2219
+ }
2220
+ },
2221
+ // Virtual module: provides the pre-generated route manifest as a JS module
2222
+ // that calls setCachedManifest() at import time.
2223
+ resolveId(id) {
2224
+ if (id === VIRTUAL_ROUTES_MANIFEST_ID) {
2225
+ return "\0" + VIRTUAL_ROUTES_MANIFEST_ID;
2226
+ }
2227
+ return null;
2228
+ },
2229
+ async load(id) {
2230
+ if (id === "\0" + VIRTUAL_ROUTES_MANIFEST_ID) {
2231
+ if (discoveryDone) {
2232
+ await discoveryDone;
2233
+ console.log(`[rsc-router] Virtual module loaded after discovery (${mergedRouteManifest ? Object.keys(mergedRouteManifest).length + " routes" : "no data"})`);
2234
+ }
2235
+ const hasManifest = mergedRouteManifest && Object.keys(mergedRouteManifest).length > 0;
2236
+ if (hasManifest) {
2237
+ const lines = [
2238
+ `import { setCachedManifest, setPrecomputedEntries, setRouteTrie } from "@rangojs/router/server";`,
2239
+ `setCachedManifest(${JSON.stringify(mergedRouteManifest)});`
2240
+ ];
2241
+ if (mergedPrecomputedEntries && mergedPrecomputedEntries.length > 0) {
2242
+ lines.push(`setPrecomputedEntries(${JSON.stringify(mergedPrecomputedEntries)});`);
2243
+ }
2244
+ if (mergedRouteTrie) {
2245
+ lines.push(`setRouteTrie(${JSON.stringify(mergedRouteTrie)});`);
2246
+ }
2247
+ return lines.join("\n");
2248
+ }
2249
+ return `// Route manifest will be populated at runtime`;
2250
+ }
2251
+ return null;
2252
+ },
2253
+ // Build-time pre-rendering: spawn a child Node.js process to import the
2254
+ // built worker and render each prerender URL to static HTML.
2255
+ // A separate process is needed because Vite registers module resolution
2256
+ // hooks that interfere with importing the bundled worker.
2257
+ //
2258
+ // RETRY SEMANTICS:
2259
+ // Vite's environment-aware builder calls closeBundle once per environment
2260
+ // build (rsc -> client -> ssr). Pre-rendering requires BOTH the client
2261
+ // assets manifest AND the SSR bundle, so early calls bail out silently.
2262
+ // The `order: "post"` ensures this runs after other plugins' closeBundle
2263
+ // hooks. `sequential: true` prevents concurrent closeBundle execution
2264
+ // across environments, avoiding race conditions on shared state like
2265
+ // prerenderBuildUrls. The null-guard on prerenderBuildUrls ensures we
2266
+ // run at most once even if closeBundle fires again after the SSR build.
2267
+ closeBundle: {
2268
+ order: "post",
2269
+ sequential: true,
2270
+ async handler() {
2271
+ if (!isBuildMode || !prerenderBuildUrls?.length) return;
2272
+ if (!rscPluginManager?.buildAssetsManifest) return;
2273
+ const ssrPath = resolve3(projectRoot, "dist/rsc/ssr/index.js");
2274
+ if (!existsSync3(ssrPath)) return;
2275
+ if (prerenderBuildUrls === null) return;
2276
+ const urlsToRender = prerenderBuildUrls;
2277
+ prerenderBuildUrls = null;
2278
+ try {
2279
+ rscPluginManager.writeAssetsManifest(["ssr", "rsc"]);
2280
+ } catch (err) {
2281
+ console.warn(
2282
+ `[rsc-router] Failed to write assets manifest early: ${err.message}`
2283
+ );
2284
+ }
2285
+ console.log(
2286
+ `[rsc-router] Pre-rendering ${urlsToRender.length} route(s)...`
2287
+ );
2288
+ const scriptPath = resolve3(projectRoot, "dist/.prerender.mjs");
2289
+ const scriptContent = generatePrerenderScript(projectRoot, urlsToRender, prerenderRouteHashMap);
2290
+ writeFileSync2(scriptPath, scriptContent);
2291
+ try {
2292
+ const { execFileSync } = await import("node:child_process");
2293
+ const cleanEnv = { ...process.env };
2294
+ delete cleanEnv.NODE_OPTIONS;
2295
+ delete cleanEnv.TSX;
2296
+ execFileSync(process.execPath, ["--no-warnings", scriptPath], {
2297
+ stdio: "inherit",
2298
+ cwd: projectRoot,
2299
+ env: cleanEnv
2300
+ });
2301
+ const chunkInfo = cfIntegrationApi?.handlerChunkInfo;
2302
+ if (chunkInfo) {
2303
+ const chunkPath = resolve3(projectRoot, "dist/rsc", chunkInfo.fileName);
2304
+ try {
2305
+ let code = readFileSync2(chunkPath, "utf-8");
2306
+ const originalSize = Buffer.byteLength(code);
2307
+ for (const { name, handlerId, passthrough } of chunkInfo.exports) {
2308
+ if (passthrough) continue;
2309
+ const callStartRe = new RegExp(
2310
+ `const\\s+${name}\\s*=\\s*createPrerenderHandler\\s*(?:<[^>]*>)?\\s*\\(`
2311
+ );
2312
+ const startMatch = callStartRe.exec(code);
2313
+ if (!startMatch) continue;
2314
+ const openParenPos = startMatch.index + startMatch[0].length;
2315
+ let depth = 1;
2316
+ let pos = openParenPos;
2317
+ while (pos < code.length && depth > 0) {
2318
+ const ch = code[pos];
2319
+ if (ch === '"' || ch === "'" || ch === "`") {
2320
+ pos++;
2321
+ while (pos < code.length && code[pos] !== ch) {
2322
+ if (code[pos] === "\\") pos++;
2323
+ pos++;
2324
+ }
2325
+ } else if (ch === "(") {
2326
+ depth++;
2327
+ } else if (ch === ")") {
2328
+ depth--;
2329
+ }
2330
+ pos++;
2331
+ }
2332
+ if (depth !== 0) continue;
2333
+ let rangeEnd = pos;
2334
+ while (rangeEnd < code.length && /\s/.test(code[rangeEnd])) rangeEnd++;
2335
+ if (code[rangeEnd] === ";") rangeEnd++;
2336
+ const matched = code.slice(startMatch.index, rangeEnd);
2337
+ if (!matched.includes(handlerId)) continue;
2338
+ const stub = `const ${name} = { __brand: "prerenderHandler", $$id: "${handlerId}" };`;
2339
+ code = code.slice(0, startMatch.index) + stub + code.slice(rangeEnd);
2340
+ code = code.replace(
2341
+ new RegExp(`\\n${name}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
2342
+ ""
2343
+ );
2344
+ }
2345
+ writeFileSync2(chunkPath, code);
2346
+ const newSize = Buffer.byteLength(code);
2347
+ const savedKB = ((originalSize - newSize) / 1024).toFixed(1);
2348
+ console.log(
2349
+ `[rsc-router] Evicted handler code from RSC bundle (${savedKB} KB saved): ${chunkInfo.fileName}`
2350
+ );
2351
+ } catch (replaceErr) {
2352
+ console.warn(
2353
+ `[rsc-router] Failed to evict handler code: ${replaceErr.message}`
2354
+ );
2355
+ }
2356
+ }
2357
+ try {
2358
+ const { readdirSync: readDir } = await import("node:fs");
2359
+ const prerenderData = {};
2360
+ const staticDir = resolve3(projectRoot, "dist/static");
2361
+ for (const hashDir of readDir(staticDir).filter((d) => d.startsWith("__"))) {
2362
+ const prerenderDir = resolve3(staticDir, hashDir, "prerender");
2363
+ if (!existsSync3(prerenderDir)) continue;
2364
+ for (const routeDir of readDir(prerenderDir)) {
2365
+ const routePath = resolve3(prerenderDir, routeDir);
2366
+ for (const file of readDir(routePath).filter((f) => f.endsWith(".flight"))) {
2367
+ const paramHash = file.slice(0, -7);
2368
+ const key = `${routeDir}/${paramHash}`;
2369
+ const content = readFileSync2(resolve3(routePath, file), "utf-8");
2370
+ prerenderData[key] = JSON.parse(content);
2371
+ }
2372
+ }
2373
+ }
2374
+ if (Object.keys(prerenderData).length > 0) {
2375
+ const rscEntryPath = resolve3(projectRoot, "dist/rsc/index.js");
2376
+ if (existsSync3(rscEntryPath)) {
2377
+ let rscCode = readFileSync2(rscEntryPath, "utf-8");
2378
+ const injection = `globalThis.__PRERENDER_DATA = ${JSON.stringify(prerenderData)};
2379
+ `;
2380
+ rscCode = injection + rscCode;
2381
+ writeFileSync2(rscEntryPath, rscCode);
2382
+ const dataSize = (Buffer.byteLength(injection) / 1024).toFixed(1);
2383
+ console.log(
2384
+ `[rsc-router] Injected prerender data into RSC bundle (${dataSize} KB, ${Object.keys(prerenderData).length} entries)`
2385
+ );
2386
+ }
2387
+ }
2388
+ } catch (injectErr) {
2389
+ console.warn(
2390
+ `[rsc-router] Failed to inject prerender data: ${injectErr.message}`
2391
+ );
2392
+ }
2393
+ } catch (err) {
2394
+ console.warn(
2395
+ `[rsc-router] Build-time pre-rendering failed: ${err.message}`
2396
+ );
2397
+ } finally {
2398
+ try {
2399
+ const { rmSync } = await import("node:fs");
2400
+ rmSync(scriptPath, { force: true });
2401
+ } catch {
2402
+ }
2403
+ }
2404
+ }
2405
+ }
2406
+ };
2407
+ }
2408
+ function generatePrerenderScript(projectRoot, urls, routeHashMap) {
2409
+ return `
2410
+ import { mkdirSync, writeFileSync, symlinkSync, existsSync, readdirSync, statSync, lstatSync, rmSync } from "node:fs";
2411
+ import { resolve } from "node:path";
2412
+
2413
+ const projectRoot = ${JSON.stringify(projectRoot)};
2414
+ const urls = ${JSON.stringify(urls)};
2415
+ const routeHashMap = ${JSON.stringify(routeHashMap)};
2416
+
2417
+ // DJB2 hash matching the runtime param-hash utility
2418
+ function djb2Hex(str) {
2419
+ let hash = 5381;
2420
+ for (let i = 0; i < str.length; i++) {
2421
+ hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0;
2422
+ }
2423
+ return hash.toString(16).padStart(8, "0");
2424
+ }
2425
+
2426
+ function hashParams(params) {
2427
+ const entries = Object.entries(params);
2428
+ if (entries.length === 0) return "_";
2429
+ const sorted = entries.sort(([a], [b]) => a.localeCompare(b));
2430
+ const str = sorted.map(([k, v]) => k + "=" + v).join("&");
2431
+ return djb2Hex(str);
2432
+ }
2433
+
2434
+ // Extract params from a URL by matching against the route pattern.
2435
+ // The route pattern uses :paramName syntax.
2436
+ function extractParams(urlPath, pattern) {
2437
+ const urlParts = urlPath.split("/").filter(Boolean);
2438
+ const patternParts = pattern.split("/").filter(Boolean);
2439
+ const params = {};
2440
+ for (let i = 0; i < patternParts.length; i++) {
2441
+ if (patternParts[i].startsWith(":")) {
2442
+ const paramName = patternParts[i].slice(1);
2443
+ params[paramName] = decodeURIComponent(urlParts[i] || "");
2444
+ }
2445
+ }
2446
+ return params;
2447
+ }
2448
+
2449
+ // Mock workerd globals (bundled worker accesses globalThis.Cloudflare.compatibilityFlags)
2450
+ globalThis.Cloudflare = { compatibilityFlags: { enable_nodejs_process_v2: false } };
2451
+
2452
+ // Create symlinks for project root directories under dist/ so that relative
2453
+ // paths from import.meta.dirname (dist/rsc/assets/) resolve correctly.
2454
+ const symlinks = [];
2455
+ try {
2456
+ for (const entry of readdirSync(projectRoot)) {
2457
+ if (entry === "dist" || entry === "node_modules" || entry.startsWith(".")) continue;
2458
+ const target = resolve(projectRoot, entry);
2459
+ const link = resolve(projectRoot, "dist", entry);
2460
+ try {
2461
+ if (!existsSync(link) && statSync(target).isDirectory()) {
2462
+ symlinkSync(target, link);
2463
+ symlinks.push(link);
2464
+ }
2465
+ } catch {}
2466
+ }
2467
+ } catch {}
2468
+
2469
+ const mockEnv = new Proxy({}, {
2470
+ get(_, prop) {
2471
+ if (prop === "toString" || prop === Symbol.toPrimitive) return () => "[PrerenderEnv]";
2472
+ if (prop === Symbol.toStringTag) return "PrerenderEnv";
2473
+ if (prop === "Variables") return {};
2474
+ if (prop === "ASSETS") return { fetch: () => new Response("", { status: 404 }) };
2475
+ throw new Error("Cloudflare binding \\"" + String(prop) + "\\" not available in prerender");
2476
+ },
2477
+ });
2478
+ const mockCtx = { waitUntil: () => {}, passThroughOnException: () => {} };
2479
+
2480
+ try {
2481
+ const mod = await import(resolve(projectRoot, "dist/rsc/index.js"));
2482
+ const worker = mod.default;
2483
+ if (!worker?.fetch) {
2484
+ console.warn("[rsc-router] Built worker has no fetch handler, skipping pre-render");
2485
+ process.exit(0);
2486
+ }
2487
+
2488
+ let rendered = 0;
2489
+ for (const urlPath of urls) {
2490
+ try {
2491
+ // Collect serialized segments for this route
2492
+ const response = await worker.fetch(
2493
+ new Request("http://localhost" + urlPath + "?__no_cache&__prerender_collect", {
2494
+ headers: { Accept: "text/html" },
2495
+ }),
2496
+ mockEnv,
2497
+ mockCtx,
2498
+ );
2499
+ if (response.status !== 200) {
2500
+ console.warn("[rsc-router] Pre-render collect " + urlPath + " returned " + response.status + ", skipping");
2501
+ continue;
2502
+ }
2503
+ const data = await response.json();
2504
+ const { segments, handles, routeName } = data;
2505
+ if (!routeName || !segments) {
2506
+ console.warn("[rsc-router] Pre-render collect " + urlPath + " missing routeName or segments, skipping");
2507
+ continue;
2508
+ }
2509
+
2510
+ const routerHash = routeHashMap[routeName];
2511
+ if (!routerHash) {
2512
+ console.warn("[rsc-router] No router hash for route " + routeName + ", skipping");
2513
+ continue;
2514
+ }
2515
+
2516
+ // Compute param hash from the matched route params
2517
+ // The response carries routeName; we compute params from the URL
2518
+ // using the route manifest pattern. For static routes, paramHash is "_".
2519
+ const paramHash = hashParams(data.params || {});
2520
+
2521
+ // Write .flight file
2522
+ const flightDir = resolve(projectRoot, "dist", "static",
2523
+ "__" + routerHash, "prerender", routeName);
2524
+ mkdirSync(flightDir, { recursive: true });
2525
+ const flightPath = resolve(flightDir, paramHash + ".flight");
2526
+ writeFileSync(flightPath, JSON.stringify({ segments, handles }));
2527
+
2528
+ rendered++;
2529
+ console.log("[rsc-router] Pre-rendered: " + routeName + " (" + urlPath + ") -> " + paramHash + ".flight");
2530
+ } catch (err) {
2531
+ console.warn("[rsc-router] Pre-render failed for " + urlPath + ": " + err.message);
2532
+ }
2533
+ }
2534
+
2535
+ if (rendered > 0) {
2536
+ console.log("[rsc-router] Pre-rendered " + rendered + "/" + urls.length + " route(s) to dist/static/");
2537
+ }
2538
+ } finally {
2539
+ for (const link of symlinks) {
2540
+ try { if (lstatSync(link).isSymbolicLink()) rmSync(link); } catch {}
2541
+ }
2542
+ }
2543
+ `.trim();
2544
+ }
2545
+ var VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
2546
+ function resolveDiscoveryEntryPath(options, routerPath) {
2547
+ if (options.preset === "cloudflare") {
2548
+ const wranglerPaths = ["wrangler.json", "wrangler.jsonc"];
2549
+ for (const filename of wranglerPaths) {
2550
+ if (existsSync3(filename)) {
2551
+ try {
2552
+ const raw = readFileSync2(filename, "utf-8");
2553
+ const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
2554
+ const config = JSON.parse(cleaned);
2555
+ if (config.main) {
2556
+ return config.main;
2557
+ }
2558
+ } catch {
2559
+ }
2560
+ }
2561
+ }
2562
+ return void 0;
2563
+ }
2564
+ return routerPath;
2565
+ }
2566
+ function createVirtualStubPlugin() {
2567
+ const STUB_PREFIXES = [
2568
+ "virtual:rsc-router/",
2569
+ "virtual:entry-",
2570
+ "virtual:vite-rsc/"
2571
+ ];
2572
+ return {
2573
+ name: "@rangojs/router:virtual-stubs",
2574
+ resolveId(id) {
2575
+ if (STUB_PREFIXES.some((p) => id.startsWith(p))) {
2576
+ return "\0stub:" + id;
2577
+ }
2578
+ return null;
2579
+ },
2580
+ load(id) {
2581
+ if (id.startsWith("\0stub:")) {
2582
+ return "export default {}";
2583
+ }
2584
+ return null;
2585
+ }
2586
+ };
2587
+ }
2588
+ function hashRouterId(id) {
2589
+ return createHash("sha256").update(id).digest("hex").slice(0, 12);
2590
+ }
2591
+ function createVersionInjectorPlugin(rscEntryPath) {
2592
+ let projectRoot = "";
2593
+ let resolvedEntryPath = "";
2594
+ return {
2595
+ name: "@rangojs/router:version-injector",
2596
+ enforce: "pre",
2597
+ configResolved(config) {
2598
+ projectRoot = config.root;
2599
+ resolvedEntryPath = resolve3(projectRoot, rscEntryPath);
2600
+ },
2601
+ transform(code, id) {
2602
+ const normalizedId = Vite.normalizePath(id);
2603
+ const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
2604
+ if (normalizedId !== normalizedEntry) {
2605
+ return null;
2606
+ }
2607
+ const prepend = [];
2608
+ let newCode = code;
2609
+ if (!code.includes("virtual:rsc-router/routes-manifest")) {
2610
+ prepend.push(`import "virtual:rsc-router/routes-manifest";`);
2611
+ }
2612
+ const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
2613
+ if (needsVersion) {
2614
+ prepend.push(`import { VERSION } from "@rangojs/router:version";`);
2615
+ newCode = newCode.replace(
2616
+ /createRSCHandler\s*\(\s*\{/,
2617
+ "createRSCHandler({\n version: VERSION,"
2618
+ );
2619
+ }
2620
+ if (prepend.length === 0 && newCode === code) return null;
2621
+ newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
2622
+ return {
2623
+ code: newCode,
2624
+ map: null
2625
+ };
2626
+ }
2627
+ };
2628
+ }
2629
+ var _require = createRequire(import.meta.url);
2630
+ var _rangoVersion = _require("../../package.json").version;
2631
+ var _bannerPrinted = false;
2632
+ function printBanner(mode, preset, version) {
2633
+ if (_bannerPrinted) return;
2634
+ _bannerPrinted = true;
2635
+ const dim = "\x1B[2m";
2636
+ const bold = "\x1B[1m";
2637
+ const reset = "\x1B[0m";
2638
+ const banner = `
2639
+ ${dim} \u2726 \u2726 \u2727. . .${reset}
2640
+ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2571 \u2726 *${reset}
2641
+ ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2642
+ ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2643
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2551 \u2551 \u2551 \u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557${reset}${dim} \u2727 \u2726${reset}
2644
+ ${dim} ${reset}${bold}\u2550\u2563\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2645
+ ${dim} ${reset}${bold}\u2551\u255A\u2550\u255D \u2554\u2550\u2550\u2550\u255D \u2569\u255A\u2550\u2569 \u2569\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D${reset}${dim} \u2726 . *${reset}
2646
+ ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2647
+ ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2648
+ ${bold}\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2649
+
2650
+ v${version} \xB7 ${preset} \xB7 ${mode}
2651
+ `;
2652
+ console.log(banner);
2653
+ }
2654
+ async function rango(options) {
2655
+ const resolvedOptions = options ?? { preset: "node" };
2656
+ const preset = resolvedOptions.preset ?? "node";
2657
+ const showBanner = resolvedOptions.banner ?? true;
2658
+ const plugins = [];
2659
+ const rangoAliases = getPackageAliases();
2660
+ const excludeDeps = getExcludeDeps();
2661
+ let rscEntryPath = null;
2662
+ let routerPath;
2663
+ const prerenderEnabled = preset === "cloudflare";
2664
+ if (preset === "cloudflare") {
2665
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
2666
+ const finalEntries = {
2667
+ client: VIRTUAL_IDS.browser,
2668
+ ssr: VIRTUAL_IDS.ssr
2669
+ };
2670
+ const cfApi = { handlerChunkInfo: null };
2671
+ let resolvedPrerenderModules;
2672
+ plugins.push({
2673
+ name: "@rangojs/router:cloudflare-integration",
2674
+ enforce: "pre",
2675
+ api: cfApi,
2676
+ config() {
2677
+ return {
2678
+ // Exclude rsc-router modules from optimization to prevent module duplication
2679
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
2680
+ optimizeDeps: {
2681
+ exclude: excludeDeps,
2682
+ esbuildOptions: sharedEsbuildOptions
2683
+ },
2684
+ resolve: {
2685
+ alias: rangoAliases
2686
+ },
2687
+ environments: {
2688
+ client: {
2689
+ build: {
2690
+ rollupOptions: {
2691
+ output: {
2692
+ manualChunks: getManualChunks
2693
+ }
2694
+ }
2695
+ },
2696
+ // Pre-bundle rsc-html-stream to prevent discovery during first request
2697
+ // Exclude rsc-router modules to ensure same Context instance
2698
+ optimizeDeps: {
2699
+ include: ["rsc-html-stream/client"],
2700
+ exclude: excludeDeps,
2701
+ esbuildOptions: sharedEsbuildOptions
2702
+ }
2703
+ },
2704
+ ssr: {
2705
+ // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
2706
+ build: {
2707
+ outDir: "./dist/rsc/ssr"
2708
+ },
2709
+ resolve: {
2710
+ // Ensure single React instance in SSR child environment
2711
+ dedupe: ["react", "react-dom"]
2712
+ },
2713
+ // Pre-bundle SSR entry and React for proper module linking with childEnvironments
2714
+ // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
2715
+ optimizeDeps: {
2716
+ entries: [finalEntries.ssr],
2717
+ include: [
2718
+ "react",
2719
+ "react-dom",
2720
+ "react-dom/server.edge",
2721
+ "react-dom/static.edge",
2722
+ "react/jsx-runtime",
2723
+ "react/jsx-dev-runtime",
2724
+ "rsc-html-stream/server",
2725
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
2726
+ ],
2727
+ exclude: excludeDeps,
2728
+ esbuildOptions: sharedEsbuildOptions
2729
+ }
2730
+ },
2731
+ rsc: {
2732
+ build: {
2733
+ rollupOptions: {
2734
+ output: {
2735
+ manualChunks(id) {
2736
+ if (resolvedPrerenderModules?.has(id)) {
2737
+ return "__prerender-handlers";
2738
+ }
2739
+ }
2740
+ }
2741
+ }
2742
+ },
2743
+ // RSC environment needs exclude list and esbuild options
2744
+ // Exclude rsc-router modules to prevent createContext in RSC environment
2745
+ optimizeDeps: {
2746
+ exclude: excludeDeps,
2747
+ esbuildOptions: sharedEsbuildOptions
2748
+ }
2749
+ }
2750
+ }
2751
+ };
2752
+ },
2753
+ configResolved(config) {
2754
+ if (showBanner) {
2755
+ const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
2756
+ printBanner(mode, "cloudflare", _rangoVersion);
2757
+ }
2758
+ const prerenderPlugin = config.plugins.find(
2759
+ (p) => p.name === "@rangojs/router:expose-prerender-handler-id"
2760
+ );
2761
+ resolvedPrerenderModules = prerenderPlugin?.api?.prerenderHandlerModules;
2762
+ },
2763
+ // Record handler chunk metadata during RSC build for post-prerender replacement.
2764
+ // Rollup minifies EXPORT names (e.g. ArticlesIndex -> r) but keeps internal
2765
+ // variable names intact. We search for original names from prerenderHandlerModules.
2766
+ generateBundle(_options, bundle) {
2767
+ if (this.environment?.name !== "rsc") return;
2768
+ if (!resolvedPrerenderModules?.size) return;
2769
+ for (const [fileName, chunk] of Object.entries(bundle)) {
2770
+ if (chunk.type !== "chunk") continue;
2771
+ if (!fileName.includes("__prerender-handlers")) continue;
2772
+ const handlers = [];
2773
+ for (const [, handlerNames] of resolvedPrerenderModules) {
2774
+ for (const name of handlerNames) {
2775
+ const idPattern = new RegExp(
2776
+ `\\b${name}\\.\\$\\$id\\s*=\\s*"([^"]+)"`
2777
+ );
2778
+ const match = chunk.code.match(idPattern);
2779
+ if (match) {
2780
+ const callStartRe = new RegExp(
2781
+ `const\\s+${name}\\s*=\\s*createPrerenderHandler\\s*(?:<[^>]*>)?\\s*\\(`
2782
+ );
2783
+ const callStart = callStartRe.exec(chunk.code);
2784
+ let isPassthrough = false;
2785
+ if (callStart) {
2786
+ const openPos = callStart.index + callStart[0].length;
2787
+ let depth = 1;
2788
+ let p = openPos;
2789
+ while (p < chunk.code.length && depth > 0) {
2790
+ const ch = chunk.code[p];
2791
+ if (ch === '"' || ch === "'" || ch === "`") {
2792
+ p++;
2793
+ while (p < chunk.code.length && chunk.code[p] !== ch) {
2794
+ if (chunk.code[p] === "\\") p++;
2795
+ p++;
2796
+ }
2797
+ } else if (ch === "(") {
2798
+ depth++;
2799
+ } else if (ch === ")") {
2800
+ depth--;
2801
+ }
2802
+ p++;
2803
+ }
2804
+ if (depth === 0) {
2805
+ const callBody = chunk.code.slice(callStart.index, p);
2806
+ isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
2807
+ }
2808
+ }
2809
+ handlers.push({ name, handlerId: match[1], passthrough: isPassthrough });
2810
+ }
2811
+ }
2812
+ }
2813
+ if (handlers.length > 0) {
2814
+ cfApi.handlerChunkInfo = { fileName, exports: handlers };
2815
+ }
2816
+ break;
2817
+ }
2818
+ }
2819
+ });
2820
+ plugins.push(createVirtualEntriesPlugin(finalEntries));
2821
+ plugins.push(
2822
+ rsc({
2823
+ get entries() {
2824
+ return finalEntries;
2825
+ },
2826
+ serverHandler: false
2827
+ })
2828
+ );
2829
+ } else {
2830
+ const nodeOptions = resolvedOptions;
2831
+ routerPath = nodeOptions.router;
2832
+ if (!routerPath) {
2833
+ const earlyFilter = createScanFilter(process.cwd(), {
2834
+ include: resolvedOptions.include,
2835
+ exclude: resolvedOptions.exclude
2836
+ });
2837
+ const candidates = findRouterFiles(process.cwd(), earlyFilter);
2838
+ if (candidates.length === 1) {
2839
+ const abs = candidates[0];
2840
+ const rel = abs.startsWith(process.cwd()) ? "./" + abs.slice(process.cwd().length + 1) : abs;
2841
+ routerPath = rel;
2842
+ } else if (candidates.length > 1) {
2843
+ const cwd = process.cwd();
2844
+ const list = candidates.map((f) => " - " + (f.startsWith(cwd) ? f.slice(cwd.length + 1) : f)).join("\n");
2845
+ throw new Error(
2846
+ `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
2847
+ ${list}`
2848
+ );
2849
+ }
2850
+ }
2851
+ const rscOption = nodeOptions.rsc ?? true;
2852
+ if (rscOption !== false) {
2853
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
2854
+ const userEntries = typeof rscOption === "boolean" ? {} : rscOption.entries || {};
2855
+ const finalEntries = {
2856
+ client: userEntries.client ?? VIRTUAL_IDS.browser,
2857
+ ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
2858
+ rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc
2859
+ };
2860
+ rscEntryPath = userEntries.rsc ?? null;
2861
+ let hasWarnedDuplicate = false;
2862
+ plugins.push({
2863
+ name: "@rangojs/router:rsc-integration",
2864
+ enforce: "pre",
2865
+ config() {
2866
+ const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
2867
+ const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
2868
+ const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
2869
+ return {
2870
+ // Exclude rsc-router modules from optimization to prevent module duplication
2871
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
2872
+ optimizeDeps: {
2873
+ exclude: excludeDeps,
2874
+ esbuildOptions: sharedEsbuildOptions
2875
+ },
2876
+ resolve: {
2877
+ alias: rangoAliases
2878
+ },
2879
+ environments: {
2880
+ client: {
2881
+ build: {
2882
+ rollupOptions: {
2883
+ output: {
2884
+ manualChunks: getManualChunks
2885
+ }
2886
+ }
2887
+ },
2888
+ // Always exclude rsc-router modules, conditionally add virtual entry
2889
+ optimizeDeps: {
2890
+ exclude: excludeDeps,
2891
+ esbuildOptions: sharedEsbuildOptions,
2892
+ ...useVirtualClient && {
2893
+ // Tell Vite to scan the virtual entry for dependencies
2894
+ entries: [VIRTUAL_IDS.browser]
2895
+ }
2896
+ }
2897
+ },
2898
+ ...useVirtualSSR && {
2899
+ ssr: {
2900
+ optimizeDeps: {
2901
+ entries: [VIRTUAL_IDS.ssr],
2902
+ // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
2903
+ include: [
2904
+ "react",
2905
+ "react-dom",
2906
+ "react-dom/server.edge",
2907
+ "react-dom/static.edge",
2908
+ "react/jsx-runtime",
2909
+ "react/jsx-dev-runtime",
2910
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
2911
+ ],
2912
+ exclude: excludeDeps,
2913
+ esbuildOptions: sharedEsbuildOptions
2914
+ }
2915
+ }
2916
+ },
2917
+ ...useVirtualRSC && {
2918
+ rsc: {
2919
+ optimizeDeps: {
2920
+ entries: [VIRTUAL_IDS.rsc],
2921
+ // Pre-bundle React for RSC to ensure single instance
2922
+ include: ["react", "react/jsx-runtime"],
2923
+ esbuildOptions: sharedEsbuildOptions
2924
+ }
2925
+ }
2926
+ }
2927
+ }
2928
+ };
2929
+ },
2930
+ configResolved(config) {
2931
+ if (showBanner) {
2932
+ const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
2933
+ printBanner(mode, "node", _rangoVersion);
2934
+ }
2935
+ const rscMinimalCount = config.plugins.filter(
2936
+ (p) => p.name === "rsc:minimal"
2937
+ ).length;
2938
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
2939
+ hasWarnedDuplicate = true;
2940
+ console.warn(
2941
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your config or use rango({ rsc: false }) for manual configuration."
2942
+ );
2943
+ }
2944
+ }
2945
+ });
2946
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerPath));
2947
+ plugins.push(
2948
+ rsc({
2949
+ entries: finalEntries
2950
+ })
2951
+ );
2952
+ }
2953
+ }
2954
+ plugins.push(exposeActionId());
2955
+ plugins.push(exposeLoaderId());
2956
+ plugins.push(exposeHandleId());
2957
+ plugins.push(exposeLocationStateId());
2958
+ plugins.push(exposePrerenderHandlerId());
2959
+ plugins.push(createVersionPlugin());
2960
+ const discoveryEntryPath = resolveDiscoveryEntryPath(
2961
+ resolvedOptions,
2962
+ preset !== "cloudflare" ? routerPath : void 0
2963
+ );
2964
+ const injectorEntryPath = rscEntryPath ?? (preset === "cloudflare" ? discoveryEntryPath : null);
2965
+ if (injectorEntryPath) {
2966
+ plugins.push(createVersionInjectorPlugin(injectorEntryPath));
2967
+ }
2968
+ plugins.push(createCjsToEsmPlugin());
2969
+ if (discoveryEntryPath) {
2970
+ plugins.push(createRouterDiscoveryPlugin(discoveryEntryPath, {
2971
+ enableBuildPrerender: prerenderEnabled,
2972
+ staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
2973
+ include: resolvedOptions.include,
2974
+ exclude: resolvedOptions.exclude
2975
+ }));
2976
+ }
2977
+ return plugins;
2978
+ }
2979
+ function createCjsToEsmPlugin() {
2980
+ return {
2981
+ name: "@rangojs/router:cjs-to-esm",
2982
+ enforce: "pre",
2983
+ transform(code, id) {
2984
+ const cleanId = id.split("?")[0];
2985
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
2986
+ const isProd = process.env.NODE_ENV === "production";
2987
+ const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
2988
+ return {
2989
+ code: `export * from "${cjsFile}";`,
2990
+ map: null
2991
+ };
2992
+ }
2993
+ if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
2994
+ let transformed = code;
2995
+ const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
2996
+ const license = licenseMatch ? licenseMatch[0] : "";
2997
+ if (license) {
2998
+ transformed = transformed.slice(license.length);
2999
+ }
3000
+ transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
3001
+ transformed = transformed.replace(
3002
+ /^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
3003
+ ""
3004
+ );
3005
+ transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
3006
+ transformed = transformed.replace(
3007
+ /var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
3008
+ 'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
3009
+ );
3010
+ transformed = transformed.replace(
3011
+ /var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
3012
+ 'import ReactDOM from "react-dom";\nvar '
3013
+ );
3014
+ transformed = transformed.replace(
3015
+ /exports\.(\w+)\s*=\s*function\s*\(/g,
3016
+ "export function $1("
3017
+ );
3018
+ transformed = transformed.replace(
3019
+ /exports\.(\w+)\s*=/g,
3020
+ "export const $1 ="
3021
+ );
3022
+ transformed = license + "\n" + transformed;
3023
+ return {
3024
+ code: transformed,
3025
+ map: null
3026
+ };
3027
+ }
3028
+ return null;
3029
+ }
3030
+ };
3031
+ }
3032
+ export {
3033
+ exposeActionId,
3034
+ exposeHandleId,
3035
+ exposeLoaderId,
3036
+ exposeLocationStateId,
3037
+ exposePrerenderHandlerId,
3038
+ rango
3039
+ };