@rangojs/router 0.0.0-experimental.8874d8d2 → 0.0.0-experimental.89
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 +126 -38
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1705 -701
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +19 -16
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- 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 +91 -17
- package/skills/loader/SKILL.md +88 -45
- 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 +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +55 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +13 -1
- package/src/__internal.ts +1 -1
- 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/navigation-bridge.ts +90 -16
- package/src/browser/navigation-client.ts +167 -59
- 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 +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- 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 +81 -9
- 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 +168 -65
- 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 +49 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- 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.tsx +84 -230
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- 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 -0
- 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 +101 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +127 -192
- 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 +104 -10
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +8 -30
- package/src/router/middleware.ts +36 -10
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- 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 +198 -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 +437 -297
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +10 -4
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +60 -8
- package/src/rsc/handler.ts +478 -374
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +16 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +17 -1
- package/src/rsc/server-action.ts +10 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- 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 +166 -17
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +194 -60
- 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 -65
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -0
- 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/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +36 -4
- package/src/vite/discovery/prerender-collection.ts +175 -74
- 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/cjs-to-esm.ts +5 -0
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +16 -4
- 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 +53 -31
- 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-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +563 -316
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +56 -43
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +393 -67
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -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,
|
|
@@ -43,9 +48,58 @@ import {
|
|
|
43
48
|
} from "./discovery/virtual-module-codegen.js";
|
|
44
49
|
import { postprocessBundle } from "./discovery/bundle-postprocess.js";
|
|
45
50
|
import { resetStagedBuildAssets } from "./utils/prerender-utils.js";
|
|
51
|
+
import { createRangoDebugger, timed, timedSync, NS } from "./debug.js";
|
|
52
|
+
|
|
53
|
+
const debugDiscovery = createRangoDebugger(NS.discovery);
|
|
54
|
+
const debugRoutes = createRangoDebugger(NS.routes);
|
|
55
|
+
const debugBuild = createRangoDebugger(NS.build);
|
|
56
|
+
const debugDev = createRangoDebugger(NS.dev);
|
|
46
57
|
|
|
47
58
|
export { VIRTUAL_ROUTES_MANIFEST_ID };
|
|
48
59
|
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Node ESM Loader Hook Registration
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Registers a Node ESM loader hook that resolves `cloudflare:*` specifiers
|
|
66
|
+
* to a data: URL stub. Defense-in-depth alongside the Vite transform in
|
|
67
|
+
* `cloudflare-protocol-stub.ts`:
|
|
68
|
+
*
|
|
69
|
+
* - The Vite transform catches `cloudflare:*` imports in modules that flow
|
|
70
|
+
* through Vite's plugin pipeline. That's the vast majority of cases.
|
|
71
|
+
* - The Node loader catches imports in modules that Vite/Rollup externalize
|
|
72
|
+
* (e.g. the `partyserver` package, which has a top-level
|
|
73
|
+
* `import { DurableObject, env } from "cloudflare:workers"` and ships
|
|
74
|
+
* shapes plugin-rsc marks as external). Externalized modules are loaded
|
|
75
|
+
* via Node's native ESM loader, which rejects URL schemes.
|
|
76
|
+
*
|
|
77
|
+
* Registration is process-global and one-shot. The hook only intercepts
|
|
78
|
+
* `cloudflare:*` specifiers; everything else passes through via
|
|
79
|
+
* `nextResolve()`. It runs in a separate worker thread (Node ESM loader
|
|
80
|
+
* architecture), so it can't read the `globalThis[BUILD_ENV_GLOBAL_KEY]`
|
|
81
|
+
* bridge that the Vite transform uses — the stubs served here always
|
|
82
|
+
* return `env = {}`. That's fine because externalized libraries don't
|
|
83
|
+
* typically access `env` at module top level; user source (where real
|
|
84
|
+
* `env` matters at build time) flows through the Vite transform.
|
|
85
|
+
*/
|
|
86
|
+
let loaderHookRegistered = false;
|
|
87
|
+
function ensureCloudflareProtocolLoaderRegistered(): void {
|
|
88
|
+
if (loaderHookRegistered) return;
|
|
89
|
+
loaderHookRegistered = true;
|
|
90
|
+
try {
|
|
91
|
+
register(
|
|
92
|
+
new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url),
|
|
93
|
+
);
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
// register() requires Node 18.19+ / 20.6+. Older Node still has the
|
|
96
|
+
// Vite transform as primary defense.
|
|
97
|
+
console.warn(
|
|
98
|
+
`[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
49
103
|
// ============================================================================
|
|
50
104
|
// Temp Server Factory
|
|
51
105
|
// ============================================================================
|
|
@@ -65,6 +119,11 @@ async function createTempRscServer(
|
|
|
65
119
|
state: DiscoveryState,
|
|
66
120
|
options: { forceBuild?: boolean; cacheDir?: string } = {},
|
|
67
121
|
) {
|
|
122
|
+
// Install the Node ESM loader hook before any module evaluation so
|
|
123
|
+
// `cloudflare:*` specifiers in externalized/loader-delegated modules
|
|
124
|
+
// (e.g. packages plugin-rsc marks as external) resolve to stubs
|
|
125
|
+
// instead of crashing Node's native loader.
|
|
126
|
+
ensureCloudflareProtocolLoaderRegistered();
|
|
68
127
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
69
128
|
return createViteServer({
|
|
70
129
|
root: state.projectRoot,
|
|
@@ -87,6 +146,7 @@ async function createTempRscServer(
|
|
|
87
146
|
...(options.forceBuild ? [hashClientRefs(state.projectRoot)] : []),
|
|
88
147
|
createVersionPlugin(),
|
|
89
148
|
createVirtualStubPlugin(),
|
|
149
|
+
createCloudflareProtocolStubPlugin(),
|
|
90
150
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
91
151
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
92
152
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
@@ -95,6 +155,111 @@ async function createTempRscServer(
|
|
|
95
155
|
});
|
|
96
156
|
}
|
|
97
157
|
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Build-Time Env Resolution
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
import type {
|
|
163
|
+
BuildEnvOption,
|
|
164
|
+
BuildEnvFactoryContext,
|
|
165
|
+
BuildEnvResult,
|
|
166
|
+
} from "./plugin-types.js";
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the buildEnv option into a concrete { env, dispose? } result.
|
|
170
|
+
* Handles all four input shapes: false, "auto", factory, plain object.
|
|
171
|
+
*/
|
|
172
|
+
async function resolveBuildEnv(
|
|
173
|
+
option: BuildEnvOption | undefined,
|
|
174
|
+
factoryCtx: BuildEnvFactoryContext,
|
|
175
|
+
): Promise<BuildEnvResult | null> {
|
|
176
|
+
if (!option) return null;
|
|
177
|
+
|
|
178
|
+
if (option === "auto") {
|
|
179
|
+
if (factoryCtx.preset !== "cloudflare") {
|
|
180
|
+
throw new Error(
|
|
181
|
+
'[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
|
|
182
|
+
"Use a factory function or plain object for other presets.",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
// Resolve wrangler from the user's project root (not the router package)
|
|
187
|
+
const userRequire = createRequire(
|
|
188
|
+
resolve(factoryCtx.root, "package.json"),
|
|
189
|
+
);
|
|
190
|
+
const wranglerPath = userRequire.resolve("wrangler");
|
|
191
|
+
const { getPlatformProxy } = (await import(
|
|
192
|
+
pathToFileURL(wranglerPath).href
|
|
193
|
+
)) as {
|
|
194
|
+
getPlatformProxy: (opts?: any) => Promise<any>;
|
|
195
|
+
};
|
|
196
|
+
const proxy = await getPlatformProxy();
|
|
197
|
+
return {
|
|
198
|
+
env: proxy.env as Record<string, unknown>,
|
|
199
|
+
dispose: proxy.dispose,
|
|
200
|
+
};
|
|
201
|
+
} catch (err: any) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
'[rsc-router] buildEnv: "auto" requires wrangler to be installed.\n' +
|
|
204
|
+
`Install it with: pnpm add -D wrangler\n${err.message}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof option === "function") {
|
|
210
|
+
return await option(factoryCtx);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Plain object
|
|
214
|
+
return { env: option };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Acquire build-time env bindings and store on discovery state.
|
|
219
|
+
* Returns true if env was acquired, false if buildEnv is disabled.
|
|
220
|
+
*/
|
|
221
|
+
async function acquireBuildEnv(
|
|
222
|
+
s: DiscoveryState,
|
|
223
|
+
command: "serve" | "build",
|
|
224
|
+
mode: string,
|
|
225
|
+
): Promise<boolean> {
|
|
226
|
+
const option = s.opts?.buildEnv;
|
|
227
|
+
if (!option) return false;
|
|
228
|
+
|
|
229
|
+
const result = await resolveBuildEnv(option, {
|
|
230
|
+
root: s.projectRoot,
|
|
231
|
+
mode,
|
|
232
|
+
command,
|
|
233
|
+
preset: s.opts?.preset ?? "node",
|
|
234
|
+
});
|
|
235
|
+
if (!result) return false;
|
|
236
|
+
|
|
237
|
+
s.resolvedBuildEnv = result.env;
|
|
238
|
+
s.buildEnvDispose = result.dispose ?? null;
|
|
239
|
+
// Bridge the resolved env into `cloudflare:workers`'s stubbed `env`
|
|
240
|
+
// export so user code that does `import { env } from "cloudflare:workers"`
|
|
241
|
+
// sees the real bindings proxy during discovery + prerender instead of
|
|
242
|
+
// an empty object. The stub reads this global at module-evaluation time.
|
|
243
|
+
(globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY] = result.env;
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Release build-time env resources and clear state.
|
|
249
|
+
*/
|
|
250
|
+
async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
|
|
251
|
+
if (s.buildEnvDispose) {
|
|
252
|
+
try {
|
|
253
|
+
await s.buildEnvDispose();
|
|
254
|
+
} catch (err: any) {
|
|
255
|
+
console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
s.buildEnvDispose = null;
|
|
258
|
+
}
|
|
259
|
+
s.resolvedBuildEnv = undefined;
|
|
260
|
+
delete (globalThis as Record<string, unknown>)[BUILD_ENV_GLOBAL_KEY];
|
|
261
|
+
}
|
|
262
|
+
|
|
98
263
|
/**
|
|
99
264
|
* Plugin that discovers router instances at dev/build time via the RSC environment.
|
|
100
265
|
*
|
|
@@ -112,6 +277,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
112
277
|
opts?: PluginOptions,
|
|
113
278
|
): Plugin {
|
|
114
279
|
const s = createDiscoveryState(entryPath, opts);
|
|
280
|
+
let viteCommand: "serve" | "build" = "build";
|
|
281
|
+
let viteMode = "production";
|
|
115
282
|
|
|
116
283
|
return {
|
|
117
284
|
name: "@rangojs/router:discovery",
|
|
@@ -122,32 +289,20 @@ export function createRouterDiscoveryPlugin(
|
|
|
122
289
|
__RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG),
|
|
123
290
|
},
|
|
124
291
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
manualChunks(id: string) {
|
|
132
|
-
if (s.resolvedPrerenderModules?.has(id)) {
|
|
133
|
-
return "__prerender-handlers";
|
|
134
|
-
}
|
|
135
|
-
if (s.resolvedStaticModules?.has(id)) {
|
|
136
|
-
return "__static-handlers";
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
292
|
+
// Prerender/static handler modules are bundled naturally with the
|
|
293
|
+
// rest of the RSC entry. A previous design forced them into dedicated
|
|
294
|
+
// __prerender-handlers / __static-handlers chunks via manualChunks,
|
|
295
|
+
// but Rollup hoisted all shared dependencies into those chunks,
|
|
296
|
+
// inflating them to ~1 MB with active runtime code. Handler code is
|
|
297
|
+
// evicted in closeBundle regardless of which chunk it lands in.
|
|
145
298
|
return config;
|
|
146
299
|
},
|
|
147
300
|
|
|
148
301
|
configResolved(config) {
|
|
149
302
|
s.projectRoot = config.root;
|
|
150
303
|
s.isBuildMode = config.command === "build";
|
|
304
|
+
viteCommand = config.command as "serve" | "build";
|
|
305
|
+
viteMode = config.mode;
|
|
151
306
|
// Capture user's resolve aliases for the temp server
|
|
152
307
|
s.userResolveAlias = config.resolve.alias;
|
|
153
308
|
// Node preset: pick up auto-discovered router path from the config() hook.
|
|
@@ -168,13 +323,6 @@ export function createRouterDiscoveryPlugin(
|
|
|
168
323
|
s.resolvedEntryPath = entries[0];
|
|
169
324
|
}
|
|
170
325
|
}
|
|
171
|
-
// Compile include/exclude patterns into a scan filter
|
|
172
|
-
if (opts?.include || opts?.exclude) {
|
|
173
|
-
s.scanFilter = createScanFilter(s.projectRoot, {
|
|
174
|
-
include: opts.include,
|
|
175
|
-
exclude: opts.exclude,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
326
|
// Generate combined named-routes.gen.ts from static source parsing.
|
|
179
327
|
// Runs before the dev server starts so the gen file exists immediately for IDE.
|
|
180
328
|
// In build mode, the runtime discovery in buildStart produces the definitive
|
|
@@ -225,12 +373,13 @@ export function createRouterDiscoveryPlugin(
|
|
|
225
373
|
let prerenderTempServer: any = null;
|
|
226
374
|
let prerenderNodeRegistry: Map<string, any> | null = null;
|
|
227
375
|
|
|
228
|
-
// Clean up the temporary server when the dev server shuts down
|
|
376
|
+
// Clean up the temporary server and build env when the dev server shuts down
|
|
229
377
|
server.httpServer?.on("close", () => {
|
|
230
378
|
if (prerenderTempServer) {
|
|
231
379
|
prerenderTempServer.close().catch(() => {});
|
|
232
380
|
prerenderTempServer = null;
|
|
233
381
|
}
|
|
382
|
+
releaseBuildEnv(s).catch(() => {});
|
|
234
383
|
});
|
|
235
384
|
|
|
236
385
|
async function getOrCreateTempServer(): Promise<any | null> {
|
|
@@ -260,20 +409,35 @@ export function createRouterDiscoveryPlugin(
|
|
|
260
409
|
}
|
|
261
410
|
|
|
262
411
|
const discover = async () => {
|
|
412
|
+
const discoverStart = performance.now();
|
|
263
413
|
const rscEnv = (server.environments as any)?.rsc;
|
|
264
414
|
if (!rscEnv?.runner) {
|
|
265
415
|
// Cloudflare dev: no module runner available (workerd-based RSC env).
|
|
266
416
|
// Set devServerOrigin so the virtual module can inject __PRERENDER_DEV_URL
|
|
267
417
|
// for on-demand prerender via the /__rsc_prerender endpoint.
|
|
418
|
+
debugDiscovery?.("dev: no rsc runner (cloudflare path)");
|
|
268
419
|
s.devServerOrigin = getDevServerOrigin();
|
|
269
420
|
|
|
270
421
|
// Create a temp Node.js server to run runtime discovery and generate
|
|
271
422
|
// named route types (static parser can't resolve factory calls).
|
|
272
423
|
try {
|
|
273
|
-
|
|
424
|
+
// Acquire build-time env bindings for dev prerender
|
|
425
|
+
await timed(debugDiscovery, "acquireBuildEnv", () =>
|
|
426
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const tempRscEnv = await timed(
|
|
430
|
+
debugDiscovery,
|
|
431
|
+
"getOrCreateTempServer",
|
|
432
|
+
() => getOrCreateTempServer(),
|
|
433
|
+
);
|
|
274
434
|
if (tempRscEnv) {
|
|
275
|
-
await discoverRouters(
|
|
276
|
-
|
|
435
|
+
await timed(debugDiscovery, "discoverRouters (cloudflare)", () =>
|
|
436
|
+
discoverRouters(s, tempRscEnv),
|
|
437
|
+
);
|
|
438
|
+
timedSync(debugDiscovery, "writeRouteTypesFiles", () =>
|
|
439
|
+
writeRouteTypesFiles(s),
|
|
440
|
+
);
|
|
277
441
|
}
|
|
278
442
|
} catch (err: any) {
|
|
279
443
|
console.warn(
|
|
@@ -281,21 +445,35 @@ export function createRouterDiscoveryPlugin(
|
|
|
281
445
|
);
|
|
282
446
|
}
|
|
283
447
|
|
|
448
|
+
debugDiscovery?.(
|
|
449
|
+
"dev discovery done (%sms)",
|
|
450
|
+
(performance.now() - discoverStart).toFixed(1),
|
|
451
|
+
);
|
|
284
452
|
resolveDiscovery!();
|
|
285
453
|
return;
|
|
286
454
|
}
|
|
287
455
|
|
|
288
456
|
try {
|
|
457
|
+
// Acquire build-time env bindings for dev prerender (Node.js path)
|
|
458
|
+
debugDiscovery?.("dev: node path start");
|
|
459
|
+
await timed(debugDiscovery, "acquireBuildEnv", () =>
|
|
460
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
461
|
+
);
|
|
462
|
+
|
|
289
463
|
// Set the readiness gate BEFORE discovery so early requests
|
|
290
464
|
// block until manifest is populated
|
|
291
|
-
const serverMod = await
|
|
292
|
-
|
|
465
|
+
const serverMod = await timed(
|
|
466
|
+
debugDiscovery,
|
|
467
|
+
"import @rangojs/router/server",
|
|
468
|
+
() => rscEnv.runner.import("@rangojs/router/server"),
|
|
293
469
|
);
|
|
294
470
|
if (serverMod?.setManifestReadyPromise) {
|
|
295
471
|
serverMod.setManifestReadyPromise(discoveryPromise);
|
|
296
472
|
}
|
|
297
473
|
|
|
298
|
-
await
|
|
474
|
+
await timed(debugDiscovery, "discoverRouters", () =>
|
|
475
|
+
discoverRouters(s, rscEnv),
|
|
476
|
+
);
|
|
299
477
|
|
|
300
478
|
// Store server origin for dev prerender endpoint (virtual module injection)
|
|
301
479
|
s.devServerOrigin = getDevServerOrigin();
|
|
@@ -305,15 +483,23 @@ export function createRouterDiscoveryPlugin(
|
|
|
305
483
|
// routes (e.g. Array.from loops) that the static parser cannot see.
|
|
306
484
|
// writeRouteTypesFiles() only writes when content changes, so this
|
|
307
485
|
// won't cause unnecessary HMR triggers.
|
|
308
|
-
writeRouteTypesFiles(
|
|
486
|
+
timedSync(debugDiscovery, "writeRouteTypesFiles", () =>
|
|
487
|
+
writeRouteTypesFiles(s),
|
|
488
|
+
);
|
|
309
489
|
|
|
310
490
|
// Populate the route map and per-router data in the RSC env
|
|
311
|
-
await propagateDiscoveryState(
|
|
491
|
+
await timed(debugDiscovery, "propagateDiscoveryState", () =>
|
|
492
|
+
propagateDiscoveryState(rscEnv),
|
|
493
|
+
);
|
|
312
494
|
} catch (err: any) {
|
|
313
495
|
console.warn(
|
|
314
496
|
`[rsc-router] Router discovery failed: ${err.message}\n${err.stack}`,
|
|
315
497
|
);
|
|
316
498
|
} finally {
|
|
499
|
+
debugDiscovery?.(
|
|
500
|
+
"dev discovery done (%sms)",
|
|
501
|
+
(performance.now() - discoverStart).toFixed(1),
|
|
502
|
+
);
|
|
317
503
|
resolveDiscovery!();
|
|
318
504
|
}
|
|
319
505
|
};
|
|
@@ -380,6 +566,17 @@ export function createRouterDiscoveryPlugin(
|
|
|
380
566
|
};
|
|
381
567
|
|
|
382
568
|
server.middlewares.use("/__rsc_prerender", async (req: any, res: any) => {
|
|
569
|
+
const reqStart = debugDev ? performance.now() : 0;
|
|
570
|
+
const logResult = (status: number, note: string) => {
|
|
571
|
+
debugDev?.(
|
|
572
|
+
"/__rsc_prerender %s -> %d %s (%sms)",
|
|
573
|
+
req.url,
|
|
574
|
+
status,
|
|
575
|
+
note,
|
|
576
|
+
(performance.now() - reqStart).toFixed(1),
|
|
577
|
+
);
|
|
578
|
+
};
|
|
579
|
+
|
|
383
580
|
if (s.discoveryDone) await s.discoveryDone;
|
|
384
581
|
|
|
385
582
|
const url = new URL(req.url || "/", "http://localhost");
|
|
@@ -387,12 +584,36 @@ export function createRouterDiscoveryPlugin(
|
|
|
387
584
|
if (!pathname) {
|
|
388
585
|
res.statusCode = 400;
|
|
389
586
|
res.end("Missing pathname");
|
|
587
|
+
logResult(400, "missing pathname");
|
|
390
588
|
return;
|
|
391
589
|
}
|
|
392
590
|
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
591
|
+
// Import the user's entry module to force re-evaluation of any
|
|
592
|
+
// HMR-invalidated modules in the chain (entry → router → urls → handlers).
|
|
593
|
+
// This ensures createRouter() re-runs with updated handler code before
|
|
594
|
+
// we read RouterRegistry. Without this, edits to prerender handler files
|
|
595
|
+
// produce stale content because the old router instance remains registered.
|
|
596
|
+
const rscEnv = (server.environments as any)?.rsc;
|
|
597
|
+
let registry: Map<string, any> | null = null;
|
|
598
|
+
if (rscEnv?.runner && s.resolvedEntryPath) {
|
|
599
|
+
try {
|
|
600
|
+
await rscEnv.runner.import(s.resolvedEntryPath);
|
|
601
|
+
const serverMod = await rscEnv.runner.import(
|
|
602
|
+
"@rangojs/router/server",
|
|
603
|
+
);
|
|
604
|
+
registry = serverMod.RouterRegistry ?? null;
|
|
605
|
+
} catch (err: any) {
|
|
606
|
+
console.warn(
|
|
607
|
+
`[rsc-router] Dev prerender module refresh failed: ${err.message}`,
|
|
608
|
+
);
|
|
609
|
+
res.statusCode = 500;
|
|
610
|
+
res.end(`Prerender handler error: ${err.message}`);
|
|
611
|
+
logResult(500, "module refresh failed");
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
registry = mainRegistry;
|
|
616
|
+
}
|
|
396
617
|
|
|
397
618
|
if (!registry) {
|
|
398
619
|
// No main registry: the RSC env has no module runner (Cloudflare dev).
|
|
@@ -406,6 +627,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
406
627
|
if (!registry || registry.size === 0) {
|
|
407
628
|
res.statusCode = 503;
|
|
408
629
|
res.end("Prerender runner not available");
|
|
630
|
+
logResult(503, "no registry");
|
|
409
631
|
return;
|
|
410
632
|
}
|
|
411
633
|
|
|
@@ -421,6 +643,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
421
643
|
{},
|
|
422
644
|
undefined,
|
|
423
645
|
wantPassthrough,
|
|
646
|
+
s.resolvedBuildEnv,
|
|
647
|
+
true, // devMode: check getParams for passthrough routes
|
|
424
648
|
);
|
|
425
649
|
if (!result) continue;
|
|
426
650
|
if (result.passthrough) continue;
|
|
@@ -442,6 +666,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
442
666
|
payload = { segments: result.segments, handles: result.handles };
|
|
443
667
|
}
|
|
444
668
|
res.end(JSON.stringify(payload));
|
|
669
|
+
logResult(200, `match ${result.routeName}`);
|
|
445
670
|
return;
|
|
446
671
|
} catch (err: any) {
|
|
447
672
|
console.warn(
|
|
@@ -452,6 +677,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
452
677
|
|
|
453
678
|
res.statusCode = 404;
|
|
454
679
|
res.end("No prerender match");
|
|
680
|
+
logResult(404, "no match");
|
|
455
681
|
});
|
|
456
682
|
|
|
457
683
|
// Watch url module and router files for changes and regenerate named-routes.gen.ts.
|
|
@@ -499,15 +725,26 @@ export function createRouterDiscoveryPlugin(
|
|
|
499
725
|
const rscEnv = (server.environments as any)?.rsc;
|
|
500
726
|
if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
|
|
501
727
|
runtimeRediscoveryInProgress = true;
|
|
728
|
+
const hmrStart = performance.now();
|
|
502
729
|
try {
|
|
503
|
-
await
|
|
504
|
-
|
|
505
|
-
|
|
730
|
+
await timed(debugDiscovery, "hmr discoverRouters", () =>
|
|
731
|
+
discoverRouters(s, rscEnv),
|
|
732
|
+
);
|
|
733
|
+
timedSync(debugDiscovery, "hmr writeRouteTypesFiles", () =>
|
|
734
|
+
writeRouteTypesFiles(s),
|
|
735
|
+
);
|
|
736
|
+
await timed(debugDiscovery, "hmr propagateDiscoveryState", () =>
|
|
737
|
+
propagateDiscoveryState(rscEnv),
|
|
738
|
+
);
|
|
506
739
|
} catch (err: any) {
|
|
507
740
|
console.warn(
|
|
508
741
|
`[rsc-router] Runtime re-discovery failed: ${err.message}`,
|
|
509
742
|
);
|
|
510
743
|
} finally {
|
|
744
|
+
debugDiscovery?.(
|
|
745
|
+
"hmr re-discovery done (%sms)",
|
|
746
|
+
(performance.now() - hmrStart).toFixed(1),
|
|
747
|
+
);
|
|
511
748
|
runtimeRediscoveryInProgress = false;
|
|
512
749
|
}
|
|
513
750
|
};
|
|
@@ -516,6 +753,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
516
753
|
clearTimeout(routeChangeTimer);
|
|
517
754
|
routeChangeTimer = setTimeout(() => {
|
|
518
755
|
routeChangeTimer = undefined;
|
|
756
|
+
const regenStart = debugDiscovery ? performance.now() : 0;
|
|
519
757
|
try {
|
|
520
758
|
writeCombinedRouteTypesWithTracking(s);
|
|
521
759
|
if (s.perRouterManifests.length > 0) {
|
|
@@ -526,6 +764,10 @@ export function createRouterDiscoveryPlugin(
|
|
|
526
764
|
`[rsc-router] Route regeneration error: ${err.message}`,
|
|
527
765
|
);
|
|
528
766
|
}
|
|
767
|
+
debugDiscovery?.(
|
|
768
|
+
"watcher: regenerated gen files (%sms)",
|
|
769
|
+
(performance.now() - regenStart).toFixed(1),
|
|
770
|
+
);
|
|
529
771
|
// Async: re-run runtime discovery to refresh factory-generated
|
|
530
772
|
// routes that the static parser cannot resolve.
|
|
531
773
|
if (s.perRouterManifests.length > 0) {
|
|
@@ -560,6 +802,12 @@ export function createRouterDiscoveryPlugin(
|
|
|
560
802
|
const hasUrls = source.includes("urls(");
|
|
561
803
|
const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
|
|
562
804
|
if (!hasUrls && !hasCreateRouter) return;
|
|
805
|
+
debugDiscovery?.(
|
|
806
|
+
"watcher: %s matches (urls=%s, router=%s)",
|
|
807
|
+
filePath,
|
|
808
|
+
hasUrls,
|
|
809
|
+
hasCreateRouter,
|
|
810
|
+
);
|
|
563
811
|
// Invalidate cache when a router file changes (new router added/removed)
|
|
564
812
|
if (hasCreateRouter) {
|
|
565
813
|
const nestedRouterConflict = findNestedRouterConflict([
|
|
@@ -604,11 +852,24 @@ export function createRouterDiscoveryPlugin(
|
|
|
604
852
|
async buildStart() {
|
|
605
853
|
if (!s.isBuildMode) return;
|
|
606
854
|
// Only run once across environment builds
|
|
607
|
-
if (s.mergedRouteManifest !== null)
|
|
855
|
+
if (s.mergedRouteManifest !== null) {
|
|
856
|
+
debugDiscovery?.(
|
|
857
|
+
"build: skip (already discovered, env=%s)",
|
|
858
|
+
this.environment?.name ?? "?",
|
|
859
|
+
);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const buildStartTime = performance.now();
|
|
863
|
+
debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
|
|
608
864
|
resetStagedBuildAssets(s.projectRoot);
|
|
609
865
|
s.prerenderManifestEntries = null;
|
|
610
866
|
s.staticManifestEntries = null;
|
|
611
867
|
|
|
868
|
+
// Acquire build-time env bindings if configured
|
|
869
|
+
await timed(debugDiscovery, "build acquireBuildEnv", () =>
|
|
870
|
+
acquireBuildEnv(s, viteCommand, viteMode),
|
|
871
|
+
);
|
|
872
|
+
|
|
612
873
|
let tempServer: any = null;
|
|
613
874
|
// Signal to user-space code (e.g. reverse.ts) that build-time discovery
|
|
614
875
|
// is active. Uses globalThis because the temp server's module runner
|
|
@@ -616,7 +877,11 @@ export function createRouterDiscoveryPlugin(
|
|
|
616
877
|
// between the vite plugin and user code loaded via runner.import().
|
|
617
878
|
(globalThis as any).__rscRouterDiscoveryActive = true;
|
|
618
879
|
try {
|
|
619
|
-
tempServer = await
|
|
880
|
+
tempServer = await timed(
|
|
881
|
+
debugDiscovery,
|
|
882
|
+
"build createTempRscServer",
|
|
883
|
+
() => createTempRscServer(s, { forceBuild: true }),
|
|
884
|
+
);
|
|
620
885
|
|
|
621
886
|
const rscEnv = (tempServer.environments as any)?.rsc;
|
|
622
887
|
if (!rscEnv?.runner) {
|
|
@@ -636,11 +901,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
636
901
|
s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
|
|
637
902
|
}
|
|
638
903
|
|
|
639
|
-
await
|
|
904
|
+
await timed(debugDiscovery, "build discoverRouters", () =>
|
|
905
|
+
discoverRouters(s, rscEnv),
|
|
906
|
+
);
|
|
640
907
|
// Update named-routes.gen.ts from runtime discovery.
|
|
641
908
|
// The runtime manifest includes dynamically generated routes
|
|
642
909
|
// that the static parser cannot extract from source code.
|
|
643
|
-
writeRouteTypesFiles(
|
|
910
|
+
timedSync(debugDiscovery, "build writeRouteTypesFiles", () =>
|
|
911
|
+
writeRouteTypesFiles(s),
|
|
912
|
+
);
|
|
644
913
|
} catch (err: any) {
|
|
645
914
|
// Extract the user source file from the stack trace (skip internal frames)
|
|
646
915
|
const sourceFile = err.stack
|
|
@@ -665,8 +934,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
665
934
|
} finally {
|
|
666
935
|
delete (globalThis as any).__rscRouterDiscoveryActive;
|
|
667
936
|
if (tempServer) {
|
|
668
|
-
await tempServer.close()
|
|
937
|
+
await timed(debugDiscovery, "build tempServer.close", () =>
|
|
938
|
+
tempServer.close(),
|
|
939
|
+
);
|
|
669
940
|
}
|
|
941
|
+
await releaseBuildEnv(s);
|
|
942
|
+
debugDiscovery?.(
|
|
943
|
+
"build discovery done (%sms)",
|
|
944
|
+
(performance.now() - buildStartTime).toFixed(1),
|
|
945
|
+
);
|
|
670
946
|
}
|
|
671
947
|
},
|
|
672
948
|
|
|
@@ -690,19 +966,38 @@ export function createRouterDiscoveryPlugin(
|
|
|
690
966
|
// This is critical for Cloudflare dev where the worker runs in a separate
|
|
691
967
|
// Miniflare process and can only receive manifest data via the virtual module.
|
|
692
968
|
if (s.discoveryDone) {
|
|
693
|
-
await
|
|
969
|
+
await timed(
|
|
970
|
+
debugRoutes,
|
|
971
|
+
"await discoveryDone (manifest)",
|
|
972
|
+
() => s.discoveryDone,
|
|
973
|
+
);
|
|
694
974
|
}
|
|
695
|
-
|
|
975
|
+
const code = await timed(
|
|
976
|
+
debugRoutes,
|
|
977
|
+
"generateRoutesManifestModule",
|
|
978
|
+
() => generateRoutesManifestModule(s),
|
|
979
|
+
);
|
|
980
|
+
debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
|
|
981
|
+
return code;
|
|
696
982
|
}
|
|
697
983
|
// Per-router virtual modules: pure data exports (no side effects).
|
|
698
984
|
// ensureRouterManifest() imports the module and stores the data.
|
|
699
985
|
const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
|
|
700
986
|
if (id.startsWith(perRouterPrefix)) {
|
|
701
987
|
if (s.discoveryDone) {
|
|
702
|
-
await
|
|
988
|
+
await timed(
|
|
989
|
+
debugRoutes,
|
|
990
|
+
"await discoveryDone (per-router)",
|
|
991
|
+
() => s.discoveryDone,
|
|
992
|
+
);
|
|
703
993
|
}
|
|
704
994
|
const routerId = id.slice(perRouterPrefix.length);
|
|
705
|
-
|
|
995
|
+
const code = await timed(
|
|
996
|
+
debugRoutes,
|
|
997
|
+
`generatePerRouterModule ${routerId}`,
|
|
998
|
+
() => generatePerRouterModule(s, routerId),
|
|
999
|
+
);
|
|
1000
|
+
return code;
|
|
706
1001
|
}
|
|
707
1002
|
// virtual:rsc-router/prerender-paths load handler removed
|
|
708
1003
|
return null;
|
|
@@ -712,6 +1007,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
712
1007
|
// Used by closeBundle for handler code eviction and prerender data injection.
|
|
713
1008
|
generateBundle(_options: any, bundle: any) {
|
|
714
1009
|
if (this.environment?.name !== "rsc") return;
|
|
1010
|
+
const genStart = debugBuild ? performance.now() : 0;
|
|
715
1011
|
|
|
716
1012
|
// Record RSC entry chunk filename for closeBundle injection
|
|
717
1013
|
for (const [fileName, chunk] of Object.entries(bundle) as [
|
|
@@ -724,8 +1020,19 @@ export function createRouterDiscoveryPlugin(
|
|
|
724
1020
|
}
|
|
725
1021
|
}
|
|
726
1022
|
|
|
727
|
-
if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
|
|
1023
|
+
if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size) {
|
|
1024
|
+
debugBuild?.(
|
|
1025
|
+
"generateBundle (rsc): no handlers to scan (%sms)",
|
|
1026
|
+
(performance.now() - genStart).toFixed(1),
|
|
1027
|
+
);
|
|
728
1028
|
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Clear maps at the start of each RSC generateBundle pass.
|
|
1032
|
+
// Vite 6 multi-environment builds run RSC twice (analysis + production);
|
|
1033
|
+
// clearing prevents stale/duplicate records from the analysis pass.
|
|
1034
|
+
s.handlerChunkInfoMap.clear();
|
|
1035
|
+
s.staticHandlerChunkInfoMap.clear();
|
|
729
1036
|
|
|
730
1037
|
for (const [fileName, chunk] of Object.entries(bundle) as [
|
|
731
1038
|
string,
|
|
@@ -733,27 +1040,28 @@ export function createRouterDiscoveryPlugin(
|
|
|
733
1040
|
][]) {
|
|
734
1041
|
if (chunk.type !== "chunk") continue;
|
|
735
1042
|
|
|
736
|
-
//
|
|
737
|
-
if (
|
|
738
|
-
fileName.includes("__prerender-handlers") &&
|
|
739
|
-
s.resolvedPrerenderModules?.size
|
|
740
|
-
) {
|
|
1043
|
+
// Scan all chunks for handler exports (handlers may land in any chunk)
|
|
1044
|
+
if (s.resolvedPrerenderModules?.size) {
|
|
741
1045
|
const handlers = extractHandlerExportsFromChunk(
|
|
742
1046
|
chunk.code,
|
|
743
1047
|
s.resolvedPrerenderModules,
|
|
744
1048
|
"Prerender",
|
|
745
|
-
|
|
1049
|
+
false,
|
|
746
1050
|
);
|
|
747
1051
|
if (handlers.length > 0) {
|
|
748
|
-
|
|
1052
|
+
const existing = s.handlerChunkInfoMap.get(fileName);
|
|
1053
|
+
if (existing) {
|
|
1054
|
+
existing.exports.push(...handlers);
|
|
1055
|
+
} else {
|
|
1056
|
+
s.handlerChunkInfoMap.set(fileName, {
|
|
1057
|
+
fileName,
|
|
1058
|
+
exports: handlers,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
749
1061
|
}
|
|
750
1062
|
}
|
|
751
1063
|
|
|
752
|
-
|
|
753
|
-
if (
|
|
754
|
-
fileName.includes("__static-handlers") &&
|
|
755
|
-
s.resolvedStaticModules?.size
|
|
756
|
-
) {
|
|
1064
|
+
if (s.resolvedStaticModules?.size) {
|
|
757
1065
|
const handlers = extractHandlerExportsFromChunk(
|
|
758
1066
|
chunk.code,
|
|
759
1067
|
s.resolvedStaticModules,
|
|
@@ -761,10 +1069,26 @@ export function createRouterDiscoveryPlugin(
|
|
|
761
1069
|
false,
|
|
762
1070
|
);
|
|
763
1071
|
if (handlers.length > 0) {
|
|
764
|
-
|
|
1072
|
+
const existing = s.staticHandlerChunkInfoMap.get(fileName);
|
|
1073
|
+
if (existing) {
|
|
1074
|
+
existing.exports.push(...handlers);
|
|
1075
|
+
} else {
|
|
1076
|
+
s.staticHandlerChunkInfoMap.set(fileName, {
|
|
1077
|
+
fileName,
|
|
1078
|
+
exports: handlers,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
765
1081
|
}
|
|
766
1082
|
}
|
|
767
1083
|
}
|
|
1084
|
+
|
|
1085
|
+
debugBuild?.(
|
|
1086
|
+
"generateBundle (rsc): scanned %d chunks, %d prerender chunk(s), %d static chunk(s) (%sms)",
|
|
1087
|
+
Object.keys(bundle).length,
|
|
1088
|
+
s.handlerChunkInfoMap.size,
|
|
1089
|
+
s.staticHandlerChunkInfoMap.size,
|
|
1090
|
+
(performance.now() - genStart).toFixed(1),
|
|
1091
|
+
);
|
|
768
1092
|
},
|
|
769
1093
|
|
|
770
1094
|
// Build-time pre-rendering: evict handler code and inject collected prerender data.
|
|
@@ -778,7 +1102,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
778
1102
|
// Only run for the RSC environment — other environments (client, ssr) have
|
|
779
1103
|
// no prerender/static data to process and would just do redundant file I/O.
|
|
780
1104
|
if (this.environment && this.environment.name !== "rsc") return;
|
|
781
|
-
postprocessBundle(
|
|
1105
|
+
timedSync(debugBuild, "closeBundle postprocessBundle", () =>
|
|
1106
|
+
postprocessBundle(s),
|
|
1107
|
+
);
|
|
782
1108
|
},
|
|
783
1109
|
},
|
|
784
1110
|
};
|