@rangojs/router 0.0.0-experimental.ae6e7825 → 0.0.0-experimental.b02a2fec

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 (104) hide show
  1. package/README.md +76 -18
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +688 -433
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/package.json +1 -1
  6. package/skills/links/SKILL.md +3 -1
  7. package/skills/middleware/SKILL.md +2 -0
  8. package/skills/prerender/SKILL.md +110 -68
  9. package/skills/router-setup/SKILL.md +35 -0
  10. package/src/__internal.ts +1 -1
  11. package/src/browser/app-version.ts +14 -0
  12. package/src/browser/navigation-bridge.ts +19 -4
  13. package/src/browser/navigation-client.ts +64 -63
  14. package/src/browser/navigation-store.ts +43 -8
  15. package/src/browser/partial-update.ts +27 -5
  16. package/src/browser/prefetch/fetch.ts +8 -2
  17. package/src/browser/react/Link.tsx +44 -8
  18. package/src/browser/react/NavigationProvider.tsx +8 -1
  19. package/src/browser/react/context.ts +7 -2
  20. package/src/browser/react/use-handle.ts +9 -58
  21. package/src/browser/react/use-router.ts +21 -8
  22. package/src/browser/rsc-router.tsx +28 -22
  23. package/src/browser/scroll-restoration.ts +10 -8
  24. package/src/browser/server-action-bridge.ts +8 -18
  25. package/src/browser/types.ts +21 -15
  26. package/src/build/generate-manifest.ts +6 -6
  27. package/src/build/generate-route-types.ts +3 -0
  28. package/src/build/route-types/include-resolution.ts +8 -1
  29. package/src/build/route-types/router-processing.ts +211 -72
  30. package/src/build/route-types/scan-filter.ts +8 -1
  31. package/src/client.tsx +2 -56
  32. package/src/deps/browser.ts +0 -1
  33. package/src/handle.ts +40 -0
  34. package/src/index.rsc.ts +3 -1
  35. package/src/index.ts +12 -0
  36. package/src/prerender/store.ts +5 -4
  37. package/src/prerender.ts +138 -77
  38. package/src/reverse.ts +22 -1
  39. package/src/route-definition/dsl-helpers.ts +42 -19
  40. package/src/route-definition/helpers-types.ts +4 -1
  41. package/src/route-definition/index.ts +3 -0
  42. package/src/route-definition/redirect.ts +9 -1
  43. package/src/route-definition/resolve-handler-use.ts +149 -0
  44. package/src/route-types.ts +11 -0
  45. package/src/router/content-negotiation.ts +100 -1
  46. package/src/router/handler-context.ts +48 -15
  47. package/src/router/intercept-resolution.ts +9 -4
  48. package/src/router/loader-resolution.ts +150 -21
  49. package/src/router/match-api.ts +124 -189
  50. package/src/router/match-middleware/cache-lookup.ts +28 -8
  51. package/src/router/match-middleware/segment-resolution.ts +53 -0
  52. package/src/router/match-result.ts +82 -4
  53. package/src/router/middleware-types.ts +0 -6
  54. package/src/router/middleware.ts +0 -3
  55. package/src/router/navigation-snapshot.ts +182 -0
  56. package/src/router/prerender-match.ts +110 -10
  57. package/src/router/preview-match.ts +30 -102
  58. package/src/router/request-classification.ts +310 -0
  59. package/src/router/route-snapshot.ts +245 -0
  60. package/src/router/router-interfaces.ts +36 -4
  61. package/src/router/router-options.ts +37 -11
  62. package/src/router/segment-resolution/fresh.ts +70 -5
  63. package/src/router/segment-resolution/revalidation.ts +87 -9
  64. package/src/router.ts +53 -5
  65. package/src/rsc/handler.ts +472 -398
  66. package/src/rsc/loader-fetch.ts +18 -3
  67. package/src/rsc/manifest-init.ts +5 -1
  68. package/src/rsc/progressive-enhancement.ts +12 -3
  69. package/src/rsc/rsc-rendering.ts +8 -3
  70. package/src/rsc/server-action.ts +8 -2
  71. package/src/rsc/ssr-setup.ts +2 -2
  72. package/src/rsc/types.ts +6 -7
  73. package/src/server/context.ts +39 -2
  74. package/src/server/handle-store.ts +19 -0
  75. package/src/server/loader-registry.ts +9 -8
  76. package/src/server/request-context.ts +131 -16
  77. package/src/ssr/index.tsx +4 -13
  78. package/src/static-handler.ts +18 -6
  79. package/src/types/cache-types.ts +4 -4
  80. package/src/types/handler-context.ts +17 -11
  81. package/src/types/loader-types.ts +32 -5
  82. package/src/types/route-entry.ts +1 -1
  83. package/src/types/segments.ts +1 -0
  84. package/src/urls/path-helper-types.ts +9 -2
  85. package/src/urls/path-helper.ts +47 -12
  86. package/src/urls/pattern-types.ts +12 -0
  87. package/src/urls/response-types.ts +16 -6
  88. package/src/use-loader.tsx +77 -5
  89. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  90. package/src/vite/discovery/discover-routers.ts +5 -1
  91. package/src/vite/discovery/prerender-collection.ts +128 -74
  92. package/src/vite/discovery/state.ts +13 -4
  93. package/src/vite/index.ts +4 -0
  94. package/src/vite/plugin-types.ts +60 -5
  95. package/src/vite/plugins/expose-id-utils.ts +12 -0
  96. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  97. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  98. package/src/vite/plugins/performance-tracks.ts +57 -280
  99. package/src/vite/plugins/refresh-cmd.ts +88 -26
  100. package/src/vite/rango.ts +17 -11
  101. package/src/vite/router-discovery.ts +178 -37
  102. package/src/vite/utils/prerender-utils.ts +18 -0
  103. package/src/vite/utils/shared-utils.ts +3 -2
  104. package/src/browser/debug-channel.ts +0 -112
@@ -1,104 +1,20 @@
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";
25
-
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;
34
- }
35
-
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__";
48
-
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
- });
58
- }
59
-
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;
86
- }
87
-
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
- // Patch for RSDW client: React's flushComponentPerformance uses splice(0) to
95
- // empty chunk._debugInfo after resolution, then tries to recover it from the
96
- // resolved value. The fallback only works for arrays, async iterables, React
97
- // elements, and lazy types — not plain objects. Since our RscPayload is a
98
- // plain object, _debugInfo is lost and the Server Components track stays empty.
99
- // This patch relaxes the check so _debugInfo is recovered from any object.
100
- //
101
- // Uses regex to be resilient to Vite's dep optimizer reformatting.
102
18
  const RSDW_PATCH_RE =
103
19
  /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
104
20
 
@@ -112,200 +28,61 @@ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.stat
112
28
  }`;
113
29
  }
114
30
 
115
- export function performanceTracksPlugin(): Plugin {
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
+
116
40
  return {
117
- name: "@rangojs/router:performance-tracks",
118
- // configureServer + transform — naturally dev-only
41
+ code: code.replace(match[1]!, buildPatchReplacement(match[1]!, match[2]!)),
42
+ debugInfoVar: match[2]!,
43
+ };
44
+ }
119
45
 
120
- transform(code, id) {
121
- // Only patch RSDW client browser bundle
122
- if (!id.includes("react-server-dom") || !id.includes("client")) return;
123
- const match = code.match(RSDW_PATCH_RE);
124
- if (!match) return;
125
- const patched = code.replace(
126
- match[1]!,
127
- buildPatchReplacement(match[1]!, match[2]!),
128
- );
129
- console.log(
130
- "[perf-tracks] patched RSDW client for plain-object _debugInfo recovery (var:",
131
- match[2],
132
- ")",
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
+ },
133
66
  );
134
- return patched;
135
67
  },
68
+ };
69
+ }
136
70
 
137
- configureServer(server) {
138
- console.log("[perf-tracks] plugin loaded, configureServer called");
139
- const hot = server.environments.client.hot;
140
- const registry = getRegistry();
141
- const sessions = registry.sessions;
142
-
143
- const sendChunk = (debugId: string, chunk: Uint8Array) => {
144
- hot.send(DEBUG_S2C_EVENT, {
145
- i: debugId,
146
- b: bytesToBase64(chunk),
147
- } satisfies DebugPayload);
148
- };
149
-
150
- const cleanupIfEnded = (debugId: string, session: DebugSession) => {
151
- if (session.pendingChunks || !session.ended) return;
152
- sessions.delete(debugId);
153
- hot.send(DEBUG_S2C_EVENT, {
154
- i: debugId,
155
- d: true,
156
- } satisfies DebugPayload);
157
- };
158
-
159
- const registerDebugChannel = (debugId: string) => {
160
- let session = sessions.get(debugId);
161
- if (!session) {
162
- session = { pendingChunks: [], ended: false };
163
- sessions.set(debugId, session);
164
- }
165
-
166
- // Readable: receives client-to-server commands via WS
167
- const readable = new ReadableStream<Uint8Array>({
168
- start(controller) {
169
- session!.cmdController = controller;
170
- },
171
- cancel() {
172
- delete session!.cmdController;
173
- },
174
- });
175
-
176
- let chunkCount = 0;
177
- // Writable: React writes debug data here, we forward to client via WS
178
- const writable = new WritableStream<Uint8Array>({
179
- write(chunk) {
180
- chunkCount++;
181
- if (chunkCount <= 3) {
182
- console.log(
183
- "[perf-tracks] writable: chunk #" + chunkCount,
184
- "size:",
185
- chunk.byteLength,
186
- "for",
187
- debugId.slice(0, 8),
188
- session!.pendingChunks ? "(buffered)" : "(sent)",
189
- );
190
- }
191
- if (session!.pendingChunks) {
192
- session!.pendingChunks.push(chunk);
193
- } else {
194
- sendChunk(debugId, chunk);
195
- }
196
- },
197
- close() {
198
- console.log(
199
- "[perf-tracks] writable: closed after",
200
- chunkCount,
201
- "chunks for",
202
- debugId.slice(0, 8),
203
- );
204
- session!.ended = true;
205
- cleanupIfEnded(debugId, session!);
206
- },
207
- abort() {
208
- session!.ended = true;
209
- cleanupIfEnded(debugId, session!);
210
- },
211
- });
212
-
213
- // Store on globalThis so the RSC handler can retrieve it
214
- registry.channels.set(debugId, { readable, writable });
215
- };
216
-
217
- // Listen for client-to-server debug messages
218
- // Payload shapes: { i, d: true } (done), { i, b } (chunk), { i } (ready)
219
- hot.on(DEBUG_C2S_EVENT, (raw: unknown) => {
220
- const payload = raw as { i: string; b?: string; d?: true };
221
- const session = sessions.get(payload.i);
222
-
223
- if (payload.d) {
224
- if (session?.cmdController) {
225
- try {
226
- session.cmdController.close();
227
- } catch {
228
- // ignore
229
- }
230
- delete session.cmdController;
231
- }
232
- return;
233
- }
234
-
235
- if (payload.b) {
236
- if (session?.cmdController) {
237
- try {
238
- session.cmdController.enqueue(base64ToBytes(payload.b));
239
- } catch {
240
- delete session!.cmdController;
241
- }
242
- }
243
- return;
244
- }
245
-
246
- // Ready signal — flush pending chunks
247
- if (session) {
248
- const pending = session.pendingChunks?.length ?? 0;
249
- const ended = session.ended;
250
- console.log(
251
- "[perf-tracks] ready signal for",
252
- payload.i.slice(0, 8),
253
- "pending:",
254
- pending,
255
- "ended:",
256
- ended,
257
- );
258
- if (session.pendingChunks) {
259
- for (const chunk of session.pendingChunks) {
260
- sendChunk(payload.i, chunk);
261
- }
262
- delete session.pendingChunks;
263
- }
264
- cleanupIfEnded(payload.i, session);
265
- } else {
266
- console.log(
267
- "[perf-tracks] ready signal for",
268
- payload.i.slice(0, 8),
269
- "(no session yet)",
270
- );
271
- sessions.set(payload.i, { ended: false });
272
- }
273
- });
274
-
275
- // Create debug channels only for RSC-handled requests:
276
- // - HTML page requests (SSR)
277
- // - RSC partial requests (client navigation)
278
- // - Requests with existing debug header (client-initiated)
279
- server.middlewares.use((req: any, _res: any, next: any) => {
280
- const url: string = req.url || "";
281
- const existingId = req.headers[DEBUG_ID_HEADER.toLowerCase()] as string;
282
- const isHtml = req.headers.accept?.includes("text/html");
283
- const isRsc = url.includes("_rsc_partial");
284
-
285
- if (!existingId && !isHtml && !isRsc) {
286
- next();
287
- return;
288
- }
289
-
290
- const debugId = existingId || crypto.randomUUID();
291
- if (!existingId) {
292
- const lowerName = DEBUG_ID_HEADER.toLowerCase();
293
- req.headers[lowerName] = debugId;
294
- if (req.rawHeaders) {
295
- req.rawHeaders.push(DEBUG_ID_HEADER, debugId);
296
- }
297
- }
71
+ export function performanceTracksPlugin(): Plugin {
72
+ return {
73
+ name: "@rangojs/router:performance-tracks",
298
74
 
299
- 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)
300
80
  console.log(
301
- "[perf-tracks] middleware: channel for",
302
- debugId,
303
- "url:",
304
- url.slice(0, 80),
305
- existingId ? "(client)" : isHtml ? "(SSR)" : "(RSC partial)",
81
+ "[perf-tracks] patched RSDW client (var:",
82
+ patched.debugInfoVar,
83
+ ")",
306
84
  );
307
- next();
308
- });
85
+ return patched.code;
309
86
  },
310
87
  };
311
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