@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1fa245e2

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 (160) hide show
  1. package/{CLAUDE.md → AGENTS.md} +4 -0
  2. package/README.md +122 -30
  3. package/dist/bin/rango.js +245 -63
  4. package/dist/vite/index.js +859 -418
  5. package/package.json +3 -3
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +32 -0
  8. package/skills/caching/SKILL.md +49 -8
  9. package/skills/document-cache/SKILL.md +2 -2
  10. package/skills/hooks/SKILL.md +33 -31
  11. package/skills/host-router/SKILL.md +218 -0
  12. package/skills/links/SKILL.md +3 -1
  13. package/skills/loader/SKILL.md +72 -22
  14. package/skills/middleware/SKILL.md +2 -0
  15. package/skills/parallel/SKILL.md +126 -0
  16. package/skills/prerender/SKILL.md +112 -70
  17. package/skills/rango/SKILL.md +0 -1
  18. package/skills/route/SKILL.md +34 -4
  19. package/skills/router-setup/SKILL.md +95 -5
  20. package/skills/typesafety/SKILL.md +35 -23
  21. package/src/__internal.ts +92 -0
  22. package/src/bin/rango.ts +18 -0
  23. package/src/browser/app-version.ts +14 -0
  24. package/src/browser/event-controller.ts +5 -0
  25. package/src/browser/link-interceptor.ts +4 -0
  26. package/src/browser/navigation-bridge.ts +114 -18
  27. package/src/browser/navigation-client.ts +126 -44
  28. package/src/browser/navigation-store.ts +43 -8
  29. package/src/browser/navigation-transaction.ts +11 -9
  30. package/src/browser/partial-update.ts +80 -15
  31. package/src/browser/prefetch/cache.ts +166 -27
  32. package/src/browser/prefetch/fetch.ts +52 -39
  33. package/src/browser/prefetch/policy.ts +6 -0
  34. package/src/browser/prefetch/queue.ts +92 -20
  35. package/src/browser/prefetch/resource-ready.ts +77 -0
  36. package/src/browser/react/Link.tsx +70 -14
  37. package/src/browser/react/NavigationProvider.tsx +40 -4
  38. package/src/browser/react/context.ts +7 -2
  39. package/src/browser/react/use-handle.ts +9 -58
  40. package/src/browser/react/use-router.ts +21 -8
  41. package/src/browser/rsc-router.tsx +143 -59
  42. package/src/browser/scroll-restoration.ts +41 -42
  43. package/src/browser/segment-reconciler.ts +6 -1
  44. package/src/browser/server-action-bridge.ts +454 -436
  45. package/src/browser/types.ts +60 -5
  46. package/src/build/generate-manifest.ts +6 -6
  47. package/src/build/generate-route-types.ts +5 -0
  48. package/src/build/route-trie.ts +19 -3
  49. package/src/build/route-types/include-resolution.ts +8 -1
  50. package/src/build/route-types/router-processing.ts +346 -87
  51. package/src/build/route-types/scan-filter.ts +8 -1
  52. package/src/cache/cache-runtime.ts +15 -11
  53. package/src/cache/cache-scope.ts +48 -7
  54. package/src/cache/cf/cf-cache-store.ts +453 -11
  55. package/src/cache/cf/index.ts +5 -1
  56. package/src/cache/document-cache.ts +17 -7
  57. package/src/cache/index.ts +1 -0
  58. package/src/cache/taint.ts +55 -0
  59. package/src/client.rsc.tsx +2 -1
  60. package/src/client.tsx +3 -102
  61. package/src/context-var.ts +72 -2
  62. package/src/debug.ts +2 -2
  63. package/src/handle.ts +40 -0
  64. package/src/handles/breadcrumbs.ts +66 -0
  65. package/src/handles/index.ts +1 -0
  66. package/src/host/index.ts +0 -3
  67. package/src/index.rsc.ts +8 -37
  68. package/src/index.ts +40 -66
  69. package/src/prerender/store.ts +57 -15
  70. package/src/prerender.ts +138 -77
  71. package/src/reverse.ts +22 -1
  72. package/src/route-definition/dsl-helpers.ts +73 -25
  73. package/src/route-definition/helpers-types.ts +10 -6
  74. package/src/route-definition/index.ts +3 -3
  75. package/src/route-definition/redirect.ts +11 -3
  76. package/src/route-definition/resolve-handler-use.ts +149 -0
  77. package/src/route-map-builder.ts +7 -1
  78. package/src/route-types.ts +11 -0
  79. package/src/router/content-negotiation.ts +100 -1
  80. package/src/router/find-match.ts +4 -2
  81. package/src/router/handler-context.ts +108 -25
  82. package/src/router/intercept-resolution.ts +11 -4
  83. package/src/router/lazy-includes.ts +4 -1
  84. package/src/router/loader-resolution.ts +123 -11
  85. package/src/router/logging.ts +5 -2
  86. package/src/router/manifest.ts +9 -3
  87. package/src/router/match-api.ts +125 -190
  88. package/src/router/match-middleware/background-revalidation.ts +30 -2
  89. package/src/router/match-middleware/cache-lookup.ts +88 -16
  90. package/src/router/match-middleware/cache-store.ts +53 -10
  91. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  92. package/src/router/match-middleware/segment-resolution.ts +61 -5
  93. package/src/router/match-result.ts +22 -15
  94. package/src/router/metrics.ts +238 -13
  95. package/src/router/middleware-types.ts +53 -12
  96. package/src/router/middleware.ts +172 -85
  97. package/src/router/navigation-snapshot.ts +182 -0
  98. package/src/router/pattern-matching.ts +20 -5
  99. package/src/router/prerender-match.ts +114 -10
  100. package/src/router/preview-match.ts +30 -102
  101. package/src/router/request-classification.ts +310 -0
  102. package/src/router/revalidation.ts +27 -7
  103. package/src/router/route-snapshot.ts +245 -0
  104. package/src/router/router-context.ts +6 -1
  105. package/src/router/router-interfaces.ts +50 -5
  106. package/src/router/router-options.ts +50 -19
  107. package/src/router/segment-resolution/fresh.ts +200 -19
  108. package/src/router/segment-resolution/helpers.ts +30 -25
  109. package/src/router/segment-resolution/loader-cache.ts +1 -0
  110. package/src/router/segment-resolution/revalidation.ts +429 -301
  111. package/src/router/segment-wrappers.ts +2 -0
  112. package/src/router/trie-matching.ts +20 -2
  113. package/src/router/types.ts +1 -0
  114. package/src/router.ts +88 -15
  115. package/src/rsc/handler.ts +546 -359
  116. package/src/rsc/index.ts +0 -20
  117. package/src/rsc/manifest-init.ts +5 -1
  118. package/src/rsc/progressive-enhancement.ts +25 -8
  119. package/src/rsc/rsc-rendering.ts +35 -43
  120. package/src/rsc/server-action.ts +16 -10
  121. package/src/rsc/ssr-setup.ts +128 -0
  122. package/src/rsc/types.ts +10 -1
  123. package/src/search-params.ts +16 -13
  124. package/src/segment-system.tsx +140 -4
  125. package/src/server/context.ts +148 -16
  126. package/src/server/loader-registry.ts +9 -8
  127. package/src/server/request-context.ts +182 -34
  128. package/src/server.ts +6 -0
  129. package/src/ssr/index.tsx +4 -0
  130. package/src/static-handler.ts +18 -6
  131. package/src/theme/index.ts +4 -13
  132. package/src/types/cache-types.ts +4 -4
  133. package/src/types/handler-context.ts +149 -49
  134. package/src/types/loader-types.ts +36 -9
  135. package/src/types/route-config.ts +17 -8
  136. package/src/types/route-entry.ts +8 -1
  137. package/src/types/segments.ts +2 -5
  138. package/src/urls/path-helper-types.ts +9 -2
  139. package/src/urls/path-helper.ts +48 -13
  140. package/src/urls/pattern-types.ts +12 -0
  141. package/src/urls/response-types.ts +16 -6
  142. package/src/use-loader.tsx +73 -4
  143. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  144. package/src/vite/discovery/discover-routers.ts +23 -5
  145. package/src/vite/discovery/prerender-collection.ts +48 -15
  146. package/src/vite/discovery/state.ts +17 -13
  147. package/src/vite/index.ts +8 -3
  148. package/src/vite/plugin-types.ts +51 -79
  149. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  150. package/src/vite/plugins/expose-action-id.ts +1 -3
  151. package/src/vite/plugins/performance-tracks.ts +88 -0
  152. package/src/vite/plugins/refresh-cmd.ts +127 -0
  153. package/src/vite/plugins/version-plugin.ts +13 -1
  154. package/src/vite/rango.ts +174 -211
  155. package/src/vite/router-discovery.ts +169 -42
  156. package/src/vite/utils/banner.ts +3 -3
  157. package/src/vite/utils/prerender-utils.ts +78 -0
  158. package/src/vite/utils/shared-utils.ts +3 -2
  159. package/skills/testing/SKILL.md +0 -226
  160. package/src/route-definition/route-function.ts +0 -119
@@ -1,6 +1,6 @@
1
1
  // src/vite/rango.ts
2
2
  import { readFileSync as readFileSync7 } from "node:fs";
3
- import { resolve as resolve7 } from "node:path";
3
+ import { resolve as resolve9 } from "node:path";
4
4
 
5
5
  // src/vite/plugins/expose-action-id.ts
6
6
  import MagicString from "magic-string";
@@ -292,7 +292,7 @@ function exposeActionId() {
292
292
  }
293
293
  if (!rscPluginApi) {
294
294
  throw new Error(
295
- "[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."
295
+ "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
296
296
  );
297
297
  }
298
298
  if (!isBuild) return;
@@ -1585,6 +1585,53 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
1585
1585
  }
1586
1586
  }
1587
1587
 
1588
+ // src/vite/plugins/client-ref-dedup.ts
1589
+ var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
1590
+ function extractPackageName(absolutePath) {
1591
+ const marker = "/node_modules/";
1592
+ const idx = absolutePath.lastIndexOf(marker);
1593
+ if (idx === -1) return null;
1594
+ const afterModules = absolutePath.slice(idx + marker.length);
1595
+ if (afterModules.startsWith("@")) {
1596
+ const parts = afterModules.split("/");
1597
+ if (parts.length < 2 || !parts[1]) return null;
1598
+ return `${parts[0]}/${parts[1]}`;
1599
+ }
1600
+ const name = afterModules.split("/")[0];
1601
+ return name || null;
1602
+ }
1603
+ function clientRefDedup() {
1604
+ let clientExclude = [];
1605
+ return {
1606
+ name: "@rangojs/router:client-ref-dedup",
1607
+ enforce: "pre",
1608
+ apply: "serve",
1609
+ configResolved(config) {
1610
+ const clientEnv = config.environments?.["client"];
1611
+ clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
1612
+ },
1613
+ resolveId(source, importer, options) {
1614
+ if (this.environment?.name !== "client") return;
1615
+ if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
1616
+ if (!source.includes("/node_modules/")) return;
1617
+ if (!importer) return;
1618
+ const packageName = extractPackageName(source);
1619
+ if (!packageName) return;
1620
+ if (clientExclude.includes(packageName)) return;
1621
+ return `\0rango:dedup/${packageName}`;
1622
+ },
1623
+ load(id) {
1624
+ if (!id.startsWith("\0rango:dedup/")) return;
1625
+ const packageName = id.slice("\0rango:dedup/".length);
1626
+ return [
1627
+ `export * from ${JSON.stringify(packageName)};`,
1628
+ `import * as __all__ from ${JSON.stringify(packageName)};`,
1629
+ `export default __all__.default;`
1630
+ ].join("\n");
1631
+ }
1632
+ };
1633
+ }
1634
+
1588
1635
  // src/vite/plugins/virtual-entries.ts
1589
1636
  var VIRTUAL_ENTRY_BROWSER = `
1590
1637
  import {
@@ -1698,7 +1745,7 @@ import { resolve } from "node:path";
1698
1745
  // package.json
1699
1746
  var package_default = {
1700
1747
  name: "@rangojs/router",
1701
- version: "0.0.0-experimental.19",
1748
+ version: "0.0.0-experimental.1fa245e2",
1702
1749
  description: "Django-inspired RSC router with composable URL patterns",
1703
1750
  keywords: [
1704
1751
  "react",
@@ -1729,7 +1776,7 @@ var package_default = {
1729
1776
  "!src/**/*.test.tsx",
1730
1777
  "dist",
1731
1778
  "skills",
1732
- "CLAUDE.md",
1779
+ "AGENTS.md",
1733
1780
  "README.md"
1734
1781
  ],
1735
1782
  type: "module",
@@ -1840,7 +1887,7 @@ var package_default = {
1840
1887
  "test:unit:watch": "vitest"
1841
1888
  },
1842
1889
  dependencies: {
1843
- "@vitejs/plugin-rsc": "^0.5.14",
1890
+ "@vitejs/plugin-rsc": "^0.5.19",
1844
1891
  "magic-string": "^0.30.17",
1845
1892
  picomatch: "^4.0.3",
1846
1893
  "rsc-html-stream": "^0.0.7"
@@ -2048,55 +2095,7 @@ declare global {
2048
2095
  }
2049
2096
 
2050
2097
  // src/build/route-types/scan-filter.ts
2051
- import { join, relative } from "node:path";
2052
- import { readdirSync } from "node:fs";
2053
2098
  import picomatch from "picomatch";
2054
- var DEFAULT_EXCLUDE_PATTERNS = [
2055
- "**/__tests__/**",
2056
- "**/__mocks__/**",
2057
- "**/dist/**",
2058
- "**/coverage/**",
2059
- "**/*.test.{ts,tsx,js,jsx}",
2060
- "**/*.spec.{ts,tsx,js,jsx}"
2061
- ];
2062
- function createScanFilter(root, opts) {
2063
- const { include, exclude } = opts;
2064
- const hasInclude = include && include.length > 0;
2065
- const hasCustomExclude = exclude !== void 0;
2066
- if (!hasInclude && !hasCustomExclude) return void 0;
2067
- const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
2068
- const includeMatcher = hasInclude ? picomatch(include) : null;
2069
- const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2070
- return (absolutePath) => {
2071
- const rel = relative(root, absolutePath);
2072
- if (excludeMatcher && excludeMatcher(rel)) return false;
2073
- if (includeMatcher) return includeMatcher(rel);
2074
- return true;
2075
- };
2076
- }
2077
- function findTsFiles(dir, filter) {
2078
- const results = [];
2079
- let entries;
2080
- try {
2081
- entries = readdirSync(dir, { withFileTypes: true });
2082
- } catch (err) {
2083
- console.warn(
2084
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2085
- );
2086
- return results;
2087
- }
2088
- for (const entry of entries) {
2089
- const fullPath = join(dir, entry.name);
2090
- if (entry.isDirectory()) {
2091
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2092
- results.push(...findTsFiles(fullPath, filter));
2093
- } else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
2094
- if (filter && !filter(fullPath)) continue;
2095
- results.push(fullPath);
2096
- }
2097
- }
2098
- return results;
2099
- }
2100
2099
 
2101
2100
  // src/build/route-types/per-module-writer.ts
2102
2101
  import ts4 from "typescript";
@@ -2318,7 +2317,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2318
2317
  }
2319
2318
  return routeMap;
2320
2319
  }
2321
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2320
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2322
2321
  visited = visited ?? /* @__PURE__ */ new Set();
2323
2322
  const realPath = resolve2(filePath);
2324
2323
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2334,7 +2333,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2334
2333
  return { routes: {}, searchSchemas: {} };
2335
2334
  }
2336
2335
  let block;
2337
- if (variableName) {
2336
+ if (inlineBlock) {
2337
+ block = inlineBlock;
2338
+ } else if (variableName) {
2338
2339
  const extracted = extractUrlsBlockForVariable(source, variableName);
2339
2340
  if (!extracted) return { routes: {}, searchSchemas: {} };
2340
2341
  block = extracted;
@@ -2355,8 +2356,20 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2355
2356
  }
2356
2357
 
2357
2358
  // src/build/route-types/router-processing.ts
2358
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, unlinkSync } from "node:fs";
2359
- import { join as join2, dirname as dirname2, resolve as resolve3, basename as pathBasename } from "node:path";
2359
+ import {
2360
+ readFileSync as readFileSync2,
2361
+ writeFileSync,
2362
+ existsSync as existsSync3,
2363
+ unlinkSync,
2364
+ readdirSync
2365
+ } from "node:fs";
2366
+ import {
2367
+ join,
2368
+ dirname as dirname2,
2369
+ resolve as resolve3,
2370
+ sep,
2371
+ basename as pathBasename
2372
+ } from "node:path";
2360
2373
  import ts5 from "typescript";
2361
2374
  function countPublicRouteEntries(source) {
2362
2375
  const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
@@ -2369,7 +2382,79 @@ function countPublicRouteEntries(source) {
2369
2382
  }
2370
2383
  return count;
2371
2384
  }
2372
- function extractUrlsVariableFromRouter(code) {
2385
+ var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2386
+ function isRoutableSourceFile(name) {
2387
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2388
+ }
2389
+ function findRouterFilesRecursive(dir, filter, results) {
2390
+ let entries;
2391
+ try {
2392
+ entries = readdirSync(dir, { withFileTypes: true });
2393
+ } catch (err) {
2394
+ console.warn(
2395
+ `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2396
+ );
2397
+ return;
2398
+ }
2399
+ const childDirs = [];
2400
+ const routerFilesInDir = [];
2401
+ for (const entry of entries) {
2402
+ const fullPath = join(dir, entry.name);
2403
+ if (entry.isDirectory()) {
2404
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
2405
+ continue;
2406
+ childDirs.push(fullPath);
2407
+ continue;
2408
+ }
2409
+ if (!isRoutableSourceFile(entry.name)) continue;
2410
+ if (filter && !filter(fullPath)) continue;
2411
+ try {
2412
+ const source = readFileSync2(fullPath, "utf-8");
2413
+ if (ROUTER_CALL_PATTERN.test(source)) {
2414
+ routerFilesInDir.push(fullPath);
2415
+ }
2416
+ } catch {
2417
+ continue;
2418
+ }
2419
+ }
2420
+ if (routerFilesInDir.length > 0) {
2421
+ results.push(...routerFilesInDir);
2422
+ return;
2423
+ }
2424
+ for (const childDir of childDirs) {
2425
+ findRouterFilesRecursive(childDir, filter, results);
2426
+ }
2427
+ }
2428
+ function findNestedRouterConflict(routerFiles) {
2429
+ const routerDirs = [
2430
+ ...new Set(routerFiles.map((filePath) => dirname2(resolve3(filePath))))
2431
+ ].sort((a, b) => a.length - b.length);
2432
+ for (let i = 0; i < routerDirs.length; i++) {
2433
+ const ancestorDir = routerDirs[i];
2434
+ const prefix = ancestorDir.endsWith(sep) ? ancestorDir : `${ancestorDir}${sep}`;
2435
+ for (let j = i + 1; j < routerDirs.length; j++) {
2436
+ const nestedDir = routerDirs[j];
2437
+ if (!nestedDir.startsWith(prefix)) continue;
2438
+ const ancestorFile = routerFiles.find(
2439
+ (filePath) => dirname2(resolve3(filePath)) === ancestorDir
2440
+ );
2441
+ const nestedFile = routerFiles.find(
2442
+ (filePath) => dirname2(resolve3(filePath)) === nestedDir
2443
+ );
2444
+ if (ancestorFile && nestedFile) {
2445
+ return { ancestor: ancestorFile, nested: nestedFile };
2446
+ }
2447
+ }
2448
+ }
2449
+ return null;
2450
+ }
2451
+ function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
2452
+ return `${prefix} Nested router roots are not supported.
2453
+ Router root: ${conflict.ancestor}
2454
+ Nested router: ${conflict.nested}
2455
+ Move the nested router into a sibling directory or configure it as a separate app root.`;
2456
+ }
2457
+ function extractUrlsFromRouter(code) {
2373
2458
  const sourceFile = ts5.createSourceFile(
2374
2459
  "router.tsx",
2375
2460
  code,
@@ -2383,24 +2468,70 @@ function extractUrlsVariableFromRouter(code) {
2383
2468
  const callee = node.expression;
2384
2469
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2385
2470
  }
2471
+ function isInlineBuilder(node) {
2472
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2473
+ }
2474
+ function isRoutesOnCreateRouter(node) {
2475
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2476
+ return false;
2477
+ let inner = node.expression.expression;
2478
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2479
+ inner = inner.expression.expression;
2480
+ }
2481
+ return isCreateRouterCall(inner);
2482
+ }
2386
2483
  function visit(node) {
2387
2484
  if (result) return;
2388
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
2389
- let inner = node.expression.expression;
2390
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2391
- inner = inner.expression.expression;
2392
- }
2393
- if (isCreateRouterCall(inner)) {
2394
- result = node.arguments[0].text;
2395
- return;
2485
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2486
+ const arg = node.arguments[0];
2487
+ if (ts5.isIdentifier(arg)) {
2488
+ result = { kind: "variable", name: arg.text };
2489
+ } else if (isInlineBuilder(arg)) {
2490
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2396
2491
  }
2492
+ return;
2397
2493
  }
2398
2494
  if (isCreateRouterCall(node)) {
2399
2495
  const callExpr = node;
2400
- for (const arg of callExpr.arguments) {
2496
+ for (const callArg of callExpr.arguments) {
2497
+ if (ts5.isObjectLiteralExpression(callArg)) {
2498
+ for (const prop of callArg.properties) {
2499
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2500
+ if (ts5.isIdentifier(prop.initializer)) {
2501
+ result = { kind: "variable", name: prop.initializer.text };
2502
+ } else if (isInlineBuilder(prop.initializer)) {
2503
+ result = {
2504
+ kind: "inline",
2505
+ block: prop.initializer.getText(sourceFile)
2506
+ };
2507
+ }
2508
+ return;
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ }
2514
+ ts5.forEachChild(node, visit);
2515
+ }
2516
+ visit(sourceFile);
2517
+ return result;
2518
+ }
2519
+ function extractBasenameFromRouter(code) {
2520
+ const sourceFile = ts5.createSourceFile(
2521
+ "router.tsx",
2522
+ code,
2523
+ ts5.ScriptTarget.Latest,
2524
+ true,
2525
+ ts5.ScriptKind.TSX
2526
+ );
2527
+ let result;
2528
+ function visit(node) {
2529
+ if (result !== void 0) return;
2530
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2531
+ for (const arg of node.arguments) {
2401
2532
  if (ts5.isObjectLiteralExpression(arg)) {
2402
2533
  for (const prop of arg.properties) {
2403
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2534
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2404
2535
  result = prop.initializer.text;
2405
2536
  return;
2406
2537
  }
@@ -2413,6 +2544,19 @@ function extractUrlsVariableFromRouter(code) {
2413
2544
  visit(sourceFile);
2414
2545
  return result;
2415
2546
  }
2547
+ function applyBasenameToRoutes(result, basename3) {
2548
+ const prefixed = {};
2549
+ for (const [name, pattern] of Object.entries(result.routes)) {
2550
+ if (pattern === "/") {
2551
+ prefixed[name] = basename3;
2552
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2553
+ prefixed[name] = basename3 + pattern.slice(1);
2554
+ } else {
2555
+ prefixed[name] = basename3 + pattern;
2556
+ }
2557
+ }
2558
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2559
+ }
2416
2560
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2417
2561
  let routerSource;
2418
2562
  try {
@@ -2420,39 +2564,49 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2420
2564
  } catch {
2421
2565
  return { routes: {}, searchSchemas: {} };
2422
2566
  }
2423
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2424
- if (!urlsVarName) {
2567
+ const extraction = extractUrlsFromRouter(routerSource);
2568
+ if (!extraction) {
2425
2569
  return { routes: {}, searchSchemas: {} };
2426
2570
  }
2427
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2428
- if (imported) {
2429
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2430
- if (!targetFile) {
2431
- return { routes: {}, searchSchemas: {} };
2571
+ const rawBasename = extractBasenameFromRouter(routerSource);
2572
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2573
+ let result;
2574
+ if (extraction.kind === "inline") {
2575
+ result = buildCombinedRouteMapWithSearch(
2576
+ routerFilePath,
2577
+ void 0,
2578
+ void 0,
2579
+ void 0,
2580
+ extraction.block
2581
+ );
2582
+ } else {
2583
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2584
+ if (imported) {
2585
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2586
+ if (!targetFile) {
2587
+ return { routes: {}, searchSchemas: {} };
2588
+ }
2589
+ result = buildCombinedRouteMapWithSearch(
2590
+ targetFile,
2591
+ imported.exportedName
2592
+ );
2593
+ } else {
2594
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2432
2595
  }
2433
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2434
2596
  }
2435
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2597
+ if (basename3) {
2598
+ result = applyBasenameToRoutes(result, basename3);
2599
+ }
2600
+ return result;
2436
2601
  }
2437
2602
  function findRouterFiles(root, filter) {
2438
- const files = findTsFiles(root, filter);
2439
2603
  const result = [];
2440
- for (const filePath of files) {
2441
- if (filePath.includes(".gen.")) continue;
2442
- try {
2443
- const source = readFileSync2(filePath, "utf-8");
2444
- if (/\bcreateRouter\s*[<(]/.test(source)) {
2445
- result.push(filePath);
2446
- }
2447
- } catch {
2448
- continue;
2449
- }
2450
- }
2604
+ findRouterFilesRecursive(root, filter, result);
2451
2605
  return result;
2452
2606
  }
2453
2607
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2454
2608
  try {
2455
- const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2609
+ const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
2456
2610
  if (existsSync3(oldCombinedPath)) {
2457
2611
  unlinkSync(oldCombinedPath);
2458
2612
  console.log(
@@ -2463,32 +2617,26 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2463
2617
  }
2464
2618
  const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
2465
2619
  if (routerFilePaths.length === 0) return;
2620
+ const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
2621
+ if (nestedRouterConflict) {
2622
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2623
+ }
2466
2624
  for (const routerFilePath of routerFilePaths) {
2467
- let routerSource;
2468
- try {
2469
- routerSource = readFileSync2(routerFilePath, "utf-8");
2470
- } catch {
2471
- continue;
2472
- }
2473
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2474
- if (!urlsVarName) continue;
2475
- let result;
2476
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2477
- if (imported) {
2478
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2479
- if (!targetFile) continue;
2480
- result = buildCombinedRouteMapWithSearch(
2481
- targetFile,
2482
- imported.exportedName
2483
- );
2484
- } else {
2485
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2625
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2626
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2627
+ let routerSource;
2628
+ try {
2629
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2630
+ } catch {
2631
+ continue;
2632
+ }
2633
+ if (!extractUrlsFromRouter(routerSource)) continue;
2486
2634
  }
2487
2635
  const routerBasename = pathBasename(routerFilePath).replace(
2488
2636
  /\.(tsx?|jsx?)$/,
2489
2637
  ""
2490
2638
  );
2491
- const outPath = join2(
2639
+ const outPath = join(
2492
2640
  dirname2(routerFilePath),
2493
2641
  `${routerBasename}.named-routes.gen.ts`
2494
2642
  );
@@ -2618,8 +2766,9 @@ function createVersionPlugin() {
2618
2766
  let isDev = false;
2619
2767
  let server = null;
2620
2768
  const clientModuleSignatures = /* @__PURE__ */ new Map();
2769
+ let versionCounter = 0;
2621
2770
  const bumpVersion = (reason) => {
2622
- currentVersion = Date.now().toString(16);
2771
+ currentVersion = Date.now().toString(16) + String(++versionCounter);
2623
2772
  console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
2624
2773
  const rscEnv = server?.environments?.rsc;
2625
2774
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -2675,6 +2824,9 @@ function createVersionPlugin() {
2675
2824
  if (!isDev) return;
2676
2825
  const isRscModule = this.environment?.name === "rsc";
2677
2826
  if (!isRscModule) return;
2827
+ if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
2828
+ return;
2829
+ }
2678
2830
  if (isCodeModule(ctx.file)) {
2679
2831
  const filePath = normalizeModuleId(ctx.file);
2680
2832
  const previousSignature = clientModuleSignatures.get(filePath);
@@ -2704,6 +2856,68 @@ function createVersionPlugin() {
2704
2856
 
2705
2857
  // src/vite/utils/shared-utils.ts
2706
2858
  import * as Vite from "vite";
2859
+
2860
+ // src/vite/plugins/performance-tracks.ts
2861
+ import { readFile } from "node:fs/promises";
2862
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2863
+ function buildPatchReplacement(match, debugInfoVar) {
2864
+ return `${match}
2865
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
2866
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
2867
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
2868
+ ${debugInfoVar} = _resolved._debugInfo;
2869
+ }
2870
+ }`;
2871
+ }
2872
+ function patchRsdwClientDebugInfoRecovery(code) {
2873
+ const match = code.match(RSDW_PATCH_RE);
2874
+ if (!match) {
2875
+ return { code, debugInfoVar: null };
2876
+ }
2877
+ return {
2878
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
2879
+ debugInfoVar: match[2]
2880
+ };
2881
+ }
2882
+ function performanceTracksOptimizeDepsPlugin() {
2883
+ return {
2884
+ name: "@rangojs/router:performance-tracks-optimize-deps",
2885
+ setup(build) {
2886
+ build.onLoad(
2887
+ {
2888
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
2889
+ },
2890
+ async (args) => {
2891
+ const code = await readFile(args.path, "utf8");
2892
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2893
+ return {
2894
+ contents: patched.code,
2895
+ loader: "js"
2896
+ };
2897
+ }
2898
+ );
2899
+ }
2900
+ };
2901
+ }
2902
+ function performanceTracksPlugin() {
2903
+ return {
2904
+ name: "@rangojs/router:performance-tracks",
2905
+ transform(code, id) {
2906
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
2907
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2908
+ if (!patched.debugInfoVar) return;
2909
+ if (process.env.INTERNAL_RANGO_DEBUG)
2910
+ console.log(
2911
+ "[perf-tracks] patched RSDW client (var:",
2912
+ patched.debugInfoVar,
2913
+ ")"
2914
+ );
2915
+ return patched.code;
2916
+ }
2917
+ };
2918
+ }
2919
+
2920
+ // src/vite/utils/shared-utils.ts
2707
2921
  var versionEsbuildPlugin = {
2708
2922
  name: "@rangojs/router-version",
2709
2923
  setup(build) {
@@ -2721,7 +2935,7 @@ var versionEsbuildPlugin = {
2721
2935
  }
2722
2936
  };
2723
2937
  var sharedEsbuildOptions = {
2724
- plugins: [versionEsbuildPlugin]
2938
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2725
2939
  };
2726
2940
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2727
2941
  const virtualModules = {};
@@ -2804,11 +3018,11 @@ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2
2804
3018
  ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2805
3019
  ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2806
3020
  ${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}
2807
- ${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}
3021
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2808
3022
  ${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}
2809
3023
  ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2810
- ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2811
- ${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}
3024
+ ${dim} * ${reset}${bold}\u2551 \u2551${reset}${dim} * \u2727. \u2571${reset}
3025
+ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2812
3026
 
2813
3027
  v${version} \xB7 ${preset} \xB7 ${mode}
2814
3028
  `;
@@ -2925,7 +3139,10 @@ function createCjsToEsmPlugin() {
2925
3139
 
2926
3140
  // src/vite/router-discovery.ts
2927
3141
  import { createServer as createViteServer } from "vite";
3142
+ import { resolve as resolve8 } from "node:path";
2928
3143
  import { readFileSync as readFileSync6 } from "node:fs";
3144
+ import { createRequire } from "node:module";
3145
+ import { pathToFileURL } from "node:url";
2929
3146
 
2930
3147
  // src/vite/plugins/virtual-stub-plugin.ts
2931
3148
  function createVirtualStubPlugin() {
@@ -2952,7 +3169,7 @@ function createVirtualStubPlugin() {
2952
3169
  }
2953
3170
 
2954
3171
  // src/vite/plugins/client-ref-hashing.ts
2955
- import { relative as relative2 } from "node:path";
3172
+ import { relative } from "node:path";
2956
3173
  import { createHash as createHash2 } from "node:crypto";
2957
3174
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
2958
3175
  var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
@@ -2965,10 +3182,10 @@ function computeProductionHash(projectRoot, refKey) {
2965
3182
  const absPath = decodeURIComponent(
2966
3183
  refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
2967
3184
  );
2968
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3185
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
2969
3186
  } else if (refKey.startsWith(FS_PREFIX)) {
2970
3187
  const absPath = refKey.slice(FS_PREFIX.length - 1);
2971
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3188
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
2972
3189
  } else if (refKey.startsWith("/")) {
2973
3190
  toHash = refKey.slice(1);
2974
3191
  } else {
@@ -3107,10 +3324,10 @@ function createDiscoveryState(entryPath, opts) {
3107
3324
  perRouterTrieMap: /* @__PURE__ */ new Map(),
3108
3325
  perRouterPrecomputedMap: /* @__PURE__ */ new Map(),
3109
3326
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3110
- prerenderCollectedData: null,
3111
- staticCollectedData: null,
3112
- handlerChunkInfo: null,
3113
- staticHandlerChunkInfo: null,
3327
+ prerenderManifestEntries: null,
3328
+ staticManifestEntries: null,
3329
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3330
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3114
3331
  rscEntryFileName: null,
3115
3332
  resolvedPrerenderModules: void 0,
3116
3333
  resolvedStaticModules: void 0,
@@ -3193,8 +3410,17 @@ function jsonParseExpression(value) {
3193
3410
  }
3194
3411
 
3195
3412
  // src/context-var.ts
3413
+ var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3414
+ "rango:non-cacheable-keys"
3415
+ );
3416
+ function getNonCacheableKeys(variables) {
3417
+ if (!variables[NON_CACHEABLE_KEYS]) {
3418
+ variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
3419
+ }
3420
+ return variables[NON_CACHEABLE_KEYS];
3421
+ }
3196
3422
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3197
- function contextSet(variables, keyOrVar, value) {
3423
+ function contextSet(variables, keyOrVar, value, options) {
3198
3424
  if (typeof keyOrVar === "string") {
3199
3425
  if (FORBIDDEN_KEYS.has(keyOrVar)) {
3200
3426
  throw new Error(
@@ -3202,12 +3428,28 @@ function contextSet(variables, keyOrVar, value) {
3202
3428
  );
3203
3429
  }
3204
3430
  variables[keyOrVar] = value;
3431
+ if (options?.cache === false) {
3432
+ getNonCacheableKeys(variables).add(keyOrVar);
3433
+ }
3205
3434
  } else {
3206
3435
  variables[keyOrVar.key] = value;
3436
+ if (options?.cache === false) {
3437
+ getNonCacheableKeys(variables).add(keyOrVar.key);
3438
+ }
3207
3439
  }
3208
3440
  }
3209
3441
 
3210
3442
  // src/vite/utils/prerender-utils.ts
3443
+ import { createHash as createHash4 } from "node:crypto";
3444
+ import {
3445
+ copyFileSync,
3446
+ existsSync as existsSync4,
3447
+ mkdirSync,
3448
+ rmSync,
3449
+ statSync,
3450
+ writeFileSync as writeFileSync2
3451
+ } from "node:fs";
3452
+ import { resolve as resolve5 } from "node:path";
3211
3453
  function escapeRegExp2(str) {
3212
3454
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3213
3455
  }
@@ -3216,6 +3458,7 @@ function encodePathParam(value) {
3216
3458
  }
3217
3459
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3218
3460
  let result = pattern;
3461
+ let hadOmittedOptional = false;
3219
3462
  for (const [key, value] of Object.entries(params)) {
3220
3463
  const escaped = escapeRegExp2(key);
3221
3464
  result = result.replace(
@@ -3224,6 +3467,15 @@ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3224
3467
  );
3225
3468
  result = result.replace(`*${key}`, encode(value));
3226
3469
  }
3470
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3471
+ hadOmittedOptional = true;
3472
+ return "";
3473
+ });
3474
+ if (hadOmittedOptional) {
3475
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3476
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3477
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3478
+ }
3227
3479
  return result;
3228
3480
  }
3229
3481
  async function runWithConcurrency(items, concurrency, fn) {
@@ -3286,6 +3538,37 @@ function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
3286
3538
  break;
3287
3539
  }
3288
3540
  }
3541
+ function getStagedAssetDir(projectRoot) {
3542
+ return resolve5(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
3543
+ }
3544
+ function resetStagedBuildAssets(projectRoot) {
3545
+ rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
3546
+ }
3547
+ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
3548
+ const stagedDir = getStagedAssetDir(projectRoot);
3549
+ mkdirSync(stagedDir, { recursive: true });
3550
+ const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
3551
+ const fileName = `${prefix}-${contentHash}.js`;
3552
+ const filePath = resolve5(stagedDir, fileName);
3553
+ if (!existsSync4(filePath)) {
3554
+ writeFileSync2(filePath, `export default ${exportValue};
3555
+ `);
3556
+ }
3557
+ return fileName;
3558
+ }
3559
+ function copyStagedBuildAssets(projectRoot, fileNames) {
3560
+ const stagedDir = getStagedAssetDir(projectRoot);
3561
+ const distAssetsDir = resolve5(projectRoot, "dist/rsc/assets");
3562
+ mkdirSync(distAssetsDir, { recursive: true });
3563
+ let totalBytes = 0;
3564
+ for (const fileName of new Set(fileNames)) {
3565
+ const stagedPath = resolve5(stagedDir, fileName);
3566
+ const distPath = resolve5(distAssetsDir, fileName);
3567
+ copyFileSync(stagedPath, distPath);
3568
+ totalBytes += statSync(stagedPath).size;
3569
+ }
3570
+ return totalBytes;
3571
+ }
3289
3572
 
3290
3573
  // src/vite/discovery/prerender-collection.ts
3291
3574
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
@@ -3304,11 +3587,12 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3304
3587
  for (const { manifest } of allManifests) {
3305
3588
  if (!manifest.prerenderRoutes) continue;
3306
3589
  const defs = manifest._prerenderDefs || {};
3590
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3307
3591
  for (const routeName of manifest.prerenderRoutes) {
3308
3592
  const pattern = manifest.routeManifest[routeName];
3309
3593
  if (!pattern) continue;
3310
3594
  const def = defs[routeName];
3311
- const isPassthroughRoute = !!def?.options?.passthrough;
3595
+ const isPassthroughRoute = passthroughSet.has(routeName);
3312
3596
  const hasDynamic = pattern.includes(":") || pattern.includes("*");
3313
3597
  if (!hasDynamic) {
3314
3598
  entries.push({
@@ -3321,12 +3605,20 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3321
3605
  if (def?.getParams) {
3322
3606
  try {
3323
3607
  const buildVars = {};
3608
+ const buildEnv = state.resolvedBuildEnv;
3324
3609
  const getParamsCtx = {
3325
3610
  build: true,
3611
+ dev: !state.isBuildMode,
3326
3612
  set: ((keyOrVar, value) => {
3327
3613
  contextSet(buildVars, keyOrVar, value);
3328
3614
  }),
3329
- reverse: getParamsReverse
3615
+ reverse: getParamsReverse,
3616
+ get env() {
3617
+ if (buildEnv !== void 0) return buildEnv;
3618
+ throw new Error(
3619
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3620
+ );
3621
+ }
3330
3622
  };
3331
3623
  const paramsList = await def.getParams(getParamsCtx);
3332
3624
  const concurrency = def.options?.concurrency ?? 1;
@@ -3387,7 +3679,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3387
3679
  `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
3388
3680
  );
3389
3681
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3390
- const collectedData = {};
3682
+ const manifestEntries = {};
3391
3683
  let doneCount = 0;
3392
3684
  let skipCount = 0;
3393
3685
  const startTotal = performance.now();
@@ -3405,7 +3697,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3405
3697
  entry.urlPath,
3406
3698
  {},
3407
3699
  entry.buildVars,
3408
- entry.isPassthroughRoute
3700
+ entry.isPassthroughRoute,
3701
+ state.resolvedBuildEnv
3409
3702
  );
3410
3703
  if (!result) continue;
3411
3704
  if (result.passthrough) {
@@ -3417,18 +3710,30 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3417
3710
  break;
3418
3711
  }
3419
3712
  const paramHash = hashParams(result.params || {});
3420
- collectedData[`${result.routeName}/${paramHash}`] = {
3713
+ const mainKey = `${result.routeName}/${paramHash}`;
3714
+ const mainValue = JSON.stringify({
3421
3715
  segments: result.segments,
3422
3716
  handles: result.handles
3423
- };
3717
+ });
3718
+ manifestEntries[mainKey] = stageBuildAssetModule(
3719
+ state.projectRoot,
3720
+ "__pr",
3721
+ mainValue
3722
+ );
3424
3723
  if (result.interceptSegments?.length) {
3425
- collectedData[`${result.routeName}/${paramHash}/i`] = {
3724
+ const interceptKey = `${result.routeName}/${paramHash}/i`;
3725
+ const interceptValue = JSON.stringify({
3426
3726
  segments: [...result.segments, ...result.interceptSegments],
3427
3727
  handles: {
3428
3728
  ...result.handles,
3429
3729
  ...result.interceptHandles || {}
3430
3730
  }
3431
- };
3731
+ });
3732
+ manifestEntries[interceptKey] = stageBuildAssetModule(
3733
+ state.projectRoot,
3734
+ "__pr",
3735
+ interceptValue
3736
+ );
3432
3737
  }
3433
3738
  const elapsed = (performance.now() - startUrl).toFixed(0);
3434
3739
  console.log(
@@ -3472,7 +3777,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3472
3777
  }
3473
3778
  const totalElapsed = (performance.now() - startTotal).toFixed(0);
3474
3779
  if (doneCount > 0) {
3475
- state.prerenderCollectedData = collectedData;
3780
+ state.prerenderManifestEntries = manifestEntries;
3476
3781
  }
3477
3782
  const parts = [`${doneCount} done`];
3478
3783
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
@@ -3483,7 +3788,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3483
3788
  async function renderStaticHandlers(state, rscEnv, registry) {
3484
3789
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3485
3790
  return;
3486
- const collected = {};
3791
+ const manifestEntries = {};
3487
3792
  let staticDone = 0;
3488
3793
  let staticSkip = 0;
3489
3794
  let totalStaticCount = 0;
@@ -3517,10 +3822,18 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3517
3822
  const result = await routerInstance.renderStaticSegment(
3518
3823
  def.handler,
3519
3824
  def.$$id,
3520
- def.$$routePrefix
3825
+ def.$$routePrefix,
3826
+ state.resolvedBuildEnv,
3827
+ !state.isBuildMode
3521
3828
  );
3522
3829
  if (result) {
3523
- collected[def.$$id] = result;
3830
+ const hasHandles = Object.keys(result.handles).length > 0;
3831
+ const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
3832
+ manifestEntries[def.$$id] = stageBuildAssetModule(
3833
+ state.projectRoot,
3834
+ "__st",
3835
+ exportValue
3836
+ );
3524
3837
  const elapsed = (performance.now() - startHandler).toFixed(0);
3525
3838
  console.log(
3526
3839
  `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
@@ -3557,7 +3870,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3557
3870
  }
3558
3871
  const totalStaticElapsed = (performance.now() - startStatic).toFixed(0);
3559
3872
  if (staticDone > 0) {
3560
- state.staticCollectedData = collected;
3873
+ state.staticManifestEntries = manifestEntries;
3561
3874
  }
3562
3875
  const staticParts = [`${staticDone} done`];
3563
3876
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
@@ -3574,8 +3887,7 @@ async function discoverRouters(state, rscEnv) {
3574
3887
  let registry = serverMod.RouterRegistry;
3575
3888
  if (!registry || registry.size === 0) {
3576
3889
  try {
3577
- const hostMod = await rscEnv.runner.import("@rangojs/router/host");
3578
- const hostRegistry = hostMod.HostRouterRegistry;
3890
+ const hostRegistry = serverMod.HostRouterRegistry;
3579
3891
  if (hostRegistry && hostRegistry.size > 0) {
3580
3892
  console.log(
3581
3893
  `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
@@ -3615,6 +3927,14 @@ async function discoverRouters(state, rscEnv) {
3615
3927
  }
3616
3928
  const buildMod = await rscEnv.runner.import("@rangojs/router/build");
3617
3929
  const generateManifestFull = buildMod.generateManifestFull;
3930
+ const nestedRouterConflict = findNestedRouterConflict(
3931
+ [...registry.values()].map((router) => router.__sourceFile).filter(
3932
+ (sourceFile) => typeof sourceFile === "string"
3933
+ )
3934
+ );
3935
+ if (nestedRouterConflict) {
3936
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
3937
+ }
3618
3938
  const newMergedRouteManifest = {};
3619
3939
  const newMergedPrecomputedEntries = [];
3620
3940
  const newPerRouterManifests = [];
@@ -3629,7 +3949,11 @@ async function discoverRouters(state, rscEnv) {
3629
3949
  if (!router.urlpatterns || !generateManifestFull) {
3630
3950
  continue;
3631
3951
  }
3632
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
3952
+ const manifest = generateManifestFull(
3953
+ router.urlpatterns,
3954
+ routerMountIndex,
3955
+ router.__basename ? { urlPrefix: router.__basename } : void 0
3956
+ );
3633
3957
  routerMountIndex++;
3634
3958
  allManifests.push({ id, manifest });
3635
3959
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -3771,8 +4095,8 @@ async function discoverRouters(state, rscEnv) {
3771
4095
  }
3772
4096
 
3773
4097
  // src/vite/discovery/route-types-writer.ts
3774
- import { dirname as dirname3, basename, join as join3, resolve as resolve5 } from "node:path";
3775
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "node:fs";
4098
+ import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4099
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
3776
4100
  function filterUserNamedRoutes(manifest) {
3777
4101
  const filtered = {};
3778
4102
  for (const [name, pattern] of Object.entries(manifest)) {
@@ -3792,7 +4116,7 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3792
4116
  /\.(tsx?|jsx?)$/,
3793
4117
  ""
3794
4118
  );
3795
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4119
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3796
4120
  try {
3797
4121
  preContent.set(outPath, readFileSync4(outPath, "utf-8"));
3798
4122
  } catch {
@@ -3805,8 +4129,8 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3805
4129
  /\.(tsx?|jsx?)$/,
3806
4130
  ""
3807
4131
  );
3808
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
3809
- if (!existsSync4(outPath)) continue;
4132
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4133
+ if (!existsSync5(outPath)) continue;
3810
4134
  try {
3811
4135
  const content = readFileSync4(outPath, "utf-8");
3812
4136
  if (content !== preContent.get(outPath)) {
@@ -3820,10 +4144,10 @@ function writeRouteTypesFiles(state) {
3820
4144
  if (state.perRouterManifests.length === 0) return;
3821
4145
  try {
3822
4146
  const entryDir = dirname3(
3823
- resolve5(state.projectRoot, state.resolvedEntryPath)
4147
+ resolve6(state.projectRoot, state.resolvedEntryPath)
3824
4148
  );
3825
- const oldCombinedPath = join3(entryDir, "named-routes.gen.ts");
3826
- if (existsSync4(oldCombinedPath)) {
4149
+ const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
4150
+ if (existsSync5(oldCombinedPath)) {
3827
4151
  unlinkSync2(oldCombinedPath);
3828
4152
  console.log(
3829
4153
  `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
@@ -3847,7 +4171,7 @@ Set an explicit \`id\` on createRouter() or check the call site.`
3847
4171
  }
3848
4172
  const routerDir = dirname3(sourceFile);
3849
4173
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3850
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4174
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3851
4175
  const userRoutes = filterUserNamedRoutes(routeManifest);
3852
4176
  let effectiveSearchSchemas = routeSearchSchemas;
3853
4177
  if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
@@ -3867,10 +4191,10 @@ Set an explicit \`id\` on createRouter() or check the call site.`
3867
4191
  userRoutes,
3868
4192
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
3869
4193
  );
3870
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4194
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
3871
4195
  if (existing !== source) {
3872
4196
  markSelfGenWrite(state, outPath, source);
3873
- writeFileSync2(outPath, source);
4197
+ writeFileSync3(outPath, source);
3874
4198
  console.log(`[rsc-router] Generated route types -> ${outPath}`);
3875
4199
  }
3876
4200
  }
@@ -3912,21 +4236,21 @@ function supplementGenFilesWithRuntimeRoutes(state) {
3912
4236
  }
3913
4237
  const routerDir = dirname3(sourceFile);
3914
4238
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3915
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4239
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3916
4240
  const source = generateRouteTypesSource(
3917
4241
  mergedRoutes,
3918
4242
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
3919
4243
  );
3920
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4244
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
3921
4245
  if (existing !== source) {
3922
4246
  markSelfGenWrite(state, outPath, source);
3923
- writeFileSync2(outPath, source);
4247
+ writeFileSync3(outPath, source);
3924
4248
  }
3925
4249
  }
3926
4250
  }
3927
4251
 
3928
4252
  // src/vite/discovery/virtual-module-codegen.ts
3929
- import { dirname as dirname4, basename as basename2, join as join4 } from "node:path";
4253
+ import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
3930
4254
  function generateRoutesManifestModule(state) {
3931
4255
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
3932
4256
  if (hasManifest) {
@@ -3941,7 +4265,7 @@ function generateRoutesManifestModule(state) {
3941
4265
  /\.(tsx?|jsx?)$/,
3942
4266
  ""
3943
4267
  );
3944
- const genPath = join4(
4268
+ const genPath = join3(
3945
4269
  routerDir,
3946
4270
  `${routerBasename}.named-routes.gen.js`
3947
4271
  ).replaceAll("\\", "/");
@@ -4038,7 +4362,7 @@ function generatePerRouterModule(state, routerId) {
4038
4362
  /\.(tsx?|jsx?)$/,
4039
4363
  ""
4040
4364
  );
4041
- const genPath = join4(
4365
+ const genPath = join3(
4042
4366
  routerDir,
4043
4367
  `${routerBasename}.named-routes.gen.js`
4044
4368
  ).replaceAll("\\", "/");
@@ -4065,100 +4389,90 @@ function generatePerRouterModule(state, routerId) {
4065
4389
  }
4066
4390
 
4067
4391
  // src/vite/discovery/bundle-postprocess.ts
4068
- import { resolve as resolve6 } from "node:path";
4069
- import { createHash as createHash4 } from "node:crypto";
4070
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync } from "node:fs";
4392
+ import { resolve as resolve7 } from "node:path";
4393
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
4071
4394
  function postprocessBundle(state) {
4072
- const hasPrerenderData = state.prerenderCollectedData && Object.keys(state.prerenderCollectedData).length > 0;
4073
- const hasStaticData = state.staticCollectedData && Object.keys(state.staticCollectedData).length > 0;
4395
+ const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
4396
+ const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
4074
4397
  if (!hasPrerenderData && !hasStaticData) return;
4075
- const rscEntryPath = resolve6(
4398
+ const rscEntryPath = resolve7(
4076
4399
  state.projectRoot,
4077
4400
  "dist/rsc",
4078
4401
  state.rscEntryFileName ?? "index.js"
4079
4402
  );
4080
4403
  const evictionTargets = [
4081
4404
  {
4082
- info: state.handlerChunkInfo,
4405
+ infos: state.handlerChunkInfoMap.values(),
4083
4406
  fnName: "Prerender",
4084
4407
  brand: "prerenderHandler",
4085
4408
  label: "handler code from RSC bundle"
4086
4409
  },
4087
4410
  {
4088
- info: state.staticHandlerChunkInfo,
4411
+ infos: state.staticHandlerChunkInfoMap.values(),
4089
4412
  fnName: "Static",
4090
4413
  brand: "staticHandler",
4091
4414
  label: "static handler code"
4092
4415
  }
4093
4416
  ];
4094
4417
  for (const target of evictionTargets) {
4095
- if (!target.info) continue;
4096
- const chunkPath = resolve6(
4097
- state.projectRoot,
4098
- "dist/rsc",
4099
- target.info.fileName
4100
- );
4101
- try {
4102
- const code = readFileSync5(chunkPath, "utf-8");
4103
- const result = evictHandlerCode(
4104
- code,
4105
- target.info.exports,
4106
- target.fnName,
4107
- target.brand
4108
- );
4109
- if (result) {
4110
- writeFileSync3(chunkPath, result.code);
4111
- const savedKB = (result.savedBytes / 1024).toFixed(1);
4112
- console.log(
4113
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
4418
+ for (const info of target.infos) {
4419
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4420
+ try {
4421
+ const code = readFileSync5(chunkPath, "utf-8");
4422
+ const result = evictHandlerCode(
4423
+ code,
4424
+ info.exports,
4425
+ target.fnName,
4426
+ target.brand
4427
+ );
4428
+ if (result) {
4429
+ writeFileSync4(chunkPath, result.code);
4430
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4431
+ console.log(
4432
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4433
+ );
4434
+ }
4435
+ } catch (replaceErr) {
4436
+ console.warn(
4437
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4114
4438
  );
4115
4439
  }
4116
- } catch (replaceErr) {
4117
- console.warn(
4118
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4119
- );
4120
4440
  }
4121
4441
  }
4122
- state.handlerChunkInfo = null;
4123
- state.staticHandlerChunkInfo = null;
4124
- if (hasPrerenderData && existsSync5(rscEntryPath)) {
4442
+ state.handlerChunkInfoMap.clear();
4443
+ state.staticHandlerChunkInfoMap.clear();
4444
+ if (hasPrerenderData && existsSync6(rscEntryPath)) {
4125
4445
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4126
4446
  if (!rscCode.includes("__prerender-manifest.js")) {
4127
4447
  try {
4128
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
4129
- mkdirSync(assetsDir, { recursive: true });
4130
- const manifestEntries = [];
4131
- let totalBytes = 0;
4132
- for (const [key, entry] of Object.entries(
4133
- state.prerenderCollectedData
4448
+ let totalBytes = copyStagedBuildAssets(
4449
+ state.projectRoot,
4450
+ Object.values(state.prerenderManifestEntries)
4451
+ );
4452
+ const manifestMap = {};
4453
+ for (const [key, assetFileName] of Object.entries(
4454
+ state.prerenderManifestEntries
4134
4455
  )) {
4135
- const entryJson = JSON.stringify(entry);
4136
- const contentHash = createHash4("sha256").update(entryJson).digest("hex").slice(0, 8);
4137
- const assetFileName = `__pr-${contentHash}.js`;
4138
- const assetPath = resolve6(assetsDir, assetFileName);
4139
- const assetCode = `export default ${entryJson};
4140
- `;
4141
- writeFileSync3(assetPath, assetCode);
4142
- totalBytes += Buffer.byteLength(assetCode);
4143
- manifestEntries.push(
4144
- `${JSON.stringify(key)}:()=>import("./assets/${assetFileName}")`
4145
- );
4456
+ manifestMap[key] = `./assets/${assetFileName}`;
4146
4457
  }
4147
- const manifestCode = `const m={${manifestEntries.join(",")}};export default m;
4148
- `;
4149
- const manifestPath = resolve6(
4458
+ const manifestCode = [
4459
+ `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
4460
+ `export function loadPrerenderAsset(s){return import(s)}`,
4461
+ `export default m;`,
4462
+ ""
4463
+ ].join("\n");
4464
+ const manifestPath = resolve7(
4150
4465
  state.projectRoot,
4151
4466
  "dist/rsc/__prerender-manifest.js"
4152
4467
  );
4153
- writeFileSync3(manifestPath, manifestCode);
4468
+ writeFileSync4(manifestPath, manifestCode);
4154
4469
  totalBytes += Buffer.byteLength(manifestCode);
4155
- const injection = `import __pm from "./__prerender-manifest.js";
4156
- globalThis.__PRERENDER_MANIFEST = __pm;
4470
+ const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");
4157
4471
  `;
4158
- writeFileSync3(rscEntryPath, injection + rscCode);
4472
+ writeFileSync4(rscEntryPath, injection + rscCode);
4159
4473
  const totalKB = (totalBytes / 1024).toFixed(1);
4160
4474
  console.log(
4161
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderCollectedData).length} entries)`
4475
+ `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
4162
4476
  );
4163
4477
  } catch (err) {
4164
4478
  throw new Error(
@@ -4167,44 +4481,36 @@ globalThis.__PRERENDER_MANIFEST = __pm;
4167
4481
  }
4168
4482
  }
4169
4483
  }
4170
- if (hasStaticData && existsSync5(rscEntryPath)) {
4484
+ if (hasStaticData && existsSync6(rscEntryPath)) {
4171
4485
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4172
- if (!rscCode.includes("__STATIC_MANIFEST")) {
4486
+ if (!rscCode.includes("__static-manifest.js")) {
4173
4487
  try {
4174
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
4175
- mkdirSync(assetsDir, { recursive: true });
4176
4488
  const manifestEntries = [];
4177
- let totalBytes = 0;
4178
- for (const [handlerId, { encoded, handles }] of Object.entries(
4179
- state.staticCollectedData
4489
+ let totalBytes = copyStagedBuildAssets(
4490
+ state.projectRoot,
4491
+ Object.values(state.staticManifestEntries)
4492
+ );
4493
+ for (const [handlerId, assetFileName] of Object.entries(
4494
+ state.staticManifestEntries
4180
4495
  )) {
4181
- const hasHandles = Object.keys(handles).length > 0;
4182
- const exportValue = hasHandles ? JSON.stringify({ encoded, handles }) : JSON.stringify(encoded);
4183
- const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
4184
- const assetFileName = `__st-${contentHash}.js`;
4185
- const assetPath = resolve6(assetsDir, assetFileName);
4186
- const assetCode = `export default ${exportValue};
4187
- `;
4188
- writeFileSync3(assetPath, assetCode);
4189
- totalBytes += Buffer.byteLength(assetCode);
4190
4496
  manifestEntries.push(
4191
4497
  `${JSON.stringify(handlerId)}:()=>import("./assets/${assetFileName}")`
4192
4498
  );
4193
4499
  }
4194
4500
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
4195
4501
  `;
4196
- const manifestPath = resolve6(
4502
+ const manifestPath = resolve7(
4197
4503
  state.projectRoot,
4198
4504
  "dist/rsc/__static-manifest.js"
4199
4505
  );
4200
- writeFileSync3(manifestPath, manifestCode);
4506
+ writeFileSync4(manifestPath, manifestCode);
4201
4507
  totalBytes += Buffer.byteLength(manifestCode);
4202
4508
  const injection = `import "./__static-manifest.js";
4203
4509
  `;
4204
- writeFileSync3(rscEntryPath, injection + rscCode);
4510
+ writeFileSync4(rscEntryPath, injection + rscCode);
4205
4511
  const totalKB = (totalBytes / 1024).toFixed(1);
4206
4512
  console.log(
4207
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticCollectedData).length} entries)`
4513
+ `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
4208
4514
  );
4209
4515
  } catch (err) {
4210
4516
  throw new Error(
@@ -4246,8 +4552,67 @@ async function createTempRscServer(state, options = {}) {
4246
4552
  ]
4247
4553
  });
4248
4554
  }
4555
+ async function resolveBuildEnv(option, factoryCtx) {
4556
+ if (!option) return null;
4557
+ if (option === "auto") {
4558
+ if (factoryCtx.preset !== "cloudflare") {
4559
+ throw new Error(
4560
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4561
+ );
4562
+ }
4563
+ try {
4564
+ const userRequire = createRequire(
4565
+ resolve8(factoryCtx.root, "package.json")
4566
+ );
4567
+ const wranglerPath = userRequire.resolve("wrangler");
4568
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
4569
+ const proxy = await getPlatformProxy();
4570
+ return {
4571
+ env: proxy.env,
4572
+ dispose: proxy.dispose
4573
+ };
4574
+ } catch (err) {
4575
+ throw new Error(
4576
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
4577
+ Install it with: pnpm add -D wrangler
4578
+ ${err.message}`
4579
+ );
4580
+ }
4581
+ }
4582
+ if (typeof option === "function") {
4583
+ return await option(factoryCtx);
4584
+ }
4585
+ return { env: option };
4586
+ }
4587
+ async function acquireBuildEnv(s, command, mode) {
4588
+ const option = s.opts?.buildEnv;
4589
+ if (!option) return false;
4590
+ const result = await resolveBuildEnv(option, {
4591
+ root: s.projectRoot,
4592
+ mode,
4593
+ command,
4594
+ preset: s.opts?.preset ?? "node"
4595
+ });
4596
+ if (!result) return false;
4597
+ s.resolvedBuildEnv = result.env;
4598
+ s.buildEnvDispose = result.dispose ?? null;
4599
+ return true;
4600
+ }
4601
+ async function releaseBuildEnv(s) {
4602
+ if (s.buildEnvDispose) {
4603
+ try {
4604
+ await s.buildEnvDispose();
4605
+ } catch (err) {
4606
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
4607
+ }
4608
+ s.buildEnvDispose = null;
4609
+ }
4610
+ s.resolvedBuildEnv = void 0;
4611
+ }
4249
4612
  function createRouterDiscoveryPlugin(entryPath, opts) {
4250
4613
  const s = createDiscoveryState(entryPath, opts);
4614
+ let viteCommand = "build";
4615
+ let viteMode = "production";
4251
4616
  return {
4252
4617
  name: "@rangojs/router:discovery",
4253
4618
  config() {
@@ -4256,31 +4621,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4256
4621
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4257
4622
  }
4258
4623
  };
4259
- if (opts?.enableBuildPrerender) {
4260
- config.environments = {
4261
- rsc: {
4262
- build: {
4263
- rollupOptions: {
4264
- output: {
4265
- manualChunks(id) {
4266
- if (s.resolvedPrerenderModules?.has(id)) {
4267
- return "__prerender-handlers";
4268
- }
4269
- if (s.resolvedStaticModules?.has(id)) {
4270
- return "__static-handlers";
4271
- }
4272
- }
4273
- }
4274
- }
4275
- }
4276
- }
4277
- };
4278
- }
4279
4624
  return config;
4280
4625
  },
4281
4626
  configResolved(config) {
4282
4627
  s.projectRoot = config.root;
4283
4628
  s.isBuildMode = config.command === "build";
4629
+ viteCommand = config.command;
4630
+ viteMode = config.mode;
4284
4631
  s.userResolveAlias = config.resolve.alias;
4285
4632
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4286
4633
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4294,12 +4641,6 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4294
4641
  s.resolvedEntryPath = entries[0];
4295
4642
  }
4296
4643
  }
4297
- if (opts?.include || opts?.exclude) {
4298
- s.scanFilter = createScanFilter(s.projectRoot, {
4299
- include: opts.include,
4300
- exclude: opts.exclude
4301
- });
4302
- }
4303
4644
  if (opts?.staticRouteTypesGeneration !== false) {
4304
4645
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
4305
4646
  writeCombinedRouteTypesWithTracking(s, { preserveIfLarger: true });
@@ -4319,8 +4660,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4319
4660
  if (globalThis.__rscRouterDiscoveryActive) return;
4320
4661
  s.devServer = server;
4321
4662
  let resolveDiscovery;
4322
- const discoveryPromise = new Promise((resolve8) => {
4323
- resolveDiscovery = resolve8;
4663
+ const discoveryPromise = new Promise((resolve10) => {
4664
+ resolveDiscovery = resolve10;
4324
4665
  });
4325
4666
  const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
4326
4667
  let prerenderTempServer = null;
@@ -4331,6 +4672,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4331
4672
  });
4332
4673
  prerenderTempServer = null;
4333
4674
  }
4675
+ releaseBuildEnv(s).catch(() => {
4676
+ });
4334
4677
  });
4335
4678
  async function getOrCreateTempServer() {
4336
4679
  if (prerenderNodeRegistry) {
@@ -4361,6 +4704,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4361
4704
  if (!rscEnv?.runner) {
4362
4705
  s.devServerOrigin = getDevServerOrigin();
4363
4706
  try {
4707
+ await acquireBuildEnv(s, viteCommand, viteMode);
4364
4708
  const tempRscEnv = await getOrCreateTempServer();
4365
4709
  if (tempRscEnv) {
4366
4710
  await discoverRouters(s, tempRscEnv);
@@ -4376,6 +4720,7 @@ ${err.stack}`
4376
4720
  return;
4377
4721
  }
4378
4722
  try {
4723
+ await acquireBuildEnv(s, viteCommand, viteMode);
4379
4724
  const serverMod = await rscEnv.runner.import(
4380
4725
  "@rangojs/router/server"
4381
4726
  );
@@ -4395,8 +4740,8 @@ ${err.stack}`
4395
4740
  resolveDiscovery();
4396
4741
  }
4397
4742
  };
4398
- s.discoveryDone = new Promise((resolve8) => {
4399
- setTimeout(() => discover().then(resolve8, resolve8), 0);
4743
+ s.discoveryDone = new Promise((resolve10) => {
4744
+ setTimeout(() => discover().then(resolve10, resolve10), 0);
4400
4745
  });
4401
4746
  let mainRegistry = null;
4402
4747
  const propagateDiscoveryState = async (rscEnv) => {
@@ -4462,7 +4807,10 @@ ${err.stack}`
4462
4807
  pathname,
4463
4808
  {},
4464
4809
  void 0,
4465
- wantPassthrough
4810
+ wantPassthrough,
4811
+ s.resolvedBuildEnv,
4812
+ true
4813
+ // devMode: check getParams for passthrough routes
4466
4814
  );
4467
4815
  if (!result) continue;
4468
4816
  if (result.passthrough) continue;
@@ -4563,6 +4911,16 @@ ${err.stack}`
4563
4911
  const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
4564
4912
  if (!hasUrls && !hasCreateRouter) return;
4565
4913
  if (hasCreateRouter) {
4914
+ const nestedRouterConflict = findNestedRouterConflict([
4915
+ ...s.cachedRouterFiles ?? [],
4916
+ resolve8(filePath)
4917
+ ]);
4918
+ if (nestedRouterConflict) {
4919
+ server.config.logger.error(
4920
+ formatNestedRouterConflictError(nestedRouterConflict)
4921
+ );
4922
+ return;
4923
+ }
4566
4924
  s.cachedRouterFiles = void 0;
4567
4925
  }
4568
4926
  scheduleRouteRegeneration();
@@ -4585,6 +4943,10 @@ ${err.stack}`
4585
4943
  async buildStart() {
4586
4944
  if (!s.isBuildMode) return;
4587
4945
  if (s.mergedRouteManifest !== null) return;
4946
+ resetStagedBuildAssets(s.projectRoot);
4947
+ s.prerenderManifestEntries = null;
4948
+ s.staticManifestEntries = null;
4949
+ await acquireBuildEnv(s, viteCommand, viteMode);
4588
4950
  let tempServer = null;
4589
4951
  globalThis.__rscRouterDiscoveryActive = true;
4590
4952
  try {
@@ -4624,6 +4986,7 @@ ${details}`
4624
4986
  if (tempServer) {
4625
4987
  await tempServer.close();
4626
4988
  }
4989
+ await releaseBuildEnv(s);
4627
4990
  }
4628
4991
  },
4629
4992
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4666,20 +5029,30 @@ ${details}`
4666
5029
  }
4667
5030
  if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
4668
5031
  return;
5032
+ s.handlerChunkInfoMap.clear();
5033
+ s.staticHandlerChunkInfoMap.clear();
4669
5034
  for (const [fileName, chunk] of Object.entries(bundle)) {
4670
5035
  if (chunk.type !== "chunk") continue;
4671
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5036
+ if (s.resolvedPrerenderModules?.size) {
4672
5037
  const handlers = extractHandlerExportsFromChunk(
4673
5038
  chunk.code,
4674
5039
  s.resolvedPrerenderModules,
4675
5040
  "Prerender",
4676
- true
5041
+ false
4677
5042
  );
4678
5043
  if (handlers.length > 0) {
4679
- s.handlerChunkInfo = { fileName, exports: handlers };
5044
+ const existing = s.handlerChunkInfoMap.get(fileName);
5045
+ if (existing) {
5046
+ existing.exports.push(...handlers);
5047
+ } else {
5048
+ s.handlerChunkInfoMap.set(fileName, {
5049
+ fileName,
5050
+ exports: handlers
5051
+ });
5052
+ }
4680
5053
  }
4681
5054
  }
4682
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5055
+ if (s.resolvedStaticModules?.size) {
4683
5056
  const handlers = extractHandlerExportsFromChunk(
4684
5057
  chunk.code,
4685
5058
  s.resolvedStaticModules,
@@ -4687,7 +5060,15 @@ ${details}`
4687
5060
  false
4688
5061
  );
4689
5062
  if (handlers.length > 0) {
4690
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5063
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5064
+ if (existing) {
5065
+ existing.exports.push(...handlers);
5066
+ } else {
5067
+ s.staticHandlerChunkInfoMap.set(fileName, {
5068
+ fileName,
5069
+ exports: handlers
5070
+ });
5071
+ }
4691
5072
  }
4692
5073
  }
4693
5074
  }
@@ -4714,8 +5095,16 @@ async function rango(options) {
4714
5095
  const showBanner = resolvedOptions.banner ?? true;
4715
5096
  const plugins = [];
4716
5097
  const rangoAliases = getPackageAliases();
4717
- const excludeDeps = getExcludeDeps();
4718
- let rscEntryPath = null;
5098
+ const excludeDeps = [
5099
+ ...getExcludeDeps(),
5100
+ // The public browser entry re-exports the RSDW browser client.
5101
+ // Excluding both keeps Vite from freezing the unpatched bundle into
5102
+ // .vite/deps before our source transforms run.
5103
+ "@vitejs/plugin-rsc/browser",
5104
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
5105
+ // cjs-to-esm transform can patch the real file.
5106
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5107
+ ];
4719
5108
  const routerRef = { path: void 0 };
4720
5109
  const prerenderEnabled = true;
4721
5110
  if (preset === "cloudflare") {
@@ -4811,160 +5200,132 @@ async function rango(options) {
4811
5200
  }
4812
5201
  });
4813
5202
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5203
+ plugins.push(performanceTracksPlugin());
4814
5204
  plugins.push(
4815
5205
  rsc({
4816
5206
  entries: finalEntries,
4817
5207
  serverHandler: false
4818
5208
  })
4819
5209
  );
5210
+ plugins.push(clientRefDedup());
4820
5211
  } else {
4821
- const nodeOptions = resolvedOptions;
4822
- routerRef.path = nodeOptions.router;
4823
- if (!routerRef.path) {
4824
- plugins.push({
4825
- name: "@rangojs/router:auto-discover",
4826
- config(userConfig) {
4827
- if (routerRef.path) return;
4828
- const root = userConfig.root ? resolve7(process.cwd(), userConfig.root) : process.cwd();
4829
- const filter = createScanFilter(root, {
4830
- include: resolvedOptions.include,
4831
- exclude: resolvedOptions.exclude
4832
- });
4833
- const candidates = findRouterFiles(root, filter);
4834
- if (candidates.length === 1) {
4835
- const abs = candidates[0];
4836
- routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
4837
- } else if (candidates.length > 1) {
4838
- const list = candidates.map(
4839
- (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
4840
- ).join("\n");
4841
- throw new Error(
4842
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
4843
- ${list}`
4844
- );
4845
- }
5212
+ plugins.push({
5213
+ name: "@rangojs/router:auto-discover",
5214
+ config(userConfig) {
5215
+ if (routerRef.path) return;
5216
+ const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
5217
+ const candidates = findRouterFiles(root);
5218
+ if (candidates.length === 1) {
5219
+ const abs = candidates[0];
5220
+ routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
5221
+ } else if (candidates.length > 1) {
5222
+ const list = candidates.map(
5223
+ (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5224
+ ).join("\n");
5225
+ throw new Error(`[rsc-router] Multiple routers found:
5226
+ ${list}`);
4846
5227
  }
4847
- });
4848
- }
4849
- const rscOption = nodeOptions.rsc ?? true;
4850
- if (rscOption !== false) {
4851
- const { default: rsc } = await import("@vitejs/plugin-rsc");
4852
- const userEntries = typeof rscOption === "boolean" ? {} : rscOption.entries || {};
4853
- const finalEntries = {
4854
- client: userEntries.client ?? VIRTUAL_IDS.browser,
4855
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
4856
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc
4857
- };
4858
- rscEntryPath = userEntries.rsc ?? null;
4859
- let hasWarnedDuplicate = false;
4860
- plugins.push({
4861
- name: "@rangojs/router:rsc-integration",
4862
- enforce: "pre",
4863
- config() {
4864
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
4865
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
4866
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
4867
- return {
4868
- // Exclude rsc-router modules from optimization to prevent module duplication
4869
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
4870
- optimizeDeps: {
4871
- exclude: excludeDeps,
4872
- esbuildOptions: sharedEsbuildOptions
4873
- },
4874
- build: {
4875
- rollupOptions: { onwarn }
4876
- },
4877
- resolve: {
4878
- alias: rangoAliases
4879
- },
4880
- environments: {
4881
- client: {
4882
- build: {
4883
- rollupOptions: {
4884
- output: {
4885
- manualChunks: getManualChunks
4886
- }
4887
- }
4888
- },
4889
- // Always exclude rsc-router modules, conditionally add virtual entry
4890
- optimizeDeps: {
4891
- // Pre-bundle React and rsc-html-stream to prevent late discovery
4892
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
4893
- include: [
4894
- "react",
4895
- "react-dom",
4896
- "react/jsx-runtime",
4897
- "react/jsx-dev-runtime",
4898
- "rsc-html-stream/client"
4899
- ],
4900
- exclude: excludeDeps,
4901
- esbuildOptions: sharedEsbuildOptions,
4902
- ...useVirtualClient && {
4903
- // Tell Vite to scan the virtual entry for dependencies
4904
- entries: [VIRTUAL_IDS.browser]
4905
- }
4906
- }
4907
- },
4908
- ...useVirtualSSR && {
4909
- ssr: {
4910
- optimizeDeps: {
4911
- entries: [VIRTUAL_IDS.ssr],
4912
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
4913
- include: [
4914
- "react",
4915
- "react-dom",
4916
- "react-dom/server.edge",
4917
- "react-dom/static.edge",
4918
- "react/jsx-runtime",
4919
- "react/jsx-dev-runtime",
4920
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
4921
- ],
4922
- exclude: excludeDeps,
4923
- esbuildOptions: sharedEsbuildOptions
5228
+ }
5229
+ });
5230
+ const finalEntries = {
5231
+ client: VIRTUAL_IDS.browser,
5232
+ ssr: VIRTUAL_IDS.ssr,
5233
+ rsc: VIRTUAL_IDS.rsc
5234
+ };
5235
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
5236
+ let hasWarnedDuplicate = false;
5237
+ plugins.push({
5238
+ name: "@rangojs/router:rsc-integration",
5239
+ enforce: "pre",
5240
+ config() {
5241
+ return {
5242
+ optimizeDeps: {
5243
+ exclude: excludeDeps,
5244
+ esbuildOptions: sharedEsbuildOptions
5245
+ },
5246
+ build: {
5247
+ rollupOptions: { onwarn }
5248
+ },
5249
+ resolve: {
5250
+ alias: rangoAliases
5251
+ },
5252
+ environments: {
5253
+ client: {
5254
+ build: {
5255
+ rollupOptions: {
5256
+ output: {
5257
+ manualChunks: getManualChunks
4924
5258
  }
4925
5259
  }
4926
5260
  },
4927
- ...useVirtualRSC && {
4928
- rsc: {
4929
- optimizeDeps: {
4930
- entries: [VIRTUAL_IDS.rsc],
4931
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
4932
- include: [
4933
- "react",
4934
- "react/jsx-runtime",
4935
- "react/jsx-dev-runtime",
4936
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
4937
- ],
4938
- esbuildOptions: sharedEsbuildOptions
4939
- }
4940
- }
5261
+ optimizeDeps: {
5262
+ include: [
5263
+ "react",
5264
+ "react-dom",
5265
+ "react/jsx-runtime",
5266
+ "react/jsx-dev-runtime",
5267
+ "rsc-html-stream/client"
5268
+ ],
5269
+ exclude: excludeDeps,
5270
+ esbuildOptions: sharedEsbuildOptions,
5271
+ entries: [VIRTUAL_IDS.browser]
5272
+ }
5273
+ },
5274
+ ssr: {
5275
+ optimizeDeps: {
5276
+ entries: [VIRTUAL_IDS.ssr],
5277
+ include: [
5278
+ "react",
5279
+ "react-dom",
5280
+ "react-dom/server.edge",
5281
+ "react-dom/static.edge",
5282
+ "react/jsx-runtime",
5283
+ "react/jsx-dev-runtime",
5284
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5285
+ ],
5286
+ exclude: excludeDeps,
5287
+ esbuildOptions: sharedEsbuildOptions
5288
+ }
5289
+ },
5290
+ rsc: {
5291
+ optimizeDeps: {
5292
+ entries: [VIRTUAL_IDS.rsc],
5293
+ include: [
5294
+ "react",
5295
+ "react/jsx-runtime",
5296
+ "react/jsx-dev-runtime",
5297
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5298
+ ],
5299
+ esbuildOptions: sharedEsbuildOptions
4941
5300
  }
4942
5301
  }
4943
- };
4944
- },
4945
- configResolved(config) {
4946
- if (showBanner) {
4947
- const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
4948
- printBanner(mode, "node", rangoVersion);
4949
- }
4950
- const rscMinimalCount = config.plugins.filter(
4951
- (p) => p.name === "rsc:minimal"
4952
- ).length;
4953
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
4954
- hasWarnedDuplicate = true;
4955
- console.warn(
4956
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your config or use rango({ rsc: false }) for manual configuration."
4957
- );
4958
5302
  }
5303
+ };
5304
+ },
5305
+ configResolved(config) {
5306
+ if (showBanner) {
5307
+ const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
5308
+ printBanner(mode, "node", rangoVersion);
4959
5309
  }
4960
- });
4961
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
4962
- plugins.push(
4963
- rsc({
4964
- entries: finalEntries
4965
- })
4966
- );
4967
- }
5310
+ const rscMinimalCount = config.plugins.filter(
5311
+ (p) => p.name === "rsc:minimal"
5312
+ ).length;
5313
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
5314
+ hasWarnedDuplicate = true;
5315
+ console.warn(
5316
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
5317
+ );
5318
+ }
5319
+ }
5320
+ });
5321
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5322
+ plugins.push(performanceTracksPlugin());
5323
+ plugins.push(
5324
+ rsc({
5325
+ entries: finalEntries
5326
+ })
5327
+ );
5328
+ plugins.push(clientRefDedup());
4968
5329
  }
4969
5330
  plugins.push({
4970
5331
  name: "@rangojs/router:client-component-hmr",
@@ -4991,22 +5352,102 @@ ${list}`
4991
5352
  plugins.push(createVersionPlugin());
4992
5353
  const discoveryEntryPath = preset !== "cloudflare" ? routerRef.path : void 0;
4993
5354
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : void 0;
4994
- const injectorEntryPath = rscEntryPath ?? (preset === "cloudflare" ? void 0 : null);
4995
- if (injectorEntryPath !== null) {
4996
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
5355
+ if (preset === "cloudflare") {
5356
+ plugins.push(createVersionInjectorPlugin(void 0));
4997
5357
  }
4998
5358
  plugins.push(createCjsToEsmPlugin());
4999
5359
  plugins.push(
5000
5360
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5001
5361
  routerPathRef: discoveryRouterRef,
5002
5362
  enableBuildPrerender: prerenderEnabled,
5003
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
5004
- include: resolvedOptions.include,
5005
- exclude: resolvedOptions.exclude
5363
+ buildEnv: options?.buildEnv,
5364
+ preset
5006
5365
  })
5007
5366
  );
5008
5367
  return plugins;
5009
5368
  }
5369
+
5370
+ // src/vite/plugins/refresh-cmd.ts
5371
+ function poke() {
5372
+ return {
5373
+ name: "vite-plugin-poke",
5374
+ apply: "serve",
5375
+ configureServer(server) {
5376
+ const stdin = process.stdin;
5377
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
5378
+ const triggerReload = (source) => {
5379
+ server.hot.send({ type: "full-reload", path: "*" });
5380
+ server.config.logger.info(` browser reload (${source})`, {
5381
+ timestamp: true
5382
+ });
5383
+ };
5384
+ const toBuffer = (chunk) => {
5385
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5386
+ };
5387
+ const formatChunk = (chunk) => {
5388
+ const data = toBuffer(chunk);
5389
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
5390
+ const ascii = Array.from(data).map((byte) => {
5391
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
5392
+ if (byte === 10) return "\\n";
5393
+ if (byte === 13) return "\\r";
5394
+ if (byte === 9) return "\\t";
5395
+ return ".";
5396
+ }).join("");
5397
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
5398
+ };
5399
+ const readCtrlR = (chunk) => {
5400
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5401
+ return data.length === 1 && data[0] === 18;
5402
+ };
5403
+ const readSubmittedCommands = (chunk) => {
5404
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5405
+ if (!text.includes("\n")) return [];
5406
+ const lines = text.split("\n");
5407
+ lines.pop();
5408
+ return lines;
5409
+ };
5410
+ if (debug) {
5411
+ server.config.logger.info(
5412
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5413
+ { timestamp: true }
5414
+ );
5415
+ }
5416
+ if (stdin.isTTY) {
5417
+ server.config.logger.info(
5418
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
5419
+ { timestamp: true }
5420
+ );
5421
+ }
5422
+ const onData = (data) => {
5423
+ if (debug) {
5424
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5425
+ timestamp: true
5426
+ });
5427
+ }
5428
+ if (readCtrlR(data)) {
5429
+ triggerReload("ctrl+r");
5430
+ return;
5431
+ }
5432
+ for (const command of readSubmittedCommands(data)) {
5433
+ if (command === "e") {
5434
+ triggerReload("e+enter");
5435
+ return;
5436
+ }
5437
+ if (command === "\x1Br") {
5438
+ triggerReload("option+r+enter");
5439
+ return;
5440
+ }
5441
+ }
5442
+ };
5443
+ stdin.on("data", onData);
5444
+ server.httpServer?.on("close", () => {
5445
+ stdin.off("data", onData);
5446
+ });
5447
+ }
5448
+ };
5449
+ }
5010
5450
  export {
5451
+ poke,
5011
5452
  rango
5012
5453
  };