@rangojs/router 0.0.0-experimental.1b930379 → 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.
- package/AGENTS.md +4 -0
- package/README.md +76 -18
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +558 -319
- package/package.json +16 -15
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +53 -43
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +126 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/route/SKILL.md +31 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +10 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +19 -13
- package/src/browser/navigation-client.ts +115 -58
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +80 -15
- package/src/browser/prefetch/cache.ts +57 -5
- package/src/browser/prefetch/fetch.ts +38 -23
- package/src/browser/prefetch/queue.ts +92 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +53 -9
- package/src/browser/react/NavigationProvider.tsx +40 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +134 -59
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +6 -1
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +36 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +453 -11
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +2 -56
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +8 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +73 -25
- package/src/route-definition/helpers-types.ts +10 -6
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +79 -23
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/loader-resolution.ts +122 -10
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +9 -3
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +88 -16
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +22 -6
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +6 -8
- package/src/router/middleware.ts +4 -6
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +183 -20
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +412 -297
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/types.ts +1 -0
- package/src/router.ts +59 -6
- package/src/rsc/handler.ts +460 -368
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/rsc-rendering.ts +5 -0
- package/src/rsc/server-action.ts +2 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +8 -1
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +140 -14
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +144 -18
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +137 -33
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-entry.ts +8 -1
- package/src/types/segments.ts +2 -0
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +73 -4
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +14 -1
- package/src/vite/discovery/state.ts +13 -6
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +51 -79
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +163 -211
- package/src/vite/router-discovery.ts +153 -42
- package/src/vite/utils/banner.ts +3 -3
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Vite plugin that triggers a full browser reload
|
|
5
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
if (data
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
};
|
|
@@ -135,8 +135,11 @@ export function createVersionPlugin(): Plugin {
|
|
|
135
135
|
let server: any = null;
|
|
136
136
|
const clientModuleSignatures = new Map<string, ClientModuleSignature>();
|
|
137
137
|
|
|
138
|
+
let versionCounter = 0;
|
|
138
139
|
const bumpVersion = (reason: string) => {
|
|
139
|
-
|
|
140
|
+
// Use timestamp + counter to guarantee uniqueness even when multiple
|
|
141
|
+
// bumps happen within the same millisecond (e.g. cascading HMR events).
|
|
142
|
+
currentVersion = Date.now().toString(16) + String(++versionCounter);
|
|
140
143
|
console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
|
|
141
144
|
|
|
142
145
|
const rscEnv = server?.environments?.rsc;
|
|
@@ -211,6 +214,15 @@ export function createVersionPlugin(): Plugin {
|
|
|
211
214
|
|
|
212
215
|
if (!isRscModule) return;
|
|
213
216
|
|
|
217
|
+
// Skip re-bumping when the version virtual module itself is invalidated
|
|
218
|
+
// (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
|
|
219
|
+
if (
|
|
220
|
+
ctx.modules.length === 1 &&
|
|
221
|
+
ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
|
|
222
|
+
) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
if (isCodeModule(ctx.file)) {
|
|
215
227
|
const filePath = normalizeModuleId(ctx.file);
|
|
216
228
|
const previousSignature = clientModuleSignatures.get(filePath);
|
package/src/vite/rango.ts
CHANGED
|
@@ -13,10 +13,7 @@ import {
|
|
|
13
13
|
getExcludeDeps,
|
|
14
14
|
getPackageAliases,
|
|
15
15
|
} from "./utils/package-resolution.js";
|
|
16
|
-
import {
|
|
17
|
-
createScanFilter,
|
|
18
|
-
findRouterFiles,
|
|
19
|
-
} from "../build/generate-route-types.js";
|
|
16
|
+
import { findRouterFiles } from "../build/generate-route-types.js";
|
|
20
17
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
21
18
|
import {
|
|
22
19
|
sharedEsbuildOptions,
|
|
@@ -24,15 +21,12 @@ import {
|
|
|
24
21
|
onwarn,
|
|
25
22
|
getManualChunks,
|
|
26
23
|
} from "./utils/shared-utils.js";
|
|
27
|
-
import type {
|
|
28
|
-
RangoOptions,
|
|
29
|
-
RangoNodeOptions,
|
|
30
|
-
RscPluginOptions,
|
|
31
|
-
} from "./plugin-types.js";
|
|
24
|
+
import type { RangoOptions } from "./plugin-types.js";
|
|
32
25
|
import { printBanner, rangoVersion } from "./utils/banner.js";
|
|
33
26
|
import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
|
|
34
27
|
import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
|
|
35
28
|
import { createRouterDiscoveryPlugin } from "./router-discovery.js";
|
|
29
|
+
import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* Vite plugin for @rangojs/router.
|
|
@@ -43,7 +37,7 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
|
|
|
43
37
|
* @example Node.js (default)
|
|
44
38
|
* ```ts
|
|
45
39
|
* export default defineConfig({
|
|
46
|
-
* plugins: [react(), rango(
|
|
40
|
+
* plugins: [react(), rango()],
|
|
47
41
|
* });
|
|
48
42
|
* ```
|
|
49
43
|
*
|
|
@@ -67,10 +61,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
67
61
|
|
|
68
62
|
// Get package resolution info (workspace vs npm install)
|
|
69
63
|
const rangoAliases = getPackageAliases();
|
|
70
|
-
const excludeDeps =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
+
];
|
|
74
74
|
|
|
75
75
|
// Mutable ref for router path (node preset only).
|
|
76
76
|
// Set immediately when user-specified, or populated by the auto-discover
|
|
@@ -192,6 +192,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
192
192
|
|
|
193
193
|
plugins.push(createVirtualEntriesPlugin(finalEntries));
|
|
194
194
|
|
|
195
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
196
|
+
plugins.push(performanceTracksPlugin());
|
|
197
|
+
|
|
195
198
|
// Add RSC plugin with cloudflare-specific options
|
|
196
199
|
// Note: loadModuleDevProxy should NOT be used with childEnvironments
|
|
197
200
|
// since SSR runs in workerd alongside RSC
|
|
@@ -207,198 +210,151 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
207
210
|
// packages that are also imported directly by client components.
|
|
208
211
|
plugins.push(clientRefDedup());
|
|
209
212
|
} else {
|
|
210
|
-
//
|
|
211
|
-
|
|
213
|
+
// Auto-discover router using Vite's resolved root (not process.cwd())
|
|
214
|
+
plugins.push({
|
|
215
|
+
name: "@rangojs/router:auto-discover",
|
|
216
|
+
config(userConfig) {
|
|
217
|
+
if (routerRef.path) return;
|
|
218
|
+
const root = userConfig.root
|
|
219
|
+
? resolve(process.cwd(), userConfig.root)
|
|
220
|
+
: process.cwd();
|
|
221
|
+
const candidates = findRouterFiles(root);
|
|
222
|
+
if (candidates.length === 1) {
|
|
223
|
+
const abs = candidates[0];
|
|
224
|
+
routerRef.path = (
|
|
225
|
+
abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
|
|
226
|
+
).replaceAll("\\", "/");
|
|
227
|
+
} else if (candidates.length > 1) {
|
|
228
|
+
const list = candidates
|
|
229
|
+
.map(
|
|
230
|
+
(f) =>
|
|
231
|
+
" - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
|
|
232
|
+
)
|
|
233
|
+
.join("\n");
|
|
234
|
+
throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
|
|
235
|
+
}
|
|
236
|
+
// 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
|
|
237
|
+
},
|
|
238
|
+
});
|
|
212
239
|
|
|
213
|
-
|
|
240
|
+
// Always use virtual entries for client, ssr, and rsc
|
|
241
|
+
const finalEntries = {
|
|
242
|
+
client: VIRTUAL_IDS.browser,
|
|
243
|
+
ssr: VIRTUAL_IDS.ssr,
|
|
244
|
+
rsc: VIRTUAL_IDS.rsc,
|
|
245
|
+
};
|
|
214
246
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
`[rsc-router] Multiple routers found. Specify \`router\` to choose one:\n${list}`,
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
// 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const rscOption = nodeOptions.rsc ?? true;
|
|
251
|
-
|
|
252
|
-
// Add RSC plugin by default (can be disabled with rsc: false)
|
|
253
|
-
if (rscOption !== false) {
|
|
254
|
-
// Dynamically import @vitejs/plugin-rsc
|
|
255
|
-
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
256
|
-
|
|
257
|
-
// Resolve entry paths: use explicit config or virtual modules
|
|
258
|
-
const userEntries =
|
|
259
|
-
typeof rscOption === "boolean" ? {} : rscOption.entries || {};
|
|
260
|
-
const finalEntries = {
|
|
261
|
-
client: userEntries.client ?? VIRTUAL_IDS.browser,
|
|
262
|
-
ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
|
|
263
|
-
rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Track RSC entry for version injection (only if custom entry provided)
|
|
267
|
-
rscEntryPath = userEntries.rsc ?? null;
|
|
268
|
-
|
|
269
|
-
// Create wrapper plugin that checks for duplicates
|
|
270
|
-
let hasWarnedDuplicate = false;
|
|
271
|
-
|
|
272
|
-
plugins.push({
|
|
273
|
-
name: "@rangojs/router:rsc-integration",
|
|
274
|
-
enforce: "pre",
|
|
275
|
-
|
|
276
|
-
config() {
|
|
277
|
-
// Configure environments for RSC
|
|
278
|
-
// When using virtual entries, we need to explicitly configure optimizeDeps
|
|
279
|
-
// so Vite pre-bundles React before processing the virtual modules.
|
|
280
|
-
// Without this, the dep optimizer may run multiple times with different hashes,
|
|
281
|
-
// causing React instance mismatches.
|
|
282
|
-
const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
|
|
283
|
-
const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
|
|
284
|
-
const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
// Exclude rsc-router modules from optimization to prevent module duplication
|
|
288
|
-
// This ensures the same Context instance is used by both browser entry and RSC proxy modules
|
|
289
|
-
optimizeDeps: {
|
|
290
|
-
exclude: excludeDeps,
|
|
291
|
-
esbuildOptions: sharedEsbuildOptions,
|
|
292
|
-
},
|
|
293
|
-
build: {
|
|
294
|
-
rollupOptions: { onwarn },
|
|
295
|
-
},
|
|
296
|
-
resolve: {
|
|
297
|
-
alias: rangoAliases,
|
|
298
|
-
},
|
|
299
|
-
environments: {
|
|
300
|
-
client: {
|
|
301
|
-
build: {
|
|
302
|
-
rollupOptions: {
|
|
303
|
-
output: {
|
|
304
|
-
manualChunks: getManualChunks,
|
|
305
|
-
},
|
|
247
|
+
// Dynamically import @vitejs/plugin-rsc
|
|
248
|
+
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
249
|
+
|
|
250
|
+
let hasWarnedDuplicate = false;
|
|
251
|
+
|
|
252
|
+
plugins.push({
|
|
253
|
+
name: "@rangojs/router:rsc-integration",
|
|
254
|
+
enforce: "pre",
|
|
255
|
+
|
|
256
|
+
config() {
|
|
257
|
+
return {
|
|
258
|
+
optimizeDeps: {
|
|
259
|
+
exclude: excludeDeps,
|
|
260
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
261
|
+
},
|
|
262
|
+
build: {
|
|
263
|
+
rollupOptions: { onwarn },
|
|
264
|
+
},
|
|
265
|
+
resolve: {
|
|
266
|
+
alias: rangoAliases,
|
|
267
|
+
},
|
|
268
|
+
environments: {
|
|
269
|
+
client: {
|
|
270
|
+
build: {
|
|
271
|
+
rollupOptions: {
|
|
272
|
+
output: {
|
|
273
|
+
manualChunks: getManualChunks,
|
|
306
274
|
},
|
|
307
275
|
},
|
|
308
|
-
// Always exclude rsc-router modules, conditionally add virtual entry
|
|
309
|
-
optimizeDeps: {
|
|
310
|
-
// Pre-bundle React and rsc-html-stream to prevent late discovery
|
|
311
|
-
// triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
|
|
312
|
-
include: [
|
|
313
|
-
"react",
|
|
314
|
-
"react-dom",
|
|
315
|
-
"react/jsx-runtime",
|
|
316
|
-
"react/jsx-dev-runtime",
|
|
317
|
-
"rsc-html-stream/client",
|
|
318
|
-
],
|
|
319
|
-
exclude: excludeDeps,
|
|
320
|
-
esbuildOptions: sharedEsbuildOptions,
|
|
321
|
-
...(useVirtualClient && {
|
|
322
|
-
// Tell Vite to scan the virtual entry for dependencies
|
|
323
|
-
entries: [VIRTUAL_IDS.browser],
|
|
324
|
-
}),
|
|
325
|
-
},
|
|
326
276
|
},
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
277
|
+
optimizeDeps: {
|
|
278
|
+
include: [
|
|
279
|
+
"react",
|
|
280
|
+
"react-dom",
|
|
281
|
+
"react/jsx-runtime",
|
|
282
|
+
"react/jsx-dev-runtime",
|
|
283
|
+
"rsc-html-stream/client",
|
|
284
|
+
],
|
|
285
|
+
exclude: excludeDeps,
|
|
286
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
287
|
+
entries: [VIRTUAL_IDS.browser],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
ssr: {
|
|
291
|
+
optimizeDeps: {
|
|
292
|
+
entries: [VIRTUAL_IDS.ssr],
|
|
293
|
+
include: [
|
|
294
|
+
"react",
|
|
295
|
+
"react-dom",
|
|
296
|
+
"react-dom/server.edge",
|
|
297
|
+
"react-dom/static.edge",
|
|
298
|
+
"react/jsx-runtime",
|
|
299
|
+
"react/jsx-dev-runtime",
|
|
300
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
|
|
301
|
+
],
|
|
302
|
+
exclude: excludeDeps,
|
|
303
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
rsc: {
|
|
307
|
+
optimizeDeps: {
|
|
308
|
+
entries: [VIRTUAL_IDS.rsc],
|
|
309
|
+
include: [
|
|
310
|
+
"react",
|
|
311
|
+
"react/jsx-runtime",
|
|
312
|
+
"react/jsx-dev-runtime",
|
|
313
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
|
|
314
|
+
],
|
|
315
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
316
|
+
},
|
|
361
317
|
},
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
: "
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
)
|
|
401
|
-
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
configResolved(config) {
|
|
323
|
+
if (showBanner) {
|
|
324
|
+
const mode =
|
|
325
|
+
config.command === "serve"
|
|
326
|
+
? process.argv.includes("preview")
|
|
327
|
+
? "preview"
|
|
328
|
+
: "dev"
|
|
329
|
+
: "build";
|
|
330
|
+
printBanner(mode, "node", rangoVersion);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const rscMinimalCount = config.plugins.filter(
|
|
334
|
+
(p) => p.name === "rsc:minimal",
|
|
335
|
+
).length;
|
|
336
|
+
|
|
337
|
+
if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
|
|
338
|
+
hasWarnedDuplicate = true;
|
|
339
|
+
console.warn(
|
|
340
|
+
"[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
|
|
341
|
+
"Remove rsc() from your vite config — rango() includes it automatically.",
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Add virtual entries plugin (RSC entry generated lazily from routerRef)
|
|
348
|
+
plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
|
|
349
|
+
|
|
350
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
351
|
+
plugins.push(performanceTracksPlugin());
|
|
352
|
+
|
|
353
|
+
plugins.push(
|
|
354
|
+
rsc({
|
|
355
|
+
entries: finalEntries,
|
|
356
|
+
}) as PluginOption,
|
|
357
|
+
);
|
|
402
358
|
|
|
403
359
|
// Deduplicate client references from third-party packages in dev mode.
|
|
404
360
|
// Prevents module duplication when server components import "use client"
|
|
@@ -479,14 +435,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
479
435
|
// Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
|
|
480
436
|
const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
|
|
481
437
|
|
|
482
|
-
// Version injector: auto-injects VERSION and routes-manifest into
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
rscEntryPath ?? (preset === "cloudflare" ? undefined : null);
|
|
488
|
-
if (injectorEntryPath !== null) {
|
|
489
|
-
plugins.push(createVersionInjectorPlugin(injectorEntryPath));
|
|
438
|
+
// Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
|
|
439
|
+
// For cloudflare preset, the entry is resolved lazily in configResolved.
|
|
440
|
+
// For node preset, the virtual entry already includes these imports.
|
|
441
|
+
if (preset === "cloudflare") {
|
|
442
|
+
plugins.push(createVersionInjectorPlugin(undefined));
|
|
490
443
|
}
|
|
491
444
|
|
|
492
445
|
// Transform CJS vendor files to ESM for browser compatibility
|
|
@@ -500,9 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
500
453
|
createRouterDiscoveryPlugin(discoveryEntryPath, {
|
|
501
454
|
routerPathRef: discoveryRouterRef,
|
|
502
455
|
enableBuildPrerender: prerenderEnabled,
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
exclude: resolvedOptions.exclude,
|
|
456
|
+
buildEnv: options?.buildEnv,
|
|
457
|
+
preset,
|
|
506
458
|
}),
|
|
507
459
|
);
|
|
508
460
|
|