@rangojs/router 0.0.0-experimental.138 → 0.0.0-experimental.139
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/README.md +273 -924
- package/dist/bin/rango.js +28 -14
- package/dist/vite/index.js +472 -77
- package/package.json +33 -19
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/host-router/SKILL.md +45 -3
- package/skills/layout/SKILL.md +26 -9
- package/skills/middleware/SKILL.md +6 -2
- package/skills/observability/SKILL.md +23 -1
- package/skills/parallel/SKILL.md +11 -3
- package/skills/rango/SKILL.md +71 -23
- package/skills/route/SKILL.md +19 -3
- package/skills/router-setup/SKILL.md +1 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/vercel/SKILL.md +128 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +77 -27
- package/src/cache/index.ts +12 -0
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +948 -0
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/state.ts +1 -1
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +84 -2
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/virtual-entries.ts +72 -0
- package/src/vite/rango.ts +128 -21
- package/src/vite/utils/shared-utils.ts +52 -2
|
@@ -125,6 +125,78 @@ export default function handler(request, env) {
|
|
|
125
125
|
`.trim();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
export function getVirtualEntryRSCHost(hostEntryPath: string): string {
|
|
129
|
+
return `
|
|
130
|
+
import * as __hostEntry from "${hostEntryPath}";
|
|
131
|
+
import { isNoRouteMatchError } from "@rangojs/router/host";
|
|
132
|
+
|
|
133
|
+
// Register every sub-app's fetchable loaders + route manifests at startup, same
|
|
134
|
+
// as the single-router entry. Discovery's host fallback populates these for all
|
|
135
|
+
// mounted sub-apps, so the aggregate manifests cover the whole host tree.
|
|
136
|
+
import "virtual:rsc-router/loader-manifest";
|
|
137
|
+
import "virtual:rsc-router/routes-manifest";
|
|
138
|
+
|
|
139
|
+
// The host entry module must export the HostRouter instance (createHostRouter()),
|
|
140
|
+
// as a default export or a named \`hostRouter\`/\`router\` export. A Cloudflare-style
|
|
141
|
+
// \`export default { fetch }\` object is not a HostRouter and is rejected (on first
|
|
142
|
+
// request; see the lazy resolution below).
|
|
143
|
+
// We require BOTH .match() and .host(): a regular createRouter() also exposes
|
|
144
|
+
// .match(), so matching on .match() alone would accept an ordinary router and then
|
|
145
|
+
// return its MatchResult (not a Response) at runtime. .host() is unique to a
|
|
146
|
+
// HostRouter, so it disambiguates a mistaken \`hostRouter\` path.
|
|
147
|
+
// Exports are read dynamically (m[name]) so Rollup does not emit IMPORT_IS_UNDEFINED
|
|
148
|
+
// warnings for the named exports a default-only host module legitimately omits.
|
|
149
|
+
const __resolveHostRouter = (m) => {
|
|
150
|
+
for (const name of ["default", "hostRouter", "router"]) {
|
|
151
|
+
const candidate = m[name];
|
|
152
|
+
if (
|
|
153
|
+
candidate &&
|
|
154
|
+
typeof candidate.match === "function" &&
|
|
155
|
+
typeof candidate.host === "function"
|
|
156
|
+
)
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Resolve + validate the HostRouter lazily on first request, mirroring the
|
|
163
|
+
// single-router entry's \`_handler\`. During HMR the host module can re-evaluate
|
|
164
|
+
// before its createHostRouter() export has resolved; validating at module-
|
|
165
|
+
// evaluation time would then throw a spurious "must export a HostRouter" error
|
|
166
|
+
// overlay for a perfectly valid app. Resolving on first request lets the live
|
|
167
|
+
// binding settle first.
|
|
168
|
+
let _hostRouter;
|
|
169
|
+
|
|
170
|
+
// input = { env, ctx } from the launcher / node server. The host router threads
|
|
171
|
+
// it unchanged to each matched sub-app's handler and cache factory.
|
|
172
|
+
// On node/vercel rango owns this entry, so there is no user worker to translate
|
|
173
|
+
// an unmatched host into a response: catch NoRouteMatchError and return 404
|
|
174
|
+
// (parity with the documented Cloudflare catch). Other errors propagate.
|
|
175
|
+
export default async function handler(request, input) {
|
|
176
|
+
if (!_hostRouter) {
|
|
177
|
+
_hostRouter = __resolveHostRouter(__hostEntry);
|
|
178
|
+
if (!_hostRouter) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"[rango] The host entry (${hostEntryPath}) must export a HostRouter instance (createHostRouter()) for the node/vercel preset: a default export, or a named 'hostRouter'/'router' export. An ordinary createRouter() is not a host router (it has no .host()), and a Cloudflare-style 'export default { fetch }' object is not supported on this preset."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return await _hostRouter.match(request, input);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
// isNoRouteMatchError also matches by name: a workspace with a duplicated
|
|
188
|
+
// @rangojs/router copy can throw a NoRouteMatchError whose prototype differs
|
|
189
|
+
// from this module's import, so a bare instanceof would turn an
|
|
190
|
+
// unmatched-host 404 into a 500.
|
|
191
|
+
if (isNoRouteMatchError(err)) {
|
|
192
|
+
return new Response("Not Found", { status: 404 });
|
|
193
|
+
}
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`.trim();
|
|
198
|
+
}
|
|
199
|
+
|
|
128
200
|
export const VIRTUAL_IDS = {
|
|
129
201
|
browser: "virtual:rsc-router/entry.browser.js",
|
|
130
202
|
ssr: "virtual:rsc-router/entry.ssr.js",
|
package/src/vite/rango.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PluginOption } from "vite";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { exposeActionId } from "./plugins/expose-action-id.js";
|
|
5
5
|
import {
|
|
@@ -15,11 +15,15 @@ import {
|
|
|
15
15
|
getPublishedPackageName,
|
|
16
16
|
getVendorAliases,
|
|
17
17
|
} from "./utils/package-resolution.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
findRouterFiles,
|
|
20
|
+
findHostRouterFiles,
|
|
21
|
+
} from "../build/generate-route-types.js";
|
|
19
22
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
20
23
|
import {
|
|
21
24
|
sharedRolldownOptions,
|
|
22
25
|
createVirtualEntriesPlugin,
|
|
26
|
+
normalizeHostRouterEntry,
|
|
23
27
|
onwarn,
|
|
24
28
|
getManualChunks,
|
|
25
29
|
} from "./utils/shared-utils.js";
|
|
@@ -27,7 +31,12 @@ import {
|
|
|
27
31
|
resolveClientChunks,
|
|
28
32
|
type ClientChunkContext,
|
|
29
33
|
} from "./utils/client-chunks.js";
|
|
30
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
RangoOptions,
|
|
36
|
+
RangoNodeOptions,
|
|
37
|
+
RangoVercelOptions,
|
|
38
|
+
} from "./plugin-types.js";
|
|
39
|
+
import { createVercelOutputPlugin } from "./plugins/vercel-output.js";
|
|
31
40
|
import { printBanner, rangoVersion } from "./utils/banner.js";
|
|
32
41
|
import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
|
|
33
42
|
import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
|
|
@@ -110,10 +119,20 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
110
119
|
const pkg = getPublishedPackageName();
|
|
111
120
|
const nested = (spec: string) => `${pkg} > ${spec}`;
|
|
112
121
|
|
|
113
|
-
// Mutable ref for
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
122
|
+
// Mutable ref for the served entry path (node/vercel presets only). Populated
|
|
123
|
+
// by the auto-discover config() hook using Vite's resolved root. `kind` selects
|
|
124
|
+
// the RSC entry template: "router" wraps a single createRouter() app in
|
|
125
|
+
// createRSCHandler; "host" wraps a createHostRouter() instance and serves it
|
|
126
|
+
// via hostRouter.match().
|
|
127
|
+
const routerRef: { path: string | undefined; kind: "router" | "host" } = {
|
|
128
|
+
path: undefined,
|
|
129
|
+
kind: "router",
|
|
130
|
+
};
|
|
131
|
+
// Explicit host-router entry (node/vercel `hostRouter` option), root-relative.
|
|
132
|
+
const explicitHostRouter =
|
|
133
|
+
preset !== "cloudflare"
|
|
134
|
+
? (resolvedOptions as RangoNodeOptions | RangoVercelOptions).hostRouter
|
|
135
|
+
: undefined;
|
|
117
136
|
|
|
118
137
|
// Build-time prerendering is enabled for both presets.
|
|
119
138
|
// Collection runs in-process via the RSC dev environment runner during discoverRouters().
|
|
@@ -238,20 +257,65 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
238
257
|
const root = userConfig.root
|
|
239
258
|
? resolve(process.cwd(), userConfig.root)
|
|
240
259
|
: process.cwd();
|
|
260
|
+
const toRootRelative = (abs: string) =>
|
|
261
|
+
(abs.startsWith(root)
|
|
262
|
+
? "./" + abs.slice(root.length + 1)
|
|
263
|
+
: abs
|
|
264
|
+
).replaceAll("\\", "/");
|
|
265
|
+
const bulletList = (files: string[]) =>
|
|
266
|
+
files.map((f) => " - " + toRootRelative(f)).join("\n");
|
|
267
|
+
|
|
268
|
+
// 1. Explicit host entry wins: serve the createHostRouter() instance.
|
|
269
|
+
if (explicitHostRouter) {
|
|
270
|
+
routerRef.path = normalizeHostRouterEntry(
|
|
271
|
+
explicitHostRouter,
|
|
272
|
+
root,
|
|
273
|
+
existsSync,
|
|
274
|
+
);
|
|
275
|
+
routerRef.kind = "host";
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 2. A createHostRouter() file means this is a multi-app host deploy:
|
|
280
|
+
// serve it via the host entry. Checked BEFORE single-router discovery,
|
|
281
|
+
// because a host app may compose just one createRouter() sub-app plus
|
|
282
|
+
// host middleware / domain rules / inline .map() routes -- and a lone
|
|
283
|
+
// sub-app must not shadow the host entry (it would bypass
|
|
284
|
+
// hostRouter.match()).
|
|
285
|
+
const hostCandidates = findHostRouterFiles(root);
|
|
286
|
+
if (hostCandidates.length === 1) {
|
|
287
|
+
const hostPath = toRootRelative(hostCandidates[0]);
|
|
288
|
+
// Auto-detection preempts single-router discovery, so name the winning
|
|
289
|
+
// file: a stale prototype still containing `createHostRouter(` would
|
|
290
|
+
// otherwise silently become the served entry in place of the app.
|
|
291
|
+
// eslint-disable-next-line no-console
|
|
292
|
+
console.info(
|
|
293
|
+
`[rango] Serving host router entry ${hostPath} (auto-detected). ` +
|
|
294
|
+
`Set the \`hostRouter\` option to override.`,
|
|
295
|
+
);
|
|
296
|
+
routerRef.path = hostPath;
|
|
297
|
+
routerRef.kind = "host";
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (hostCandidates.length > 1) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`[rango] Multiple host routers found:\n${bulletList(hostCandidates)}\n\n` +
|
|
303
|
+
`Set the \`hostRouter\` option to the entry to serve, e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" }).`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 3. No host entry: single createRouter() app.
|
|
241
308
|
const candidates = findRouterFiles(root);
|
|
242
309
|
if (candidates.length === 1) {
|
|
243
|
-
|
|
244
|
-
routerRef.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
(
|
|
251
|
-
|
|
252
|
-
)
|
|
253
|
-
.join("\n");
|
|
254
|
-
throw new Error(`[rango] Multiple routers found:\n${list}`);
|
|
310
|
+
routerRef.path = toRootRelative(candidates[0]);
|
|
311
|
+
routerRef.kind = "router";
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (candidates.length > 1) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`[rango] Multiple routers found:\n${bulletList(candidates)}\n\n` +
|
|
317
|
+
`If this is a multi-app host router, export a createHostRouter() instance and set the \`hostRouter\` option (e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" })), or use preset: "cloudflare" where you own the worker entry.`,
|
|
318
|
+
);
|
|
255
319
|
}
|
|
256
320
|
},
|
|
257
321
|
});
|
|
@@ -270,8 +334,34 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
270
334
|
name: "@rangojs/router:rsc-integration",
|
|
271
335
|
enforce: "pre",
|
|
272
336
|
|
|
273
|
-
config() {
|
|
337
|
+
config(_userConfig, configEnv) {
|
|
338
|
+
// Fold NODE_ENV for the vercel preset's build. The cloudflare plugin
|
|
339
|
+
// does this automatically and node apps do it themselves; vercel has no
|
|
340
|
+
// platform plugin, so without this React's CJS dev branch survives and
|
|
341
|
+
// doubles the SSR/RSC bundle (Bundle Hygiene rule #2). Only the exact
|
|
342
|
+
// `process.env.NODE_ENV` token is replaced.
|
|
343
|
+
const vercelDefine =
|
|
344
|
+
preset === "vercel" && configEnv.command === "build"
|
|
345
|
+
? { "process.env.NODE_ENV": JSON.stringify("production") }
|
|
346
|
+
: undefined;
|
|
347
|
+
// The vercel preset's deployed function has no node_modules, so the
|
|
348
|
+
// server bundles must be fully self-contained. Bundle every dependency
|
|
349
|
+
// into the rsc + ssr builds instead of externalizing them (the node
|
|
350
|
+
// default, which only works because `vite preview` runs where
|
|
351
|
+
// node_modules exists). node: builtins stay external automatically.
|
|
352
|
+
//
|
|
353
|
+
// BUILD ONLY. In `vite dev` this must NOT apply: noExternal forces every
|
|
354
|
+
// server dependency through the RSC/SSR dev module runners, which cannot
|
|
355
|
+
// load many CJS packages (pg, mysql2, most SDKs, @vercel/functions) and
|
|
356
|
+
// crashes the dev server -- while the same app runs fine under
|
|
357
|
+
// preset: "node" and in the production build. Gating on `build` restores
|
|
358
|
+
// node-preset dev semantics (deps externalized to Node's require).
|
|
359
|
+
const vercelServerEnv =
|
|
360
|
+
preset === "vercel" && configEnv.command === "build"
|
|
361
|
+
? { resolve: { noExternal: true as const } }
|
|
362
|
+
: undefined;
|
|
274
363
|
return {
|
|
364
|
+
...(vercelDefine ? { define: vercelDefine } : {}),
|
|
275
365
|
optimizeDeps: {
|
|
276
366
|
exclude: excludeDeps,
|
|
277
367
|
rolldownOptions: sharedRolldownOptions,
|
|
@@ -314,6 +404,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
314
404
|
},
|
|
315
405
|
},
|
|
316
406
|
ssr: {
|
|
407
|
+
...(vercelServerEnv ?? {}),
|
|
317
408
|
optimizeDeps: {
|
|
318
409
|
entries: [VIRTUAL_IDS.ssr],
|
|
319
410
|
include: [
|
|
@@ -332,6 +423,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
332
423
|
},
|
|
333
424
|
},
|
|
334
425
|
rsc: {
|
|
426
|
+
...(vercelServerEnv ?? {}),
|
|
335
427
|
optimizeDeps: {
|
|
336
428
|
entries: [VIRTUAL_IDS.rsc],
|
|
337
429
|
include: [
|
|
@@ -363,7 +455,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
363
455
|
? "preview"
|
|
364
456
|
: "dev"
|
|
365
457
|
: "build";
|
|
366
|
-
printBanner(
|
|
458
|
+
printBanner(
|
|
459
|
+
mode,
|
|
460
|
+
preset === "vercel" ? "vercel" : "node",
|
|
461
|
+
rangoVersion,
|
|
462
|
+
);
|
|
367
463
|
}
|
|
368
464
|
|
|
369
465
|
const rscMinimalCount = config.plugins.filter(
|
|
@@ -443,6 +539,17 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
443
539
|
}),
|
|
444
540
|
);
|
|
445
541
|
|
|
542
|
+
// Vercel preset: assemble .vercel/output from dist/ after the build. Pushed
|
|
543
|
+
// last so its buildApp (order "post") hook runs after the discovery plugin's
|
|
544
|
+
// rsc-env postprocess. buildApp fires once after the whole multi-environment
|
|
545
|
+
// build, so dist/ is complete (closeBundle is unusable here -- it fires per
|
|
546
|
+
// environment, twice for ssr; see the plugin's own note).
|
|
547
|
+
if (preset === "vercel") {
|
|
548
|
+
plugins.push(
|
|
549
|
+
createVercelOutputPlugin(resolvedOptions as RangoVercelOptions),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
446
553
|
debugConfig?.(
|
|
447
554
|
"rango(%s) setup done: %d plugin(s) (%sms)",
|
|
448
555
|
preset,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
2
|
import * as Vite from "vite";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
4
|
import { getPublishedPackageName } from "./package-resolution.js";
|
|
4
5
|
import { performanceTracksOptimizeDepsPlugin } from "../plugins/performance-tracks.js";
|
|
5
6
|
import {
|
|
6
7
|
VIRTUAL_ENTRY_BROWSER,
|
|
7
8
|
VIRTUAL_ENTRY_SSR,
|
|
8
9
|
getVirtualEntryRSC,
|
|
10
|
+
getVirtualEntryRSCHost,
|
|
9
11
|
getVirtualVersionContent,
|
|
10
12
|
VIRTUAL_IDS,
|
|
11
13
|
} from "../plugins/virtual-entries.js";
|
|
@@ -54,13 +56,59 @@ export const sharedRolldownOptions: {
|
|
|
54
56
|
plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()],
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Normalize an explicit `hostRouter` option into the specifier emitted as the
|
|
61
|
+
* host entry's `import ... from "<path>"`, and — for the forms that resolve
|
|
62
|
+
* unambiguously against the project root — verify the file exists (failing with
|
|
63
|
+
* a rango message instead of a downstream bundler "failed to resolve").
|
|
64
|
+
*
|
|
65
|
+
* Only a BARE specifier ("src/worker.rsc.tsx") that exists under the root is
|
|
66
|
+
* rewritten (to "./src/...") so it is not read as a package. A bare specifier
|
|
67
|
+
* that does NOT exist under the root is passed through VERBATIM: it may be a
|
|
68
|
+
* Vite alias ("@/worker.rsc.tsx"), an imports-map entry ("#app/worker"), or a
|
|
69
|
+
* workspace package the bundler resolves — rewriting or rejecting those was a
|
|
70
|
+
* regression (aliases worked before normalization existed). "./"/"../", a
|
|
71
|
+
* leading-slash (Vite root-relative), and a filesystem-absolute path are
|
|
72
|
+
* emitted unchanged — Vite resolves each. A leading-slash path and a
|
|
73
|
+
* filesystem-absolute path are indistinguishable on POSIX, so both are treated
|
|
74
|
+
* as "rooted": passed through WITHOUT an existence check (guessing between the
|
|
75
|
+
* two would wrongly reject a valid file — this was a regression the first time
|
|
76
|
+
* the check was added). Only the explicitly-relative forms, which resolve
|
|
77
|
+
* unambiguously against the root, fail fast with a rango message.
|
|
78
|
+
*
|
|
79
|
+
* `exists` is injected so the logic is unit-testable without a real filesystem.
|
|
80
|
+
*/
|
|
81
|
+
export function normalizeHostRouterEntry(
|
|
82
|
+
rawInput: string,
|
|
83
|
+
root: string,
|
|
84
|
+
exists: (absPath: string) => boolean,
|
|
85
|
+
): string {
|
|
86
|
+
const raw = rawInput.replaceAll("\\", "/");
|
|
87
|
+
const isRelative = raw.startsWith("./") || raw.startsWith("../");
|
|
88
|
+
const isRooted = raw.startsWith("/") || isAbsolute(rawInput);
|
|
89
|
+
if (isRooted) return raw;
|
|
90
|
+
if (isRelative) {
|
|
91
|
+
if (!exists(resolve(root, raw))) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`[rango] hostRouter entry not found: "${rawInput}" (resolved under ` +
|
|
94
|
+
`${root}). Point it at your createHostRouter() module, e.g. ` +
|
|
95
|
+
`rango({ hostRouter: "./src/worker.rsc.tsx" }).`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return raw;
|
|
99
|
+
}
|
|
100
|
+
// Bare specifier: an existing root-relative file gets the explicit "./";
|
|
101
|
+
// anything else (alias, imports-map, package) is the bundler's to resolve.
|
|
102
|
+
return exists(resolve(root, raw)) ? "./" + raw : raw;
|
|
103
|
+
}
|
|
104
|
+
|
|
57
105
|
/**
|
|
58
106
|
* Create a virtual modules plugin for default entry files.
|
|
59
107
|
* Provides virtual module content when entries use VIRTUAL_IDS (no custom entry configured).
|
|
60
108
|
*/
|
|
61
109
|
export function createVirtualEntriesPlugin(
|
|
62
110
|
entries: { client: string; ssr: string; rsc?: string },
|
|
63
|
-
routerPathRef?: { path?: string },
|
|
111
|
+
routerPathRef?: { path?: string; kind?: "router" | "host" },
|
|
64
112
|
): Plugin {
|
|
65
113
|
// Build virtual modules map based on which entries use virtual IDs
|
|
66
114
|
const virtualModules: Record<string, string> = {};
|
|
@@ -108,7 +156,9 @@ export function createVirtualEntriesPlugin(
|
|
|
108
156
|
: routerPathRef.path;
|
|
109
157
|
// Normalize backslashes for Windows (path.join/slice preserve native separators)
|
|
110
158
|
const absoluteRouterPath = raw.replaceAll("\\", "/");
|
|
111
|
-
return
|
|
159
|
+
return routerPathRef.kind === "host"
|
|
160
|
+
? getVirtualEntryRSCHost(absoluteRouterPath)
|
|
161
|
+
: getVirtualEntryRSC(absoluteRouterPath);
|
|
112
162
|
}
|
|
113
163
|
}
|
|
114
164
|
return null;
|