@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fad716ff

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 (118) hide show
  1. package/README.md +76 -18
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +526 -168
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/package.json +2 -2
  6. package/skills/cache-guide/SKILL.md +32 -0
  7. package/skills/caching/SKILL.md +8 -0
  8. package/skills/links/SKILL.md +3 -1
  9. package/skills/loader/SKILL.md +53 -43
  10. package/skills/middleware/SKILL.md +2 -0
  11. package/skills/parallel/SKILL.md +67 -0
  12. package/skills/prerender/SKILL.md +110 -68
  13. package/skills/route/SKILL.md +31 -0
  14. package/skills/router-setup/SKILL.md +87 -2
  15. package/skills/typesafety/SKILL.md +10 -0
  16. package/src/__internal.ts +1 -1
  17. package/src/browser/app-version.ts +14 -0
  18. package/src/browser/navigation-bridge.ts +16 -3
  19. package/src/browser/navigation-client.ts +64 -40
  20. package/src/browser/navigation-store.ts +43 -8
  21. package/src/browser/partial-update.ts +37 -4
  22. package/src/browser/prefetch/fetch.ts +8 -2
  23. package/src/browser/prefetch/queue.ts +61 -29
  24. package/src/browser/prefetch/resource-ready.ts +77 -0
  25. package/src/browser/react/Link.tsx +44 -8
  26. package/src/browser/react/NavigationProvider.tsx +13 -4
  27. package/src/browser/react/context.ts +7 -2
  28. package/src/browser/react/use-handle.ts +9 -58
  29. package/src/browser/react/use-router.ts +21 -8
  30. package/src/browser/rsc-router.tsx +26 -3
  31. package/src/browser/scroll-restoration.ts +10 -8
  32. package/src/browser/server-action-bridge.ts +8 -6
  33. package/src/browser/types.ts +27 -5
  34. package/src/build/generate-manifest.ts +6 -6
  35. package/src/build/generate-route-types.ts +3 -0
  36. package/src/build/route-types/include-resolution.ts +8 -1
  37. package/src/build/route-types/router-processing.ts +211 -72
  38. package/src/build/route-types/scan-filter.ts +8 -1
  39. package/src/cache/cache-runtime.ts +15 -11
  40. package/src/cache/cache-scope.ts +46 -5
  41. package/src/cache/taint.ts +55 -0
  42. package/src/client.tsx +2 -56
  43. package/src/context-var.ts +72 -2
  44. package/src/handle.ts +40 -0
  45. package/src/index.rsc.ts +3 -1
  46. package/src/index.ts +12 -0
  47. package/src/prerender/store.ts +5 -4
  48. package/src/prerender.ts +138 -77
  49. package/src/reverse.ts +22 -1
  50. package/src/route-definition/dsl-helpers.ts +42 -19
  51. package/src/route-definition/helpers-types.ts +10 -6
  52. package/src/route-definition/index.ts +3 -0
  53. package/src/route-definition/redirect.ts +9 -1
  54. package/src/route-definition/resolve-handler-use.ts +149 -0
  55. package/src/route-types.ts +11 -0
  56. package/src/router/content-negotiation.ts +100 -1
  57. package/src/router/handler-context.ts +79 -23
  58. package/src/router/intercept-resolution.ts +9 -4
  59. package/src/router/loader-resolution.ts +156 -21
  60. package/src/router/match-api.ts +124 -189
  61. package/src/router/match-middleware/background-revalidation.ts +12 -1
  62. package/src/router/match-middleware/cache-lookup.ts +72 -13
  63. package/src/router/match-middleware/cache-store.ts +21 -4
  64. package/src/router/match-middleware/segment-resolution.ts +53 -0
  65. package/src/router/match-result.ts +11 -5
  66. package/src/router/metrics.ts +6 -1
  67. package/src/router/middleware-types.ts +6 -8
  68. package/src/router/middleware.ts +2 -5
  69. package/src/router/navigation-snapshot.ts +182 -0
  70. package/src/router/prerender-match.ts +110 -10
  71. package/src/router/preview-match.ts +30 -102
  72. package/src/router/request-classification.ts +310 -0
  73. package/src/router/route-snapshot.ts +245 -0
  74. package/src/router/router-context.ts +1 -0
  75. package/src/router/router-interfaces.ts +36 -4
  76. package/src/router/router-options.ts +37 -11
  77. package/src/router/segment-resolution/fresh.ts +101 -18
  78. package/src/router/segment-resolution/helpers.ts +29 -24
  79. package/src/router/segment-resolution/revalidation.ts +122 -26
  80. package/src/router/types.ts +1 -0
  81. package/src/router.ts +54 -5
  82. package/src/rsc/handler.ts +464 -377
  83. package/src/rsc/loader-fetch.ts +23 -3
  84. package/src/rsc/manifest-init.ts +5 -1
  85. package/src/rsc/progressive-enhancement.ts +14 -2
  86. package/src/rsc/rsc-rendering.ts +10 -1
  87. package/src/rsc/server-action.ts +8 -0
  88. package/src/rsc/ssr-setup.ts +2 -2
  89. package/src/rsc/types.ts +9 -1
  90. package/src/server/context.ts +50 -1
  91. package/src/server/handle-store.ts +19 -0
  92. package/src/server/loader-registry.ts +9 -8
  93. package/src/server/request-context.ts +175 -15
  94. package/src/ssr/index.tsx +3 -0
  95. package/src/static-handler.ts +18 -6
  96. package/src/types/cache-types.ts +4 -4
  97. package/src/types/handler-context.ts +137 -33
  98. package/src/types/loader-types.ts +36 -9
  99. package/src/types/route-entry.ts +1 -1
  100. package/src/urls/path-helper-types.ts +9 -2
  101. package/src/urls/path-helper.ts +47 -12
  102. package/src/urls/pattern-types.ts +12 -0
  103. package/src/urls/response-types.ts +16 -6
  104. package/src/use-loader.tsx +73 -4
  105. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  106. package/src/vite/discovery/discover-routers.ts +5 -1
  107. package/src/vite/discovery/prerender-collection.ts +14 -1
  108. package/src/vite/discovery/state.ts +13 -4
  109. package/src/vite/index.ts +4 -0
  110. package/src/vite/plugin-types.ts +60 -5
  111. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  112. package/src/vite/plugins/expose-internal-ids.ts +118 -39
  113. package/src/vite/plugins/performance-tracks.ts +88 -0
  114. package/src/vite/plugins/refresh-cmd.ts +88 -26
  115. package/src/vite/rango.ts +19 -2
  116. package/src/vite/router-discovery.ts +178 -37
  117. package/src/vite/utils/prerender-utils.ts +18 -0
  118. package/src/vite/utils/shared-utils.ts +3 -2
@@ -23,6 +23,7 @@ import {
23
23
  getImportedFnNames,
24
24
  collectCreateExportBindings,
25
25
  buildUnsupportedShapeWarning,
26
+ isExportOnlyFile,
26
27
  } from "./expose-ids/export-analysis.js";
27
28
  import {
28
29
  hasCreateLoaderImport,
@@ -34,6 +35,7 @@ import {
34
35
  transformLocationState,
35
36
  generateWholeFileStubs,
36
37
  generateExprStubs,
38
+ stubHandlerExprs,
37
39
  transformHandlerIds,
38
40
  } from "./expose-ids/handler-transform.js";
39
41
 
@@ -385,7 +387,9 @@ ${lazyImports.join(",\n")}
385
387
  if (stubResult) return stubResult;
386
388
  }
387
389
 
388
- // --- PrerenderHandler: non-RSC stub replacement ---
390
+ // --- PrerenderHandler: non-RSC whole-file stub replacement ---
391
+ // When ALL exports are Prerender() calls, replace the entire file.
392
+ // Mixed-export files are handled in the unified pipeline below.
389
393
  if (hasPrerenderHandlerCode && !isRscEnv) {
390
394
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
391
395
  const bindings = getBindings(code, fnNames);
@@ -397,16 +401,6 @@ ${lazyImports.join(",\n")}
397
401
  isBuild,
398
402
  );
399
403
  if (wholeFile) return wholeFile;
400
-
401
- const exprStubs = generateExprStubs(
402
- PRERENDER_CONFIG,
403
- bindings,
404
- code,
405
- filePath,
406
- id,
407
- isBuild,
408
- );
409
- if (exprStubs) return exprStubs;
410
404
  }
411
405
 
412
406
  // --- PrerenderHandler: RSC build module tracking ---
@@ -467,7 +461,8 @@ ${lazyImports.join(",\n")}
467
461
  }
468
462
  }
469
463
 
470
- // --- StaticHandler: non-RSC stub replacement ---
464
+ // --- StaticHandler: non-RSC whole-file stub replacement ---
465
+ // When ALL exports are Static() calls, replace the entire file.
471
466
  if (hasStaticHandlerCode && !isRscEnv) {
472
467
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
473
468
  const bindings = getBindings(code, fnNames);
@@ -479,16 +474,79 @@ ${lazyImports.join(",\n")}
479
474
  isBuild,
480
475
  );
481
476
  if (wholeFile) return wholeFile;
477
+ }
482
478
 
483
- const exprStubs = generateExprStubs(
484
- STATIC_CONFIG,
485
- bindings,
486
- code,
487
- filePath,
488
- id,
489
- isBuild,
490
- );
491
- if (exprStubs) return exprStubs;
479
+ // --- Mixed-type whole-file stub replacement (non-RSC) ---
480
+ // When the individual whole-file checks above fail (each only checks
481
+ // one type), the file has mixed exports (e.g. createLoader + Prerender).
482
+ // Gather ALL recognized bindings and check if they cover every export.
483
+ // If yes, replace the entire file with stubs — this strips server-only
484
+ // imports (node:fs, DB clients, etc.) that would crash in the browser.
485
+ if (!isRscEnv) {
486
+ const allBindings: CreateExportBinding[] = [];
487
+ if (hasLoaderCode) {
488
+ allBindings.push(...getBindings(code, getFnNames("createLoader")));
489
+ }
490
+ if (hasHandleCode) {
491
+ allBindings.push(...getBindings(code, getFnNames("createHandle")));
492
+ }
493
+ if (hasLocationStateCode) {
494
+ allBindings.push(
495
+ ...getBindings(code, getFnNames("createLocationState")),
496
+ );
497
+ }
498
+ if (hasPrerenderHandlerCode) {
499
+ allBindings.push(
500
+ ...getBindings(code, getFnNames(PRERENDER_CONFIG.fnName)),
501
+ );
502
+ }
503
+ if (hasStaticHandlerCode) {
504
+ allBindings.push(
505
+ ...getBindings(code, getFnNames(STATIC_CONFIG.fnName)),
506
+ );
507
+ }
508
+ if (allBindings.length > 0 && isExportOnlyFile(code, allBindings)) {
509
+ const stubs: string[] = [];
510
+ for (const binding of allBindings) {
511
+ const name = binding.exportNames[0];
512
+ const stubId = isBuild
513
+ ? hashId(filePath, name)
514
+ : `${filePath}#${name}`;
515
+ // Determine brand from which type this binding belongs to
516
+ const fnCall = code.slice(
517
+ binding.callExprStart,
518
+ binding.callOpenParenPos + 1,
519
+ );
520
+ let brand = "loader";
521
+ if (
522
+ hasPrerenderHandlerCode &&
523
+ getFnNames(PRERENDER_CONFIG.fnName).some((n) =>
524
+ fnCall.includes(n),
525
+ )
526
+ ) {
527
+ brand = PRERENDER_CONFIG.brand;
528
+ } else if (
529
+ hasStaticHandlerCode &&
530
+ getFnNames(STATIC_CONFIG.fnName).some((n) => fnCall.includes(n))
531
+ ) {
532
+ brand = STATIC_CONFIG.brand;
533
+ } else if (
534
+ hasHandleCode &&
535
+ getFnNames("createHandle").some((n) => fnCall.includes(n))
536
+ ) {
537
+ brand = "handle";
538
+ } else if (
539
+ hasLocationStateCode &&
540
+ getFnNames("createLocationState").some((n) => fnCall.includes(n))
541
+ ) {
542
+ brand = "locationState";
543
+ }
544
+ stubs.push(
545
+ `export const ${name} = { __brand: "${brand}", $$id: "${stubId}" };`,
546
+ );
547
+ }
548
+ return { code: stubs.join("\n") + "\n", map: null };
549
+ }
492
550
  }
493
551
 
494
552
  // --- StaticHandler: RSC build module tracking ---
@@ -535,27 +593,48 @@ ${lazyImports.join(",\n")}
535
593
  isBuild,
536
594
  ) || changed;
537
595
  }
538
- if (hasPrerenderHandlerCode && isRscEnv) {
596
+ if (hasPrerenderHandlerCode) {
539
597
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
540
- changed =
541
- transformHandlerIds(
542
- PRERENDER_CONFIG,
543
- getBindings(code, fnNames),
544
- s,
545
- filePath,
546
- isBuild,
547
- ) || changed;
598
+ const bindings = getBindings(code, fnNames);
599
+ if (isRscEnv) {
600
+ changed =
601
+ transformHandlerIds(
602
+ PRERENDER_CONFIG,
603
+ bindings,
604
+ s,
605
+ filePath,
606
+ isBuild,
607
+ ) || changed;
608
+ } else {
609
+ // Non-RSC mixed-export file: replace Prerender() calls with stubs
610
+ // on the shared MagicString so sourcemaps stay accurate.
611
+ changed =
612
+ stubHandlerExprs(
613
+ PRERENDER_CONFIG,
614
+ bindings,
615
+ s,
616
+ filePath,
617
+ isBuild,
618
+ ) || changed;
619
+ }
548
620
  }
549
- if (hasStaticHandlerCode && isRscEnv) {
621
+ if (hasStaticHandlerCode) {
550
622
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
551
- changed =
552
- transformHandlerIds(
553
- STATIC_CONFIG,
554
- getBindings(code, fnNames),
555
- s,
556
- filePath,
557
- isBuild,
558
- ) || changed;
623
+ const bindings = getBindings(code, fnNames);
624
+ if (isRscEnv) {
625
+ changed =
626
+ transformHandlerIds(
627
+ STATIC_CONFIG,
628
+ bindings,
629
+ s,
630
+ filePath,
631
+ isBuild,
632
+ ) || changed;
633
+ } else {
634
+ changed =
635
+ stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
636
+ changed;
637
+ }
559
638
  }
560
639
 
561
640
  if (!changed) return;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * React Performance Tracks — RSDW client patch
3
+ *
4
+ * Patches the RSDW client so _debugInfo recovery works for plain-object
5
+ * payloads (our RscPayload shape). Without this, the Server Components
6
+ * track in Chrome DevTools stays empty.
7
+ *
8
+ * React's flushComponentPerformance uses splice(0) to empty _debugInfo
9
+ * after resolution, then recovers it from the resolved value — but only
10
+ * for arrays, async iterables, React elements, and lazy types. Since our
11
+ * RscPayload is a plain object, _debugInfo is lost. This patch relaxes
12
+ * the check so _debugInfo is recovered from any object.
13
+ */
14
+
15
+ import type { Plugin } from "vite";
16
+ import { readFile } from "node:fs/promises";
17
+
18
+ const RSDW_PATCH_RE =
19
+ /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
20
+
21
+ function buildPatchReplacement(match: string, debugInfoVar: string): string {
22
+ return `${match}
23
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
24
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
25
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
26
+ ${debugInfoVar} = _resolved._debugInfo;
27
+ }
28
+ }`;
29
+ }
30
+
31
+ export function patchRsdwClientDebugInfoRecovery(code: string): {
32
+ code: string;
33
+ debugInfoVar: string | null;
34
+ } {
35
+ const match = code.match(RSDW_PATCH_RE);
36
+ if (!match) {
37
+ return { code, debugInfoVar: null };
38
+ }
39
+
40
+ return {
41
+ code: code.replace(match[1]!, buildPatchReplacement(match[1]!, match[2]!)),
42
+ debugInfoVar: match[2]!,
43
+ };
44
+ }
45
+
46
+ export function performanceTracksOptimizeDepsPlugin(): {
47
+ name: string;
48
+ setup(build: any): void;
49
+ } {
50
+ return {
51
+ name: "@rangojs/router:performance-tracks-optimize-deps",
52
+ setup(build: any): void {
53
+ build.onLoad(
54
+ {
55
+ filter:
56
+ /react-server-dom-webpack-client\.browser\.(development|production)\.js$/,
57
+ },
58
+ async (args: { path: string }) => {
59
+ const code = await readFile(args.path, "utf8");
60
+ const patched = patchRsdwClientDebugInfoRecovery(code);
61
+ return {
62
+ contents: patched.code,
63
+ loader: "js",
64
+ };
65
+ },
66
+ );
67
+ },
68
+ };
69
+ }
70
+
71
+ export function performanceTracksPlugin(): Plugin {
72
+ return {
73
+ name: "@rangojs/router:performance-tracks",
74
+
75
+ transform(code, id) {
76
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
77
+ const patched = patchRsdwClientDebugInfoRecovery(code);
78
+ if (!patched.debugInfoVar) return;
79
+ if (process.env.INTERNAL_RANGO_DEBUG)
80
+ console.log(
81
+ "[perf-tracks] patched RSDW client (var:",
82
+ patched.debugInfoVar,
83
+ ")",
84
+ );
85
+ return patched.code;
86
+ },
87
+ };
88
+ }
@@ -1,8 +1,13 @@
1
1
  import type { Plugin } from "vite";
2
2
 
3
3
  /**
4
- * Vite plugin that triggers a full browser reload when Ctrl+R is pressed
5
- * in the terminal running the dev server.
4
+ * Vite plugin that triggers a full browser reload from terminal input.
5
+ *
6
+ * This plugin is intentionally passive:
7
+ * - it never enables raw mode on stdin
8
+ * - it never restores terminal state
9
+ * - it reacts to Ctrl+R when that raw byte reaches the process
10
+ * - it also supports safe line-based fallbacks like "e" + Enter
6
11
  *
7
12
  * Usage:
8
13
  * ```ts
@@ -20,35 +25,95 @@ export function poke(): Plugin {
20
25
 
21
26
  configureServer(server) {
22
27
  const stdin = process.stdin;
28
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
29
+
30
+ const triggerReload = (source: string) => {
31
+ server.hot.send({ type: "full-reload", path: "*" });
32
+ server.config.logger.info(` browser reload (${source})`, {
33
+ timestamp: true,
34
+ });
35
+ };
36
+
37
+ const toBuffer = (chunk: string | Buffer): Buffer => {
38
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
39
+ };
40
+
41
+ const formatChunk = (chunk: string | Buffer): string => {
42
+ const data = toBuffer(chunk);
43
+ const hex = Array.from(data)
44
+ .map((byte) => `0x${byte.toString(16).padStart(2, "0")}`)
45
+ .join(" ");
46
+ const ascii = Array.from(data)
47
+ .map((byte) => {
48
+ if (byte >= 0x20 && byte <= 0x7e) return String.fromCharCode(byte);
49
+ if (byte === 0x0a) return "\\n";
50
+ if (byte === 0x0d) return "\\r";
51
+ if (byte === 0x09) return "\\t";
52
+ return ".";
53
+ })
54
+ .join("");
55
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
56
+ };
57
+
58
+ const readCtrlR = (chunk: string | Buffer): boolean => {
59
+ const data =
60
+ typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
61
+ return data.length === 1 && data[0] === 0x12;
62
+ };
63
+
64
+ const readSubmittedCommands = (chunk: string | Buffer): string[] => {
65
+ const text = toBuffer(chunk)
66
+ .toString("utf8")
67
+ .replace(/\r\n/g, "\n")
68
+ .replace(/\r/g, "\n");
69
+
70
+ if (!text.includes("\n")) return [];
71
+
72
+ const lines = text.split("\n");
73
+ lines.pop();
74
+ return lines;
75
+ };
76
+
77
+ if (debug) {
78
+ server.config.logger.info(
79
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? (stdin.isRaw ? "yes" : "no") : "n/a"})`,
80
+ { timestamp: true },
81
+ );
82
+ }
23
83
 
24
- // Raw mode delivers individual keystrokes as immediate single-byte
25
- // events instead of waiting for Enter (cooked/line-buffered mode).
26
- // Without it, Ctrl+R (0x12) is never delivered as a discrete byte.
27
- // When stdin is a pipe (CI, spawned process) setRawMode is unavailable
28
- // but data already arrives unbuffered, so the isTTY guard suffices.
29
- const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
30
84
  if (stdin.isTTY) {
31
- stdin.setRawMode(true);
85
+ server.config.logger.info(
86
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
87
+ { timestamp: true },
88
+ );
32
89
  }
33
90
 
34
- const onData = (data: Buffer) => {
35
- if (data.length !== 1) return;
91
+ const onData = (data: string | Buffer) => {
92
+ if (debug) {
93
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
94
+ timestamp: true,
95
+ });
96
+ }
36
97
 
37
- // Ctrl+C (0x03) defensive fallback. This plugin enables raw mode
38
- // before Vite's internal stdin handler is registered (user plugins
39
- // run first), so there is a brief window where Ctrl+C would be
40
- // swallowed. Re-emit SIGINT so the process exits as expected.
41
- if (data[0] === 0x03) {
42
- process.emit("SIGINT", "SIGINT");
98
+ // Only react to the exact Ctrl+R byte when some host terminal or
99
+ // wrapper already delivers it to this process. We intentionally do
100
+ // not enable raw mode here because that can steal Vite shortcuts
101
+ // like "r" / "q" and interfere with terminal-level controls.
102
+ if (readCtrlR(data)) {
103
+ triggerReload("ctrl+r");
43
104
  return;
44
105
  }
45
106
 
46
- // Ctrl+R = 0x12 in raw mode
47
- if (data[0] === 0x12) {
48
- server.hot.send({ type: "full-reload", path: "*" });
49
- server.config.logger.info(" browser reload (ctrl+r)", {
50
- timestamp: true,
51
- });
107
+ for (const command of readSubmittedCommands(data)) {
108
+ if (command === "e") {
109
+ triggerReload("e+enter");
110
+ return;
111
+ }
112
+
113
+ if (command === "\u001br") {
114
+ triggerReload("option+r+enter");
115
+ return;
116
+ }
52
117
  }
53
118
  };
54
119
 
@@ -56,9 +121,6 @@ export function poke(): Plugin {
56
121
 
57
122
  server.httpServer?.on("close", () => {
58
123
  stdin.off("data", onData);
59
- if (stdin.isTTY && previousRawMode !== null) {
60
- stdin.setRawMode(previousRawMode);
61
- }
62
124
  });
63
125
  },
64
126
  };
package/src/vite/rango.ts CHANGED
@@ -26,6 +26,7 @@ import { printBanner, rangoVersion } from "./utils/banner.js";
26
26
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
27
27
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
28
28
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
+ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
29
30
 
30
31
  /**
31
32
  * Vite plugin for @rangojs/router.
@@ -60,7 +61,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
60
61
 
61
62
  // Get package resolution info (workspace vs npm install)
62
63
  const rangoAliases = getPackageAliases();
63
- const excludeDeps = getExcludeDeps();
64
+ const excludeDeps = [
65
+ ...getExcludeDeps(),
66
+ // The public browser entry re-exports the RSDW browser client.
67
+ // Excluding both keeps Vite from freezing the unpatched bundle into
68
+ // .vite/deps before our source transforms run.
69
+ "@vitejs/plugin-rsc/browser",
70
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
71
+ // cjs-to-esm transform can patch the real file.
72
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
73
+ ];
64
74
 
65
75
  // Mutable ref for router path (node preset only).
66
76
  // Set immediately when user-specified, or populated by the auto-discover
@@ -182,6 +192,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
182
192
 
183
193
  plugins.push(createVirtualEntriesPlugin(finalEntries));
184
194
 
195
+ // Dev-only: RSDW client patch for React Performance Tracks
196
+ plugins.push(performanceTracksPlugin());
197
+
185
198
  // Add RSC plugin with cloudflare-specific options
186
199
  // Note: loadModuleDevProxy should NOT be used with childEnvironments
187
200
  // since SSR runs in workerd alongside RSC
@@ -334,6 +347,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
334
347
  // Add virtual entries plugin (RSC entry generated lazily from routerRef)
335
348
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
336
349
 
350
+ // Dev-only: RSDW client patch for React Performance Tracks
351
+ plugins.push(performanceTracksPlugin());
352
+
337
353
  plugins.push(
338
354
  rsc({
339
355
  entries: finalEntries,
@@ -437,7 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
437
453
  createRouterDiscoveryPlugin(discoveryEntryPath, {
438
454
  routerPathRef: discoveryRouterRef,
439
455
  enableBuildPrerender: prerenderEnabled,
440
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
456
+ buildEnv: options?.buildEnv,
457
+ preset,
441
458
  }),
442
459
  );
443
460