@rangojs/router 0.20.0 → 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/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 +27 -0
- 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 +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- 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 +1537 -444
- package/package.json +1 -2
- 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 +15 -1
- 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/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 +28 -0
- 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 +31 -5
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +82 -13
- package/src/vite/utils/client-chunks.ts +5 -1
- 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
|
@@ -10,6 +10,7 @@ import type { ScanFilter } from "../../build/generate-route-types.js";
|
|
|
10
10
|
import type { ClientUrlProjection } from "../../client-urls/server-projection.js";
|
|
11
11
|
|
|
12
12
|
export const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
|
|
13
|
+
export const VIRTUAL_LOADER_MANIFEST_ID = "virtual:rsc-router/loader-manifest";
|
|
13
14
|
|
|
14
15
|
export interface PluginOptions {
|
|
15
16
|
enableBuildPrerender?: boolean;
|
|
@@ -147,6 +148,19 @@ export interface DiscoveryState {
|
|
|
147
148
|
* buildEnd (aborted build).
|
|
148
149
|
*/
|
|
149
150
|
shellPhaseTempServer: import("vite").ViteDevServer | null;
|
|
151
|
+
/**
|
|
152
|
+
* Inputs of the per-router cache versions (build-versions.ts), recorded by
|
|
153
|
+
* generateBundle. plugin-rsc builds the RSC and SSR environments twice (a
|
|
154
|
+
* scan pass, then the real pass); each pass overwrites its slot, so the
|
|
155
|
+
* buildApp post hook reads the real pass.
|
|
156
|
+
*/
|
|
157
|
+
serverBuildGraph: import("./build-versions.js").ServerBuildGraph | null;
|
|
158
|
+
clientBuildGraph: import("./build-versions.js").ClientBuildGraph | null;
|
|
159
|
+
ssrBundle: import("./build-versions.js").BundleFiles | null;
|
|
160
|
+
/** Per environment, the chunk files holding the version module. */
|
|
161
|
+
versionModuleFiles: Map<string, string[]>;
|
|
162
|
+
/** Prerender and Static payloads the build rendered, by content digest. */
|
|
163
|
+
buildData: import("./build-versions.js").BuildDataRecord[];
|
|
150
164
|
handlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
151
165
|
staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
|
|
152
166
|
rscEntryFileName: string | null;
|
|
@@ -204,6 +218,11 @@ export function createDiscoveryState(
|
|
|
204
218
|
shellCandidates: null,
|
|
205
219
|
prerenderPayloadValues: null,
|
|
206
220
|
shellPhaseTempServer: null,
|
|
221
|
+
serverBuildGraph: null,
|
|
222
|
+
clientBuildGraph: null,
|
|
223
|
+
ssrBundle: null,
|
|
224
|
+
versionModuleFiles: new Map(),
|
|
225
|
+
buildData: [],
|
|
207
226
|
handlerChunkInfoMap: new Map(),
|
|
208
227
|
staticHandlerChunkInfoMap: new Map(),
|
|
209
228
|
rscEntryFileName: null,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, basename, join } from "node:path";
|
|
10
10
|
import { jsonParseExpression } from "../utils/manifest-utils.js";
|
|
11
|
+
import { compareStrings } from "../utils/compare-strings.js";
|
|
11
12
|
import { VIRTUAL_ROUTES_MANIFEST_ID } from "./state.js";
|
|
12
13
|
import type { DiscoveryState } from "./state.js";
|
|
13
14
|
|
|
@@ -111,8 +112,12 @@ export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
|
111
112
|
|
|
112
113
|
if (hasClientUrlModules) {
|
|
113
114
|
lines.push(`clearClientUrlProjections();`);
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
// Sorted: the map fills in transform order, which varies between runs,
|
|
116
|
+
// and this module's bytes feed cache versions (build-versions.ts).
|
|
117
|
+
const projections = [...(state.clientUrlProjectionMap ?? [])].sort(
|
|
118
|
+
([a], [b]) => compareStrings(a, b),
|
|
119
|
+
);
|
|
120
|
+
for (const [referenceId, projection] of projections) {
|
|
116
121
|
lines.push(
|
|
117
122
|
`setClientUrlProjection(${JSON.stringify(referenceId)}, ${jsonParseExpression(projection)});`,
|
|
118
123
|
);
|
|
@@ -9,17 +9,104 @@ import { randomBytes } from "node:crypto";
|
|
|
9
9
|
// via plugin-rsc's `defineEncryptionKey` so build-time-encrypted bound args
|
|
10
10
|
// decrypt at runtime.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
// buildStart) and the main build share one key.
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
12
|
+
// Resolved once per process so the temp server (created during the main
|
|
13
|
+
// build's buildStart) and the main build share one key. Source order:
|
|
14
|
+
// rango({ encryptionKey }), then RANGO_ENCRYPTION_KEY, then a key generated for
|
|
15
|
+
// this build. Only the first two are stable across builds.
|
|
16
|
+
//
|
|
17
|
+
// The key is part of the cache version of every router whose server code
|
|
18
|
+
// encrypts with it (discovery/build-versions.ts adds the emitted key file to
|
|
19
|
+
// that router's hash): a payload cached under the old key carries bound args
|
|
20
|
+
// the new build cannot decrypt. So a generated key gives those routers a new
|
|
21
|
+
// version on every build, which is correct and also means their cache is
|
|
22
|
+
// cleared on every deploy.
|
|
23
|
+
|
|
24
|
+
/** AES key sizes Web Crypto imports for AES-GCM, in bytes. */
|
|
25
|
+
const AES_KEY_BYTES: ReadonlySet<number> = new Set([16, 24, 32]);
|
|
26
|
+
|
|
27
|
+
const EXPECTED =
|
|
28
|
+
"a base64-encoded AES key of 16, 24 or 32 bytes (openssl rand -base64 32)";
|
|
29
|
+
|
|
30
|
+
interface ResolvedEncryptionKey {
|
|
31
|
+
key: string;
|
|
32
|
+
/** True when the consumer supplied the key (option or env variable). */
|
|
33
|
+
stable: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let resolvedKey: ResolvedEncryptionKey | undefined;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Throws unless `value` is the base64 form of an AES key (16, 24 or 32 bytes).
|
|
40
|
+
* `source` names where the value came from, for the message.
|
|
41
|
+
*/
|
|
42
|
+
export function assertValidEncryptionKey(value: unknown, source: string): void {
|
|
43
|
+
if (typeof value !== "string") {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`[rango] ${source} must be a string, received ${typeof value}. Expected ${EXPECTED}.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (value === "") {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`[rango] ${source} is an empty string. Unset it to generate a key per build, ` +
|
|
51
|
+
`or set it to ${EXPECTED}.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const bytes = /^[A-Za-z0-9+/]+={0,2}$/.test(value)
|
|
55
|
+
? Buffer.from(value, "base64")
|
|
56
|
+
: undefined;
|
|
57
|
+
// Buffer.from tolerates malformed base64 by dropping what it cannot decode;
|
|
58
|
+
// the round-trip rejects those inputs instead of using a shorter key.
|
|
59
|
+
if (
|
|
60
|
+
!bytes ||
|
|
61
|
+
bytes.toString("base64").replace(/=+$/, "") !== value.replace(/=+$/, "")
|
|
62
|
+
) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`[rango] ${source} is not valid base64. Expected ${EXPECTED}.`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (!AES_KEY_BYTES.has(bytes.length)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`[rango] ${source} decodes to ${bytes.length} bytes. Expected ${EXPECTED}.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the build's encryption key from rango({ encryptionKey }) or the
|
|
76
|
+
* RANGO_ENCRYPTION_KEY fallback, validating whichever is set. Called by
|
|
77
|
+
* rango() at config time, before any plugin-rsc instance reads the key, so an
|
|
78
|
+
* invalid key fails the config load instead of the first encrypted action.
|
|
79
|
+
* A key generated earlier in the process is kept when neither source is set:
|
|
80
|
+
* the discovery temp server must encrypt with the main build's key.
|
|
81
|
+
*/
|
|
82
|
+
export function configureEncryptionKey(option: string | undefined): void {
|
|
83
|
+
if (option !== undefined) {
|
|
84
|
+
assertValidEncryptionKey(option, "rango({ encryptionKey })");
|
|
85
|
+
resolvedKey = { key: option, stable: true };
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const fromEnv = process.env.RANGO_ENCRYPTION_KEY;
|
|
89
|
+
if (fromEnv !== undefined) {
|
|
90
|
+
assertValidEncryptionKey(fromEnv, "RANGO_ENCRYPTION_KEY");
|
|
91
|
+
resolvedKey = { key: fromEnv, stable: true };
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (resolvedKey && !resolvedKey.stable) return;
|
|
95
|
+
resolvedKey = { key: randomBytes(32).toString("base64"), stable: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolved(): ResolvedEncryptionKey {
|
|
99
|
+
if (!resolvedKey) configureEncryptionKey(undefined);
|
|
100
|
+
return resolvedKey!;
|
|
101
|
+
}
|
|
18
102
|
|
|
19
103
|
export function buildEncryptionKey(): string {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
104
|
+
return resolved().key;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** False when this build generated its own key (it differs on every build). */
|
|
108
|
+
export function isEncryptionKeyStable(): boolean {
|
|
109
|
+
return resolved().stable;
|
|
23
110
|
}
|
|
24
111
|
|
|
25
112
|
// The value plugin-rsc inlines as `export default () => (<expr>)`. A JSON string
|
|
@@ -27,3 +114,8 @@ export function buildEncryptionKey(): string {
|
|
|
27
114
|
export function defineEncryptionKeyExpr(): string {
|
|
28
115
|
return JSON.stringify(buildEncryptionKey());
|
|
29
116
|
}
|
|
117
|
+
|
|
118
|
+
/** @internal Test seam: forget the process-wide key. */
|
|
119
|
+
export function resetEncryptionKeyForTests(): void {
|
|
120
|
+
resolvedKey = undefined;
|
|
121
|
+
}
|
package/src/vite/plugin-types.ts
CHANGED
|
@@ -239,6 +239,34 @@ interface RangoBaseOptions {
|
|
|
239
239
|
prerender?: {
|
|
240
240
|
onError?: "fail" | "warn";
|
|
241
241
|
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The key inline server actions encrypt their bound arguments with:
|
|
245
|
+
* base64-encoded 32 bytes (`openssl rand -base64 32`). Pass it from the
|
|
246
|
+
* environment, never as a literal in the config file:
|
|
247
|
+
*
|
|
248
|
+
* ```ts
|
|
249
|
+
* rango({ encryptionKey: process.env.RANGO_ENCRYPTION_KEY })
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* Why set it: the key is part of the cache version of every router whose
|
|
253
|
+
* server code encrypts with it, because a payload cached under one key
|
|
254
|
+
* carries arguments another key cannot decrypt. Without a stable key rango
|
|
255
|
+
* generates one per build, so those routers get a new version, and a cleared
|
|
256
|
+
* cache, on every deploy. With a stable key their cache survives a deploy
|
|
257
|
+
* that does not change their server code. It also lets a build decrypt
|
|
258
|
+
* arguments an earlier build encrypted (a cached or prerendered payload, an
|
|
259
|
+
* open tab).
|
|
260
|
+
*
|
|
261
|
+
* Validated when `rango()` is called: a value that is not base64 or does not
|
|
262
|
+
* decode to an AES key size (16, 24 or 32 bytes) throws. `undefined` (the
|
|
263
|
+
* variable is unset) falls back to the `RANGO_ENCRYPTION_KEY` environment
|
|
264
|
+
* variable, then to a generated key. Rotating the key clears the cache of
|
|
265
|
+
* the routers that use it.
|
|
266
|
+
*
|
|
267
|
+
* @default process.env.RANGO_ENCRYPTION_KEY, else a key generated per build
|
|
268
|
+
*/
|
|
269
|
+
encryptionKey?: string;
|
|
242
270
|
}
|
|
243
271
|
|
|
244
272
|
/**
|
|
@@ -5,11 +5,9 @@ import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
|
|
|
5
5
|
import { RSC_ENTRY_BOOTSTRAP_IMPORTS } from "./virtual-entries.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Plugin that auto-injects
|
|
9
|
-
* custom entry.rsc
|
|
10
|
-
*
|
|
11
|
-
* automatically. It also ensures the routes-manifest and loader-manifest
|
|
12
|
-
* virtual modules are always imported.
|
|
8
|
+
* Plugin that auto-injects the routes-manifest and loader-manifest imports
|
|
9
|
+
* into a custom entry.rsc file (the Cloudflare preset, where the consumer owns
|
|
10
|
+
* the worker entry).
|
|
13
11
|
*
|
|
14
12
|
* The loader-manifest import is what makes fetchable loaders resolvable on a
|
|
15
13
|
* custom worker entry. The virtual RSC entry (getVirtualEntryRSC) imports the
|
|
@@ -23,13 +21,13 @@ import { RSC_ENTRY_BOOTSTRAP_IMPORTS } from "./virtual-entries.js";
|
|
|
23
21
|
* manifest, hence the production-only failure this fixes.
|
|
24
22
|
* @internal
|
|
25
23
|
*/
|
|
26
|
-
export function
|
|
24
|
+
export function createEntryBootstrapInjectorPlugin(
|
|
27
25
|
rscEntryPath: string | undefined,
|
|
28
26
|
): Plugin {
|
|
29
27
|
let resolvedEntryPath = "";
|
|
30
28
|
|
|
31
29
|
return {
|
|
32
|
-
name: "@rangojs/router:
|
|
30
|
+
name: "@rangojs/router:entry-bootstrap-injector",
|
|
33
31
|
enforce: "pre",
|
|
34
32
|
|
|
35
33
|
configResolved(config) {
|
|
@@ -54,21 +52,8 @@ export function createVersionInjectorPlugin(
|
|
|
54
52
|
const prepend: string[] = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
|
|
55
53
|
(id) => `import "${id}";`,
|
|
56
54
|
);
|
|
57
|
-
let newCode = code;
|
|
58
|
-
const needsVersion =
|
|
59
|
-
code.includes("createRSCHandler") &&
|
|
60
|
-
!code.includes("@rangojs/router:version") &&
|
|
61
|
-
/createRSCHandler\s*\(\s*\{/.test(code);
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
prepend.push(`import { VERSION } from "@rangojs/router:version";`);
|
|
65
|
-
newCode = newCode.replace(
|
|
66
|
-
/createRSCHandler\s*\(\s*\{/,
|
|
67
|
-
"createRSCHandler({\n version: VERSION,",
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const lines = newCode.split("\n");
|
|
56
|
+
const lines = code.split("\n");
|
|
72
57
|
let insertAt = 0;
|
|
73
58
|
while (insertAt < lines.length) {
|
|
74
59
|
const trimmed = lines[insertAt]!.trim();
|
|
@@ -78,7 +63,7 @@ export function createVersionInjectorPlugin(
|
|
|
78
63
|
break;
|
|
79
64
|
}
|
|
80
65
|
}
|
|
81
|
-
newCode = [
|
|
66
|
+
const newCode = [
|
|
82
67
|
...lines.slice(0, insertAt),
|
|
83
68
|
...prepend,
|
|
84
69
|
...lines.slice(insertAt),
|
|
@@ -55,7 +55,7 @@ export function isExportOnlyFile(
|
|
|
55
55
|
// (which may be nested, e.g. createLoader<A<B>>(...)) and the call paren are
|
|
56
56
|
// resolved per match via findCallParenAfterGenerics, so a nested `>` no longer
|
|
57
57
|
// defeats the scan the way `<[^>]*>` did (it stopped at the first `>`).
|
|
58
|
-
function createCallPattern(fnNames: string[]): RegExp {
|
|
58
|
+
export function createCallPattern(fnNames: string[]): RegExp {
|
|
59
59
|
return new RegExp(`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\b`, "g");
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
findCallParenAfterGenerics,
|
|
9
9
|
skipStringOrComment,
|
|
10
10
|
} from "../expose-id-utils.js";
|
|
11
|
-
import { escapeRegExp } from "../../../regex-escape.js";
|
|
12
11
|
import {
|
|
13
12
|
getImportedFnNames,
|
|
14
13
|
buildUnsupportedShapeWarning,
|
|
14
|
+
createCallPattern,
|
|
15
|
+
offsetToLineColumn,
|
|
15
16
|
} from "./export-analysis.js";
|
|
16
17
|
import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
|
|
17
18
|
import { createRangoDebugger, createCounter, NS } from "../../debug.js";
|
|
@@ -42,6 +43,26 @@ function skipLeadingTrivia(code: string, start: number, end: number): number {
|
|
|
42
43
|
return i;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A router's `$$id`: a hash of its root-relative file and of its position
|
|
48
|
+
* among the file's createRouter() calls.
|
|
49
|
+
*
|
|
50
|
+
* Not of the call's line. `code` is TS-transformed, and a dev server keeps
|
|
51
|
+
* comments there that a build drops, so a line of `code` gave build-time
|
|
52
|
+
* discovery and the bundle different ids for any router with a comment above
|
|
53
|
+
* it (e2e/test-app, tests/cloudflare-basic): the build registered the lazy
|
|
54
|
+
* route manifest under an id no running router had. And a line of the file as
|
|
55
|
+
* written changes with every blank line or comment added above the call,
|
|
56
|
+
* which renamed the state cookie, the route manifest chunk and the router's
|
|
57
|
+
* cache versions for an edit that changed nothing.
|
|
58
|
+
*/
|
|
59
|
+
export function routerId(filePath: string, callIndex: number): string {
|
|
60
|
+
return createHash("sha256")
|
|
61
|
+
.update(`${filePath}#${callIndex}`)
|
|
62
|
+
.digest("hex")
|
|
63
|
+
.slice(0, 8);
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
export function transformRouter(
|
|
46
67
|
code: string,
|
|
47
68
|
filePath: string,
|
|
@@ -53,14 +74,12 @@ export function transformRouter(
|
|
|
53
74
|
// e.g. createRouter<Config<Env>>(...)) and the opening paren are located
|
|
54
75
|
// separately via findCallParenAfterGenerics so a nested `>` does not defeat
|
|
55
76
|
// the scan (a `<[^>]*>` regex stopped at the first `>`).
|
|
56
|
-
const pat =
|
|
57
|
-
`\\b(?:${routerFnNames.map(escapeRegExp).join("|")})\\b`,
|
|
58
|
-
"g",
|
|
59
|
-
);
|
|
77
|
+
const pat = createCallPattern(routerFnNames);
|
|
60
78
|
let match: RegExpExecArray | null;
|
|
61
79
|
const s = new MagicString(code);
|
|
62
80
|
let changed = false;
|
|
63
81
|
const unsupportedSites: Array<{ line: number; column: number }> = [];
|
|
82
|
+
let callIndex = -1;
|
|
64
83
|
|
|
65
84
|
// Compute the import path for the generated route names file.
|
|
66
85
|
// filePath is relative to project root (e.g., "src/router.tsx")
|
|
@@ -86,6 +105,7 @@ export function transformRouter(
|
|
|
86
105
|
// type position) yields -1 and is skipped.
|
|
87
106
|
const parenPos = findCallParenAfterGenerics(code, calleeEnd);
|
|
88
107
|
if (parenPos === -1) continue;
|
|
108
|
+
callIndex++;
|
|
89
109
|
|
|
90
110
|
const closeParen = findMatchingParen(code, parenPos + 1);
|
|
91
111
|
const callArgs = code.slice(parenPos + 1, closeParen);
|
|
@@ -98,12 +118,7 @@ export function transformRouter(
|
|
|
98
118
|
if (callArgs.includes(`$$routeNames: ${routeNamesVar}`)) continue;
|
|
99
119
|
|
|
100
120
|
const sourceFilePath = absolutePath ?? filePath;
|
|
101
|
-
const
|
|
102
|
-
const hash = createHash("sha256")
|
|
103
|
-
.update(`${filePath}:${lineNumber}`)
|
|
104
|
-
.digest("hex")
|
|
105
|
-
.slice(0, 8);
|
|
106
|
-
const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
|
|
121
|
+
const injected = ` $$id: "${routerId(filePath, callIndex)}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
|
|
107
122
|
|
|
108
123
|
// Skip a leading comment/whitespace run so `createRouter(/* c *\/ {...})`
|
|
109
124
|
// and a newline-then-comment prefix still resolve to the object literal.
|
|
@@ -124,9 +139,7 @@ export function transformRouter(
|
|
|
124
139
|
// stable $$id can be injected here. Record it so the plugin can warn —
|
|
125
140
|
// and crucially do NOT mark changed, so a dead named-routes.gen import is
|
|
126
141
|
// not prepended for a call we never touched.
|
|
127
|
-
|
|
128
|
-
const column = callStart - (lastNl + 1) + 1;
|
|
129
|
-
unsupportedSites.push({ line: lineNumber, column });
|
|
142
|
+
unsupportedSites.push(offsetToLineColumn(code, callStart));
|
|
130
143
|
}
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -156,11 +169,19 @@ export function transformRouter(
|
|
|
156
169
|
*/
|
|
157
170
|
export function exposeRouterId(): Plugin {
|
|
158
171
|
let projectRoot = "";
|
|
172
|
+
// $$sourceFile is read by route discovery and the CLI, which run on a dev
|
|
173
|
+
// server (the build's discovery temp server included) and need the absolute
|
|
174
|
+
// path. Nothing reads it when serving, so a production build ships the
|
|
175
|
+
// root-relative path: an absolute one would put the build machine's checkout
|
|
176
|
+
// directory in every router's chunk and give the same source a different
|
|
177
|
+
// cache version per directory (discovery/build-versions.ts hashes the chunk).
|
|
178
|
+
let rootRelativeSourceFile = false;
|
|
159
179
|
const counter = createCounter(debug, "expose-router-id");
|
|
160
180
|
return {
|
|
161
181
|
name: "@rangojs/router:expose-router-id",
|
|
162
182
|
configResolved(config) {
|
|
163
183
|
projectRoot = config.root;
|
|
184
|
+
rootRelativeSourceFile = config.command === "build";
|
|
164
185
|
},
|
|
165
186
|
buildEnd() {
|
|
166
187
|
counter?.flush();
|
|
@@ -188,7 +209,7 @@ export function exposeRouterId(): Plugin {
|
|
|
188
209
|
code,
|
|
189
210
|
filePath,
|
|
190
211
|
routerFnNames,
|
|
191
|
-
normalizePath(id),
|
|
212
|
+
rootRelativeSourceFile ? undefined : normalizePath(id),
|
|
192
213
|
warn,
|
|
193
214
|
);
|
|
194
215
|
} finally {
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
transformHandlerIds,
|
|
46
46
|
} from "./expose-ids/handler-transform.js";
|
|
47
47
|
import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
48
|
+
import { compareStrings } from "../utils/compare-strings.js";
|
|
49
|
+
import { VIRTUAL_LOADER_MANIFEST_ID } from "../discovery/state.js";
|
|
48
50
|
|
|
49
51
|
const debug = createRangoDebugger(NS.transform);
|
|
50
52
|
|
|
@@ -56,7 +58,7 @@ export type { ExposeInternalIdsApi } from "./expose-ids/types.js";
|
|
|
56
58
|
// Virtual module for loader manifest
|
|
57
59
|
// ---------------------------------------------------------------------------
|
|
58
60
|
|
|
59
|
-
const VIRTUAL_LOADER_MANIFEST =
|
|
61
|
+
const VIRTUAL_LOADER_MANIFEST = VIRTUAL_LOADER_MANIFEST_ID;
|
|
60
62
|
const RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
|
|
61
63
|
|
|
62
64
|
// ---------------------------------------------------------------------------
|
|
@@ -245,10 +247,17 @@ setLoaderImports({});
|
|
|
245
247
|
`;
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
// Build mode: generate lazy import map
|
|
250
|
+
// Build mode: generate lazy import map. Sorted by id: the registry fills
|
|
251
|
+
// in directory-scan order (the pre-scan below) and transform order, both
|
|
252
|
+
// of which vary between machines and runs, and this module's bytes feed
|
|
253
|
+
// the cache version of every router bundled with it
|
|
254
|
+
// (discovery/build-versions.ts).
|
|
249
255
|
const lazyImports: string[] = [];
|
|
256
|
+
const sortedLoaders = [...loaderRegistry].sort(([a], [b]) =>
|
|
257
|
+
compareStrings(a, b),
|
|
258
|
+
);
|
|
250
259
|
|
|
251
|
-
for (const [hashedId, { filePath, exportName }] of
|
|
260
|
+
for (const [hashedId, { filePath, exportName }] of sortedLoaders) {
|
|
252
261
|
lazyImports.push(
|
|
253
262
|
` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`,
|
|
254
263
|
);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { parseAst, type Plugin } from "vite";
|
|
2
|
+
import { escapeRegExp } from "../../regex-escape.js";
|
|
3
|
+
import { compareStrings } from "../utils/compare-strings.js";
|
|
4
|
+
import { SERVER_REFERENCES_MODULE_ID } from "../discovery/build-versions.js";
|
|
5
|
+
|
|
6
|
+
/** One property of an object literal, by its position in the source. */
|
|
7
|
+
export interface LiteralProperty {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
/** Range of the whole property, key included. */
|
|
10
|
+
readonly start: number;
|
|
11
|
+
readonly end: number;
|
|
12
|
+
/** The value's AST node. */
|
|
13
|
+
readonly value: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The properties of an object-literal AST node, or undefined when it is not an
|
|
18
|
+
* object literal of plain keys (a spread, a computed key).
|
|
19
|
+
*/
|
|
20
|
+
export function literalProperties(object: any): LiteralProperty[] | undefined {
|
|
21
|
+
if (object?.type !== "ObjectExpression") return undefined;
|
|
22
|
+
const properties: LiteralProperty[] = [];
|
|
23
|
+
for (const property of object.properties ?? []) {
|
|
24
|
+
const key =
|
|
25
|
+
property?.type === "Property" && !property.computed
|
|
26
|
+
? (property.key?.value ?? property.key?.name)
|
|
27
|
+
: undefined;
|
|
28
|
+
if (typeof key !== "string") return undefined;
|
|
29
|
+
properties.push({
|
|
30
|
+
key,
|
|
31
|
+
start: property.start,
|
|
32
|
+
end: property.end,
|
|
33
|
+
value: property.value,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return properties;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The properties of a module's `export default { ... }`, or undefined when
|
|
41
|
+
* the module does not parse or is not that shape. plugin-rsc generates its
|
|
42
|
+
* registries (the server-reference map, the assets manifest) in this form.
|
|
43
|
+
*/
|
|
44
|
+
export function defaultExportProperties(
|
|
45
|
+
code: string,
|
|
46
|
+
): LiteralProperty[] | undefined {
|
|
47
|
+
let program: any;
|
|
48
|
+
try {
|
|
49
|
+
program = parseAst(code);
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return literalProperties(
|
|
54
|
+
(program.body ?? []).find(
|
|
55
|
+
(node: any) => node?.type === "ExportDefaultDeclaration",
|
|
56
|
+
)?.declaration,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Return `code` with the properties of its default-exported object literal
|
|
62
|
+
* sorted by key, or undefined when there is nothing to reorder (or the module
|
|
63
|
+
* is not that shape).
|
|
64
|
+
*/
|
|
65
|
+
export function sortDefaultExportProperties(code: string): string | undefined {
|
|
66
|
+
const properties = defaultExportProperties(code);
|
|
67
|
+
if (!properties || properties.length < 2) return undefined;
|
|
68
|
+
const sorted = [...properties].sort((a, b) => compareStrings(a.key, b.key));
|
|
69
|
+
if (sorted.every((property, index) => property === properties[index])) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return (
|
|
73
|
+
code.slice(0, properties[0]!.start) +
|
|
74
|
+
sorted
|
|
75
|
+
.map((property) => code.slice(property.start, property.end))
|
|
76
|
+
.join(",\n") +
|
|
77
|
+
code.slice(properties[properties.length - 1]!.end)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ASSETS_MANIFEST_ID = "virtual:vite-rsc/assets-manifest";
|
|
82
|
+
const ASSETS_MANIFEST_IMPORT =
|
|
83
|
+
/import\s+([A-Za-z_$][\w$]*)\s+from\s*["']virtual:vite-rsc\/assets-manifest["']/;
|
|
84
|
+
/** The local name plugin-rsc's own runtime modules import the manifest under. */
|
|
85
|
+
const ASSETS_MANIFEST_BINDING = "assetsManifest";
|
|
86
|
+
/**
|
|
87
|
+
* The names plugin-rsc's GENERATED code imports it under. Only these are
|
|
88
|
+
* renamed: the rename is textual, and a name an app author picked
|
|
89
|
+
* (`import manifest from ...`) can also be a word in that module's strings.
|
|
90
|
+
*/
|
|
91
|
+
const GENERATED_BINDINGS: ReadonlySet<string> = new Set([
|
|
92
|
+
"__vite_rsc_assets_manifest__",
|
|
93
|
+
"__vite_rsc_assets_manifest",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
function identifier(name: string): RegExp {
|
|
97
|
+
return new RegExp(`(?<![\\w$.])${escapeRegExp(name)}(?![\\w$])`, "g");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Return `code` with its default import of plugin-rsc's assets manifest
|
|
102
|
+
* renamed to {@link ASSETS_MANIFEST_BINDING}, or undefined when there is
|
|
103
|
+
* nothing to rename (no such import under a generated name, or the module
|
|
104
|
+
* uses the canonical name for something else).
|
|
105
|
+
*/
|
|
106
|
+
export function canonicalAssetsManifestBinding(
|
|
107
|
+
code: string,
|
|
108
|
+
): string | undefined {
|
|
109
|
+
const local = code.match(ASSETS_MANIFEST_IMPORT)?.[1];
|
|
110
|
+
if (local === undefined || !GENERATED_BINDINGS.has(local)) return undefined;
|
|
111
|
+
if (identifier(ASSETS_MANIFEST_BINDING).test(code)) return undefined;
|
|
112
|
+
return code.replace(identifier(local), ASSETS_MANIFEST_BINDING);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Removes two run-to-run differences from plugin-rsc 0.5.35's build output.
|
|
117
|
+
* The cache versions hash these bytes (discovery/build-versions.ts).
|
|
118
|
+
*
|
|
119
|
+
* 1. Property order of the server-reference map. plugin-rsc writes it in the
|
|
120
|
+
* order its transforms first saw each `"use server"` module
|
|
121
|
+
* (`ServerReferencesManager.claimMap`), which varies with the concurrent
|
|
122
|
+
* scan builds (e2e/test-app: 2 of 2 consecutive builds differed). Sorted
|
|
123
|
+
* here so a minified server build, where stripServerReferenceMap finds no
|
|
124
|
+
* region comments, still hashes the same bytes.
|
|
125
|
+
* 2. Local name of the assets-manifest import. plugin-rsc imports the manifest
|
|
126
|
+
* under three names; the bundler merges them into one import per chunk and
|
|
127
|
+
* keeps a different one from build to build (the cache-versions fixture:
|
|
128
|
+
* 1 of 10 concurrent builds). One name in, one name out.
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export function stableRscOutput(): Plugin {
|
|
132
|
+
return {
|
|
133
|
+
name: "@rangojs/router:stable-rsc-output",
|
|
134
|
+
apply: "build",
|
|
135
|
+
// After plugin-rsc's transforms, which add the manifest import.
|
|
136
|
+
enforce: "post",
|
|
137
|
+
transform(code, id) {
|
|
138
|
+
const stable =
|
|
139
|
+
id === SERVER_REFERENCES_MODULE_ID
|
|
140
|
+
? sortDefaultExportProperties(code)
|
|
141
|
+
: code.includes(ASSETS_MANIFEST_ID)
|
|
142
|
+
? canonicalAssetsManifestBinding(code)
|
|
143
|
+
: undefined;
|
|
144
|
+
return stable === undefined ? null : { code: stable, map: null };
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { parseAst, type Plugin } from "vite";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
VIRTUAL_IDS,
|
|
4
|
+
getVirtualBuildVersionContent,
|
|
5
|
+
getVirtualVersionContent,
|
|
6
|
+
} from "./virtual-entries.js";
|
|
3
7
|
import { hasUseClientDirective } from "../utils/directive-prologue.js";
|
|
4
8
|
|
|
5
9
|
interface ClientModuleSignature {
|
|
@@ -100,25 +104,22 @@ function getClientModuleSignature(
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/**
|
|
103
|
-
* Plugin providing
|
|
104
|
-
* Exports VERSION that changes when RSC modules change (dev) or at build time (production).
|
|
107
|
+
* Plugin providing the `@rangojs/router:version` virtual module.
|
|
105
108
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
+
* Dev: exports a VERSION stamp used for both versions of every router. It
|
|
110
|
+
* updates when the server starts and when an RSC module changes via HMR (the
|
|
111
|
+
* version module is invalidated). Client-only HMR changes do not update it:
|
|
112
|
+
* they do not affect server-rendered content or cached RSC payloads.
|
|
109
113
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Client-only HMR changes don't update the version since they don't affect
|
|
115
|
-
* server-rendered content or cached RSC payloads.
|
|
114
|
+
* Build: exports a placeholder for the per-router versions table. The module
|
|
115
|
+
* holds nothing that varies between builds; the table is written into the
|
|
116
|
+
* built chunk after the server output is hashed (the buildApp post hook in
|
|
117
|
+
* router-discovery.ts). See docs/design/per-app-cache-version.md.
|
|
116
118
|
* @internal
|
|
117
119
|
*/
|
|
118
120
|
export function createVersionPlugin(): Plugin {
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
let currentVersion = buildVersion;
|
|
121
|
+
// Dev stamp, generated at plugin creation (server start).
|
|
122
|
+
let currentVersion = Date.now().toString(16);
|
|
122
123
|
let isDev = false;
|
|
123
124
|
let server: any = null;
|
|
124
125
|
let resolvedCacheDir: string | undefined;
|
|
@@ -142,14 +143,6 @@ export function createVersionPlugin(): Plugin {
|
|
|
142
143
|
name: "@rangojs/router:version",
|
|
143
144
|
enforce: "pre",
|
|
144
145
|
|
|
145
|
-
// The build-time shell capture phase (producer B, #699) stamps entries
|
|
146
|
-
// with THIS instance's version — the value folded into the shipped worker
|
|
147
|
-
// — never the discovery temp server's own version-plugin value (a
|
|
148
|
-
// different Date.now() stamp that would fail the serve-side gate).
|
|
149
|
-
api: {
|
|
150
|
-
getBuildVersion: (): string => currentVersion,
|
|
151
|
-
},
|
|
152
|
-
|
|
153
146
|
configResolved(config) {
|
|
154
147
|
isDev = config.command === "serve";
|
|
155
148
|
resolvedCacheDir = config.cacheDir
|
|
@@ -177,7 +170,9 @@ export function createVersionPlugin(): Plugin {
|
|
|
177
170
|
|
|
178
171
|
load(id) {
|
|
179
172
|
if (id === "\0" + VIRTUAL_IDS.version) {
|
|
180
|
-
return
|
|
173
|
+
return isDev
|
|
174
|
+
? getVirtualVersionContent(currentVersion)
|
|
175
|
+
: getVirtualBuildVersionContent();
|
|
181
176
|
}
|
|
182
177
|
return null;
|
|
183
178
|
},
|