@rangojs/router 0.0.0-experimental.146 → 0.0.0-experimental.147
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/dist/bin/rango.js +7 -0
- package/dist/vite/index.js +807 -235
- package/package.json +6 -1
- package/src/browser/event-controller.ts +16 -2
- package/src/browser/rsc-router.tsx +11 -0
- package/src/cache/cache-scope.ts +11 -2
- package/src/cache/cf/cf-cache-store.ts +23 -0
- package/src/cache/memory-segment-store.ts +32 -0
- package/src/cache/segment-codec.ts +47 -0
- package/src/cache/types.ts +14 -0
- package/src/cache/vercel/vercel-cache-store.ts +71 -2
- package/src/prerender/build-shell-capture.ts +237 -0
- package/src/prerender/shell-manifest-key.ts +20 -0
- package/src/prerender/store.ts +10 -1
- package/src/router/match-middleware/cache-lookup.ts +12 -1
- package/src/router/prerender-match.ts +21 -0
- package/src/rsc/capture-queue.ts +67 -0
- package/src/rsc/rsc-rendering.ts +82 -23
- package/src/rsc/shell-build-manifest.ts +244 -0
- package/src/rsc/shell-capture.ts +100 -39
- package/src/segment-fragments.ts +124 -0
- package/src/server/request-context.ts +65 -11
- package/src/ssr/index.tsx +47 -9
- package/src/ssr/ssr-root.tsx +35 -2
- package/src/vite/discovery/discover-routers.ts +27 -0
- package/src/vite/discovery/prerender-collection.ts +16 -0
- package/src/vite/discovery/shell-prerender-phase.ts +395 -0
- package/src/vite/discovery/state.ts +42 -0
- package/src/vite/plugins/version-plugin.ts +8 -0
- package/src/vite/rango.ts +1 -0
- package/src/vite/router-discovery.ts +292 -8
- package/src/vite/utils/prerender-utils.ts +25 -6
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
internalDebugNoCacheMiddleware,
|
|
25
25
|
} from "./inject-client-debug.js";
|
|
26
26
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
27
|
+
import { getVirtualEntrySSR, VIRTUAL_IDS } from "./plugins/virtual-entries.js";
|
|
27
28
|
import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
|
|
28
29
|
import {
|
|
29
30
|
BUILD_ENV_GLOBAL_KEY,
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
peekSelfGenWrite,
|
|
47
48
|
} from "./discovery/self-gen-tracking.js";
|
|
48
49
|
import { discoverRouters } from "./discovery/discover-routers.js";
|
|
50
|
+
import { runShellPrerenderPhase } from "./discovery/shell-prerender-phase.js";
|
|
49
51
|
import { describeDiscoveryFailure } from "./discovery/discovery-errors.js";
|
|
50
52
|
import {
|
|
51
53
|
createDevPrerenderCache,
|
|
@@ -138,7 +140,21 @@ function ensureCloudflareProtocolLoaderRegistered(): void {
|
|
|
138
140
|
*/
|
|
139
141
|
async function createTempRscServer(
|
|
140
142
|
state: DiscoveryState,
|
|
141
|
-
options: {
|
|
143
|
+
options: {
|
|
144
|
+
forceBuild?: boolean;
|
|
145
|
+
cacheDir?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Serve the REAL rango SSR entry (getVirtualEntrySSR) for
|
|
148
|
+
* "virtual:entry-ssr" instead of the discovery stub, so the dev
|
|
149
|
+
* /__rsc_shell endpoint can drive captureShellHTML in this server's SSR
|
|
150
|
+
* realm. Dev-correct by construction: the entry's bootstrap resolves to
|
|
151
|
+
* plugin-rsc's stable virtual browser-entry URL, which the MAIN dev
|
|
152
|
+
* server serves to the browser. Loaded lazily — discovery never imports
|
|
153
|
+
* the SSR entry, so the temp server stays as light as before until a
|
|
154
|
+
* shell capture actually runs.
|
|
155
|
+
*/
|
|
156
|
+
realSsrEntry?: boolean;
|
|
157
|
+
} = {},
|
|
142
158
|
) {
|
|
143
159
|
// Install the Node ESM loader hook before any module evaluation so
|
|
144
160
|
// `cloudflare:*` specifiers in externalized/loader-delegated modules
|
|
@@ -178,6 +194,26 @@ async function createTempRscServer(
|
|
|
178
194
|
// hashClientRefs only in build mode — production bundles need hashed refs
|
|
179
195
|
...(options.forceBuild ? [hashClientRefs(state.projectRoot)] : []),
|
|
180
196
|
createVersionPlugin(),
|
|
197
|
+
// Before the stub plugin, so "virtual:entry-ssr" resolves to the real
|
|
198
|
+
// SSR entry when the shell endpoint needs it (see the option doc).
|
|
199
|
+
...(options.realSsrEntry
|
|
200
|
+
? [
|
|
201
|
+
{
|
|
202
|
+
name: "@rangojs/router:temp-real-ssr-entry",
|
|
203
|
+
enforce: "pre" as const,
|
|
204
|
+
resolveId(id: string) {
|
|
205
|
+
return id === "virtual:entry-ssr"
|
|
206
|
+
? "\0rango-temp-real-ssr-entry"
|
|
207
|
+
: null;
|
|
208
|
+
},
|
|
209
|
+
load(id: string) {
|
|
210
|
+
return id === "\0rango-temp-real-ssr-entry"
|
|
211
|
+
? getVirtualEntrySSR(state.opts?.headScripts)
|
|
212
|
+
: null;
|
|
213
|
+
},
|
|
214
|
+
} satisfies import("vite").Plugin,
|
|
215
|
+
]
|
|
216
|
+
: []),
|
|
181
217
|
createVirtualStubPlugin(),
|
|
182
218
|
createCloudflareProtocolStubPlugin(),
|
|
183
219
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
@@ -281,6 +317,19 @@ async function acquireBuildEnv(
|
|
|
281
317
|
return true;
|
|
282
318
|
}
|
|
283
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Reset the per-build prerender collection state. A helper (not inline
|
|
322
|
+
* assignments in buildStart) so TS's property narrowing does not pin
|
|
323
|
+
* `s.shellCandidates` to `null` across the discovery call that repopulates
|
|
324
|
+
* it — the finally block re-reads it to decide the temp-server keep-alive.
|
|
325
|
+
*/
|
|
326
|
+
function resetPrerenderCollection(s: DiscoveryState): void {
|
|
327
|
+
s.prerenderManifestEntries = null;
|
|
328
|
+
s.staticManifestEntries = null;
|
|
329
|
+
s.shellCandidates = null;
|
|
330
|
+
s.prerenderPayloadValues = null;
|
|
331
|
+
}
|
|
332
|
+
|
|
284
333
|
/**
|
|
285
334
|
* Release build-time env resources and clear state.
|
|
286
335
|
*/
|
|
@@ -541,6 +590,10 @@ export function createRouterDiscoveryPlugin(
|
|
|
541
590
|
try {
|
|
542
591
|
prerenderTempServer = await createTempRscServer(s, {
|
|
543
592
|
cacheDir: "node_modules/.vite_prerender",
|
|
593
|
+
// The dev /__rsc_shell endpoint drives captureShellHTML in this
|
|
594
|
+
// server's SSR realm; the entry is only imported when a shell
|
|
595
|
+
// capture runs, so discovery cost is unchanged.
|
|
596
|
+
realSsrEntry: true,
|
|
544
597
|
});
|
|
545
598
|
|
|
546
599
|
const tempRscEnv = (prerenderTempServer.environments as any)?.rsc;
|
|
@@ -1057,6 +1110,190 @@ export function createRouterDiscoveryPlugin(
|
|
|
1057
1110
|
logResult(404, "no match");
|
|
1058
1111
|
});
|
|
1059
1112
|
|
|
1113
|
+
// Dev on-demand PPR shell production (producer B, #699). There is no
|
|
1114
|
+
// build manifest in dev, so the serve path's read-through
|
|
1115
|
+
// (rsc/shell-build-manifest.ts) fetches the shell entry from here on a
|
|
1116
|
+
// Prerender+ppr route's first request — dev serves x-rango-shell: HIT
|
|
1117
|
+
// from request one, mirroring production. Memoized per router HMR
|
|
1118
|
+
// generation AND per caller version (a client-module edit bumps the
|
|
1119
|
+
// version without rotating the router instance; the stale entry would
|
|
1120
|
+
// fail the serve gate forever). The endpoint is policy-free: the caller
|
|
1121
|
+
// (the serve gate, which resolved the route's ppr option) sends
|
|
1122
|
+
// ttl/swr/tags/version. Only prerender-backed routes produce entries —
|
|
1123
|
+
// the /__rsc_prerender pre-flight below both warms the payload memo the
|
|
1124
|
+
// capture's dev store fetch will hit AND refuses non-prerenderable
|
|
1125
|
+
// routes (a live-handler render must never be served as a baked shell).
|
|
1126
|
+
server.middlewares.use("/__rsc_shell", async (req: any, res: any) => {
|
|
1127
|
+
await s.discoveryDone;
|
|
1128
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
1129
|
+
const pathname = url.searchParams.get("pathname");
|
|
1130
|
+
const routeName = url.searchParams.get("routeName");
|
|
1131
|
+
const version = url.searchParams.get("version");
|
|
1132
|
+
// ttl is required like the identifiers: the endpoint is policy-free
|
|
1133
|
+
// (the serve gate resolved the route's ppr option and always sends
|
|
1134
|
+
// it), so there is deliberately no default to drift from
|
|
1135
|
+
// resolvePprConfig's.
|
|
1136
|
+
const ttlRaw = url.searchParams.get("ttl");
|
|
1137
|
+
if (!pathname || !routeName || !version || !ttlRaw) {
|
|
1138
|
+
res.statusCode = 400;
|
|
1139
|
+
res.end("Missing pathname/routeName/version/ttl");
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const ttl = Number(ttlRaw);
|
|
1143
|
+
const swrRaw = url.searchParams.get("swr");
|
|
1144
|
+
const swr = swrRaw === null ? undefined : Number(swrRaw);
|
|
1145
|
+
const tagsRaw = url.searchParams.get("tags");
|
|
1146
|
+
const tags = tagsRaw ? tagsRaw.split(",") : undefined;
|
|
1147
|
+
|
|
1148
|
+
// Resolve the capture realms: main-server envs (Node preset) or the
|
|
1149
|
+
// shared temp Node server (Cloudflare preset — no main RSC runner).
|
|
1150
|
+
// Entry re-import per request picks up HMR edits, exactly like the
|
|
1151
|
+
// prerender endpoint above.
|
|
1152
|
+
const rscEnvMain = (server.environments as any)?.rsc;
|
|
1153
|
+
let rscRealm: any = null;
|
|
1154
|
+
let ssrRealm: any = null;
|
|
1155
|
+
let ssrEntryId: string;
|
|
1156
|
+
if (rscEnvMain?.runner && s.resolvedEntryPath) {
|
|
1157
|
+
try {
|
|
1158
|
+
await rscEnvMain.runner.import(s.resolvedEntryPath);
|
|
1159
|
+
} catch (err: any) {
|
|
1160
|
+
res.statusCode = 500;
|
|
1161
|
+
res.end(`Shell capture module refresh failed: ${err.message}`);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
rscRealm = rscEnvMain;
|
|
1165
|
+
ssrRealm = (server.environments as any)?.ssr;
|
|
1166
|
+
ssrEntryId =
|
|
1167
|
+
(server.environments as any)?.ssr?.config?.build?.rollupOptions
|
|
1168
|
+
?.input?.index ?? VIRTUAL_IDS.ssr;
|
|
1169
|
+
} else {
|
|
1170
|
+
const tempRscEnv = await getOrCreateTempServer();
|
|
1171
|
+
if (tempRscEnv) {
|
|
1172
|
+
try {
|
|
1173
|
+
await importEntryAndRegistry(tempRscEnv);
|
|
1174
|
+
} catch (err: any) {
|
|
1175
|
+
res.statusCode = 500;
|
|
1176
|
+
res.end(`Shell capture module refresh failed: ${err.message}`);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
rscRealm = tempRscEnv;
|
|
1181
|
+
ssrRealm = (prerenderTempServer?.environments as any)?.ssr;
|
|
1182
|
+
ssrEntryId = "virtual:entry-ssr";
|
|
1183
|
+
}
|
|
1184
|
+
if (!rscRealm?.runner || !ssrRealm?.runner) {
|
|
1185
|
+
res.statusCode = 503;
|
|
1186
|
+
res.end("Shell capture runners not available");
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
let registry: Map<string, any> | null = null;
|
|
1190
|
+
try {
|
|
1191
|
+
const serverMod = await rscRealm.runner.import(
|
|
1192
|
+
"@rangojs/router/server",
|
|
1193
|
+
);
|
|
1194
|
+
registry = serverMod.RouterRegistry ?? null;
|
|
1195
|
+
} catch {
|
|
1196
|
+
registry = null;
|
|
1197
|
+
}
|
|
1198
|
+
if (!registry || registry.size === 0) {
|
|
1199
|
+
res.statusCode = 503;
|
|
1200
|
+
res.end("Shell capture registry not available");
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Memo sweep FIRST: after request one the common case is a memo HIT
|
|
1205
|
+
// (this fetch blocks a foreground document request), and the memoized
|
|
1206
|
+
// body needs neither the pre-flight round-trip nor a capture. Keyed
|
|
1207
|
+
// per router instance (= HMR generation) like the prerender memo.
|
|
1208
|
+
const cacheKey = `shell|${pathname}|r=${routeName}|t=${ttl}|s=${swr ?? ""}|g=${(tags ?? []).join("+")}|v=${version}`;
|
|
1209
|
+
for (const [, routerInstance] of registry) {
|
|
1210
|
+
if (typeof routerInstance.match !== "function") continue;
|
|
1211
|
+
const cached = devPrerenderCache.get(routerInstance, cacheKey);
|
|
1212
|
+
if (cached !== undefined) {
|
|
1213
|
+
res.setHeader("content-type", "application/json");
|
|
1214
|
+
res.setHeader("x-rango-shell-dev", "HIT");
|
|
1215
|
+
res.end(cached);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Pre-flight: the route must be prerender-backed. Warms the payload
|
|
1221
|
+
// memo the capture's dev prerender store will fetch, and closes the
|
|
1222
|
+
// live-handler-bake hole (a non-pr route 404s here).
|
|
1223
|
+
if (s.devServerOrigin) {
|
|
1224
|
+
try {
|
|
1225
|
+
const probe = await fetch(
|
|
1226
|
+
`${s.devServerOrigin}/__rsc_prerender?pathname=${encodeURIComponent(pathname)}&routeName=${encodeURIComponent(routeName)}`,
|
|
1227
|
+
{ signal: AbortSignal.timeout(10_000) },
|
|
1228
|
+
);
|
|
1229
|
+
if (!probe.ok) {
|
|
1230
|
+
res.statusCode = 404;
|
|
1231
|
+
res.end("Route is not prerenderable");
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
} catch {
|
|
1235
|
+
res.statusCode = 404;
|
|
1236
|
+
res.end("Prerender pre-flight failed");
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
for (const [, routerInstance] of registry) {
|
|
1242
|
+
if (typeof routerInstance.match !== "function") continue;
|
|
1243
|
+
try {
|
|
1244
|
+
const ssrModule = await ssrRealm.runner.import(ssrEntryId);
|
|
1245
|
+
if (typeof ssrModule?.captureShellHTML !== "function") {
|
|
1246
|
+
res.statusCode = 404;
|
|
1247
|
+
res.end("SSR entry has no captureShellHTML");
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
const captureMod = await rscRealm.runner.import(
|
|
1251
|
+
"@rangojs/router/build/shell-capture",
|
|
1252
|
+
);
|
|
1253
|
+
const result = await captureMod.captureShellForBuild({
|
|
1254
|
+
router: routerInstance,
|
|
1255
|
+
urlPath: pathname,
|
|
1256
|
+
routeName,
|
|
1257
|
+
key: `${pathname}:shell`,
|
|
1258
|
+
ttl,
|
|
1259
|
+
swr,
|
|
1260
|
+
tags,
|
|
1261
|
+
buildEnv: s.resolvedBuildEnv,
|
|
1262
|
+
buildVersion: version,
|
|
1263
|
+
captureShellHTML: ssrModule.captureShellHTML,
|
|
1264
|
+
debug: !!debugDiscovery,
|
|
1265
|
+
});
|
|
1266
|
+
if (result.outcome === "route-mismatch") continue;
|
|
1267
|
+
if (result.outcome !== "stored" || !result.entry) {
|
|
1268
|
+
res.statusCode = 404;
|
|
1269
|
+
res.end(`Shell capture ${result.outcome}`);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const body = JSON.stringify({
|
|
1273
|
+
entry: result.entry,
|
|
1274
|
+
ttl,
|
|
1275
|
+
swr,
|
|
1276
|
+
tags: result.tags,
|
|
1277
|
+
routeName,
|
|
1278
|
+
});
|
|
1279
|
+
devPrerenderCache.set(routerInstance, cacheKey, body);
|
|
1280
|
+
res.setHeader("content-type", "application/json");
|
|
1281
|
+
res.setHeader("x-rango-shell-dev", "MISS");
|
|
1282
|
+
res.end(body);
|
|
1283
|
+
return;
|
|
1284
|
+
} catch (err: any) {
|
|
1285
|
+
console.warn(
|
|
1286
|
+
`[rango] Dev shell capture error for ${pathname} (route keeps runtime capture): ${err.message}`,
|
|
1287
|
+
);
|
|
1288
|
+
res.statusCode = 404;
|
|
1289
|
+
res.end(`Shell capture error: ${err.message}`);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
res.statusCode = 404;
|
|
1294
|
+
res.end("No router matched");
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1060
1297
|
// Watch url module and router files for changes and regenerate named-routes.gen.ts.
|
|
1061
1298
|
// Process files containing urls( or createRouter( to update the combined route map.
|
|
1062
1299
|
if (opts?.staticRouteTypesGeneration !== false) {
|
|
@@ -1430,8 +1667,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1430
1667
|
const buildStartTime = performance.now();
|
|
1431
1668
|
debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
|
|
1432
1669
|
resetStagedBuildAssets(s.projectRoot);
|
|
1433
|
-
s
|
|
1434
|
-
s.staticManifestEntries = null;
|
|
1670
|
+
resetPrerenderCollection(s);
|
|
1435
1671
|
|
|
1436
1672
|
// Acquire build-time env bindings if configured
|
|
1437
1673
|
await timed(debugDiscovery, "build acquireBuildEnv", () =>
|
|
@@ -1502,12 +1738,23 @@ export function createRouterDiscoveryPlugin(
|
|
|
1502
1738
|
);
|
|
1503
1739
|
} finally {
|
|
1504
1740
|
delete (globalThis as any).__rscRouterDiscoveryActive;
|
|
1505
|
-
if (tempServer) {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
)
|
|
1741
|
+
if (tempServer && s.shellCandidates?.length) {
|
|
1742
|
+
// Prerender+ppr candidates exist: keep the temp server (and its
|
|
1743
|
+
// realm — tries installed, registry populated) alive for the
|
|
1744
|
+
// post-build shell capture phase (buildApp post, producer B #699).
|
|
1745
|
+
// The prelude embeds built client asset URLs, so the capture can
|
|
1746
|
+
// only run after the client build; that phase closes the server.
|
|
1747
|
+
// buildEnv release is deferred with it — a bake-lane loader
|
|
1748
|
+
// executing during the capture may read ctx.env.
|
|
1749
|
+
s.shellPhaseTempServer = tempServer;
|
|
1750
|
+
} else {
|
|
1751
|
+
if (tempServer) {
|
|
1752
|
+
await timed(debugDiscovery, "build tempServer.close", () =>
|
|
1753
|
+
tempServer.close(),
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
await releaseBuildEnv(s);
|
|
1509
1757
|
}
|
|
1510
|
-
await releaseBuildEnv(s);
|
|
1511
1758
|
debugDiscovery?.(
|
|
1512
1759
|
"build discovery done (%sms)",
|
|
1513
1760
|
(performance.now() - buildStartTime).toFixed(1),
|
|
@@ -1515,6 +1762,43 @@ export function createRouterDiscoveryPlugin(
|
|
|
1515
1762
|
}
|
|
1516
1763
|
},
|
|
1517
1764
|
|
|
1765
|
+
// Post-build PPR shell capture (producer B, #699): runs after EVERY
|
|
1766
|
+
// environment bundle is written — the shell prelude embeds built client
|
|
1767
|
+
// asset URLs (bootstrap entry), which do not exist at buildStart. The
|
|
1768
|
+
// kept temp server and the buildEnv were deferred AS A PAIR in
|
|
1769
|
+
// buildStart's finally; this finally is the pair's success-path owner
|
|
1770
|
+
// (buildEnd below owns the aborted-build path) — the phase itself is a
|
|
1771
|
+
// pure producer and tears down only the globals it installs.
|
|
1772
|
+
buildApp: {
|
|
1773
|
+
order: "post",
|
|
1774
|
+
async handler(builder) {
|
|
1775
|
+
try {
|
|
1776
|
+
await runShellPrerenderPhase(s, builder as any);
|
|
1777
|
+
} finally {
|
|
1778
|
+
if (s.isBuildMode) {
|
|
1779
|
+
const tempServer = s.shellPhaseTempServer;
|
|
1780
|
+
s.shellPhaseTempServer = null;
|
|
1781
|
+
if (tempServer) await tempServer.close();
|
|
1782
|
+
await releaseBuildEnv(s);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
},
|
|
1787
|
+
|
|
1788
|
+
// An environment build failure aborts the builder before the buildApp
|
|
1789
|
+
// post hook — never leak the kept temp server (open handles hang the CLI)
|
|
1790
|
+
// or the deferred buildEnv (a live miniflare proxy).
|
|
1791
|
+
async buildEnd(error) {
|
|
1792
|
+
if (!error || !s.shellPhaseTempServer) return;
|
|
1793
|
+
const tempServer = s.shellPhaseTempServer;
|
|
1794
|
+
s.shellPhaseTempServer = null;
|
|
1795
|
+
try {
|
|
1796
|
+
await tempServer.close();
|
|
1797
|
+
} finally {
|
|
1798
|
+
await releaseBuildEnv(s);
|
|
1799
|
+
}
|
|
1800
|
+
},
|
|
1801
|
+
|
|
1518
1802
|
// Suppress vite's HMR cascade for our own gen-file writes.
|
|
1519
1803
|
//
|
|
1520
1804
|
// After every cf HMR cycle, refreshTempRscEnv → writeRouteTypesFiles
|
|
@@ -207,20 +207,27 @@ export function resetStagedBuildAssets(projectRoot: string): void {
|
|
|
207
207
|
rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Write one content-hashed `export default <value>;` asset module into `dir`
|
|
212
|
+
* (created if needed) and return its file name. Identical payloads dedupe to
|
|
213
|
+
* one file (the hash is the content). Shared by the staged prerender/static
|
|
214
|
+
* flow (stageBuildAssetModule below) and the shell prerender phase, which
|
|
215
|
+
* writes directly into the final RSC assets dir — it runs after buildApp,
|
|
216
|
+
* past the staging/copy window.
|
|
217
|
+
*/
|
|
218
|
+
export function writeBuildAssetModule(
|
|
219
|
+
dir: string,
|
|
220
|
+
prefix: "__pr" | "__st" | "__ps",
|
|
213
221
|
exportValue: string,
|
|
214
222
|
): string {
|
|
215
|
-
|
|
216
|
-
mkdirSync(stagedDir, { recursive: true });
|
|
223
|
+
mkdirSync(dir, { recursive: true });
|
|
217
224
|
|
|
218
225
|
const contentHash = createHash("sha256")
|
|
219
226
|
.update(exportValue)
|
|
220
227
|
.digest("hex")
|
|
221
228
|
.slice(0, 8);
|
|
222
229
|
const fileName = `${prefix}-${contentHash}.js`;
|
|
223
|
-
const filePath = resolve(
|
|
230
|
+
const filePath = resolve(dir, fileName);
|
|
224
231
|
|
|
225
232
|
if (!existsSync(filePath)) {
|
|
226
233
|
writeFileSync(filePath, `export default ${exportValue};\n`);
|
|
@@ -229,6 +236,18 @@ export function stageBuildAssetModule(
|
|
|
229
236
|
return fileName;
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
export function stageBuildAssetModule(
|
|
240
|
+
projectRoot: string,
|
|
241
|
+
prefix: "__pr" | "__st",
|
|
242
|
+
exportValue: string,
|
|
243
|
+
): string {
|
|
244
|
+
return writeBuildAssetModule(
|
|
245
|
+
getStagedAssetDir(projectRoot),
|
|
246
|
+
prefix,
|
|
247
|
+
exportValue,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
232
251
|
export function copyStagedBuildAssets(
|
|
233
252
|
projectRoot: string,
|
|
234
253
|
fileNames: Iterable<string>,
|