@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c
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 +172 -50
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1160 -508
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +17 -16
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +49 -8
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +61 -51
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +107 -24
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +185 -0
- package/skills/prerender/SKILL.md +112 -70
- package/skills/rango/SKILL.md +24 -23
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +58 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +38 -24
- package/src/__internal.ts +92 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +175 -17
- package/src/browser/navigation-client.ts +177 -44
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +113 -17
- package/src/browser/prefetch/cache.ts +275 -28
- package/src/browser/prefetch/fetch.ts +191 -46
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +98 -14
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- 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 +29 -9
- package/src/browser/rsc-router.tsx +177 -66
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +73 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +67 -25
- 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 +455 -15
- 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.rsc.tsx +2 -1
- package/src/client.tsx +85 -276
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/host/index.ts +0 -3
- package/src/index.rsc.ts +9 -36
- package/src/index.ts +79 -70
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +57 -15
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- package/src/route-definition/helpers-types.ts +67 -19
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +129 -26
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +160 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -193
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- 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 +103 -18
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +48 -27
- package/src/router/middleware.ts +201 -86
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +77 -11
- package/src/router/prerender-match.ts +114 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +27 -7
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +50 -5
- package/src/router/router-options.ts +50 -19
- package/src/router/segment-resolution/fresh.ts +215 -19
- package/src/router/segment-resolution/helpers.ts +30 -25
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +454 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +30 -6
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +89 -17
- package/src/rsc/handler.ts +563 -364
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +37 -10
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +47 -44
- package/src/rsc/server-action.ts +24 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +11 -1
- package/src/search-params.ts +16 -13
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +174 -19
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +218 -65
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/theme/index.ts +4 -13
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +140 -72
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -5
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +7 -4
- package/src/vite/discovery/prerender-collection.ts +162 -88
- package/src/vite/discovery/state.ts +17 -13
- package/src/vite/index.ts +8 -3
- package/src/vite/plugin-types.ts +51 -79
- 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-action-id.ts +1 -3
- 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 +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +190 -217
- package/src/vite/router-discovery.ts +241 -45
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +34 -1
- package/src/vite/utils/prerender-utils.ts +97 -5
- package/src/vite/utils/shared-utils.ts +3 -2
- package/skills/testing/SKILL.md +0 -226
- package/src/route-definition/route-function.ts +0 -119
|
@@ -10,14 +10,19 @@ 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, register } from "node:module";
|
|
14
|
+
import { pathToFileURL } from "node:url";
|
|
13
15
|
import {
|
|
14
16
|
formatNestedRouterConflictError,
|
|
15
17
|
findNestedRouterConflict,
|
|
16
18
|
findRouterFiles,
|
|
17
|
-
createScanFilter,
|
|
18
19
|
} from "../build/generate-route-types.js";
|
|
19
20
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
20
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";
|
|
21
26
|
import {
|
|
22
27
|
exposeInternalIds,
|
|
23
28
|
exposeRouterId,
|
|
@@ -42,9 +47,53 @@ import {
|
|
|
42
47
|
generatePerRouterModule,
|
|
43
48
|
} from "./discovery/virtual-module-codegen.js";
|
|
44
49
|
import { postprocessBundle } from "./discovery/bundle-postprocess.js";
|
|
50
|
+
import { resetStagedBuildAssets } from "./utils/prerender-utils.js";
|
|
45
51
|
|
|
46
52
|
export { VIRTUAL_ROUTES_MANIFEST_ID };
|
|
47
53
|
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Node ESM Loader Hook Registration
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Registers a Node ESM loader hook that resolves `cloudflare:*` specifiers
|
|
60
|
+
* to a data: URL stub. Defense-in-depth alongside the Vite transform in
|
|
61
|
+
* `cloudflare-protocol-stub.ts`:
|
|
62
|
+
*
|
|
63
|
+
* - The Vite transform catches `cloudflare:*` imports in modules that flow
|
|
64
|
+
* through Vite's plugin pipeline. That's the vast majority of cases.
|
|
65
|
+
* - The Node loader catches imports in modules that Vite/Rollup externalize
|
|
66
|
+
* (e.g. the `partyserver` package, which has a top-level
|
|
67
|
+
* `import { DurableObject, env } from "cloudflare:workers"` and ships
|
|
68
|
+
* shapes plugin-rsc marks as external). Externalized modules are loaded
|
|
69
|
+
* via Node's native ESM loader, which rejects URL schemes.
|
|
70
|
+
*
|
|
71
|
+
* Registration is process-global and one-shot. The hook only intercepts
|
|
72
|
+
* `cloudflare:*` specifiers; everything else passes through via
|
|
73
|
+
* `nextResolve()`. It runs in a separate worker thread (Node ESM loader
|
|
74
|
+
* architecture), so it can't read the `globalThis[BUILD_ENV_GLOBAL_KEY]`
|
|
75
|
+
* bridge that the Vite transform uses — the stubs served here always
|
|
76
|
+
* return `env = {}`. That's fine because externalized libraries don't
|
|
77
|
+
* typically access `env` at module top level; user source (where real
|
|
78
|
+
* `env` matters at build time) flows through the Vite transform.
|
|
79
|
+
*/
|
|
80
|
+
let loaderHookRegistered = false;
|
|
81
|
+
function ensureCloudflareProtocolLoaderRegistered(): void {
|
|
82
|
+
if (loaderHookRegistered) return;
|
|
83
|
+
loaderHookRegistered = true;
|
|
84
|
+
try {
|
|
85
|
+
register(
|
|
86
|
+
new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url),
|
|
87
|
+
);
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
// register() requires Node 18.19+ / 20.6+. Older Node still has the
|
|
90
|
+
// Vite transform as primary defense.
|
|
91
|
+
console.warn(
|
|
92
|
+
`[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
48
97
|
// ============================================================================
|
|
49
98
|
// Temp Server Factory
|
|
50
99
|
// ============================================================================
|
|
@@ -64,6 +113,11 @@ async function createTempRscServer(
|
|
|
64
113
|
state: DiscoveryState,
|
|
65
114
|
options: { forceBuild?: boolean; cacheDir?: string } = {},
|
|
66
115
|
) {
|
|
116
|
+
// Install the Node ESM loader hook before any module evaluation so
|
|
117
|
+
// `cloudflare:*` specifiers in externalized/loader-delegated modules
|
|
118
|
+
// (e.g. packages plugin-rsc marks as external) resolve to stubs
|
|
119
|
+
// instead of crashing Node's native loader.
|
|
120
|
+
ensureCloudflareProtocolLoaderRegistered();
|
|
67
121
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
68
122
|
return createViteServer({
|
|
69
123
|
root: state.projectRoot,
|
|
@@ -86,6 +140,7 @@ async function createTempRscServer(
|
|
|
86
140
|
...(options.forceBuild ? [hashClientRefs(state.projectRoot)] : []),
|
|
87
141
|
createVersionPlugin(),
|
|
88
142
|
createVirtualStubPlugin(),
|
|
143
|
+
createCloudflareProtocolStubPlugin(),
|
|
89
144
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
90
145
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
91
146
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
@@ -94,6 +149,111 @@ async function createTempRscServer(
|
|
|
94
149
|
});
|
|
95
150
|
}
|
|
96
151
|
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Build-Time Env Resolution
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
import type {
|
|
157
|
+
BuildEnvOption,
|
|
158
|
+
BuildEnvFactoryContext,
|
|
159
|
+
BuildEnvResult,
|
|
160
|
+
} from "./plugin-types.js";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the buildEnv option into a concrete { env, dispose? } result.
|
|
164
|
+
* Handles all four input shapes: false, "auto", factory, plain object.
|
|
165
|
+
*/
|
|
166
|
+
async function resolveBuildEnv(
|
|
167
|
+
option: BuildEnvOption | undefined,
|
|
168
|
+
factoryCtx: BuildEnvFactoryContext,
|
|
169
|
+
): Promise<BuildEnvResult | null> {
|
|
170
|
+
if (!option) return null;
|
|
171
|
+
|
|
172
|
+
if (option === "auto") {
|
|
173
|
+
if (factoryCtx.preset !== "cloudflare") {
|
|
174
|
+
throw new Error(
|
|
175
|
+
'[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
|
|
176
|
+
"Use a factory function or plain object for other presets.",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
// Resolve wrangler from the user's project root (not the router package)
|
|
181
|
+
const userRequire = createRequire(
|
|
182
|
+
resolve(factoryCtx.root, "package.json"),
|
|
183
|
+
);
|
|
184
|
+
const wranglerPath = userRequire.resolve("wrangler");
|
|
185
|
+
const { getPlatformProxy } = (await import(
|
|
186
|
+
pathToFileURL(wranglerPath).href
|
|
187
|
+
)) as {
|
|
188
|
+
getPlatformProxy: (opts?: any) => Promise<any>;
|
|
189
|
+
};
|
|
190
|
+
const proxy = await getPlatformProxy();
|
|
191
|
+
return {
|
|
192
|
+
env: proxy.env as Record<string, unknown>,
|
|
193
|
+
dispose: proxy.dispose,
|
|
194
|
+
};
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
'[rsc-router] buildEnv: "auto" requires wrangler to be installed.\n' +
|
|
198
|
+
`Install it with: pnpm add -D wrangler\n${err.message}`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (typeof option === "function") {
|
|
204
|
+
return await option(factoryCtx);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Plain object
|
|
208
|
+
return { env: option };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Acquire build-time env bindings and store on discovery state.
|
|
213
|
+
* Returns true if env was acquired, false if buildEnv is disabled.
|
|
214
|
+
*/
|
|
215
|
+
async function acquireBuildEnv(
|
|
216
|
+
s: DiscoveryState,
|
|
217
|
+
command: "serve" | "build",
|
|
218
|
+
mode: string,
|
|
219
|
+
): Promise<boolean> {
|
|
220
|
+
const option = s.opts?.buildEnv;
|
|
221
|
+
if (!option) return false;
|
|
222
|
+
|
|
223
|
+
const result = await resolveBuildEnv(option, {
|
|
224
|
+
root: s.projectRoot,
|
|
225
|
+
mode,
|
|
226
|
+
command,
|
|
227
|
+
preset: s.opts?.preset ?? "node",
|
|
228
|
+
});
|
|
229
|
+
if (!result) return false;
|
|
230
|
+
|
|
231
|
+
s.resolvedBuildEnv = result.env;
|
|
232
|
+
s.buildEnvDispose = result.dispose ?? null;
|
|
233
|
+
// Bridge the resolved env into `cloudflare:workers`'s stubbed `env`
|
|
234
|
+
// export so user code that does `import { env } from "cloudflare:workers"`
|
|
235
|
+
// sees the real bindings proxy during discovery + prerender instead of
|
|
236
|
+
// an empty object. The stub reads this global at module-evaluation time.
|
|
237
|
+
(globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY] = result.env;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Release build-time env resources and clear state.
|
|
243
|
+
*/
|
|
244
|
+
async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
|
|
245
|
+
if (s.buildEnvDispose) {
|
|
246
|
+
try {
|
|
247
|
+
await s.buildEnvDispose();
|
|
248
|
+
} catch (err: any) {
|
|
249
|
+
console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
|
|
250
|
+
}
|
|
251
|
+
s.buildEnvDispose = null;
|
|
252
|
+
}
|
|
253
|
+
s.resolvedBuildEnv = undefined;
|
|
254
|
+
delete (globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY];
|
|
255
|
+
}
|
|
256
|
+
|
|
97
257
|
/**
|
|
98
258
|
* Plugin that discovers router instances at dev/build time via the RSC environment.
|
|
99
259
|
*
|
|
@@ -111,6 +271,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
111
271
|
opts?: PluginOptions,
|
|
112
272
|
): Plugin {
|
|
113
273
|
const s = createDiscoveryState(entryPath, opts);
|
|
274
|
+
let viteCommand: "serve" | "build" = "build";
|
|
275
|
+
let viteMode = "production";
|
|
114
276
|
|
|
115
277
|
return {
|
|
116
278
|
name: "@rangojs/router:discovery",
|
|
@@ -121,32 +283,20 @@ export function createRouterDiscoveryPlugin(
|
|
|
121
283
|
__RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG),
|
|
122
284
|
},
|
|
123
285
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
manualChunks(id: string) {
|
|
131
|
-
if (s.resolvedPrerenderModules?.has(id)) {
|
|
132
|
-
return "__prerender-handlers";
|
|
133
|
-
}
|
|
134
|
-
if (s.resolvedStaticModules?.has(id)) {
|
|
135
|
-
return "__static-handlers";
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
286
|
+
// Prerender/static handler modules are bundled naturally with the
|
|
287
|
+
// rest of the RSC entry. A previous design forced them into dedicated
|
|
288
|
+
// __prerender-handlers / __static-handlers chunks via manualChunks,
|
|
289
|
+
// but Rollup hoisted all shared dependencies into those chunks,
|
|
290
|
+
// inflating them to ~1 MB with active runtime code. Handler code is
|
|
291
|
+
// evicted in closeBundle regardless of which chunk it lands in.
|
|
144
292
|
return config;
|
|
145
293
|
},
|
|
146
294
|
|
|
147
295
|
configResolved(config) {
|
|
148
296
|
s.projectRoot = config.root;
|
|
149
297
|
s.isBuildMode = config.command === "build";
|
|
298
|
+
viteCommand = config.command as "serve" | "build";
|
|
299
|
+
viteMode = config.mode;
|
|
150
300
|
// Capture user's resolve aliases for the temp server
|
|
151
301
|
s.userResolveAlias = config.resolve.alias;
|
|
152
302
|
// Node preset: pick up auto-discovered router path from the config() hook.
|
|
@@ -167,13 +317,6 @@ export function createRouterDiscoveryPlugin(
|
|
|
167
317
|
s.resolvedEntryPath = entries[0];
|
|
168
318
|
}
|
|
169
319
|
}
|
|
170
|
-
// Compile include/exclude patterns into a scan filter
|
|
171
|
-
if (opts?.include || opts?.exclude) {
|
|
172
|
-
s.scanFilter = createScanFilter(s.projectRoot, {
|
|
173
|
-
include: opts.include,
|
|
174
|
-
exclude: opts.exclude,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
320
|
// Generate combined named-routes.gen.ts from static source parsing.
|
|
178
321
|
// Runs before the dev server starts so the gen file exists immediately for IDE.
|
|
179
322
|
// In build mode, the runtime discovery in buildStart produces the definitive
|
|
@@ -224,12 +367,13 @@ export function createRouterDiscoveryPlugin(
|
|
|
224
367
|
let prerenderTempServer: any = null;
|
|
225
368
|
let prerenderNodeRegistry: Map<string, any> | null = null;
|
|
226
369
|
|
|
227
|
-
// Clean up the temporary server when the dev server shuts down
|
|
370
|
+
// Clean up the temporary server and build env when the dev server shuts down
|
|
228
371
|
server.httpServer?.on("close", () => {
|
|
229
372
|
if (prerenderTempServer) {
|
|
230
373
|
prerenderTempServer.close().catch(() => {});
|
|
231
374
|
prerenderTempServer = null;
|
|
232
375
|
}
|
|
376
|
+
releaseBuildEnv(s).catch(() => {});
|
|
233
377
|
});
|
|
234
378
|
|
|
235
379
|
async function getOrCreateTempServer(): Promise<any | null> {
|
|
@@ -269,6 +413,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
269
413
|
// Create a temp Node.js server to run runtime discovery and generate
|
|
270
414
|
// named route types (static parser can't resolve factory calls).
|
|
271
415
|
try {
|
|
416
|
+
// Acquire build-time env bindings for dev prerender
|
|
417
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
418
|
+
|
|
272
419
|
const tempRscEnv = await getOrCreateTempServer();
|
|
273
420
|
if (tempRscEnv) {
|
|
274
421
|
await discoverRouters(s, tempRscEnv);
|
|
@@ -285,6 +432,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
285
432
|
}
|
|
286
433
|
|
|
287
434
|
try {
|
|
435
|
+
// Acquire build-time env bindings for dev prerender (Node.js path)
|
|
436
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
437
|
+
|
|
288
438
|
// Set the readiness gate BEFORE discovery so early requests
|
|
289
439
|
// block until manifest is populated
|
|
290
440
|
const serverMod = await rscEnv.runner.import(
|
|
@@ -389,9 +539,31 @@ export function createRouterDiscoveryPlugin(
|
|
|
389
539
|
return;
|
|
390
540
|
}
|
|
391
541
|
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
542
|
+
// Import the user's entry module to force re-evaluation of any
|
|
543
|
+
// HMR-invalidated modules in the chain (entry → router → urls → handlers).
|
|
544
|
+
// This ensures createRouter() re-runs with updated handler code before
|
|
545
|
+
// we read RouterRegistry. Without this, edits to prerender handler files
|
|
546
|
+
// produce stale content because the old router instance remains registered.
|
|
547
|
+
const rscEnv = (server.environments as any)?.rsc;
|
|
548
|
+
let registry: Map<string, any> | null = null;
|
|
549
|
+
if (rscEnv?.runner && s.resolvedEntryPath) {
|
|
550
|
+
try {
|
|
551
|
+
await rscEnv.runner.import(s.resolvedEntryPath);
|
|
552
|
+
const serverMod = await rscEnv.runner.import(
|
|
553
|
+
"@rangojs/router/server",
|
|
554
|
+
);
|
|
555
|
+
registry = serverMod.RouterRegistry ?? null;
|
|
556
|
+
} catch (err: any) {
|
|
557
|
+
console.warn(
|
|
558
|
+
`[rsc-router] Dev prerender module refresh failed: ${err.message}`,
|
|
559
|
+
);
|
|
560
|
+
res.statusCode = 500;
|
|
561
|
+
res.end(`Prerender handler error: ${err.message}`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
registry = mainRegistry;
|
|
566
|
+
}
|
|
395
567
|
|
|
396
568
|
if (!registry) {
|
|
397
569
|
// No main registry: the RSC env has no module runner (Cloudflare dev).
|
|
@@ -420,6 +592,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
420
592
|
{},
|
|
421
593
|
undefined,
|
|
422
594
|
wantPassthrough,
|
|
595
|
+
s.resolvedBuildEnv,
|
|
596
|
+
true, // devMode: check getParams for passthrough routes
|
|
423
597
|
);
|
|
424
598
|
if (!result) continue;
|
|
425
599
|
if (result.passthrough) continue;
|
|
@@ -604,6 +778,12 @@ export function createRouterDiscoveryPlugin(
|
|
|
604
778
|
if (!s.isBuildMode) return;
|
|
605
779
|
// Only run once across environment builds
|
|
606
780
|
if (s.mergedRouteManifest !== null) return;
|
|
781
|
+
resetStagedBuildAssets(s.projectRoot);
|
|
782
|
+
s.prerenderManifestEntries = null;
|
|
783
|
+
s.staticManifestEntries = null;
|
|
784
|
+
|
|
785
|
+
// Acquire build-time env bindings if configured
|
|
786
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
607
787
|
|
|
608
788
|
let tempServer: any = null;
|
|
609
789
|
// Signal to user-space code (e.g. reverse.ts) that build-time discovery
|
|
@@ -663,6 +843,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
663
843
|
if (tempServer) {
|
|
664
844
|
await tempServer.close();
|
|
665
845
|
}
|
|
846
|
+
await releaseBuildEnv(s);
|
|
666
847
|
}
|
|
667
848
|
},
|
|
668
849
|
|
|
@@ -723,33 +904,40 @@ export function createRouterDiscoveryPlugin(
|
|
|
723
904
|
if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
|
|
724
905
|
return;
|
|
725
906
|
|
|
907
|
+
// Clear maps at the start of each RSC generateBundle pass.
|
|
908
|
+
// Vite 6 multi-environment builds run RSC twice (analysis + production);
|
|
909
|
+
// clearing prevents stale/duplicate records from the analysis pass.
|
|
910
|
+
s.handlerChunkInfoMap.clear();
|
|
911
|
+
s.staticHandlerChunkInfoMap.clear();
|
|
912
|
+
|
|
726
913
|
for (const [fileName, chunk] of Object.entries(bundle) as [
|
|
727
914
|
string,
|
|
728
915
|
any,
|
|
729
916
|
][]) {
|
|
730
917
|
if (chunk.type !== "chunk") continue;
|
|
731
918
|
|
|
732
|
-
//
|
|
733
|
-
if (
|
|
734
|
-
fileName.includes("__prerender-handlers") &&
|
|
735
|
-
s.resolvedPrerenderModules?.size
|
|
736
|
-
) {
|
|
919
|
+
// Scan all chunks for handler exports (handlers may land in any chunk)
|
|
920
|
+
if (s.resolvedPrerenderModules?.size) {
|
|
737
921
|
const handlers = extractHandlerExportsFromChunk(
|
|
738
922
|
chunk.code,
|
|
739
923
|
s.resolvedPrerenderModules,
|
|
740
924
|
"Prerender",
|
|
741
|
-
|
|
925
|
+
false,
|
|
742
926
|
);
|
|
743
927
|
if (handlers.length > 0) {
|
|
744
|
-
|
|
928
|
+
const existing = s.handlerChunkInfoMap.get(fileName);
|
|
929
|
+
if (existing) {
|
|
930
|
+
existing.exports.push(...handlers);
|
|
931
|
+
} else {
|
|
932
|
+
s.handlerChunkInfoMap.set(fileName, {
|
|
933
|
+
fileName,
|
|
934
|
+
exports: handlers,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
745
937
|
}
|
|
746
938
|
}
|
|
747
939
|
|
|
748
|
-
|
|
749
|
-
if (
|
|
750
|
-
fileName.includes("__static-handlers") &&
|
|
751
|
-
s.resolvedStaticModules?.size
|
|
752
|
-
) {
|
|
940
|
+
if (s.resolvedStaticModules?.size) {
|
|
753
941
|
const handlers = extractHandlerExportsFromChunk(
|
|
754
942
|
chunk.code,
|
|
755
943
|
s.resolvedStaticModules,
|
|
@@ -757,7 +945,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
757
945
|
false,
|
|
758
946
|
);
|
|
759
947
|
if (handlers.length > 0) {
|
|
760
|
-
|
|
948
|
+
const existing = s.staticHandlerChunkInfoMap.get(fileName);
|
|
949
|
+
if (existing) {
|
|
950
|
+
existing.exports.push(...handlers);
|
|
951
|
+
} else {
|
|
952
|
+
s.staticHandlerChunkInfoMap.set(fileName, {
|
|
953
|
+
fileName,
|
|
954
|
+
exports: handlers,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
761
957
|
}
|
|
762
958
|
}
|
|
763
959
|
}
|
package/src/vite/utils/banner.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import packageJson from "../../../package.json"
|
|
1
|
+
import packageJson from "../../../package.json";
|
|
2
2
|
|
|
3
3
|
export const rangoVersion: string = packageJson.version;
|
|
4
4
|
|
|
@@ -23,11 +23,11 @@ ${dim} ╱${reset} ${bold}╔═╗${reset}${dim} * ╱
|
|
|
23
23
|
${dim} ${reset}${bold}║ ║${reset} ${bold}╔═╗${reset}${dim} * ✧. ╱${reset}
|
|
24
24
|
${dim} ${reset}${bold}╔╗ ║ ║ ║ ║${reset}${dim} * ╱${reset}
|
|
25
25
|
${dim} ${reset}${bold}║║ ║ ║ ║ ║ ╦═╗╔═╗╔╗╔╔═╗╔═╗${reset}${dim} ✧ ✦${reset}
|
|
26
|
-
${dim}
|
|
26
|
+
${dim} ${reset}${bold}║║ ║ ╠═╝ ║ ╠╦╝╠═╣║║║║ ╦║ ║${reset}${dim} * ✧${reset}
|
|
27
27
|
${dim} ${reset}${bold}║╚═╝ ╔═══╝ ╩╚═╩ ╩╝╚╝╚═╝╚═╝${reset}${dim} ✦ . *${reset}
|
|
28
28
|
${dim} ${reset}${bold}╚══╗ ║${reset}${dim} * RSC Wrangler ✧ ✦${reset}
|
|
29
|
-
${dim} * ${reset}${bold}║
|
|
30
|
-
${bold}
|
|
29
|
+
${dim} * ${reset}${bold}║ ║${reset}${dim} * ✧. ╱${reset}
|
|
30
|
+
${dim} ${reset}${bold}═══╝ ╚════${reset}${dim} ✦ *${reset}
|
|
31
31
|
|
|
32
32
|
v${version} · ${preset} · ${mode}
|
|
33
33
|
`;
|
|
@@ -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,33 @@ 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
|
+
const specs = [
|
|
141
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
|
|
142
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
|
|
143
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
|
|
144
|
+
];
|
|
145
|
+
const aliases: Record<string, string> = {};
|
|
146
|
+
for (const spec of specs) {
|
|
147
|
+
try {
|
|
148
|
+
aliases[spec] = require.resolve(spec);
|
|
149
|
+
} catch {
|
|
150
|
+
// Spec unresolvable (unexpected but non-fatal — Vite will warn as before).
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return aliases;
|
|
154
|
+
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* Escape special RegExp characters in a string for safe interpolation
|
|
3
14
|
* into new RegExp() patterns.
|
|
@@ -20,6 +31,7 @@ export function encodePathParam(value: unknown): string {
|
|
|
20
31
|
/**
|
|
21
32
|
* Substitute route params into a pattern, stripping constraint and optional
|
|
22
33
|
* syntax (:param(a|b)? -> value). Also handles wildcard params (*key).
|
|
34
|
+
* Optional params not present in `params` are removed from the output.
|
|
23
35
|
*/
|
|
24
36
|
export function substituteRouteParams(
|
|
25
37
|
pattern: string,
|
|
@@ -27,14 +39,45 @@ export function substituteRouteParams(
|
|
|
27
39
|
encode: (value: string) => string = encodeURIComponent,
|
|
28
40
|
): string {
|
|
29
41
|
let result = pattern;
|
|
42
|
+
let hadOmittedOptional = false;
|
|
43
|
+
|
|
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.
|
|
30
49
|
for (const [key, value] of Object.entries(params)) {
|
|
31
50
|
const escaped = escapeRegExp(key);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Second pass: strip remaining optional param placeholders not in params
|
|
69
|
+
result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
|
|
70
|
+
hadOmittedOptional = true;
|
|
71
|
+
return "";
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Clean up slashes from omitted optional segments
|
|
75
|
+
if (hadOmittedOptional) {
|
|
76
|
+
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
77
|
+
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
78
|
+
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
37
79
|
}
|
|
80
|
+
|
|
38
81
|
return result;
|
|
39
82
|
}
|
|
40
83
|
|
|
@@ -127,3 +170,52 @@ export function notifyOnError(
|
|
|
127
170
|
break; // Only notify the first router with onError
|
|
128
171
|
}
|
|
129
172
|
}
|
|
173
|
+
|
|
174
|
+
function getStagedAssetDir(projectRoot: string): string {
|
|
175
|
+
return resolve(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function resetStagedBuildAssets(projectRoot: string): void {
|
|
179
|
+
rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function stageBuildAssetModule(
|
|
183
|
+
projectRoot: string,
|
|
184
|
+
prefix: "__pr" | "__st",
|
|
185
|
+
exportValue: string,
|
|
186
|
+
): string {
|
|
187
|
+
const stagedDir = getStagedAssetDir(projectRoot);
|
|
188
|
+
mkdirSync(stagedDir, { recursive: true });
|
|
189
|
+
|
|
190
|
+
const contentHash = createHash("sha256")
|
|
191
|
+
.update(exportValue)
|
|
192
|
+
.digest("hex")
|
|
193
|
+
.slice(0, 8);
|
|
194
|
+
const fileName = `${prefix}-${contentHash}.js`;
|
|
195
|
+
const filePath = resolve(stagedDir, fileName);
|
|
196
|
+
|
|
197
|
+
if (!existsSync(filePath)) {
|
|
198
|
+
writeFileSync(filePath, `export default ${exportValue};\n`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return fileName;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function copyStagedBuildAssets(
|
|
205
|
+
projectRoot: string,
|
|
206
|
+
fileNames: Iterable<string>,
|
|
207
|
+
): number {
|
|
208
|
+
const stagedDir = getStagedAssetDir(projectRoot);
|
|
209
|
+
const distAssetsDir = resolve(projectRoot, "dist/rsc/assets");
|
|
210
|
+
mkdirSync(distAssetsDir, { recursive: true });
|
|
211
|
+
|
|
212
|
+
let totalBytes = 0;
|
|
213
|
+
for (const fileName of new Set(fileNames)) {
|
|
214
|
+
const stagedPath = resolve(stagedDir, fileName);
|
|
215
|
+
const distPath = resolve(distAssetsDir, fileName);
|
|
216
|
+
copyFileSync(stagedPath, distPath);
|
|
217
|
+
totalBytes += statSync(stagedPath).size;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return totalBytes;
|
|
221
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import * as Vite from "vite";
|
|
3
3
|
import { getPublishedPackageName } from "./package-resolution.js";
|
|
4
|
+
import { performanceTracksOptimizeDepsPlugin } from "../plugins/performance-tracks.js";
|
|
4
5
|
import {
|
|
5
6
|
VIRTUAL_ENTRY_BROWSER,
|
|
6
7
|
VIRTUAL_ENTRY_SSR,
|
|
@@ -35,9 +36,9 @@ const versionEsbuildPlugin = {
|
|
|
35
36
|
* Includes the version stub plugin for all environments.
|
|
36
37
|
*/
|
|
37
38
|
export const sharedEsbuildOptions: {
|
|
38
|
-
plugins:
|
|
39
|
+
plugins: any[];
|
|
39
40
|
} = {
|
|
40
|
-
plugins: [versionEsbuildPlugin],
|
|
41
|
+
plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()],
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
/**
|