@rangojs/router 0.20.0 → 0.22.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 +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -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/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- 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 +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- 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 +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- 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 +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- 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 +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- 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/bundle-analysis.d.ts +6 -2
- 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 +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- 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 +167 -22
- 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 +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- 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/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- 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-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -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 +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- 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 +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- 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
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -33,6 +33,10 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
// 1. Evict handler code from whichever chunks contain handler exports.
|
|
36
|
+
// onDemand retention needs no cross-check here: the chunk scan marks
|
|
37
|
+
// onDemand exports directly from state.onDemandHandlerIds (the evaluated
|
|
38
|
+
// source's $$id set), so retention and the runtime od trie flag share one
|
|
39
|
+
// derivation and cannot disagree.
|
|
36
40
|
// handlerChunkInfoMap/staticHandlerChunkInfoMap are populated by generateBundle
|
|
37
41
|
// after the production RSC build. In Vite 6 multi-environment builds, the
|
|
38
42
|
// RSC build runs twice (analysis + production). The maps are cleared at the
|
|
@@ -337,6 +337,20 @@ export async function discoverRouters(
|
|
|
337
337
|
(performance.now() - trieStart).toFixed(1),
|
|
338
338
|
);
|
|
339
339
|
|
|
340
|
+
// handlerId -> route name for onDemand routes, from the evaluated source —
|
|
341
|
+
// the authoritative side of postprocessBundle's retention cross-check.
|
|
342
|
+
const newOnDemandHandlerIds = new Map<string, string>();
|
|
343
|
+
for (const { manifest } of allManifests) {
|
|
344
|
+
if (!manifest.onDemandRoutes) continue;
|
|
345
|
+
const defs = manifest._prerenderDefs || {};
|
|
346
|
+
for (const routeName of manifest.onDemandRoutes) {
|
|
347
|
+
const handlerId = defs[routeName]?.$$id;
|
|
348
|
+
if (typeof handlerId === "string") {
|
|
349
|
+
newOnDemandHandlerIds.set(handlerId, routeName);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
340
354
|
// Commit all local state to the shared discovery state atomically.
|
|
341
355
|
// This ensures a failed re-discovery (e.g. from a transient module
|
|
342
356
|
// evaluation error) preserves the last known-good state.
|
|
@@ -345,6 +359,7 @@ export async function discoverRouters(
|
|
|
345
359
|
state.perRouterManifestDataMap = newPerRouterManifestDataMap;
|
|
346
360
|
state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
|
|
347
361
|
state.perRouterTrieMap = newPerRouterTrieMap;
|
|
362
|
+
state.onDemandHandlerIds = newOnDemandHandlerIds;
|
|
348
363
|
|
|
349
364
|
// Install the route tries into the RSC realm BEFORE prerender collection.
|
|
350
365
|
// matchForPrerender resolves each enumerated URL via findMatch, and without
|
|
@@ -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
|
+
}
|