@rangojs/router 0.0.0-experimental.62 → 0.0.0-experimental.63
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 +61 -8
- package/dist/bin/rango.js +2 -1
- package/dist/vite/index.js +142 -62
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +14 -15
- package/skills/prerender/SKILL.md +110 -68
- package/src/__internal.ts +1 -1
- package/src/build/generate-manifest.ts +3 -6
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +8 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/route-definition/dsl-helpers.ts +37 -18
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-types.ts +11 -0
- package/src/router/handler-context.ts +20 -5
- package/src/router/match-api.ts +2 -8
- package/src/router/match-middleware/cache-lookup.ts +2 -6
- package/src/router/prerender-match.ts +104 -8
- package/src/router/router-interfaces.ts +4 -0
- package/src/router/segment-resolution/fresh.ts +7 -2
- package/src/router/segment-resolution/revalidation.ts +10 -5
- package/src/router.ts +9 -1
- package/src/server/context.ts +5 -1
- package/src/static-handler.ts +18 -6
- package/src/types/handler-context.ts +12 -2
- package/src/types/route-entry.ts +1 -1
- package/src/urls/path-helper-types.ts +5 -1
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/response-types.ts +16 -6
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/prerender-collection.ts +14 -1
- package/src/vite/discovery/state.ts +13 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- package/src/vite/rango.ts +2 -1
- package/src/vite/router-discovery.ts +153 -34
|
@@ -31,25 +31,25 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
31
31
|
state.rscEntryFileName ?? "index.js",
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
// 1. Evict handler code from
|
|
35
|
-
//
|
|
34
|
+
// 1. Evict handler code from whichever chunks contain handler exports.
|
|
35
|
+
// handlerChunkInfoMap/staticHandlerChunkInfoMap are populated by generateBundle
|
|
36
36
|
// after the production RSC build. In Vite 6 multi-environment builds, the
|
|
37
|
-
// RSC build runs twice (analysis + production).
|
|
38
|
-
//
|
|
37
|
+
// RSC build runs twice (analysis + production). The maps are cleared at the
|
|
38
|
+
// start of each generateBundle pass so only production data is used here.
|
|
39
39
|
const evictionTargets: Array<{
|
|
40
|
-
|
|
40
|
+
infos: Iterable<import("./state.js").ChunkInfo>;
|
|
41
41
|
fnName: string;
|
|
42
42
|
brand: string;
|
|
43
43
|
label: string;
|
|
44
44
|
}> = [
|
|
45
45
|
{
|
|
46
|
-
|
|
46
|
+
infos: state.handlerChunkInfoMap.values(),
|
|
47
47
|
fnName: "Prerender",
|
|
48
48
|
brand: "prerenderHandler",
|
|
49
49
|
label: "handler code from RSC bundle",
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
|
|
52
|
+
infos: state.staticHandlerChunkInfoMap.values(),
|
|
53
53
|
fnName: "Static",
|
|
54
54
|
brand: "staticHandler",
|
|
55
55
|
label: "static handler code",
|
|
@@ -57,35 +57,32 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
57
57
|
];
|
|
58
58
|
|
|
59
59
|
for (const target of evictionTargets) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
for (const info of target.infos) {
|
|
61
|
+
const chunkPath = resolve(state.projectRoot, "dist/rsc", info.fileName);
|
|
62
|
+
try {
|
|
63
|
+
const code = readFileSync(chunkPath, "utf-8");
|
|
64
|
+
const result = evictHandlerCode(
|
|
65
|
+
code,
|
|
66
|
+
info.exports,
|
|
67
|
+
target.fnName,
|
|
68
|
+
target.brand,
|
|
69
|
+
);
|
|
70
|
+
if (result) {
|
|
71
|
+
writeFileSync(chunkPath, result.code);
|
|
72
|
+
const savedKB = (result.savedBytes / 1024).toFixed(1);
|
|
73
|
+
console.log(
|
|
74
|
+
`[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} catch (replaceErr: any) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
|
-
} catch (replaceErr: any) {
|
|
82
|
-
console.warn(
|
|
83
|
-
`[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
|
|
84
|
-
);
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
|
-
state.
|
|
88
|
-
state.
|
|
84
|
+
state.handlerChunkInfoMap.clear();
|
|
85
|
+
state.staticHandlerChunkInfoMap.clear();
|
|
89
86
|
|
|
90
87
|
// 2. Write prerender data as separate importable asset modules
|
|
91
88
|
// and inject a lazy manifest loader into the RSC entry.
|
|
@@ -138,7 +135,7 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
138
135
|
// and inject a __STATIC_MANIFEST import into the RSC entry.
|
|
139
136
|
if (hasStaticData && existsSync(rscEntryPath)) {
|
|
140
137
|
const rscCode = readFileSync(rscEntryPath, "utf-8");
|
|
141
|
-
if (!rscCode.includes("
|
|
138
|
+
if (!rscCode.includes("__static-manifest.js")) {
|
|
142
139
|
try {
|
|
143
140
|
const manifestEntries: string[] = [];
|
|
144
141
|
let totalBytes = copyStagedBuildAssets(
|
|
@@ -54,11 +54,12 @@ export async function expandPrerenderRoutes(
|
|
|
54
54
|
for (const { manifest } of allManifests) {
|
|
55
55
|
if (!manifest.prerenderRoutes) continue;
|
|
56
56
|
const defs = manifest._prerenderDefs || {};
|
|
57
|
+
const passthroughSet = new Set(manifest.passthroughRoutes || []);
|
|
57
58
|
for (const routeName of manifest.prerenderRoutes) {
|
|
58
59
|
const pattern = manifest.routeManifest[routeName];
|
|
59
60
|
if (!pattern) continue;
|
|
60
61
|
const def = defs[routeName];
|
|
61
|
-
const isPassthroughRoute =
|
|
62
|
+
const isPassthroughRoute = passthroughSet.has(routeName);
|
|
62
63
|
const hasDynamic = pattern.includes(":") || pattern.includes("*");
|
|
63
64
|
if (!hasDynamic) {
|
|
64
65
|
// Static route: use pattern directly (strip trailing slash for URL)
|
|
@@ -73,12 +74,21 @@ export async function expandPrerenderRoutes(
|
|
|
73
74
|
if (def?.getParams) {
|
|
74
75
|
try {
|
|
75
76
|
const buildVars: Record<string, any> = {};
|
|
77
|
+
const buildEnv = state.resolvedBuildEnv;
|
|
76
78
|
const getParamsCtx = {
|
|
77
79
|
build: true as const,
|
|
80
|
+
dev: !state.isBuildMode,
|
|
78
81
|
set: ((keyOrVar: any, value: any) => {
|
|
79
82
|
contextSet(buildVars, keyOrVar, value);
|
|
80
83
|
}) as any,
|
|
81
84
|
reverse: getParamsReverse,
|
|
85
|
+
get env() {
|
|
86
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
87
|
+
throw new Error(
|
|
88
|
+
"[rsc-router] ctx.env is not available during build-time getParams(). " +
|
|
89
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
90
|
+
);
|
|
91
|
+
},
|
|
82
92
|
};
|
|
83
93
|
const paramsList = await def.getParams(getParamsCtx);
|
|
84
94
|
const concurrency = def.options?.concurrency ?? 1;
|
|
@@ -175,6 +185,7 @@ export async function expandPrerenderRoutes(
|
|
|
175
185
|
{},
|
|
176
186
|
entry.buildVars,
|
|
177
187
|
entry.isPassthroughRoute,
|
|
188
|
+
state.resolvedBuildEnv,
|
|
178
189
|
);
|
|
179
190
|
if (!result) continue;
|
|
180
191
|
|
|
@@ -326,6 +337,8 @@ export async function renderStaticHandlers(
|
|
|
326
337
|
def.handler,
|
|
327
338
|
def.$$id,
|
|
328
339
|
(def as any).$$routePrefix,
|
|
340
|
+
state.resolvedBuildEnv,
|
|
341
|
+
!state.isBuildMode,
|
|
329
342
|
);
|
|
330
343
|
if (result) {
|
|
331
344
|
const hasHandles = Object.keys(result.handles).length > 0;
|
|
@@ -16,6 +16,10 @@ export interface PluginOptions {
|
|
|
16
16
|
// Mutable ref for deferred auto-discovery (node preset).
|
|
17
17
|
// The auto-discover config() hook populates this before configResolved.
|
|
18
18
|
routerPathRef?: { path?: string };
|
|
19
|
+
/** Build-time env option from rango() config. */
|
|
20
|
+
buildEnv?: import("../plugin-types.js").BuildEnvOption;
|
|
21
|
+
/** Deployment preset (needed for buildEnv "auto" resolution). */
|
|
22
|
+
preset?: "node" | "cloudflare";
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export interface PrecomputedEntry {
|
|
@@ -56,8 +60,8 @@ export interface DiscoveryState {
|
|
|
56
60
|
|
|
57
61
|
prerenderManifestEntries: Record<string, string> | null;
|
|
58
62
|
staticManifestEntries: Record<string, string> | null;
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
handlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
64
|
+
staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
61
65
|
rscEntryFileName: string | null;
|
|
62
66
|
resolvedPrerenderModules: Map<string, string[]> | undefined;
|
|
63
67
|
resolvedStaticModules: Map<string, string[]> | undefined;
|
|
@@ -67,6 +71,11 @@ export interface DiscoveryState {
|
|
|
67
71
|
devServer: any;
|
|
68
72
|
selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
|
|
69
73
|
SELF_WRITE_WINDOW_MS: number;
|
|
74
|
+
|
|
75
|
+
/** Resolved build-time env bindings (set during buildStart/configureServer). */
|
|
76
|
+
resolvedBuildEnv?: Record<string, unknown>;
|
|
77
|
+
/** Cleanup function for build-time env resources (e.g., miniflare). */
|
|
78
|
+
buildEnvDispose?: (() => Promise<void> | void) | null;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
export function createDiscoveryState(
|
|
@@ -93,8 +102,8 @@ export function createDiscoveryState(
|
|
|
93
102
|
|
|
94
103
|
prerenderManifestEntries: null,
|
|
95
104
|
staticManifestEntries: null,
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
handlerChunkInfoMap: new Map(),
|
|
106
|
+
staticHandlerChunkInfoMap: new Map(),
|
|
98
107
|
rscEntryFileName: null,
|
|
99
108
|
resolvedPrerenderModules: undefined,
|
|
100
109
|
resolvedStaticModules: undefined,
|
package/src/vite/index.ts
CHANGED
package/src/vite/plugin-types.ts
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
// -- Build-time environment types -------------------------------------------
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context passed to a buildEnv factory function.
|
|
5
|
+
* Provides Vite config details for conditional env setup.
|
|
6
|
+
*/
|
|
7
|
+
export interface BuildEnvFactoryContext {
|
|
8
|
+
/** Vite project root directory. */
|
|
9
|
+
root: string;
|
|
10
|
+
/** Vite mode (e.g. "development", "production"). */
|
|
11
|
+
mode: string;
|
|
12
|
+
/** Vite command ("serve" for dev, "build" for production). */
|
|
13
|
+
command: "serve" | "build";
|
|
14
|
+
/** Router deployment preset. */
|
|
15
|
+
preset: "node" | "cloudflare";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Factory function that creates build-time environment bindings.
|
|
20
|
+
* Called once at plugin startup. Return `dispose` to clean up resources.
|
|
21
|
+
*/
|
|
22
|
+
export type BuildEnvFactory = (
|
|
23
|
+
ctx: BuildEnvFactoryContext,
|
|
24
|
+
) => Promise<BuildEnvResult> | BuildEnvResult;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Result of resolving build-time environment bindings.
|
|
28
|
+
*/
|
|
29
|
+
export interface BuildEnvResult {
|
|
30
|
+
/** Environment bindings available to Prerender/Static handlers via ctx.env. */
|
|
31
|
+
env: Record<string, unknown>;
|
|
32
|
+
/** Called after build completes to clean up resources (e.g., miniflare). */
|
|
33
|
+
dispose?: () => Promise<void> | void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build-time environment configuration for Prerender and Static handlers.
|
|
38
|
+
*
|
|
39
|
+
* - `false` (default): no build-time env, `ctx.env` throws.
|
|
40
|
+
* - `"auto"`: calls `wrangler.getPlatformProxy()` (cloudflare preset only).
|
|
41
|
+
* - Object: used directly as `ctx.env` during build.
|
|
42
|
+
* - Factory: called once at startup, must return `{ env, dispose? }`.
|
|
43
|
+
*/
|
|
44
|
+
export type BuildEnvOption =
|
|
45
|
+
| false
|
|
46
|
+
| "auto"
|
|
47
|
+
| Record<string, unknown>
|
|
48
|
+
| BuildEnvFactory;
|
|
49
|
+
|
|
50
|
+
// -- Plugin options ---------------------------------------------------------
|
|
51
|
+
|
|
1
52
|
/**
|
|
2
53
|
* Base options shared by all presets
|
|
3
54
|
*/
|
|
@@ -9,12 +60,16 @@ interface RangoBaseOptions {
|
|
|
9
60
|
banner?: boolean;
|
|
10
61
|
|
|
11
62
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
63
|
+
* Environment bindings available to Prerender and Static handlers at build
|
|
64
|
+
* time via `ctx.env`. Applies to both production build and dev on-demand
|
|
65
|
+
* prerender (`/__rsc_prerender`).
|
|
66
|
+
*
|
|
67
|
+
* This is the build-time env supplied by the Vite plugin, not the live
|
|
68
|
+
* request env. It is shared across all prerender invocations for the build.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
16
71
|
*/
|
|
17
|
-
|
|
72
|
+
buildEnv?: BuildEnvOption;
|
|
18
73
|
}
|
|
19
74
|
|
|
20
75
|
/**
|
package/src/vite/rango.ts
CHANGED
|
@@ -453,7 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
453
453
|
createRouterDiscoveryPlugin(discoveryEntryPath, {
|
|
454
454
|
routerPathRef: discoveryRouterRef,
|
|
455
455
|
enableBuildPrerender: prerenderEnabled,
|
|
456
|
-
|
|
456
|
+
buildEnv: options?.buildEnv,
|
|
457
|
+
preset,
|
|
457
458
|
}),
|
|
458
459
|
);
|
|
459
460
|
|
|
@@ -10,6 +10,8 @@ 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";
|
|
14
|
+
import { pathToFileURL } from "node:url";
|
|
13
15
|
import {
|
|
14
16
|
formatNestedRouterConflictError,
|
|
15
17
|
findNestedRouterConflict,
|
|
@@ -94,6 +96,105 @@ async function createTempRscServer(
|
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Build-Time Env Resolution
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
import type {
|
|
104
|
+
BuildEnvOption,
|
|
105
|
+
BuildEnvFactoryContext,
|
|
106
|
+
BuildEnvResult,
|
|
107
|
+
} from "./plugin-types.js";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the buildEnv option into a concrete { env, dispose? } result.
|
|
111
|
+
* Handles all four input shapes: false, "auto", factory, plain object.
|
|
112
|
+
*/
|
|
113
|
+
async function resolveBuildEnv(
|
|
114
|
+
option: BuildEnvOption | undefined,
|
|
115
|
+
factoryCtx: BuildEnvFactoryContext,
|
|
116
|
+
): Promise<BuildEnvResult | null> {
|
|
117
|
+
if (!option) return null;
|
|
118
|
+
|
|
119
|
+
if (option === "auto") {
|
|
120
|
+
if (factoryCtx.preset !== "cloudflare") {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
|
|
123
|
+
"Use a factory function or plain object for other presets.",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
// Resolve wrangler from the user's project root (not the router package)
|
|
128
|
+
const userRequire = createRequire(
|
|
129
|
+
resolve(factoryCtx.root, "package.json"),
|
|
130
|
+
);
|
|
131
|
+
const wranglerPath = userRequire.resolve("wrangler");
|
|
132
|
+
const { getPlatformProxy } = (await import(
|
|
133
|
+
pathToFileURL(wranglerPath).href
|
|
134
|
+
)) as {
|
|
135
|
+
getPlatformProxy: (opts?: any) => Promise<any>;
|
|
136
|
+
};
|
|
137
|
+
const proxy = await getPlatformProxy();
|
|
138
|
+
return {
|
|
139
|
+
env: proxy.env as Record<string, unknown>,
|
|
140
|
+
dispose: proxy.dispose,
|
|
141
|
+
};
|
|
142
|
+
} catch (err: any) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
'[rsc-router] buildEnv: "auto" requires wrangler to be installed.\n' +
|
|
145
|
+
`Install it with: pnpm add -D wrangler\n${err.message}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (typeof option === "function") {
|
|
151
|
+
return await option(factoryCtx);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Plain object
|
|
155
|
+
return { env: option };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Acquire build-time env bindings and store on discovery state.
|
|
160
|
+
* Returns true if env was acquired, false if buildEnv is disabled.
|
|
161
|
+
*/
|
|
162
|
+
async function acquireBuildEnv(
|
|
163
|
+
s: DiscoveryState,
|
|
164
|
+
command: "serve" | "build",
|
|
165
|
+
mode: string,
|
|
166
|
+
): Promise<boolean> {
|
|
167
|
+
const option = s.opts?.buildEnv;
|
|
168
|
+
if (!option) return false;
|
|
169
|
+
|
|
170
|
+
const result = await resolveBuildEnv(option, {
|
|
171
|
+
root: s.projectRoot,
|
|
172
|
+
mode,
|
|
173
|
+
command,
|
|
174
|
+
preset: s.opts?.preset ?? "node",
|
|
175
|
+
});
|
|
176
|
+
if (!result) return false;
|
|
177
|
+
|
|
178
|
+
s.resolvedBuildEnv = result.env;
|
|
179
|
+
s.buildEnvDispose = result.dispose ?? null;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Release build-time env resources and clear state.
|
|
185
|
+
*/
|
|
186
|
+
async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
|
|
187
|
+
if (s.buildEnvDispose) {
|
|
188
|
+
try {
|
|
189
|
+
await s.buildEnvDispose();
|
|
190
|
+
} catch (err: any) {
|
|
191
|
+
console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
|
|
192
|
+
}
|
|
193
|
+
s.buildEnvDispose = null;
|
|
194
|
+
}
|
|
195
|
+
s.resolvedBuildEnv = undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
97
198
|
/**
|
|
98
199
|
* Plugin that discovers router instances at dev/build time via the RSC environment.
|
|
99
200
|
*
|
|
@@ -111,6 +212,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
111
212
|
opts?: PluginOptions,
|
|
112
213
|
): Plugin {
|
|
113
214
|
const s = createDiscoveryState(entryPath, opts);
|
|
215
|
+
let viteCommand: "serve" | "build" = "build";
|
|
216
|
+
let viteMode = "production";
|
|
114
217
|
|
|
115
218
|
return {
|
|
116
219
|
name: "@rangojs/router:discovery",
|
|
@@ -121,32 +224,20 @@ export function createRouterDiscoveryPlugin(
|
|
|
121
224
|
__RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG),
|
|
122
225
|
},
|
|
123
226
|
};
|
|
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
|
-
}
|
|
227
|
+
// Prerender/static handler modules are bundled naturally with the
|
|
228
|
+
// rest of the RSC entry. A previous design forced them into dedicated
|
|
229
|
+
// __prerender-handlers / __static-handlers chunks via manualChunks,
|
|
230
|
+
// but Rollup hoisted all shared dependencies into those chunks,
|
|
231
|
+
// inflating them to ~1 MB with active runtime code. Handler code is
|
|
232
|
+
// evicted in closeBundle regardless of which chunk it lands in.
|
|
144
233
|
return config;
|
|
145
234
|
},
|
|
146
235
|
|
|
147
236
|
configResolved(config) {
|
|
148
237
|
s.projectRoot = config.root;
|
|
149
238
|
s.isBuildMode = config.command === "build";
|
|
239
|
+
viteCommand = config.command as "serve" | "build";
|
|
240
|
+
viteMode = config.mode;
|
|
150
241
|
// Capture user's resolve aliases for the temp server
|
|
151
242
|
s.userResolveAlias = config.resolve.alias;
|
|
152
243
|
// Node preset: pick up auto-discovered router path from the config() hook.
|
|
@@ -217,12 +308,13 @@ export function createRouterDiscoveryPlugin(
|
|
|
217
308
|
let prerenderTempServer: any = null;
|
|
218
309
|
let prerenderNodeRegistry: Map<string, any> | null = null;
|
|
219
310
|
|
|
220
|
-
// Clean up the temporary server when the dev server shuts down
|
|
311
|
+
// Clean up the temporary server and build env when the dev server shuts down
|
|
221
312
|
server.httpServer?.on("close", () => {
|
|
222
313
|
if (prerenderTempServer) {
|
|
223
314
|
prerenderTempServer.close().catch(() => {});
|
|
224
315
|
prerenderTempServer = null;
|
|
225
316
|
}
|
|
317
|
+
releaseBuildEnv(s).catch(() => {});
|
|
226
318
|
});
|
|
227
319
|
|
|
228
320
|
async function getOrCreateTempServer(): Promise<any | null> {
|
|
@@ -262,6 +354,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
262
354
|
// Create a temp Node.js server to run runtime discovery and generate
|
|
263
355
|
// named route types (static parser can't resolve factory calls).
|
|
264
356
|
try {
|
|
357
|
+
// Acquire build-time env bindings for dev prerender
|
|
358
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
359
|
+
|
|
265
360
|
const tempRscEnv = await getOrCreateTempServer();
|
|
266
361
|
if (tempRscEnv) {
|
|
267
362
|
await discoverRouters(s, tempRscEnv);
|
|
@@ -278,6 +373,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
278
373
|
}
|
|
279
374
|
|
|
280
375
|
try {
|
|
376
|
+
// Acquire build-time env bindings for dev prerender (Node.js path)
|
|
377
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
378
|
+
|
|
281
379
|
// Set the readiness gate BEFORE discovery so early requests
|
|
282
380
|
// block until manifest is populated
|
|
283
381
|
const serverMod = await rscEnv.runner.import(
|
|
@@ -413,6 +511,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
413
511
|
{},
|
|
414
512
|
undefined,
|
|
415
513
|
wantPassthrough,
|
|
514
|
+
s.resolvedBuildEnv,
|
|
515
|
+
true, // devMode: check getParams for passthrough routes
|
|
416
516
|
);
|
|
417
517
|
if (!result) continue;
|
|
418
518
|
if (result.passthrough) continue;
|
|
@@ -601,6 +701,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
601
701
|
s.prerenderManifestEntries = null;
|
|
602
702
|
s.staticManifestEntries = null;
|
|
603
703
|
|
|
704
|
+
// Acquire build-time env bindings if configured
|
|
705
|
+
await acquireBuildEnv(s, viteCommand, viteMode);
|
|
706
|
+
|
|
604
707
|
let tempServer: any = null;
|
|
605
708
|
// Signal to user-space code (e.g. reverse.ts) that build-time discovery
|
|
606
709
|
// is active. Uses globalThis because the temp server's module runner
|
|
@@ -659,6 +762,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
659
762
|
if (tempServer) {
|
|
660
763
|
await tempServer.close();
|
|
661
764
|
}
|
|
765
|
+
await releaseBuildEnv(s);
|
|
662
766
|
}
|
|
663
767
|
},
|
|
664
768
|
|
|
@@ -719,33 +823,40 @@ export function createRouterDiscoveryPlugin(
|
|
|
719
823
|
if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
|
|
720
824
|
return;
|
|
721
825
|
|
|
826
|
+
// Clear maps at the start of each RSC generateBundle pass.
|
|
827
|
+
// Vite 6 multi-environment builds run RSC twice (analysis + production);
|
|
828
|
+
// clearing prevents stale/duplicate records from the analysis pass.
|
|
829
|
+
s.handlerChunkInfoMap.clear();
|
|
830
|
+
s.staticHandlerChunkInfoMap.clear();
|
|
831
|
+
|
|
722
832
|
for (const [fileName, chunk] of Object.entries(bundle) as [
|
|
723
833
|
string,
|
|
724
834
|
any,
|
|
725
835
|
][]) {
|
|
726
836
|
if (chunk.type !== "chunk") continue;
|
|
727
837
|
|
|
728
|
-
//
|
|
729
|
-
if (
|
|
730
|
-
fileName.includes("__prerender-handlers") &&
|
|
731
|
-
s.resolvedPrerenderModules?.size
|
|
732
|
-
) {
|
|
838
|
+
// Scan all chunks for handler exports (handlers may land in any chunk)
|
|
839
|
+
if (s.resolvedPrerenderModules?.size) {
|
|
733
840
|
const handlers = extractHandlerExportsFromChunk(
|
|
734
841
|
chunk.code,
|
|
735
842
|
s.resolvedPrerenderModules,
|
|
736
843
|
"Prerender",
|
|
737
|
-
|
|
844
|
+
false,
|
|
738
845
|
);
|
|
739
846
|
if (handlers.length > 0) {
|
|
740
|
-
|
|
847
|
+
const existing = s.handlerChunkInfoMap.get(fileName);
|
|
848
|
+
if (existing) {
|
|
849
|
+
existing.exports.push(...handlers);
|
|
850
|
+
} else {
|
|
851
|
+
s.handlerChunkInfoMap.set(fileName, {
|
|
852
|
+
fileName,
|
|
853
|
+
exports: handlers,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
741
856
|
}
|
|
742
857
|
}
|
|
743
858
|
|
|
744
|
-
|
|
745
|
-
if (
|
|
746
|
-
fileName.includes("__static-handlers") &&
|
|
747
|
-
s.resolvedStaticModules?.size
|
|
748
|
-
) {
|
|
859
|
+
if (s.resolvedStaticModules?.size) {
|
|
749
860
|
const handlers = extractHandlerExportsFromChunk(
|
|
750
861
|
chunk.code,
|
|
751
862
|
s.resolvedStaticModules,
|
|
@@ -753,7 +864,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
753
864
|
false,
|
|
754
865
|
);
|
|
755
866
|
if (handlers.length > 0) {
|
|
756
|
-
|
|
867
|
+
const existing = s.staticHandlerChunkInfoMap.get(fileName);
|
|
868
|
+
if (existing) {
|
|
869
|
+
existing.exports.push(...handlers);
|
|
870
|
+
} else {
|
|
871
|
+
s.staticHandlerChunkInfoMap.set(fileName, {
|
|
872
|
+
fileName,
|
|
873
|
+
exports: handlers,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
757
876
|
}
|
|
758
877
|
}
|
|
759
878
|
}
|