@rangojs/router 0.0.0-experimental.8123bb7e → 0.0.0-experimental.82

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 (129) hide show
  1. package/README.md +76 -18
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +829 -380
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +4 -4
  7. package/skills/handler-use/SKILL.md +362 -0
  8. package/skills/hooks/SKILL.md +24 -18
  9. package/skills/intercept/SKILL.md +20 -0
  10. package/skills/layout/SKILL.md +22 -0
  11. package/skills/links/SKILL.md +3 -1
  12. package/skills/middleware/SKILL.md +34 -3
  13. package/skills/migrate-nextjs/SKILL.md +560 -0
  14. package/skills/migrate-react-router/SKILL.md +765 -0
  15. package/skills/parallel/SKILL.md +59 -0
  16. package/skills/prerender/SKILL.md +110 -68
  17. package/skills/rango/SKILL.md +24 -22
  18. package/skills/route/SKILL.md +24 -0
  19. package/skills/router-setup/SKILL.md +35 -0
  20. package/src/__internal.ts +1 -1
  21. package/src/browser/app-version.ts +14 -0
  22. package/src/browser/navigation-bridge.ts +37 -5
  23. package/src/browser/navigation-client.ts +128 -77
  24. package/src/browser/navigation-store.ts +43 -8
  25. package/src/browser/partial-update.ts +41 -7
  26. package/src/browser/prefetch/cache.ts +113 -21
  27. package/src/browser/prefetch/fetch.ts +156 -18
  28. package/src/browser/prefetch/queue.ts +36 -5
  29. package/src/browser/react/Link.tsx +72 -8
  30. package/src/browser/react/NavigationProvider.tsx +14 -3
  31. package/src/browser/react/context.ts +7 -2
  32. package/src/browser/react/use-handle.ts +9 -58
  33. package/src/browser/react/use-navigation.ts +22 -2
  34. package/src/browser/react/use-params.ts +11 -1
  35. package/src/browser/react/use-router.ts +21 -8
  36. package/src/browser/rsc-router.tsx +26 -3
  37. package/src/browser/scroll-restoration.ts +10 -8
  38. package/src/browser/segment-reconciler.ts +36 -14
  39. package/src/browser/server-action-bridge.ts +8 -18
  40. package/src/browser/types.ts +20 -5
  41. package/src/build/generate-manifest.ts +6 -6
  42. package/src/build/generate-route-types.ts +3 -0
  43. package/src/build/route-trie.ts +50 -24
  44. package/src/build/route-types/include-resolution.ts +8 -1
  45. package/src/build/route-types/router-processing.ts +211 -72
  46. package/src/build/route-types/scan-filter.ts +8 -1
  47. package/src/client.tsx +84 -230
  48. package/src/deps/browser.ts +0 -1
  49. package/src/handle.ts +40 -0
  50. package/src/index.rsc.ts +3 -1
  51. package/src/index.ts +46 -6
  52. package/src/prerender/store.ts +5 -4
  53. package/src/prerender.ts +138 -77
  54. package/src/reverse.ts +25 -1
  55. package/src/route-definition/dsl-helpers.ts +194 -32
  56. package/src/route-definition/helpers-types.ts +61 -14
  57. package/src/route-definition/index.ts +3 -0
  58. package/src/route-definition/redirect.ts +9 -1
  59. package/src/route-definition/resolve-handler-use.ts +149 -0
  60. package/src/route-types.ts +18 -0
  61. package/src/router/content-negotiation.ts +100 -1
  62. package/src/router/handler-context.ts +51 -15
  63. package/src/router/intercept-resolution.ts +9 -4
  64. package/src/router/lazy-includes.ts +5 -5
  65. package/src/router/loader-resolution.ts +150 -21
  66. package/src/router/manifest.ts +22 -13
  67. package/src/router/match-api.ts +124 -189
  68. package/src/router/match-middleware/cache-lookup.ts +28 -8
  69. package/src/router/match-middleware/segment-resolution.ts +53 -0
  70. package/src/router/match-result.ts +82 -4
  71. package/src/router/middleware-types.ts +0 -6
  72. package/src/router/middleware.ts +0 -3
  73. package/src/router/navigation-snapshot.ts +182 -0
  74. package/src/router/prerender-match.ts +110 -10
  75. package/src/router/preview-match.ts +30 -102
  76. package/src/router/request-classification.ts +310 -0
  77. package/src/router/route-snapshot.ts +245 -0
  78. package/src/router/router-interfaces.ts +36 -4
  79. package/src/router/router-options.ts +37 -11
  80. package/src/router/segment-resolution/fresh.ts +70 -5
  81. package/src/router/segment-resolution/revalidation.ts +87 -9
  82. package/src/router.ts +53 -5
  83. package/src/rsc/handler.ts +472 -397
  84. package/src/rsc/loader-fetch.ts +18 -3
  85. package/src/rsc/manifest-init.ts +5 -1
  86. package/src/rsc/progressive-enhancement.ts +14 -3
  87. package/src/rsc/rsc-rendering.ts +15 -2
  88. package/src/rsc/server-action.ts +10 -2
  89. package/src/rsc/ssr-setup.ts +2 -2
  90. package/src/rsc/types.ts +6 -4
  91. package/src/segment-content-promise.ts +67 -0
  92. package/src/segment-loader-promise.ts +122 -0
  93. package/src/segment-system.tsx +11 -61
  94. package/src/server/context.ts +65 -5
  95. package/src/server/handle-store.ts +19 -0
  96. package/src/server/loader-registry.ts +9 -8
  97. package/src/server/request-context.ts +132 -13
  98. package/src/ssr/index.tsx +3 -0
  99. package/src/static-handler.ts +18 -6
  100. package/src/types/cache-types.ts +4 -4
  101. package/src/types/handler-context.ts +17 -11
  102. package/src/types/loader-types.ts +32 -5
  103. package/src/types/route-entry.ts +12 -1
  104. package/src/types/segments.ts +1 -1
  105. package/src/urls/include-helper.ts +24 -14
  106. package/src/urls/path-helper-types.ts +39 -6
  107. package/src/urls/path-helper.ts +47 -12
  108. package/src/urls/pattern-types.ts +12 -0
  109. package/src/urls/response-types.ts +16 -6
  110. package/src/use-loader.tsx +77 -5
  111. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  112. package/src/vite/discovery/discover-routers.ts +5 -1
  113. package/src/vite/discovery/prerender-collection.ts +128 -74
  114. package/src/vite/discovery/state.ts +13 -4
  115. package/src/vite/index.ts +4 -0
  116. package/src/vite/plugin-types.ts +60 -5
  117. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  118. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  119. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  120. package/src/vite/plugins/expose-id-utils.ts +12 -0
  121. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  122. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  123. package/src/vite/plugins/performance-tracks.ts +64 -211
  124. package/src/vite/plugins/refresh-cmd.ts +88 -26
  125. package/src/vite/rango.ts +17 -11
  126. package/src/vite/router-discovery.ts +237 -37
  127. package/src/vite/utils/prerender-utils.ts +37 -5
  128. package/src/vite/utils/shared-utils.ts +3 -2
  129. package/src/browser/debug-channel.ts +0 -93
@@ -1,235 +1,88 @@
1
1
  /**
2
- * React Performance Tracks — Vite plugin
2
+ * React Performance Tracks — RSDW client patch
3
3
  *
4
- * Dev-only plugin that enables Chrome DevTools Performance tab integration
5
- * for React Server Components. Creates a bidirectional debug channel per
6
- * RSC request and transports data over Vite's HMR WebSocket.
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
7
  *
8
- * Architecture:
9
- * - Server: renderToReadableStream writes timing data to debugChannel.writable
10
- * - Transport: chunks are base64-encoded and sent via HMR custom events
11
- * - Client: createFromFetch reads from debugChannel.readable
12
- *
13
- * Each request gets a unique debugId (UUID) to correlate the two sides.
14
- *
15
- * Uses globalThis to share state between the Vite plugin (main process)
16
- * and the RSC handler (RSC module graph) — they run in the same Node.js
17
- * process but different module evaluation contexts.
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.
18
13
  */
19
14
 
20
15
  import type { Plugin } from "vite";
16
+ import { readFile } from "node:fs/promises";
21
17
 
22
- export const DEBUG_ID_HEADER = "X-RSC-Debug-Id";
23
- const DEBUG_S2C_EVENT = "rango:perf-s2c";
24
- const DEBUG_C2S_EVENT = "rango:perf-c2s";
18
+ const RSDW_PATCH_RE =
19
+ /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
25
20
 
26
- type DebugPayload =
27
- | { i: string; b: string } // chunk (base64)
28
- | { i: string; d: true }; // done
29
-
30
- interface DebugSession {
31
- cmdController?: ReadableStreamDefaultController<Uint8Array>;
32
- pendingChunks?: Uint8Array[];
33
- ended: boolean;
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
+ }`;
34
29
  }
35
30
 
36
- type DebugChannelRegistry = {
37
- channels: Map<
38
- string,
39
- {
40
- readable: ReadableStream<Uint8Array>;
41
- writable: WritableStream<Uint8Array>;
42
- }
43
- >;
44
- sessions: Map<string, DebugSession>;
45
- };
46
-
47
- const GLOBAL_KEY = "__RANGO_DEBUG_CHANNELS__";
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
+ }
48
39
 
49
- // Use Node.js `Module` built-in as carrier — Vite's RSC module runner
50
- // uses a separate VM context where both `globalThis` and `process` are
51
- // different objects, but built-in module singletons ARE shared.
52
- import { Module } from "node:module";
53
- function getRegistry(): DebugChannelRegistry {
54
- return ((Module as any)[GLOBAL_KEY] ??= {
55
- channels: new Map(),
56
- sessions: new Map(),
57
- });
40
+ return {
41
+ code: code.replace(match[1]!, buildPatchReplacement(match[1]!, match[2]!)),
42
+ debugInfoVar: match[2]!,
43
+ };
58
44
  }
59
45
 
60
- /**
61
- * Create a debug channel for a given request.
62
- * Called by the RSC handler for each request that has a debugId.
63
- * Returns the { readable, writable } pair for renderToReadableStream.
64
- *
65
- * This works across module graphs because the channel is pre-created
66
- * by the Vite plugin and stored on globalThis.
67
- */
68
- export function createServerDebugChannel(debugId: string): {
69
- readable: ReadableStream<Uint8Array>;
70
- writable: WritableStream<Uint8Array>;
71
- } | null {
72
- const registry = getRegistry();
73
- const channel = registry.channels.get(debugId);
74
- if (channel) {
75
- registry.channels.delete(debugId);
76
- console.log("[perf-tracks] debug channel attached for", debugId);
77
- return channel;
78
- }
79
- console.log(
80
- "[perf-tracks] no channel found for",
81
- debugId,
82
- "channels:",
83
- registry.channels.size,
84
- );
85
- return null;
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
+ };
86
69
  }
87
70
 
88
- const bytesToBase64 = (bytes: Uint8Array) =>
89
- Buffer.from(bytes).toString("base64");
90
-
91
- const base64ToBytes = (base64: string) =>
92
- new Uint8Array(Buffer.from(base64, "base64"));
93
-
94
71
  export function performanceTracksPlugin(): Plugin {
95
72
  return {
96
73
  name: "@rangojs/router:performance-tracks",
97
- // Only configureServer hook — naturally dev-only
98
-
99
- configureServer(server) {
100
- console.log("[perf-tracks] plugin loaded, configureServer called");
101
- const hot = server.environments.client.hot;
102
- const registry = getRegistry();
103
- const sessions = registry.sessions;
104
-
105
- const sendChunk = (debugId: string, chunk: Uint8Array) => {
106
- hot.send(DEBUG_S2C_EVENT, {
107
- i: debugId,
108
- b: bytesToBase64(chunk),
109
- } satisfies DebugPayload);
110
- };
111
-
112
- const cleanupIfEnded = (debugId: string, session: DebugSession) => {
113
- if (session.pendingChunks || !session.ended) return;
114
- sessions.delete(debugId);
115
- hot.send(DEBUG_S2C_EVENT, {
116
- i: debugId,
117
- d: true,
118
- } satisfies DebugPayload);
119
- };
120
-
121
- const registerDebugChannel = (debugId: string) => {
122
- let session = sessions.get(debugId);
123
- if (!session) {
124
- session = { pendingChunks: [], ended: false };
125
- sessions.set(debugId, session);
126
- }
127
-
128
- // Readable: receives client-to-server commands via WS
129
- const readable = new ReadableStream<Uint8Array>({
130
- start(controller) {
131
- session!.cmdController = controller;
132
- },
133
- cancel() {
134
- delete session!.cmdController;
135
- },
136
- });
137
-
138
- // Writable: React writes debug data here, we forward to client via WS
139
- const writable = new WritableStream<Uint8Array>({
140
- write(chunk) {
141
- if (session!.pendingChunks) {
142
- session!.pendingChunks.push(chunk);
143
- } else {
144
- sendChunk(debugId, chunk);
145
- }
146
- },
147
- close() {
148
- session!.ended = true;
149
- cleanupIfEnded(debugId, session!);
150
- },
151
- abort() {
152
- session!.ended = true;
153
- cleanupIfEnded(debugId, session!);
154
- },
155
- });
156
-
157
- // Store on globalThis so the RSC handler can retrieve it
158
- registry.channels.set(debugId, { readable, writable });
159
- };
160
-
161
- // Listen for client-to-server debug messages
162
- // Payload shapes: { i, d: true } (done), { i, b } (chunk), { i } (ready)
163
- hot.on(DEBUG_C2S_EVENT, (raw: unknown) => {
164
- const payload = raw as { i: string; b?: string; d?: true };
165
- const session = sessions.get(payload.i);
166
-
167
- if (payload.d) {
168
- if (session?.cmdController) {
169
- try {
170
- session.cmdController.close();
171
- } catch {
172
- // ignore
173
- }
174
- delete session.cmdController;
175
- }
176
- return;
177
- }
178
-
179
- if (payload.b) {
180
- if (session?.cmdController) {
181
- try {
182
- session.cmdController.enqueue(base64ToBytes(payload.b));
183
- } catch {
184
- delete session!.cmdController;
185
- }
186
- }
187
- return;
188
- }
189
-
190
- // Ready signal — flush pending chunks
191
- if (session) {
192
- if (session.pendingChunks) {
193
- for (const chunk of session.pendingChunks) {
194
- sendChunk(payload.i, chunk);
195
- }
196
- delete session.pendingChunks;
197
- }
198
- cleanupIfEnded(payload.i, session);
199
- } else {
200
- sessions.set(payload.i, { ended: false });
201
- }
202
- });
203
-
204
- // Register middleware directly (not as post-hook) so it runs
205
- // BEFORE the RSC handler — the channel must exist before rendering.
206
- server.middlewares.use((req: any, _res: any, next: any) => {
207
- const existingId = req.headers[DEBUG_ID_HEADER.toLowerCase()] as string;
208
- const isHtml = req.headers.accept?.includes("text/html");
209
-
210
- if (!existingId && !isHtml) {
211
- next();
212
- return;
213
- }
214
-
215
- const debugId = existingId || crypto.randomUUID();
216
- if (!existingId) {
217
- const lowerName = DEBUG_ID_HEADER.toLowerCase();
218
- req.headers[lowerName] = debugId;
219
- if (req.rawHeaders) {
220
- req.rawHeaders.push(DEBUG_ID_HEADER, debugId);
221
- }
222
- }
223
74
 
224
- registerDebugChannel(debugId);
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)
225
80
  console.log(
226
- "[perf-tracks] middleware: created channel for",
227
- debugId,
228
- "from",
229
- existingId ? "client header" : "SSR inject",
81
+ "[perf-tracks] patched RSDW client (var:",
82
+ patched.debugInfoVar,
83
+ ")",
230
84
  );
231
- next();
232
- });
85
+ return patched.code;
233
86
  },
234
87
  };
235
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
@@ -55,14 +55,22 @@ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
55
55
  export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
56
56
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
57
57
  const preset = resolvedOptions.preset ?? "node";
58
- console.log("[perf-tracks] rango() called, preset:", preset);
59
58
  const showBanner = resolvedOptions.banner ?? true;
60
59
 
61
60
  const plugins: PluginOption[] = [];
62
61
 
63
62
  // Get package resolution info (workspace vs npm install)
64
63
  const rangoAliases = getPackageAliases();
65
- 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
+ ];
66
74
 
67
75
  // Mutable ref for router path (node preset only).
68
76
  // Set immediately when user-specified, or populated by the auto-discover
@@ -184,6 +192,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
184
192
 
185
193
  plugins.push(createVirtualEntriesPlugin(finalEntries));
186
194
 
195
+ // Dev-only: RSDW client patch for React Performance Tracks
196
+ plugins.push(performanceTracksPlugin());
197
+
187
198
  // Add RSC plugin with cloudflare-specific options
188
199
  // Note: loadModuleDevProxy should NOT be used with childEnvironments
189
200
  // since SSR runs in workerd alongside RSC
@@ -336,14 +347,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
336
347
  // Add virtual entries plugin (RSC entry generated lazily from routerRef)
337
348
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
338
349
 
339
- // Dev-only: React Performance Tracks (debugChannel transport via HMR WS)
340
- // Must be before rsc() so middleware runs before RSC handler.
341
- const perfPlugin = performanceTracksPlugin();
342
- console.log(
343
- "[perf-tracks] rango: plugin created, has configureServer:",
344
- !!perfPlugin.configureServer,
345
- );
346
- plugins.push(perfPlugin);
350
+ // Dev-only: RSDW client patch for React Performance Tracks
351
+ plugins.push(performanceTracksPlugin());
347
352
 
348
353
  plugins.push(
349
354
  rsc({
@@ -448,7 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
448
453
  createRouterDiscoveryPlugin(discoveryEntryPath, {
449
454
  routerPathRef: discoveryRouterRef,
450
455
  enableBuildPrerender: prerenderEnabled,
451
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
456
+ buildEnv: options?.buildEnv,
457
+ preset,
452
458
  }),
453
459
  );
454
460