@rangojs/router 0.19.1 → 0.21.0
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 +26 -14
- package/dist/types/browser/event-controller.d.ts +32 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/context.d.ts +11 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +4 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/handle-snapshot.d.ts +36 -50
- package/dist/types/cache/index.d.ts +2 -0
- package/dist/types/cache/shell-snapshot.d.ts +19 -3
- package/dist/types/cache/types.d.ts +21 -10
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/handles/deferred-resolution.d.ts +11 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/router-interfaces.d.ts +2 -1
- package/dist/types/router/router-options.d.ts +17 -7
- package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +3 -2
- package/dist/types/rsc/types.d.ts +4 -6
- package/dist/types/server/build-version-table.d.ts +44 -0
- package/dist/types/server/handle-store.d.ts +87 -46
- package/dist/types/server/request-context.d.ts +22 -8
- package/dist/types/ssr/entry-preload-priority.d.ts +69 -0
- package/dist/types/ssr/index.d.ts +1 -0
- package/dist/types/ssr/preinit-client-references.d.ts +17 -5
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +2 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +21 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/types/handler-context.d.ts +8 -5
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +14 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/plugin-types.d.ts +47 -6
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +21 -2
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/client-chunks.d.ts +24 -2
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1544 -420
- package/package.json +1 -2
- package/skills/bundle-analysis/SKILL.md +1 -1
- package/skills/cache-guide/SKILL.md +84 -20
- package/skills/caching/SKILL.md +16 -5
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +2 -1
- package/skills/comparison/references/framework-comparison.md +16 -12
- package/skills/hooks/SKILL.md +3 -0
- package/skills/hooks/handle-and-actions.md +6 -0
- package/skills/hooks/navigation.md +28 -0
- package/skills/hooks/state.md +209 -8
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +13 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +19 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/ppr/SKILL.md +119 -40
- package/skills/prerender/SKILL.md +7 -3
- package/skills/rango/SKILL.md +8 -7
- package/skills/router-setup/SKILL.md +23 -3
- package/skills/testing/SKILL.md +30 -26
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +63 -14
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +16 -5
- package/skills/vercel/SKILL.md +3 -3
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +75 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/navigation-bridge.ts +41 -18
- package/src/browser/navigation-transaction.ts +15 -10
- package/src/browser/partial-update.ts +10 -2
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +2 -3
- package/src/browser/react/NavigationProvider.tsx +25 -1
- package/src/browser/react/context.ts +13 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +40 -75
- package/src/browser/react/subscribe-to-nothing.ts +7 -0
- package/src/browser/react/use-handle.ts +20 -1
- package/src/browser/rsc-router.tsx +33 -11
- package/src/browser/server-action-bridge.ts +17 -1
- package/src/cache/cache-scope.ts +18 -8
- package/src/cache/cf/cf-cache-store.ts +75 -55
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/handle-snapshot.ts +69 -87
- package/src/cache/index.ts +4 -0
- package/src/cache/shell-snapshot.ts +35 -5
- package/src/cache/types.ts +21 -10
- package/src/cache/vercel/vercel-cache-store.ts +48 -20
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/handles/deferred-resolution.ts +31 -0
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +11 -18
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/request-classification.ts +15 -12
- package/src/router/router-interfaces.ts +2 -1
- package/src/router/router-options.ts +17 -7
- package/src/router/segment-resolution/loader-cache.ts +95 -60
- package/src/router-versions.ts +60 -0
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +14 -4
- package/src/rsc/rsc-rendering.ts +12 -1
- package/src/rsc/shell-build-manifest.ts +10 -7
- package/src/rsc/shell-capture.ts +36 -26
- package/src/rsc/types.ts +4 -6
- package/src/server/build-version-table.ts +115 -0
- package/src/server/handle-store.ts +344 -127
- package/src/server/request-context.ts +62 -11
- package/src/ssr/entry-preload-priority.ts +308 -0
- package/src/ssr/index.tsx +46 -9
- package/src/ssr/preinit-client-references.ts +17 -5
- package/src/ssr/ssr-root.tsx +16 -4
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +277 -43
- package/src/testing/serve-shell-request.ts +80 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/types/handler-context.ts +8 -5
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +28 -38
- package/src/vite/discovery/state.ts +19 -0
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/plugin-types.ts +48 -6
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +40 -7
- package/src/vite/rango.ts +30 -3
- package/src/vite/router-discovery.ts +82 -13
- package/src/vite/utils/client-chunks.ts +84 -14
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is installed for a package a server build leaves external.
|
|
3
|
+
*
|
|
4
|
+
* The node preset bundles only packages that need the React server condition
|
|
5
|
+
* (plugin-rsc's `noExternal`); every other dependency stays an import the
|
|
6
|
+
* deployed process resolves from node_modules. The built chunk then says
|
|
7
|
+
* `from "pkg"` whichever version is installed, so a lockfile bump changes what
|
|
8
|
+
* the server renders and nothing the cache versions hash
|
|
9
|
+
* (discovery/build-versions.ts). This module supplies the missing input: the
|
|
10
|
+
* `name@version` of the package and of everything it depends on, as installed
|
|
11
|
+
* next to the project at build time.
|
|
12
|
+
*
|
|
13
|
+
* A registry package's code is fixed by its version. A linked package (a
|
|
14
|
+
* workspace package, resolved to a directory outside node_modules) is not.
|
|
15
|
+
* Vite bundles those unless the config lists one in `resolve.external`; then
|
|
16
|
+
* it comes through here and its files are digested instead.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
readdirSync,
|
|
23
|
+
readFileSync,
|
|
24
|
+
realpathSync,
|
|
25
|
+
statSync,
|
|
26
|
+
type Stats,
|
|
27
|
+
} from "node:fs";
|
|
28
|
+
import { dirname, join, sep } from "node:path";
|
|
29
|
+
import { compareStrings } from "../utils/compare-strings.js";
|
|
30
|
+
|
|
31
|
+
interface InstalledPackage {
|
|
32
|
+
readonly label: string;
|
|
33
|
+
readonly dependencies: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** What a symlink points at, or nothing for a dangling or circular one: an
|
|
37
|
+
* import could not load it either. */
|
|
38
|
+
function linkTarget(path: string): Stats | undefined {
|
|
39
|
+
try {
|
|
40
|
+
return statSync(path);
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Digest of every file under `dir`, by relative path, skipping installs.
|
|
48
|
+
* Symlinks are followed (a linked file is importable like any other); a
|
|
49
|
+
* directory is entered once, so a link back up the tree ends there.
|
|
50
|
+
*/
|
|
51
|
+
function digestDirectory(dir: string): string {
|
|
52
|
+
const hash = createHash("sha256");
|
|
53
|
+
const entered = new Set<string>();
|
|
54
|
+
const visit = (current: string, prefix: string): void => {
|
|
55
|
+
const real = realpathSync(current);
|
|
56
|
+
if (entered.has(real)) return;
|
|
57
|
+
entered.add(real);
|
|
58
|
+
const entries = readdirSync(current, { withFileTypes: true }).sort((a, b) =>
|
|
59
|
+
compareStrings(a.name, b.name),
|
|
60
|
+
);
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
63
|
+
const path = join(current, entry.name);
|
|
64
|
+
const name = `${prefix}${entry.name}`;
|
|
65
|
+
const kind = entry.isSymbolicLink() ? linkTarget(path) : entry;
|
|
66
|
+
if (kind?.isDirectory()) {
|
|
67
|
+
visit(path, `${name}/`);
|
|
68
|
+
} else if (kind?.isFile()) {
|
|
69
|
+
const bytes = readFileSync(path);
|
|
70
|
+
hash.update(`${name}\0${bytes.byteLength}\0`);
|
|
71
|
+
hash.update(bytes);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
visit(dir, "");
|
|
76
|
+
return hash.digest("hex");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Node's lookup for a bare specifier: the nearest node_modules holding it. */
|
|
80
|
+
function findPackageDir(name: string, from: string): string | undefined {
|
|
81
|
+
for (let dir = from; ; ) {
|
|
82
|
+
const candidate = join(dir, "node_modules", name);
|
|
83
|
+
if (existsSync(join(candidate, "package.json"))) {
|
|
84
|
+
return realpathSync(candidate);
|
|
85
|
+
}
|
|
86
|
+
const parent = dirname(dir);
|
|
87
|
+
if (parent === dir) return undefined;
|
|
88
|
+
dir = parent;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns a function describing an installed package by name: the sorted
|
|
94
|
+
* `name@version` lines of the package and its transitive dependencies
|
|
95
|
+
* (dependencies, optionalDependencies and peerDependencies, each resolved from
|
|
96
|
+
* the package that declares it), with a digest of its files for a linked
|
|
97
|
+
* one, or `undefined` when the package is not installed. Results are kept per
|
|
98
|
+
* name for the life of the returned function.
|
|
99
|
+
*/
|
|
100
|
+
export function createInstalledPackageDescriber(
|
|
101
|
+
projectRoot: string,
|
|
102
|
+
): (name: string) => string | undefined {
|
|
103
|
+
const packages = new Map<string, InstalledPackage | undefined>();
|
|
104
|
+
const read = (dir: string): InstalledPackage | undefined => {
|
|
105
|
+
if (packages.has(dir)) return packages.get(dir);
|
|
106
|
+
let manifest: any;
|
|
107
|
+
try {
|
|
108
|
+
manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
109
|
+
} catch {
|
|
110
|
+
packages.set(dir, undefined);
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
// Outside the try: a linked package whose files cannot be read must fail
|
|
114
|
+
// the build, not drop out of the version.
|
|
115
|
+
const linked = !dir.split(sep).includes("node_modules");
|
|
116
|
+
const found: InstalledPackage = {
|
|
117
|
+
label:
|
|
118
|
+
`${manifest.name}@${manifest.version}` +
|
|
119
|
+
(linked ? ` files:${digestDirectory(dir)}` : ""),
|
|
120
|
+
dependencies: Object.keys({
|
|
121
|
+
...manifest.dependencies,
|
|
122
|
+
...manifest.optionalDependencies,
|
|
123
|
+
...manifest.peerDependencies,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
packages.set(dir, found);
|
|
127
|
+
return found;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Packages share most of their dependencies (the same peers, the same
|
|
131
|
+
// utilities): each lookup is done once for every package described.
|
|
132
|
+
const located = new Map<string, string | undefined>();
|
|
133
|
+
const locate = (name: string, from: string): string | undefined => {
|
|
134
|
+
const key = `${from}\0${name}`;
|
|
135
|
+
if (!located.has(key)) located.set(key, findPackageDir(name, from));
|
|
136
|
+
return located.get(key);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const described = new Map<string, string | undefined>();
|
|
140
|
+
return (name) => {
|
|
141
|
+
if (described.has(name)) return described.get(name);
|
|
142
|
+
const labels = new Set<string>();
|
|
143
|
+
const seen = new Set<string>();
|
|
144
|
+
const pending: Array<[name: string, from: string]> = [[name, projectRoot]];
|
|
145
|
+
while (pending.length > 0) {
|
|
146
|
+
const [dependency, from] = pending.pop()!;
|
|
147
|
+
const dir = locate(dependency, from);
|
|
148
|
+
if (dir === undefined || seen.has(dir)) continue;
|
|
149
|
+
seen.add(dir);
|
|
150
|
+
const installed = read(dir);
|
|
151
|
+
if (!installed) continue;
|
|
152
|
+
labels.add(installed.label);
|
|
153
|
+
for (const next of installed.dependencies) pending.push([next, dir]);
|
|
154
|
+
}
|
|
155
|
+
const text = labels.size > 0 ? [...labels].sort().join("\n") : undefined;
|
|
156
|
+
described.set(name, text);
|
|
157
|
+
return text;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { contextSet, hasContextVars } from "../../context-var.js";
|
|
10
|
+
import { sha256 } from "./build-versions.js";
|
|
10
11
|
import {
|
|
11
12
|
encodePathParam,
|
|
12
13
|
substituteRouteParams,
|
|
@@ -21,6 +22,27 @@ import { createRangoDebugger, NS } from "../debug.js";
|
|
|
21
22
|
|
|
22
23
|
const debug = createRangoDebugger(NS.prerender);
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Note a payload the build rendered and will ship as an asset module, for the
|
|
27
|
+
* owning router's cache version (build-versions.ts). The handler that produced
|
|
28
|
+
* it is evicted from the bundle, so the payload is the only thing left in the
|
|
29
|
+
* server output that changes when the handler or its build-time data does.
|
|
30
|
+
*/
|
|
31
|
+
function recordBuildData(
|
|
32
|
+
state: DiscoveryState,
|
|
33
|
+
kind: "prerender" | "static",
|
|
34
|
+
key: string,
|
|
35
|
+
value: string,
|
|
36
|
+
owner: { routerId?: string; moduleId?: string },
|
|
37
|
+
): void {
|
|
38
|
+
state.buildData.push({
|
|
39
|
+
kind,
|
|
40
|
+
key,
|
|
41
|
+
digest: sha256(value),
|
|
42
|
+
...owner,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
/**
|
|
25
47
|
* Expand prerender routes into concrete URLs and render them via the
|
|
26
48
|
* RSC runner. Stages asset modules and stores key-to-file entries in
|
|
@@ -248,7 +270,7 @@ export async function expandPrerenderRoutes(
|
|
|
248
270
|
group.concurrency,
|
|
249
271
|
async (entry) => {
|
|
250
272
|
const startUrl = performance.now();
|
|
251
|
-
for (const [, routerInstance] of registry) {
|
|
273
|
+
for (const [routerId, routerInstance] of registry) {
|
|
252
274
|
if (!routerInstance.matchForPrerender) continue;
|
|
253
275
|
try {
|
|
254
276
|
const result = await routerInstance.matchForPrerender(
|
|
@@ -281,6 +303,9 @@ export async function expandPrerenderRoutes(
|
|
|
281
303
|
"__pr",
|
|
282
304
|
mainValue,
|
|
283
305
|
);
|
|
306
|
+
recordBuildData(state, "prerender", mainKey, mainValue, {
|
|
307
|
+
routerId,
|
|
308
|
+
});
|
|
284
309
|
// Prerender + ppr composition: flag the URL as a build-time shell
|
|
285
310
|
// candidate for the post-build capture phase (producer B, #699).
|
|
286
311
|
// The payload JSON is retained in memory so that phase can seed an
|
|
@@ -310,6 +335,15 @@ export async function expandPrerenderRoutes(
|
|
|
310
335
|
"__pr",
|
|
311
336
|
interceptValue,
|
|
312
337
|
);
|
|
338
|
+
recordBuildData(
|
|
339
|
+
state,
|
|
340
|
+
"prerender",
|
|
341
|
+
interceptKey,
|
|
342
|
+
interceptValue,
|
|
343
|
+
{
|
|
344
|
+
routerId,
|
|
345
|
+
},
|
|
346
|
+
);
|
|
313
347
|
}
|
|
314
348
|
const elapsed = (performance.now() - startUrl).toFixed(0);
|
|
315
349
|
console.log(
|
|
@@ -447,6 +481,9 @@ export async function renderStaticHandlers(
|
|
|
447
481
|
"__st",
|
|
448
482
|
exportValue,
|
|
449
483
|
);
|
|
484
|
+
recordBuildData(state, "static", def.$$id, exportValue, {
|
|
485
|
+
moduleId,
|
|
486
|
+
});
|
|
450
487
|
const elapsed = (performance.now() - startHandler).toFixed(0);
|
|
451
488
|
console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
|
|
452
489
|
staticDone++;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-build cache version phase.
|
|
3
|
+
*
|
|
4
|
+
* Runs from the buildApp post hook, after every environment bundle is written
|
|
5
|
+
* and postprocessBundle has rewritten the RSC chunks, and before the shell
|
|
6
|
+
* capture phase, which stamps its entries with the document versions computed
|
|
7
|
+
* here. Reads the bytes that ship, computes each router's versions
|
|
8
|
+
* (build-versions.ts) and writes the table into the built
|
|
9
|
+
* `@rangojs/router:version` module in place of its placeholder.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_ROUTER_VERSIONS_KEY,
|
|
17
|
+
type RouterVersionsTable,
|
|
18
|
+
} from "../../router-versions.js";
|
|
19
|
+
import { isEncryptionKeyStable } from "../encryption-key.js";
|
|
20
|
+
import { createRangoDebugger, NS } from "../debug.js";
|
|
21
|
+
import {
|
|
22
|
+
defaultExportProperties,
|
|
23
|
+
literalProperties,
|
|
24
|
+
} from "../plugins/stable-rsc-output.js";
|
|
25
|
+
import { RANGO_BUILD_DIR } from "../utils/prerender-utils.js";
|
|
26
|
+
import {
|
|
27
|
+
computeRouterVersions,
|
|
28
|
+
fillRouterVersions,
|
|
29
|
+
portableModuleId,
|
|
30
|
+
sha256,
|
|
31
|
+
type BuildDataRecord,
|
|
32
|
+
type ComputedRouterVersions,
|
|
33
|
+
} from "./build-versions.js";
|
|
34
|
+
import { createInstalledPackageDescriber } from "./installed-packages.js";
|
|
35
|
+
import type { DiscoveryState } from "./state.js";
|
|
36
|
+
|
|
37
|
+
const debug = createRangoDebugger(NS.build);
|
|
38
|
+
|
|
39
|
+
/** plugin-rsc's client asset manifest, written into the SSR and RSC out dirs. */
|
|
40
|
+
const ASSETS_MANIFEST_FILE = "__vite_rsc_assets_manifest.js";
|
|
41
|
+
const EMPTY_SERVER_RESOURCES = /"serverResources":\s*\{\s*\}/;
|
|
42
|
+
|
|
43
|
+
/** Minimal builder surface the post-build phases read. */
|
|
44
|
+
export interface BuilderLike {
|
|
45
|
+
config?: { base?: string };
|
|
46
|
+
environments?: Record<
|
|
47
|
+
string,
|
|
48
|
+
{ config?: { build?: { outDir?: string } } } | undefined
|
|
49
|
+
>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An environment's output directory, or Vite's default layout without one.
|
|
54
|
+
* Resolved against the root: a relative `build.outDir` is relative to it, not
|
|
55
|
+
* to the directory `vite build <root>` was run from.
|
|
56
|
+
*/
|
|
57
|
+
export function environmentOutDir(
|
|
58
|
+
builder: BuilderLike | undefined,
|
|
59
|
+
projectRoot: string,
|
|
60
|
+
environment: string,
|
|
61
|
+
): string {
|
|
62
|
+
return resolve(
|
|
63
|
+
projectRoot,
|
|
64
|
+
builder?.environments?.[environment]?.config?.build?.outDir ??
|
|
65
|
+
join("dist", environment),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Where the build leaves what each version was computed from. Outside the
|
|
71
|
+
* output directories, so it never ships.
|
|
72
|
+
*/
|
|
73
|
+
export const VERSIONS_REPORT_FILE: string = `${RANGO_BUILD_DIR}/cache-versions.json`;
|
|
74
|
+
|
|
75
|
+
function readIfPresent(path: string): Buffer | undefined {
|
|
76
|
+
try {
|
|
77
|
+
return readFileSync(path);
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
if (err?.code === "ENOENT") return undefined;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The `serverResources` map of plugin-rsc's assets manifest
|
|
86
|
+
* (`export default { ..., serverResources: { "<module>": { js, css } } }`):
|
|
87
|
+
* each server module that imports CSS, to the source text of its entry. Text,
|
|
88
|
+
* not a parsed value: with `renderBuiltUrl` an entry holds runtime expressions.
|
|
89
|
+
* Throws when the manifest is not that shape, so a plugin-rsc that moves it
|
|
90
|
+
* fails the build instead of dropping stylesheets from the versions.
|
|
91
|
+
*/
|
|
92
|
+
export function readServerResources(manifest: string): Map<string, string> {
|
|
93
|
+
// Most apps import no CSS from a server component: skip parsing a manifest
|
|
94
|
+
// that lists every client reference. (Inside a JSON string the quotes would
|
|
95
|
+
// be escaped, so this text can only be the property itself.)
|
|
96
|
+
if (EMPTY_SERVER_RESOURCES.test(manifest)) return new Map();
|
|
97
|
+
const entries = literalProperties(
|
|
98
|
+
defaultExportProperties(manifest)?.find(
|
|
99
|
+
(property) => property.key === "serverResources",
|
|
100
|
+
)?.value,
|
|
101
|
+
);
|
|
102
|
+
if (!entries) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${ASSETS_MANIFEST_FILE} has no serverResources object literal: the installed ` +
|
|
105
|
+
`@vitejs/plugin-rsc writes a manifest @rangojs/router cannot read. ` +
|
|
106
|
+
`Report it with the plugin-rsc version.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return new Map(
|
|
110
|
+
entries.map((entry) => [
|
|
111
|
+
entry.key,
|
|
112
|
+
manifest.slice(entry.value.start, entry.value.end),
|
|
113
|
+
]),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* One record per clientUrls() module: the projection the server materializes
|
|
119
|
+
* the group from. The map holds it under each of the module's reference ids.
|
|
120
|
+
*/
|
|
121
|
+
function clientUrlRecords(s: DiscoveryState): BuildDataRecord[] {
|
|
122
|
+
const projectionOf = new Map<string, string>();
|
|
123
|
+
for (const [referenceId, projection] of s.clientUrlProjectionMap ?? []) {
|
|
124
|
+
projectionOf.set(
|
|
125
|
+
s.clientUrlSourceByReferenceId?.get(referenceId) ?? referenceId,
|
|
126
|
+
JSON.stringify(projection),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return [...projectionOf].map(([source, projection]) => ({
|
|
130
|
+
kind: "client-urls",
|
|
131
|
+
key: portableModuleId(source, s.projectRoot),
|
|
132
|
+
digest: sha256(projection),
|
|
133
|
+
moduleId: source,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Write every version's inputs as `name: digest`. A version is one hash, so a
|
|
139
|
+
* changed version says nothing about what changed; diffing two builds' reports
|
|
140
|
+
* names the file, payload or key that did.
|
|
141
|
+
*/
|
|
142
|
+
function writeVersionsReport(
|
|
143
|
+
projectRoot: string,
|
|
144
|
+
computed: ComputedRouterVersions,
|
|
145
|
+
sourceOf: ReadonlyMap<string, string>,
|
|
146
|
+
): void {
|
|
147
|
+
const routers: Record<string, unknown> = {};
|
|
148
|
+
for (const detail of computed.details) {
|
|
149
|
+
routers[detail.routerId] = {
|
|
150
|
+
source: sourceOf.get(detail.routerId) ?? "",
|
|
151
|
+
data: detail.data,
|
|
152
|
+
document: detail.document,
|
|
153
|
+
dataInputs: Object.fromEntries(detail.dataInputs),
|
|
154
|
+
documentInputs: Object.fromEntries(detail.documentInputs),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Listed once, not per router. `ssrAndClient`: what the `ssr-and-client`
|
|
158
|
+
// input of every document version is a digest of. `unownedFiles`: server
|
|
159
|
+
// files in no router's version but the whole build's (the host entry, the
|
|
160
|
+
// registries); a change there keeps every app's cache.
|
|
161
|
+
const report = {
|
|
162
|
+
routers,
|
|
163
|
+
ssrAndClient: {
|
|
164
|
+
base: computed.ssrAndClient.base,
|
|
165
|
+
ssr: Object.fromEntries(computed.ssrAndClient.ssr),
|
|
166
|
+
externals: Object.fromEntries(computed.ssrAndClient.externals),
|
|
167
|
+
client: computed.ssrAndClient.client,
|
|
168
|
+
},
|
|
169
|
+
unownedFiles: computed.unownedFiles,
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
const path = resolve(projectRoot, VERSIONS_REPORT_FILE);
|
|
173
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
174
|
+
writeFileSync(path, JSON.stringify(report, null, 2) + "\n");
|
|
175
|
+
} catch (err: any) {
|
|
176
|
+
debug?.("could not write %s: %s", VERSIONS_REPORT_FILE, err?.message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Compute the versions, write them into the built version module and return
|
|
182
|
+
* the table (`undefined` outside a build). Fails the build when an input was
|
|
183
|
+
* not recorded or cannot be read: a version computed without it would stay
|
|
184
|
+
* the same when it changes.
|
|
185
|
+
*/
|
|
186
|
+
export function runRouterVersionsPhase(
|
|
187
|
+
s: DiscoveryState,
|
|
188
|
+
builder: BuilderLike | undefined,
|
|
189
|
+
): RouterVersionsTable | undefined {
|
|
190
|
+
if (!s.isBuildMode) return undefined;
|
|
191
|
+
const { serverBuildGraph: server, clientBuildGraph: client } = s;
|
|
192
|
+
const ssr = s.ssrBundle;
|
|
193
|
+
const rscVersionFiles = s.versionModuleFiles.get("rsc") ?? [];
|
|
194
|
+
// The built version module holds a placeholder only this phase replaces: a
|
|
195
|
+
// build that skipped it would throw a ReferenceError on its first import.
|
|
196
|
+
if (!server || !client || !ssr || rscVersionFiles.length === 0) {
|
|
197
|
+
const missing = [
|
|
198
|
+
!server && "the RSC bundle",
|
|
199
|
+
!client && "the client bundle",
|
|
200
|
+
!ssr && "the SSR bundle",
|
|
201
|
+
rscVersionFiles.length === 0 &&
|
|
202
|
+
"the RSC chunk that holds the version module",
|
|
203
|
+
].filter(Boolean);
|
|
204
|
+
throw new Error(
|
|
205
|
+
`[rango] The build did not record ${missing.join(", ")}, so the cache versions cannot be computed. ` +
|
|
206
|
+
"This is a bug in @rangojs/router; please report it.",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const start = performance.now();
|
|
211
|
+
const rscOutDir = environmentOutDir(builder, s.projectRoot, "rsc");
|
|
212
|
+
const ssrOutDir = environmentOutDir(builder, s.projectRoot, "ssr");
|
|
213
|
+
|
|
214
|
+
let computed: ComputedRouterVersions;
|
|
215
|
+
try {
|
|
216
|
+
const assetsManifest = readIfPresent(join(rscOutDir, ASSETS_MANIFEST_FILE));
|
|
217
|
+
if (assetsManifest === undefined) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`${ASSETS_MANIFEST_FILE} is not in the RSC output directory, so the stylesheets ` +
|
|
220
|
+
`server components link cannot be read`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
computed = computeRouterVersions({
|
|
224
|
+
projectRoot: s.projectRoot,
|
|
225
|
+
server,
|
|
226
|
+
client,
|
|
227
|
+
// plugin-rsc writes its manifest next to the SSR bundle, not into it.
|
|
228
|
+
ssr: {
|
|
229
|
+
chunks: ssr.chunks,
|
|
230
|
+
assets: [
|
|
231
|
+
...ssr.assets.filter(
|
|
232
|
+
(asset) => asset.fileName !== ASSETS_MANIFEST_FILE,
|
|
233
|
+
),
|
|
234
|
+
{ fileName: ASSETS_MANIFEST_FILE, name: ASSETS_MANIFEST_FILE },
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
routers: s.perRouterManifests.map((entry) => ({
|
|
238
|
+
id: entry.id,
|
|
239
|
+
moduleId: entry.sourceFile,
|
|
240
|
+
})),
|
|
241
|
+
buildData: [...s.buildData, ...clientUrlRecords(s)],
|
|
242
|
+
readServerFile: (fileName) => readIfPresent(join(rscOutDir, fileName)),
|
|
243
|
+
readSsrFile: (fileName) => readIfPresent(join(ssrOutDir, fileName)),
|
|
244
|
+
base: builder?.config?.base ?? "/",
|
|
245
|
+
serverResources: readServerResources(assetsManifest.toString("utf-8")),
|
|
246
|
+
externals: {
|
|
247
|
+
describePackage: createInstalledPackageDescriber(s.projectRoot),
|
|
248
|
+
readFile: readIfPresent,
|
|
249
|
+
buildId: randomUUID(),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
} catch (err: any) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`[rango] Could not compute the cache versions: ${err?.message ?? err}`,
|
|
255
|
+
{ cause: err },
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const { table, details } = computed;
|
|
259
|
+
|
|
260
|
+
for (const [environment, fileNames] of s.versionModuleFiles) {
|
|
261
|
+
const outDir = environmentOutDir(builder, s.projectRoot, environment);
|
|
262
|
+
let filled = 0;
|
|
263
|
+
for (const fileName of fileNames) {
|
|
264
|
+
const path = join(outDir, fileName);
|
|
265
|
+
const source = readIfPresent(path);
|
|
266
|
+
const next =
|
|
267
|
+
source === undefined
|
|
268
|
+
? undefined
|
|
269
|
+
: fillRouterVersions(source.toString("utf-8"), table);
|
|
270
|
+
if (next === undefined) continue;
|
|
271
|
+
writeFileSync(path, next);
|
|
272
|
+
filled++;
|
|
273
|
+
}
|
|
274
|
+
if (environment === "rsc" && filled === 0) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`[rango] Could not write the cache versions into the server build: no placeholder in ${fileNames.join(", ")} under ${outDir}. ` +
|
|
277
|
+
`The build would fail to start. This is a bug in @rangojs/router; please report it.`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const elapsed = (performance.now() - start).toFixed(1);
|
|
283
|
+
const sourceOf = new Map(
|
|
284
|
+
s.perRouterManifests.map((entry) => [
|
|
285
|
+
entry.id,
|
|
286
|
+
entry.sourceFile ? portableModuleId(entry.sourceFile, s.projectRoot) : "",
|
|
287
|
+
]),
|
|
288
|
+
);
|
|
289
|
+
const detailOf = new Map(details.map((detail) => [detail.routerId, detail]));
|
|
290
|
+
const wholeBuild = detailOf.get(DEFAULT_ROUTER_VERSIONS_KEY)!;
|
|
291
|
+
let keyed = 0;
|
|
292
|
+
if (s.perRouterManifests.length > 0) {
|
|
293
|
+
console.log(
|
|
294
|
+
`[rango] Cache versions for ${s.perRouterManifests.length} router(s), data / document (${elapsed}ms):`,
|
|
295
|
+
);
|
|
296
|
+
for (const { id } of s.perRouterManifests) {
|
|
297
|
+
// A router whose module is not in the server bundle serves with the
|
|
298
|
+
// whole-build pair: any change clears it. Say so, it is not isolated.
|
|
299
|
+
const own = detailOf.get(id);
|
|
300
|
+
const detail = own ?? wholeBuild;
|
|
301
|
+
if (detail.usesEncryptionKey) keyed++;
|
|
302
|
+
console.log(
|
|
303
|
+
`[rango] ${detail.data} / ${detail.document} ${sourceOf.get(id) || id}${own ? "" : " (whole build)"}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
debug?.(
|
|
308
|
+
"router versions: %d server file(s), %sms, table=%o",
|
|
309
|
+
server.chunks.length + server.assets.length,
|
|
310
|
+
elapsed,
|
|
311
|
+
table,
|
|
312
|
+
);
|
|
313
|
+
writeVersionsReport(s.projectRoot, computed, sourceOf);
|
|
314
|
+
|
|
315
|
+
if (computed.undetermined.length > 0) {
|
|
316
|
+
console.log(
|
|
317
|
+
`[rango] ${computed.undetermined.length} import(s) the server build leaves external could not be read ` +
|
|
318
|
+
`at build time (${computed.undetermined.join(", ")}): a package that is not installed where the build ` +
|
|
319
|
+
`can find it, a path that does not resolve, or a URL. What they resolve to at run time is unknown, so ` +
|
|
320
|
+
`the routers importing them get a new cache version on every build.`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// The one condition under which a deploy of unchanged code still clears a
|
|
325
|
+
// cache: the key is part of these routers' versions and this build made its
|
|
326
|
+
// own. Said once per build, and only when it applies.
|
|
327
|
+
if (keyed > 0 && !isEncryptionKeyStable()) {
|
|
328
|
+
console.log(
|
|
329
|
+
`[rango] No stable encryption key: ${keyed} router(s) encrypt server-action arguments ` +
|
|
330
|
+
`with a key generated for this build, so their cache version changes on every build and each ` +
|
|
331
|
+
`deploy clears their cache. To keep it, set rango({ encryptionKey: process.env.RANGO_ENCRYPTION_KEY }) ` +
|
|
332
|
+
`with a base64 32-byte key (openssl rand -base64 32).`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return table;
|
|
336
|
+
}
|
|
@@ -24,6 +24,14 @@ import { join, resolve } from "node:path";
|
|
|
24
24
|
import { jsonParseExpression } from "../utils/manifest-utils.js";
|
|
25
25
|
import { writeBuildAssetModule } from "../utils/prerender-utils.js";
|
|
26
26
|
import { buildShellManifestKey } from "../../prerender/shell-manifest-key.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveVersionsFrom,
|
|
29
|
+
type RouterVersionsTable,
|
|
30
|
+
} from "../../router-versions.js";
|
|
31
|
+
import {
|
|
32
|
+
environmentOutDir,
|
|
33
|
+
type BuilderLike,
|
|
34
|
+
} from "./router-versions-phase.js";
|
|
27
35
|
// Type-only: the producer stages exactly the record shape the runtime
|
|
28
36
|
// read-through consumes, so the contract cannot drift (the KEY half of that
|
|
29
37
|
// contract is shell-manifest-key.ts). No runtime coupling — the RSC-runtime
|
|
@@ -35,30 +43,22 @@ import { createRangoDebugger, NS } from "../debug.js";
|
|
|
35
43
|
const debug = createRangoDebugger(NS.prerender);
|
|
36
44
|
|
|
37
45
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
46
|
+
* @param versionsTable The versions the shipped build serves with
|
|
47
|
+
* (router-versions-phase.ts), `undefined` outside a build. Never the temp
|
|
48
|
+
* server's own version-plugin stamp, which is a dev Date.now() and would
|
|
49
|
+
* fail the serve-side isValidShellHit gate forever.
|
|
40
50
|
*/
|
|
41
|
-
interface BuilderLike {
|
|
42
|
-
config?: {
|
|
43
|
-
base?: string;
|
|
44
|
-
plugins?: readonly unknown[];
|
|
45
|
-
};
|
|
46
|
-
environments?: Record<
|
|
47
|
-
string,
|
|
48
|
-
{ config?: { build?: { outDir?: string } } } | undefined
|
|
49
|
-
>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
51
|
export async function runShellPrerenderPhase(
|
|
53
52
|
s: DiscoveryState,
|
|
54
53
|
builder: BuilderLike | undefined,
|
|
54
|
+
versionsTable: RouterVersionsTable | undefined,
|
|
55
55
|
): Promise<void> {
|
|
56
56
|
// The kept temp server (and the buildEnv deferred with it) is OWNED by the
|
|
57
57
|
// callers — the buildApp post hook's finally on success, buildEnd on an
|
|
58
58
|
// aborted build — so this function stays a pure producer: it only tears
|
|
59
59
|
// down the globals it installs itself.
|
|
60
60
|
const tempServer = s.shellPhaseTempServer;
|
|
61
|
-
if (!
|
|
61
|
+
if (!versionsTable || !s.shellCandidates?.length || !tempServer) return;
|
|
62
62
|
|
|
63
63
|
const candidates: ShellPrerenderCandidate[] = s.shellCandidates;
|
|
64
64
|
const startTotal = performance.now();
|
|
@@ -87,21 +87,6 @@ export async function runShellPrerenderPhase(
|
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// The MAIN build's version (the value folded into the shipped worker) —
|
|
91
|
-
// never the temp server's own version-plugin stamp, which is a different
|
|
92
|
-
// Date.now() and would fail the serve-side isValidShellHit gate forever.
|
|
93
|
-
const versionPlugin = (builder?.config?.plugins ?? []).find(
|
|
94
|
-
(p: any) => p?.name === "@rangojs/router:version",
|
|
95
|
-
) as { api?: { getBuildVersion?: () => string } } | undefined;
|
|
96
|
-
const buildVersion = versionPlugin?.api?.getBuildVersion?.();
|
|
97
|
-
if (!buildVersion) {
|
|
98
|
-
console.warn(
|
|
99
|
-
"[rango] shell prerender: main build version unavailable; skipping — " +
|
|
100
|
-
"routes keep runtime shell capture.",
|
|
101
|
-
);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
90
|
// In-realm prerender store over the retained phase-A payloads, so the
|
|
106
91
|
// capture's match() re-enters withCacheLookup and REPLAYS the build-time
|
|
107
92
|
// segments (globalThis is shared between the plugin process and the temp
|
|
@@ -130,9 +115,7 @@ export async function runShellPrerenderPhase(
|
|
|
130
115
|
);
|
|
131
116
|
|
|
132
117
|
// Built client bootstrap: the prelude must embed the BUILT entry URL.
|
|
133
|
-
const clientOutDir =
|
|
134
|
-
builder?.environments?.client?.config?.build?.outDir ??
|
|
135
|
-
resolve(s.projectRoot, "dist/client");
|
|
118
|
+
const clientOutDir = environmentOutDir(builder, s.projectRoot, "client");
|
|
136
119
|
const clientAssetsDir = join(clientOutDir, "assets");
|
|
137
120
|
const entryFile = existsSync(clientAssetsDir)
|
|
138
121
|
? readdirSync(clientAssetsDir).find((f) => /^index-.*\.js$/.test(f))
|
|
@@ -245,8 +228,14 @@ export async function runShellPrerenderPhase(
|
|
|
245
228
|
const mainKey = `${cand.routeName}/${cand.paramHash}`;
|
|
246
229
|
let handled = false;
|
|
247
230
|
const mismatches: string[] = [];
|
|
248
|
-
for (const [, routerInstance] of registry) {
|
|
231
|
+
for (const [routerId, routerInstance] of registry) {
|
|
249
232
|
if (typeof routerInstance.match !== "function") continue;
|
|
233
|
+
// The build's table always holds the whole-build pair.
|
|
234
|
+
const versions = resolveVersionsFrom(
|
|
235
|
+
versionsTable,
|
|
236
|
+
routerId,
|
|
237
|
+
routerInstance.version,
|
|
238
|
+
)!;
|
|
250
239
|
try {
|
|
251
240
|
const res = await captureMod.captureShellForBuild({
|
|
252
241
|
router: routerInstance,
|
|
@@ -259,7 +248,7 @@ export async function runShellPrerenderPhase(
|
|
|
259
248
|
maxSnapshotBytes: policy.maxSnapshotBytes,
|
|
260
249
|
captureTimeout: policy.captureTimeout,
|
|
261
250
|
buildEnv: s.resolvedBuildEnv,
|
|
262
|
-
|
|
251
|
+
versions,
|
|
263
252
|
captureShellHTML,
|
|
264
253
|
debug: !!debug,
|
|
265
254
|
});
|
|
@@ -323,10 +312,11 @@ export async function runShellPrerenderPhase(
|
|
|
323
312
|
}
|
|
324
313
|
|
|
325
314
|
if (staged.length > 0) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
315
|
+
writeShellManifest(
|
|
316
|
+
s,
|
|
317
|
+
environmentOutDir(builder, s.projectRoot, "rsc"),
|
|
318
|
+
staged,
|
|
319
|
+
);
|
|
330
320
|
}
|
|
331
321
|
|
|
332
322
|
const totalElapsed = (performance.now() - startTotal).toFixed(0);
|