@rangojs/router 0.0.0-experimental.88a3b2f7 → 0.0.0-experimental.8bcfea43
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 +50 -20
- package/dist/vite/index.js +647 -176
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +7 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +28 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +88 -16
- package/skills/loader/SKILL.md +35 -2
- package/skills/middleware/SKILL.md +32 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +59 -0
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +24 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +3 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/navigation-bridge.ts +72 -4
- package/src/browser/navigation-client.ts +64 -13
- package/src/browser/navigation-store.ts +25 -1
- package/src/browser/partial-update.ts +34 -3
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +148 -16
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +50 -11
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +8 -1
- package/src/browser/rsc-router.tsx +34 -6
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/types.ts +13 -0
- package/src/build/route-trie.ts +50 -24
- package/src/cache/cf/cf-cache-store.ts +5 -7
- package/src/client.tsx +84 -230
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +44 -9
- package/src/outlet-context.ts +1 -1
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +7 -3
- package/src/route-definition/dsl-helpers.ts +180 -24
- package/src/route-definition/helpers-types.ts +61 -14
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-types.ts +7 -0
- package/src/router/handler-context.ts +24 -4
- package/src/router/lazy-includes.ts +6 -6
- package/src/router/loader-resolution.ts +73 -46
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +3 -3
- package/src/router/match-middleware/cache-lookup.ts +10 -5
- package/src/router/match-middleware/segment-resolution.ts +1 -1
- package/src/router/match-result.ts +82 -4
- package/src/router/middleware-types.ts +2 -22
- package/src/router/middleware.ts +32 -4
- package/src/router/pattern-matching.ts +60 -9
- package/src/router/segment-resolution/fresh.ts +52 -0
- package/src/router/segment-resolution/revalidation.ts +69 -1
- package/src/router/trie-matching.ts +10 -4
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +1 -2
- package/src/rsc/handler.ts +21 -9
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/progressive-enhancement.ts +12 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +12 -1
- package/src/rsc/server-action.ts +8 -0
- package/src/rsc/types.ts +1 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +11 -61
- package/src/server/context.ts +26 -3
- package/src/server/handle-store.ts +19 -0
- package/src/server/request-context.ts +64 -56
- package/src/types/handler-context.ts +2 -34
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +1 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +34 -5
- package/src/urls/response-types.ts +2 -10
- package/src/use-loader.tsx +77 -5
- package/src/vite/debug.ts +55 -0
- package/src/vite/discovery/prerender-collection.ts +124 -83
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- package/src/vite/plugins/performance-tracks.ts +4 -6
- package/src/vite/rango.ts +49 -14
- package/src/vite/router-discovery.ts +186 -26
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +20 -6
|
@@ -10,7 +10,7 @@ import type { Plugin } from "vite";
|
|
|
10
10
|
import { createServer as createViteServer } from "vite";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import { readFileSync } from "node:fs";
|
|
13
|
-
import { createRequire } from "node:module";
|
|
13
|
+
import { createRequire, register } from "node:module";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import {
|
|
16
16
|
formatNestedRouterConflictError,
|
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
} from "../build/generate-route-types.js";
|
|
20
20
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
21
21
|
import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
|
|
22
|
+
import {
|
|
23
|
+
BUILD_ENV_GLOBAL_KEY,
|
|
24
|
+
createCloudflareProtocolStubPlugin,
|
|
25
|
+
} from "./plugins/cloudflare-protocol-stub.js";
|
|
22
26
|
import {
|
|
23
27
|
exposeInternalIds,
|
|
24
28
|
exposeRouterId,
|
|
@@ -44,9 +48,56 @@ import {
|
|
|
44
48
|
} from "./discovery/virtual-module-codegen.js";
|
|
45
49
|
import { postprocessBundle } from "./discovery/bundle-postprocess.js";
|
|
46
50
|
import { resetStagedBuildAssets } from "./utils/prerender-utils.js";
|
|
51
|
+
import { createRangoDebugger, timed } from "./debug.js";
|
|
52
|
+
|
|
53
|
+
const debugDiscovery = createRangoDebugger("rango:discovery");
|
|
54
|
+
const debugRoutes = createRangoDebugger("rango:routes");
|
|
47
55
|
|
|
48
56
|
export { VIRTUAL_ROUTES_MANIFEST_ID };
|
|
49
57
|
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Node ESM Loader Hook Registration
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Registers a Node ESM loader hook that resolves `cloudflare:*` specifiers
|
|
64
|
+
* to a data: URL stub. Defense-in-depth alongside the Vite transform in
|
|
65
|
+
* `cloudflare-protocol-stub.ts`:
|
|
66
|
+
*
|
|
67
|
+
* - The Vite transform catches `cloudflare:*` imports in modules that flow
|
|
68
|
+
* through Vite's plugin pipeline. That's the vast majority of cases.
|
|
69
|
+
* - The Node loader catches imports in modules that Vite/Rollup externalize
|
|
70
|
+
* (e.g. the `partyserver` package, which has a top-level
|
|
71
|
+
* `import { DurableObject, env } from "cloudflare:workers"` and ships
|
|
72
|
+
* shapes plugin-rsc marks as external). Externalized modules are loaded
|
|
73
|
+
* via Node's native ESM loader, which rejects URL schemes.
|
|
74
|
+
*
|
|
75
|
+
* Registration is process-global and one-shot. The hook only intercepts
|
|
76
|
+
* `cloudflare:*` specifiers; everything else passes through via
|
|
77
|
+
* `nextResolve()`. It runs in a separate worker thread (Node ESM loader
|
|
78
|
+
* architecture), so it can't read the `globalThis[BUILD_ENV_GLOBAL_KEY]`
|
|
79
|
+
* bridge that the Vite transform uses — the stubs served here always
|
|
80
|
+
* return `env = {}`. That's fine because externalized libraries don't
|
|
81
|
+
* typically access `env` at module top level; user source (where real
|
|
82
|
+
* `env` matters at build time) flows through the Vite transform.
|
|
83
|
+
*/
|
|
84
|
+
let loaderHookRegistered = false;
|
|
85
|
+
function ensureCloudflareProtocolLoaderRegistered(): void {
|
|
86
|
+
if (loaderHookRegistered) return;
|
|
87
|
+
loaderHookRegistered = true;
|
|
88
|
+
try {
|
|
89
|
+
register(
|
|
90
|
+
new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url),
|
|
91
|
+
);
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
// register() requires Node 18.19+ / 20.6+. Older Node still has the
|
|
94
|
+
// Vite transform as primary defense.
|
|
95
|
+
console.warn(
|
|
96
|
+
`[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
50
101
|
// ============================================================================
|
|
51
102
|
// Temp Server Factory
|
|
52
103
|
// ============================================================================
|
|
@@ -66,6 +117,11 @@ async function createTempRscServer(
|
|
|
66
117
|
state: DiscoveryState,
|
|
67
118
|
options: { forceBuild?: boolean; cacheDir?: string } = {},
|
|
68
119
|
) {
|
|
120
|
+
// Install the Node ESM loader hook before any module evaluation so
|
|
121
|
+
// `cloudflare:*` specifiers in externalized/loader-delegated modules
|
|
122
|
+
// (e.g. packages plugin-rsc marks as external) resolve to stubs
|
|
123
|
+
// instead of crashing Node's native loader.
|
|
124
|
+
ensureCloudflareProtocolLoaderRegistered();
|
|
69
125
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
70
126
|
return createViteServer({
|
|
71
127
|
root: state.projectRoot,
|
|
@@ -88,6 +144,7 @@ async function createTempRscServer(
|
|
|
88
144
|
...(options.forceBuild ? [hashClientRefs(state.projectRoot)] : []),
|
|
89
145
|
createVersionPlugin(),
|
|
90
146
|
createVirtualStubPlugin(),
|
|
147
|
+
createCloudflareProtocolStubPlugin(),
|
|
91
148
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
92
149
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
93
150
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
@@ -177,6 +234,11 @@ async function acquireBuildEnv(
|
|
|
177
234
|
|
|
178
235
|
s.resolvedBuildEnv = result.env;
|
|
179
236
|
s.buildEnvDispose = result.dispose ?? null;
|
|
237
|
+
// Bridge the resolved env into `cloudflare:workers`'s stubbed `env`
|
|
238
|
+
// export so user code that does `import { env } from "cloudflare:workers"`
|
|
239
|
+
// sees the real bindings proxy during discovery + prerender instead of
|
|
240
|
+
// an empty object. The stub reads this global at module-evaluation time.
|
|
241
|
+
(globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY] = result.env;
|
|
180
242
|
return true;
|
|
181
243
|
}
|
|
182
244
|
|
|
@@ -193,6 +255,7 @@ async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
|
|
|
193
255
|
s.buildEnvDispose = null;
|
|
194
256
|
}
|
|
195
257
|
s.resolvedBuildEnv = undefined;
|
|
258
|
+
delete (globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY];
|
|
196
259
|
}
|
|
197
260
|
|
|
198
261
|
/**
|
|
@@ -344,23 +407,35 @@ export function createRouterDiscoveryPlugin(
|
|
|
344
407
|
}
|
|
345
408
|
|
|
346
409
|
const discover = async () => {
|
|
410
|
+
const discoverStart = performance.now();
|
|
347
411
|
const rscEnv = (server.environments as any)?.rsc;
|
|
348
412
|
if (!rscEnv?.runner) {
|
|
349
413
|
// Cloudflare dev: no module runner available (workerd-based RSC env).
|
|
350
414
|
// Set devServerOrigin so the virtual module can inject __PRERENDER_DEV_URL
|
|
351
415
|
// for on-demand prerender via the /__rsc_prerender endpoint.
|
|
416
|
+
debugDiscovery?.("dev: no rsc runner (cloudflare path)");
|
|
352
417
|
s.devServerOrigin = getDevServerOrigin();
|
|
353
418
|
|
|
354
419
|
// Create a temp Node.js server to run runtime discovery and generate
|
|
355
420
|
// named route types (static parser can't resolve factory calls).
|
|
356
421
|
try {
|
|
357
422
|
// Acquire build-time env bindings for dev prerender
|
|
358
|
-
await
|
|
423
|
+
await timed(debugDiscovery, "acquireBuildEnv", () =>
|
|
424
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
425
|
+
);
|
|
359
426
|
|
|
360
|
-
const tempRscEnv = await
|
|
427
|
+
const tempRscEnv = await timed(
|
|
428
|
+
debugDiscovery,
|
|
429
|
+
"getOrCreateTempServer",
|
|
430
|
+
() => getOrCreateTempServer(),
|
|
431
|
+
);
|
|
361
432
|
if (tempRscEnv) {
|
|
362
|
-
await discoverRouters(
|
|
363
|
-
|
|
433
|
+
await timed(debugDiscovery, "discoverRouters (cloudflare)", () =>
|
|
434
|
+
discoverRouters(s, tempRscEnv),
|
|
435
|
+
);
|
|
436
|
+
timed(debugDiscovery, "writeRouteTypesFiles", () =>
|
|
437
|
+
writeRouteTypesFiles(s),
|
|
438
|
+
);
|
|
364
439
|
}
|
|
365
440
|
} catch (err: any) {
|
|
366
441
|
console.warn(
|
|
@@ -368,24 +443,35 @@ export function createRouterDiscoveryPlugin(
|
|
|
368
443
|
);
|
|
369
444
|
}
|
|
370
445
|
|
|
446
|
+
debugDiscovery?.(
|
|
447
|
+
"dev discovery done (%sms)",
|
|
448
|
+
(performance.now() - discoverStart).toFixed(1),
|
|
449
|
+
);
|
|
371
450
|
resolveDiscovery!();
|
|
372
451
|
return;
|
|
373
452
|
}
|
|
374
453
|
|
|
375
454
|
try {
|
|
376
455
|
// Acquire build-time env bindings for dev prerender (Node.js path)
|
|
377
|
-
|
|
456
|
+
debugDiscovery?.("dev: node path start");
|
|
457
|
+
await timed(debugDiscovery, "acquireBuildEnv", () =>
|
|
458
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
459
|
+
);
|
|
378
460
|
|
|
379
461
|
// Set the readiness gate BEFORE discovery so early requests
|
|
380
462
|
// block until manifest is populated
|
|
381
|
-
const serverMod = await
|
|
382
|
-
|
|
463
|
+
const serverMod = await timed(
|
|
464
|
+
debugDiscovery,
|
|
465
|
+
"import @rangojs/router/server",
|
|
466
|
+
() => rscEnv.runner.import("@rangojs/router/server"),
|
|
383
467
|
);
|
|
384
468
|
if (serverMod?.setManifestReadyPromise) {
|
|
385
469
|
serverMod.setManifestReadyPromise(discoveryPromise);
|
|
386
470
|
}
|
|
387
471
|
|
|
388
|
-
await
|
|
472
|
+
await timed(debugDiscovery, "discoverRouters", () =>
|
|
473
|
+
discoverRouters(s, rscEnv),
|
|
474
|
+
);
|
|
389
475
|
|
|
390
476
|
// Store server origin for dev prerender endpoint (virtual module injection)
|
|
391
477
|
s.devServerOrigin = getDevServerOrigin();
|
|
@@ -395,15 +481,23 @@ export function createRouterDiscoveryPlugin(
|
|
|
395
481
|
// routes (e.g. Array.from loops) that the static parser cannot see.
|
|
396
482
|
// writeRouteTypesFiles() only writes when content changes, so this
|
|
397
483
|
// won't cause unnecessary HMR triggers.
|
|
398
|
-
writeRouteTypesFiles(
|
|
484
|
+
timed(debugDiscovery, "writeRouteTypesFiles", () =>
|
|
485
|
+
writeRouteTypesFiles(s),
|
|
486
|
+
);
|
|
399
487
|
|
|
400
488
|
// Populate the route map and per-router data in the RSC env
|
|
401
|
-
await propagateDiscoveryState(
|
|
489
|
+
await timed(debugDiscovery, "propagateDiscoveryState", () =>
|
|
490
|
+
propagateDiscoveryState(rscEnv),
|
|
491
|
+
);
|
|
402
492
|
} catch (err: any) {
|
|
403
493
|
console.warn(
|
|
404
494
|
`[rsc-router] Router discovery failed: ${err.message}\n${err.stack}`,
|
|
405
495
|
);
|
|
406
496
|
} finally {
|
|
497
|
+
debugDiscovery?.(
|
|
498
|
+
"dev discovery done (%sms)",
|
|
499
|
+
(performance.now() - discoverStart).toFixed(1),
|
|
500
|
+
);
|
|
407
501
|
resolveDiscovery!();
|
|
408
502
|
}
|
|
409
503
|
};
|
|
@@ -480,9 +574,31 @@ export function createRouterDiscoveryPlugin(
|
|
|
480
574
|
return;
|
|
481
575
|
}
|
|
482
576
|
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
|
|
577
|
+
// Import the user's entry module to force re-evaluation of any
|
|
578
|
+
// HMR-invalidated modules in the chain (entry → router → urls → handlers).
|
|
579
|
+
// This ensures createRouter() re-runs with updated handler code before
|
|
580
|
+
// we read RouterRegistry. Without this, edits to prerender handler files
|
|
581
|
+
// produce stale content because the old router instance remains registered.
|
|
582
|
+
const rscEnv = (server.environments as any)?.rsc;
|
|
583
|
+
let registry: Map<string, any> | null = null;
|
|
584
|
+
if (rscEnv?.runner && s.resolvedEntryPath) {
|
|
585
|
+
try {
|
|
586
|
+
await rscEnv.runner.import(s.resolvedEntryPath);
|
|
587
|
+
const serverMod = await rscEnv.runner.import(
|
|
588
|
+
"@rangojs/router/server",
|
|
589
|
+
);
|
|
590
|
+
registry = serverMod.RouterRegistry ?? null;
|
|
591
|
+
} catch (err: any) {
|
|
592
|
+
console.warn(
|
|
593
|
+
`[rsc-router] Dev prerender module refresh failed: ${err.message}`,
|
|
594
|
+
);
|
|
595
|
+
res.statusCode = 500;
|
|
596
|
+
res.end(`Prerender handler error: ${err.message}`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
registry = mainRegistry;
|
|
601
|
+
}
|
|
486
602
|
|
|
487
603
|
if (!registry) {
|
|
488
604
|
// No main registry: the RSC env has no module runner (Cloudflare dev).
|
|
@@ -591,15 +707,26 @@ export function createRouterDiscoveryPlugin(
|
|
|
591
707
|
const rscEnv = (server.environments as any)?.rsc;
|
|
592
708
|
if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
|
|
593
709
|
runtimeRediscoveryInProgress = true;
|
|
710
|
+
const hmrStart = performance.now();
|
|
594
711
|
try {
|
|
595
|
-
await
|
|
596
|
-
|
|
597
|
-
|
|
712
|
+
await timed(debugDiscovery, "hmr discoverRouters", () =>
|
|
713
|
+
discoverRouters(s, rscEnv),
|
|
714
|
+
);
|
|
715
|
+
timed(debugDiscovery, "hmr writeRouteTypesFiles", () =>
|
|
716
|
+
writeRouteTypesFiles(s),
|
|
717
|
+
);
|
|
718
|
+
await timed(debugDiscovery, "hmr propagateDiscoveryState", () =>
|
|
719
|
+
propagateDiscoveryState(rscEnv),
|
|
720
|
+
);
|
|
598
721
|
} catch (err: any) {
|
|
599
722
|
console.warn(
|
|
600
723
|
`[rsc-router] Runtime re-discovery failed: ${err.message}`,
|
|
601
724
|
);
|
|
602
725
|
} finally {
|
|
726
|
+
debugDiscovery?.(
|
|
727
|
+
"hmr re-discovery done (%sms)",
|
|
728
|
+
(performance.now() - hmrStart).toFixed(1),
|
|
729
|
+
);
|
|
603
730
|
runtimeRediscoveryInProgress = false;
|
|
604
731
|
}
|
|
605
732
|
};
|
|
@@ -697,12 +824,16 @@ export function createRouterDiscoveryPlugin(
|
|
|
697
824
|
if (!s.isBuildMode) return;
|
|
698
825
|
// Only run once across environment builds
|
|
699
826
|
if (s.mergedRouteManifest !== null) return;
|
|
827
|
+
const buildStartTime = performance.now();
|
|
828
|
+
debugDiscovery?.("build: start");
|
|
700
829
|
resetStagedBuildAssets(s.projectRoot);
|
|
701
830
|
s.prerenderManifestEntries = null;
|
|
702
831
|
s.staticManifestEntries = null;
|
|
703
832
|
|
|
704
833
|
// Acquire build-time env bindings if configured
|
|
705
|
-
await
|
|
834
|
+
await timed(debugDiscovery, "build acquireBuildEnv", () =>
|
|
835
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
836
|
+
);
|
|
706
837
|
|
|
707
838
|
let tempServer: any = null;
|
|
708
839
|
// Signal to user-space code (e.g. reverse.ts) that build-time discovery
|
|
@@ -711,7 +842,11 @@ export function createRouterDiscoveryPlugin(
|
|
|
711
842
|
// between the vite plugin and user code loaded via runner.import().
|
|
712
843
|
(globalThis as any).__rscRouterDiscoveryActive = true;
|
|
713
844
|
try {
|
|
714
|
-
tempServer = await
|
|
845
|
+
tempServer = await timed(
|
|
846
|
+
debugDiscovery,
|
|
847
|
+
"build createTempRscServer",
|
|
848
|
+
() => createTempRscServer(s, { forceBuild: true }),
|
|
849
|
+
);
|
|
715
850
|
|
|
716
851
|
const rscEnv = (tempServer.environments as any)?.rsc;
|
|
717
852
|
if (!rscEnv?.runner) {
|
|
@@ -731,11 +866,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
731
866
|
s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
|
|
732
867
|
}
|
|
733
868
|
|
|
734
|
-
await
|
|
869
|
+
await timed(debugDiscovery, "build discoverRouters", () =>
|
|
870
|
+
discoverRouters(s, rscEnv),
|
|
871
|
+
);
|
|
735
872
|
// Update named-routes.gen.ts from runtime discovery.
|
|
736
873
|
// The runtime manifest includes dynamically generated routes
|
|
737
874
|
// that the static parser cannot extract from source code.
|
|
738
|
-
writeRouteTypesFiles(
|
|
875
|
+
timed(debugDiscovery, "build writeRouteTypesFiles", () =>
|
|
876
|
+
writeRouteTypesFiles(s),
|
|
877
|
+
);
|
|
739
878
|
} catch (err: any) {
|
|
740
879
|
// Extract the user source file from the stack trace (skip internal frames)
|
|
741
880
|
const sourceFile = err.stack
|
|
@@ -760,9 +899,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
760
899
|
} finally {
|
|
761
900
|
delete (globalThis as any).__rscRouterDiscoveryActive;
|
|
762
901
|
if (tempServer) {
|
|
763
|
-
await tempServer.close()
|
|
902
|
+
await timed(debugDiscovery, "build tempServer.close", () =>
|
|
903
|
+
tempServer.close(),
|
|
904
|
+
);
|
|
764
905
|
}
|
|
765
906
|
await releaseBuildEnv(s);
|
|
907
|
+
debugDiscovery?.(
|
|
908
|
+
"build discovery done (%sms)",
|
|
909
|
+
(performance.now() - buildStartTime).toFixed(1),
|
|
910
|
+
);
|
|
766
911
|
}
|
|
767
912
|
},
|
|
768
913
|
|
|
@@ -786,19 +931,34 @@ export function createRouterDiscoveryPlugin(
|
|
|
786
931
|
// This is critical for Cloudflare dev where the worker runs in a separate
|
|
787
932
|
// Miniflare process and can only receive manifest data via the virtual module.
|
|
788
933
|
if (s.discoveryDone) {
|
|
789
|
-
await
|
|
934
|
+
await timed(debugRoutes, "await discoveryDone (manifest)", () =>
|
|
935
|
+
Promise.resolve(s.discoveryDone),
|
|
936
|
+
);
|
|
790
937
|
}
|
|
791
|
-
|
|
938
|
+
const code = await timed(
|
|
939
|
+
debugRoutes,
|
|
940
|
+
"generateRoutesManifestModule",
|
|
941
|
+
() => generateRoutesManifestModule(s),
|
|
942
|
+
);
|
|
943
|
+
debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
|
|
944
|
+
return code;
|
|
792
945
|
}
|
|
793
946
|
// Per-router virtual modules: pure data exports (no side effects).
|
|
794
947
|
// ensureRouterManifest() imports the module and stores the data.
|
|
795
948
|
const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
|
|
796
949
|
if (id.startsWith(perRouterPrefix)) {
|
|
797
950
|
if (s.discoveryDone) {
|
|
798
|
-
await
|
|
951
|
+
await timed(debugRoutes, "await discoveryDone (per-router)", () =>
|
|
952
|
+
Promise.resolve(s.discoveryDone),
|
|
953
|
+
);
|
|
799
954
|
}
|
|
800
955
|
const routerId = id.slice(perRouterPrefix.length);
|
|
801
|
-
|
|
956
|
+
const code = await timed(
|
|
957
|
+
debugRoutes,
|
|
958
|
+
`generatePerRouterModule ${routerId}`,
|
|
959
|
+
() => generatePerRouterModule(s, routerId),
|
|
960
|
+
);
|
|
961
|
+
return code;
|
|
802
962
|
}
|
|
803
963
|
// virtual:rsc-router/prerender-paths load handler removed
|
|
804
964
|
return null;
|
package/src/vite/utils/banner.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
9
10
|
import { resolve } from "node:path";
|
|
10
|
-
import packageJson from "../../../package.json"
|
|
11
|
+
import packageJson from "../../../package.json";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* The canonical name used in virtual entries (without scope)
|
|
@@ -119,3 +122,40 @@ export function getPackageAliases(): Record<string, string> {
|
|
|
119
122
|
|
|
120
123
|
return aliases;
|
|
121
124
|
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Plugin-rsc pushes bare specs like
|
|
128
|
+
* `@vitejs/plugin-rsc/vendor/react-server-dom/client.edge` into
|
|
129
|
+
* `optimizeDeps.include` for the ssr and rsc environments. In strict pnpm
|
|
130
|
+
* consumer apps, `@vitejs/plugin-rsc` is only reachable from @rangojs/router's
|
|
131
|
+
* node_modules, so Vite's optimizer — which resolves from the project root —
|
|
132
|
+
* can't find them and emits "Failed to resolve dependency" warnings.
|
|
133
|
+
*
|
|
134
|
+
* We resolve those specs from this plugin's location (where plugin-rsc is
|
|
135
|
+
* guaranteed to be installed as our dep) and expose them as `resolve.alias`
|
|
136
|
+
* entries. The optimizer's resolver honors aliases, so the bare specs map to
|
|
137
|
+
* absolute paths and resolve cleanly.
|
|
138
|
+
*/
|
|
139
|
+
export function getVendorAliases(): Record<string, string> {
|
|
140
|
+
// client.browser is intentionally NOT aliased. plugin-rsc injects it into
|
|
141
|
+
// the client env's optimizeDeps.include; Vite's manual-include path resolves
|
|
142
|
+
// and pre-bundles regardless of optimizeDeps.exclude, so aliasing would
|
|
143
|
+
// trigger esbuild pre-bundling of the CJS vendor file and bypass the
|
|
144
|
+
// cjs-to-esm transform that patches `require('react'|'react-dom')` into
|
|
145
|
+
// real ESM imports. The consumer may still see a single "Failed to resolve"
|
|
146
|
+
// warning for client.browser; runtime resolution from plugin-rsc's own
|
|
147
|
+
// importer works because Vite resolves relative to the importer (not root).
|
|
148
|
+
const specs = [
|
|
149
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
|
|
150
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
|
|
151
|
+
];
|
|
152
|
+
const aliases: Record<string, string> = {};
|
|
153
|
+
for (const spec of specs) {
|
|
154
|
+
try {
|
|
155
|
+
aliases[spec] = require.resolve(spec);
|
|
156
|
+
} catch {
|
|
157
|
+
// Spec unresolvable (unexpected but non-fatal — Vite will warn as before).
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return aliases;
|
|
161
|
+
}
|
|
@@ -41,14 +41,28 @@ export function substituteRouteParams(
|
|
|
41
41
|
let result = pattern;
|
|
42
42
|
let hadOmittedOptional = false;
|
|
43
43
|
|
|
44
|
-
// First pass: substitute provided params
|
|
44
|
+
// First pass: substitute provided params.
|
|
45
|
+
// Empty string on an optional placeholder is treated as omitted (the trie
|
|
46
|
+
// matcher fills unmatched optionals with "" — letting the second pass
|
|
47
|
+
// strip them keeps slash cleanup consistent). Empty string on required
|
|
48
|
+
// `:key` or wildcard `*key` still substitutes, matching prior behaviour.
|
|
45
49
|
for (const [key, value] of Object.entries(params)) {
|
|
46
50
|
const escaped = escapeRegExp(key);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
if (value === "") {
|
|
52
|
+
// Only replace required placeholders (negative lookahead for `?`);
|
|
53
|
+
// leave `:key?` for the second pass.
|
|
54
|
+
result = result.replace(
|
|
55
|
+
new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
|
|
56
|
+
"",
|
|
57
|
+
);
|
|
58
|
+
result = result.replace(`*${key}`, "");
|
|
59
|
+
} else {
|
|
60
|
+
result = result.replace(
|
|
61
|
+
new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
|
|
62
|
+
encode(value),
|
|
63
|
+
);
|
|
64
|
+
result = result.replace(`*${key}`, encode(value));
|
|
65
|
+
}
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
// Second pass: strip remaining optional param placeholders not in params
|