@rangojs/router 0.21.0 → 0.22.1
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 +13 -1
- package/dist/types/browser/event-controller.d.ts +30 -13
- package/dist/types/browser/navigation-store.d.ts +9 -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/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +14 -0
- 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 +1 -1
- package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +23 -0
- package/dist/types/cache/index.d.ts +1 -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 +10 -1
- 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 +63 -14
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +7 -0
- 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/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 +14 -0
- 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 +30 -0
- package/dist/types/router/router-options.d.ts +24 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
- 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/rsc/shell-build-manifest.d.ts +6 -4
- 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 +8 -0
- 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 +9 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +63 -2
- 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/flight.entry.d.ts +2 -0
- package/dist/types/testing/index.d.ts +4 -0
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/serve-shell-request.d.ts +34 -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/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +7 -0
- 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/state.d.ts +11 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/vite/index.js +180 -497
- package/package.json +13 -3
- package/skills/cache-guide/SKILL.md +18 -17
- package/skills/caching/SKILL.md +40 -4
- package/skills/catalog.json +1 -1
- package/skills/cloudflare/SKILL.md +131 -0
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +22 -0
- package/skills/hooks/handle-and-actions.md +3 -4
- package/skills/hooks/navigation.md +10 -0
- package/skills/hooks/state.md +5 -4
- package/skills/host-router/SKILL.md +18 -0
- package/skills/loader/SKILL.md +6 -4
- package/skills/middleware/SKILL.md +1 -1
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +31 -8
- package/skills/prerender/SKILL.md +645 -8
- package/skills/rango/SKILL.md +1 -0
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/serve-shell-request.md +104 -8
- package/skills/use-cache/SKILL.md +9 -5
- package/skills/vercel/SKILL.md +2 -0
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/browser/event-controller.ts +69 -25
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +75 -35
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +9 -2
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +48 -12
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/react/Link.tsx +37 -42
- package/src/browser/react/NavigationProvider.tsx +63 -3
- package/src/browser/react/context.ts +17 -0
- package/src/browser/react/location-state.ts +5 -19
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +4 -20
- 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 +79 -39
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +26 -6
- 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 +38 -36
- package/src/cache/cf/cf-cache-store.ts +16 -11
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +50 -0
- package/src/cache/index.ts +1 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +17 -0
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +69 -14
- package/src/cache/vercel/vercel-cache-store.ts +20 -6
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +7 -2
- 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/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/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +230 -38
- 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/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +33 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +15 -17
- 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.ts +107 -1
- package/src/rsc/handler.ts +51 -4
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +53 -11
- package/src/rsc/shell-build-manifest.ts +10 -8
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +108 -19
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +8 -0
- 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 +15 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +13 -0
- package/src/server/loader-registry.ts +6 -1
- package/src/server/request-context.ts +87 -1
- package/src/ssr/ssr-root.tsx +20 -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/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +3 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/render-route.tsx +25 -12
- package/src/testing/serve-shell-request.ts +190 -34
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +7 -0
- 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/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/shell-prerender-phase.ts +5 -5
- package/src/vite/discovery/state.ts +17 -1
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugins/virtual-entries.ts +8 -1
- package/src/vite/router-discovery.ts +14 -0
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/browser/react/subscribe-to-nothing.ts +0 -7
- package/src/segment-content-promise.ts +0 -67
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -41,7 +41,9 @@ import {
|
|
|
41
41
|
SHELL_CAPTURE_TASK_HARD_CAP_MS,
|
|
42
42
|
resetShellWarningsForTests,
|
|
43
43
|
warnOnce,
|
|
44
|
+
type ShellCaptureRefusal,
|
|
44
45
|
} from "./shell-capture-constants.js";
|
|
46
|
+
import { noteWarmWrite } from "../prerender/warm-request.js";
|
|
45
47
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
46
48
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
47
49
|
import type { TraceSpan } from "../router/tracing.js";
|
|
@@ -75,6 +77,7 @@ import type {
|
|
|
75
77
|
CachedEntryData,
|
|
76
78
|
ShellCacheEntry,
|
|
77
79
|
SegmentCacheStore,
|
|
80
|
+
ShellSnapshotHandlesValue,
|
|
78
81
|
ShellSnapshotRecord,
|
|
79
82
|
} from "../cache/types.js";
|
|
80
83
|
import {
|
|
@@ -83,6 +86,7 @@ import {
|
|
|
83
86
|
} from "../router/segment-resolution/loader-snapshot.js";
|
|
84
87
|
import {
|
|
85
88
|
RecordingShellStore,
|
|
89
|
+
SHELL_HANDLES_RECORD_KEY,
|
|
86
90
|
SnapshotOnlySegmentStore,
|
|
87
91
|
countSnapshotFamilies,
|
|
88
92
|
estimateShellEntryBytes,
|
|
@@ -90,10 +94,17 @@ import {
|
|
|
90
94
|
hasDocRecord,
|
|
91
95
|
pruneShellSnapshot,
|
|
92
96
|
} from "../cache/shell-snapshot.js";
|
|
97
|
+
import {
|
|
98
|
+
captureOwnedHandles,
|
|
99
|
+
encodeHandles,
|
|
100
|
+
} from "../cache/handle-snapshot.js";
|
|
93
101
|
import type { HandlerContext } from "./handler-context.js";
|
|
94
102
|
import type { SSRModule } from "./types.js";
|
|
95
103
|
import { buildFullPayload, payloadInitialTheme } from "./full-payload.js";
|
|
96
|
-
import {
|
|
104
|
+
import {
|
|
105
|
+
recordedHandleData,
|
|
106
|
+
resolveDeferredHandleValues,
|
|
107
|
+
} from "../handles/deferred-resolution.js";
|
|
97
108
|
import { renderRscFlightStage } from "./render-pipeline.js";
|
|
98
109
|
import { stripInternalParams } from "../router/handler-context.js";
|
|
99
110
|
|
|
@@ -482,6 +493,20 @@ function refuseOnCaptureGuard(
|
|
|
482
493
|
return true;
|
|
483
494
|
}
|
|
484
495
|
|
|
496
|
+
/** The refusal a tripped capture guard stands for (refuseOnCaptureGuard). */
|
|
497
|
+
function guardRefusal(derivedCtx: RequestContext<any>): ShellCaptureRefusal {
|
|
498
|
+
return derivedCtx._dynamic ? "dynamic" : "identity";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** A refused attempt; the reason rides its event (ShellCaptureDebugEvent.refusal). */
|
|
502
|
+
function refuse(
|
|
503
|
+
stats: Pick<ShellCaptureDebugEvent, "refusal">,
|
|
504
|
+
refusal: ShellCaptureRefusal,
|
|
505
|
+
): "refused" {
|
|
506
|
+
stats.refusal = refusal;
|
|
507
|
+
return "refused";
|
|
508
|
+
}
|
|
509
|
+
|
|
485
510
|
/** The no-shell cause for handler output that missed the capture deadline. */
|
|
486
511
|
const HANDLER_OUTPUT_TIMEOUT_REASON: string =
|
|
487
512
|
"the handler output (a promise it passes or pushes, or an async server " +
|
|
@@ -658,6 +683,8 @@ export interface ShellCaptureDebugEvent {
|
|
|
658
683
|
untaggedBake?: true;
|
|
659
684
|
/** Outcome reported by a store that supports shell-write acknowledgements. */
|
|
660
685
|
storeWrite?: "stored" | "invalidated" | "uncacheable";
|
|
686
|
+
/** Why a `refused` attempt refused. */
|
|
687
|
+
refusal?: ShellCaptureRefusal;
|
|
661
688
|
/** Consecutive failure count in the key's backoff entry, when one exists. */
|
|
662
689
|
backoffFailures?: number;
|
|
663
690
|
/** Ms remaining in the key's backoff window, when one exists. */
|
|
@@ -719,6 +746,7 @@ export function describeShellCaptureEvent(
|
|
|
719
746
|
if (event.storeWrite !== undefined) {
|
|
720
747
|
parts.push(`store-write=${event.storeWrite}`);
|
|
721
748
|
}
|
|
749
|
+
if (event.refusal !== undefined) parts.push(`refusal=${event.refusal}`);
|
|
722
750
|
if (event.backoffFailures !== undefined) {
|
|
723
751
|
parts.push(`backoff-failures=${event.backoffFailures}`);
|
|
724
752
|
}
|
|
@@ -1042,6 +1070,15 @@ export interface ShellCaptureDescriptor {
|
|
|
1042
1070
|
* re-render the page and drop the isolate's memo of the shell just stored.
|
|
1043
1071
|
*/
|
|
1044
1072
|
storedSeqAtRead?: number;
|
|
1073
|
+
/**
|
|
1074
|
+
* A `router.prerender()` warm's capture (rsc-rendering.ts shellServePlan):
|
|
1075
|
+
* one explicit call that must store, so the guards that shed a herd of
|
|
1076
|
+
* visitors do not apply to it. scheduleShellCapture skips the in-flight
|
|
1077
|
+
* dedup and the refused-capture backoff, and attemptCapture waits longer on
|
|
1078
|
+
* the write barrier. Every correctness guard, the inert-store skip and the
|
|
1079
|
+
* queue limits still decide.
|
|
1080
|
+
*/
|
|
1081
|
+
force?: true;
|
|
1045
1082
|
}
|
|
1046
1083
|
|
|
1047
1084
|
/** Per key, the sequence number of its latest stored capture (bounded). */
|
|
@@ -1086,7 +1123,10 @@ export function scheduleShellCapture(
|
|
|
1086
1123
|
descriptor: ShellCaptureDescriptor,
|
|
1087
1124
|
): void {
|
|
1088
1125
|
const key = descriptor.key;
|
|
1089
|
-
|
|
1126
|
+
// A forced capture installs its own token below: a visitor's capture that
|
|
1127
|
+
// started before the warm may have read data from before the change the
|
|
1128
|
+
// warm publishes, and the queue runs it first, so the warm stores last.
|
|
1129
|
+
const inFlight = descriptor.force ? undefined : inFlightCaptures.get(key);
|
|
1090
1130
|
if (inFlight) {
|
|
1091
1131
|
if (Date.now() - inFlight.startedAt <= SHELL_CAPTURE_TASK_HARD_CAP_MS) {
|
|
1092
1132
|
publishCaptureDebugEvent(descriptor, { key, outcome: "skip-in-flight" });
|
|
@@ -1107,7 +1147,7 @@ export function scheduleShellCapture(
|
|
|
1107
1147
|
}
|
|
1108
1148
|
// Refused/failed within the window → skip the doomed re-render (one probe per
|
|
1109
1149
|
// key per window per isolate). Expired entries self-evict inside the check.
|
|
1110
|
-
if (isCaptureBackedOff(key)) {
|
|
1150
|
+
if (!descriptor.force && isCaptureBackedOff(key)) {
|
|
1111
1151
|
publishCaptureDebugEvent(descriptor, {
|
|
1112
1152
|
key,
|
|
1113
1153
|
outcome: "skip-backoff",
|
|
@@ -1436,6 +1476,7 @@ type CaptureAttemptStats = Pick<
|
|
|
1436
1476
|
| "snapshotSkipped"
|
|
1437
1477
|
| "untaggedBake"
|
|
1438
1478
|
| "storeWrite"
|
|
1479
|
+
| "refusal"
|
|
1439
1480
|
| "recordSettleMs"
|
|
1440
1481
|
| "entryBytes"
|
|
1441
1482
|
> & {
|
|
@@ -1597,7 +1638,7 @@ async function runShellCapture(
|
|
|
1597
1638
|
export type CaptureRecordOutcome =
|
|
1598
1639
|
| { kind: "record" | "prerender"; match: MatchResult }
|
|
1599
1640
|
| { kind: "timeout"; reason: string }
|
|
1600
|
-
| { kind: "refused" };
|
|
1641
|
+
| { kind: "refused"; refusal: ShellCaptureRefusal };
|
|
1601
1642
|
|
|
1602
1643
|
/**
|
|
1603
1644
|
* Record-first capture: the doc record is both the settle signal and the
|
|
@@ -1643,7 +1684,13 @@ export async function settleCaptureRecord(
|
|
|
1643
1684
|
const loaderRecords = derivedCtx._shellCaptureLoaderRecords;
|
|
1644
1685
|
const settled = await raceDeadline(
|
|
1645
1686
|
Promise.all([
|
|
1646
|
-
freshHandleStore
|
|
1687
|
+
freshHandleStore
|
|
1688
|
+
.getData()
|
|
1689
|
+
.then((data) =>
|
|
1690
|
+
resolveDeferredHandleValues(
|
|
1691
|
+
recordedHandleData(freshHandleStore, data),
|
|
1692
|
+
),
|
|
1693
|
+
),
|
|
1647
1694
|
derivation.handlerPushesSettled(),
|
|
1648
1695
|
loaderRecords && loaderRecords.size > 0
|
|
1649
1696
|
? Promise.allSettled([...loaderRecords.values()])
|
|
@@ -1655,7 +1702,7 @@ export async function settleCaptureRecord(
|
|
|
1655
1702
|
// write starts (no encode for a capture that already failed).
|
|
1656
1703
|
if (!settled.done) {
|
|
1657
1704
|
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1658
|
-
return { kind: "refused" };
|
|
1705
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1659
1706
|
return { kind: "timeout", reason: HANDLER_OUTPUT_TIMEOUT_REASON };
|
|
1660
1707
|
}
|
|
1661
1708
|
|
|
@@ -1676,7 +1723,9 @@ export async function settleCaptureRecord(
|
|
|
1676
1723
|
|
|
1677
1724
|
// Capture guards first: a tripped guard usually also fails the record,
|
|
1678
1725
|
// and its own message names the cause.
|
|
1679
|
-
if (refuseOnCaptureGuard(capture.key, derivedCtx))
|
|
1726
|
+
if (refuseOnCaptureGuard(capture.key, derivedCtx)) {
|
|
1727
|
+
return { kind: "refused", refusal: guardRefusal(derivedCtx) };
|
|
1728
|
+
}
|
|
1680
1729
|
|
|
1681
1730
|
const docKey = derivedCtx._shellImplicitCache?.docKey;
|
|
1682
1731
|
const record =
|
|
@@ -1694,7 +1743,7 @@ export async function settleCaptureRecord(
|
|
|
1694
1743
|
return { kind: "timeout", reason: HANDLER_OUTPUT_TIMEOUT_REASON };
|
|
1695
1744
|
}
|
|
1696
1745
|
warnCaptureRefusedOnce(capture.key, NO_DOC_RECORD_REASON);
|
|
1697
|
-
return { kind: "refused" };
|
|
1746
|
+
return { kind: "refused", refusal: "no-record" };
|
|
1698
1747
|
}
|
|
1699
1748
|
|
|
1700
1749
|
// A handle encode that timed out stores "" (handle-snapshot.ts
|
|
@@ -1708,7 +1757,7 @@ export async function settleCaptureRecord(
|
|
|
1708
1757
|
"record. A promise inside a Map, Set, or class instance is not awaited " +
|
|
1709
1758
|
"before the encode; push plain objects and arrays.",
|
|
1710
1759
|
);
|
|
1711
|
-
return { kind: "refused" };
|
|
1760
|
+
return { kind: "refused", refusal: "handles" };
|
|
1712
1761
|
}
|
|
1713
1762
|
|
|
1714
1763
|
const { fragmentSegments } = await import("../cache/segment-codec.js");
|
|
@@ -1805,8 +1854,21 @@ async function attemptCapture(
|
|
|
1805
1854
|
// skipped, cache-store middleware's write path gated off by state.cacheHit), so
|
|
1806
1855
|
// prelude, snapshot, and ring-3 agree on the foreground's generation. Runs per
|
|
1807
1856
|
// attempt (the retry re-checks; already-settled promises are free).
|
|
1857
|
+
//
|
|
1858
|
+
// A forced capture (a warm) is not latency-bound and must not bake an entry
|
|
1859
|
+
// its own foreground is still replacing, so it waits for as long as the task
|
|
1860
|
+
// hard cap leaves after the capture budget.
|
|
1808
1861
|
const barrierStart = performance.now();
|
|
1809
|
-
await settleTrackedBackgroundTasks(
|
|
1862
|
+
await settleTrackedBackgroundTasks(
|
|
1863
|
+
reqCtx,
|
|
1864
|
+
descriptor.force
|
|
1865
|
+
? Math.max(
|
|
1866
|
+
SHELL_CAPTURE_WRITE_BARRIER_MS,
|
|
1867
|
+
SHELL_CAPTURE_TASK_HARD_CAP_MS -
|
|
1868
|
+
(descriptor.captureTimeout ?? SHELL_CAPTURE_MAX_WAIT_MS),
|
|
1869
|
+
)
|
|
1870
|
+
: SHELL_CAPTURE_WRITE_BARRIER_MS,
|
|
1871
|
+
);
|
|
1810
1872
|
stats.barrierWaitMs = Math.round(performance.now() - barrierStart);
|
|
1811
1873
|
|
|
1812
1874
|
const derivation = deriveShellCaptureContext(
|
|
@@ -1863,7 +1925,7 @@ async function attemptCapture(
|
|
|
1863
1925
|
stats.recordSettleMs,
|
|
1864
1926
|
);
|
|
1865
1927
|
}
|
|
1866
|
-
if (settled.kind === "refused") return
|
|
1928
|
+
if (settled.kind === "refused") return refuse(stats, settled.refusal);
|
|
1867
1929
|
if (settled.kind === "timeout") {
|
|
1868
1930
|
stats.noShellCause = settled.reason;
|
|
1869
1931
|
return "no-shell";
|
|
@@ -2122,6 +2184,7 @@ export function deriveShellCaptureContext(
|
|
|
2122
2184
|
// Own list: the capture's render errors refuse the capture only, and the
|
|
2123
2185
|
// foreground's never reach it.
|
|
2124
2186
|
derivedCtx._renderErrors = [];
|
|
2187
|
+
derivedCtx._recoveredHandlerErrors = undefined;
|
|
2125
2188
|
derivedCtx._shellCaptureRun = true;
|
|
2126
2189
|
// Own dynamic() latch. The inherited method writes the FOREGROUND context
|
|
2127
2190
|
// (its closure), which already served, and nothing read it for a runtime
|
|
@@ -2240,7 +2303,9 @@ async function captureAndStoreShell(
|
|
|
2240
2303
|
// (boundary-less segment — lands in the catch) or is swallowed into
|
|
2241
2304
|
// per-loader error UI (the render completes — caught after the try).
|
|
2242
2305
|
const refuseOnGuardTrip = (): "refused" | undefined =>
|
|
2243
|
-
refuseOnCaptureGuard(capture.key, reqCtx)
|
|
2306
|
+
refuseOnCaptureGuard(capture.key, reqCtx)
|
|
2307
|
+
? refuse(stats, guardRefusal(reqCtx))
|
|
2308
|
+
: undefined;
|
|
2244
2309
|
|
|
2245
2310
|
// Dev diagnostics for a no-shell attempt (warnNullCaptureOnce): the stacks of
|
|
2246
2311
|
// the tasks React reports as still pending at the capture's abort. React's
|
|
@@ -2356,7 +2421,7 @@ async function captureAndStoreShell(
|
|
|
2356
2421
|
"Fix the loader, or drop its ssr: false to move it to the live lane.",
|
|
2357
2422
|
PPR_LANE_HINT,
|
|
2358
2423
|
);
|
|
2359
|
-
return "
|
|
2424
|
+
return refuse(stats, "loader");
|
|
2360
2425
|
}
|
|
2361
2426
|
// The container itself never settled: it is a hole (under an ancestor
|
|
2362
2427
|
// boundary) or the trivial-prelude gate already fired. Omit — no pin.
|
|
@@ -2373,7 +2438,7 @@ async function captureAndStoreShell(
|
|
|
2373
2438
|
"Drop its ssr: false to move it to the live lane, or move the decision into middleware.",
|
|
2374
2439
|
PPR_LANE_HINT,
|
|
2375
2440
|
);
|
|
2376
|
-
return "
|
|
2441
|
+
return refuse(stats, "loader");
|
|
2377
2442
|
}
|
|
2378
2443
|
// Past the hole check: this container settled with real material that
|
|
2379
2444
|
// bakes into the shell prelude (independent of the snapshot pin below).
|
|
@@ -2413,6 +2478,21 @@ async function captureAndStoreShell(
|
|
|
2413
2478
|
}
|
|
2414
2479
|
}
|
|
2415
2480
|
|
|
2481
|
+
// Built before the render-error check below so its encode errors refuse
|
|
2482
|
+
// the capture (ShellSnapshotHandlesValue).
|
|
2483
|
+
const docKey = reqCtx._shellImplicitCache?.docKey;
|
|
2484
|
+
let prerenderHandles: ShellSnapshotHandlesValue | undefined;
|
|
2485
|
+
if (!capture.navigationOnly && !hasDocRecord(snapshot, docKey)) {
|
|
2486
|
+
// The store is sealed (settleCaptureRecord) and settled (quiesce).
|
|
2487
|
+
const { handles, owners } = captureOwnedHandles(reqCtx._handleStore);
|
|
2488
|
+
prerenderHandles = {
|
|
2489
|
+
handles: await encodeHandles(handles, (error) => {
|
|
2490
|
+
reqCtx._renderErrors?.push(error);
|
|
2491
|
+
}),
|
|
2492
|
+
handleOwners: owners,
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2416
2496
|
// A shell component that threw inside a Suspense boundary does not reject
|
|
2417
2497
|
// the capture: Flight and Fizz report it through onError and the prelude
|
|
2418
2498
|
// carries the errored boundary, which every HIT would serve (issue #915).
|
|
@@ -2426,7 +2506,6 @@ async function captureAndStoreShell(
|
|
|
2426
2506
|
// Store only what a HIT reads (issue #941): the doc record and, for a
|
|
2427
2507
|
// document entry, the bake-lane loader pins. Before the size guards, so
|
|
2428
2508
|
// they measure what is stored.
|
|
2429
|
-
const docKey = reqCtx._shellImplicitCache?.docKey;
|
|
2430
2509
|
let prunedRecords: string | undefined;
|
|
2431
2510
|
const { kept, pruned } = pruneShellSnapshot(
|
|
2432
2511
|
snapshot,
|
|
@@ -2464,6 +2543,15 @@ async function captureAndStoreShell(
|
|
|
2464
2543
|
stats.snapshotSkipped = true;
|
|
2465
2544
|
}
|
|
2466
2545
|
}
|
|
2546
|
+
// Exempt from maxSnapshotBytes: the HIT needs it for its HTML and
|
|
2547
|
+
// hydration data to match.
|
|
2548
|
+
if (prerenderHandles) {
|
|
2549
|
+
snapshot.push({
|
|
2550
|
+
family: "handles",
|
|
2551
|
+
key: SHELL_HANDLES_RECORD_KEY,
|
|
2552
|
+
value: prerenderHandles,
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2467
2555
|
|
|
2468
2556
|
// settleCaptureRecord refused every capture without a doc record except a
|
|
2469
2557
|
// prerender-served one (the prerender store supplies its handler layer on
|
|
@@ -2525,7 +2613,7 @@ async function captureAndStoreShell(
|
|
|
2525
2613
|
"Shrink the page the shell bakes, or move large regions under a live " +
|
|
2526
2614
|
"loader's boundary.",
|
|
2527
2615
|
);
|
|
2528
|
-
return "
|
|
2616
|
+
return refuse(stats, "size");
|
|
2529
2617
|
}
|
|
2530
2618
|
|
|
2531
2619
|
// Missing tags are valid: the shell follows TTL/SWR-only invalidation. Expose
|
|
@@ -2597,7 +2685,7 @@ async function captureAndStoreShell(
|
|
|
2597
2685
|
record,
|
|
2598
2686
|
`${sinceWriteMs} ms after this capture wrote it`,
|
|
2599
2687
|
);
|
|
2600
|
-
return "
|
|
2688
|
+
return refuse(stats, "record-expired");
|
|
2601
2689
|
}
|
|
2602
2690
|
stats.expired = {
|
|
2603
2691
|
route,
|
|
@@ -2623,7 +2711,7 @@ async function captureAndStoreShell(
|
|
|
2623
2711
|
"If capture code deterministically calls updateTag() on its own shell tag, move that mutation out of the render; " +
|
|
2624
2712
|
"otherwise every generation is invalidated before it can be served.",
|
|
2625
2713
|
);
|
|
2626
|
-
return "
|
|
2714
|
+
return refuse(stats, "invalidated");
|
|
2627
2715
|
}
|
|
2628
2716
|
if (storeWrite === "uncacheable") {
|
|
2629
2717
|
// The store declared the entry unstorable under its current
|
|
@@ -2638,8 +2726,9 @@ async function captureAndStoreShell(
|
|
|
2638
2726
|
"the write as permanently refusable, so the key is backed off. See the cache store's " +
|
|
2639
2727
|
"own warning for the specific limit and remedy.",
|
|
2640
2728
|
);
|
|
2641
|
-
return "
|
|
2729
|
+
return refuse(stats, "uncacheable");
|
|
2642
2730
|
}
|
|
2731
|
+
noteWarmWrite(reqCtx, "shell");
|
|
2643
2732
|
} catch (error) {
|
|
2644
2733
|
// Best-effort: a failed put must never throw out of the background task.
|
|
2645
2734
|
reportCacheError(
|
package/src/rsc/shell-serve.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
resetShellWarningsForTests,
|
|
36
36
|
warnOnce,
|
|
37
37
|
} from "./shell-capture-constants.js";
|
|
38
|
+
import { routerKeyPrefix } from "../cache/cache-key-utils.js";
|
|
38
39
|
|
|
39
40
|
export {
|
|
40
41
|
PPR_REPLAY_STATUS_HEADER,
|
|
@@ -529,8 +530,9 @@ export function warnPprWindowCappedOnce(
|
|
|
529
530
|
|
|
530
531
|
/**
|
|
531
532
|
* Paths a partitioned request found without a build shell. Build shells are
|
|
532
|
-
* per path (a param route prerenders some of its paths), so a
|
|
533
|
-
* kept per path, not per route; cleared when full, so
|
|
533
|
+
* per router and path (a param route prerenders some of its paths), so a
|
|
534
|
+
* negative is kept per router and path, not per route; cleared when full, so
|
|
535
|
+
* it stays bounded.
|
|
534
536
|
*/
|
|
535
537
|
const pathsWithoutBuildShell = new Set<string>();
|
|
536
538
|
const PATHS_WITHOUT_BUILD_SHELL_MAX = 1_000;
|
|
@@ -540,12 +542,14 @@ const PATHS_WITHOUT_BUILD_SHELL_MAX = 1_000;
|
|
|
540
542
|
* already warned, or its path was already found without one.
|
|
541
543
|
*/
|
|
542
544
|
export function partitionBuildShellCheckDone(
|
|
545
|
+
routerId: string,
|
|
543
546
|
pathname: string,
|
|
544
547
|
routeName: string | undefined,
|
|
545
548
|
): boolean {
|
|
549
|
+
const router = routerKeyPrefix(routerId);
|
|
546
550
|
return (
|
|
547
|
-
hasWarnedOnce("partition-build-shell", routeName ?? pathname) ||
|
|
548
|
-
pathsWithoutBuildShell.has(pathname)
|
|
551
|
+
hasWarnedOnce("partition-build-shell", router + (routeName ?? pathname)) ||
|
|
552
|
+
pathsWithoutBuildShell.has(router + pathname)
|
|
549
553
|
);
|
|
550
554
|
}
|
|
551
555
|
|
|
@@ -560,21 +564,23 @@ export function partitionBuildShellCheckDone(
|
|
|
560
564
|
* with no build shell stays silent.
|
|
561
565
|
*/
|
|
562
566
|
export function notePartitionBuildShellCheck(
|
|
567
|
+
routerId: string,
|
|
563
568
|
pathname: string,
|
|
564
569
|
routeName: string | undefined,
|
|
565
570
|
found: boolean,
|
|
566
571
|
): void {
|
|
572
|
+
const router = routerKeyPrefix(routerId);
|
|
567
573
|
if (!found) {
|
|
568
574
|
if (pathsWithoutBuildShell.size >= PATHS_WITHOUT_BUILD_SHELL_MAX) {
|
|
569
575
|
pathsWithoutBuildShell.clear();
|
|
570
576
|
}
|
|
571
|
-
pathsWithoutBuildShell.add(pathname);
|
|
577
|
+
pathsWithoutBuildShell.add(router + pathname);
|
|
572
578
|
return;
|
|
573
579
|
}
|
|
574
580
|
// Concurrent first requests can both probe; one of them warns.
|
|
575
581
|
warnOnce(
|
|
576
582
|
"partition-build-shell",
|
|
577
|
-
routeName ?? pathname,
|
|
583
|
+
router + (routeName ?? pathname),
|
|
578
584
|
() =>
|
|
579
585
|
`[rango] Route ${routeName ? `"${routeName}" ` : ""}("${pathname}") has a ` +
|
|
580
586
|
"build-time shell, but its request partition (a cache({ key }) enclosing the route, " +
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SSR module loader a handler created without `loadSSRModule` falls back
|
|
3
|
+
* to in tests (`router.fetch` creates its handler that way). A leaf with no
|
|
4
|
+
* runtime imports: testing/serve-shell-request.ts installs its stub here when
|
|
5
|
+
* it loads, without importing rsc/handler.ts (which binds build-only virtual
|
|
6
|
+
* modules a flight-only test config does not alias).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { LoadSSRModule } from "./types.js";
|
|
10
|
+
|
|
11
|
+
let testLoader: LoadSSRModule | undefined;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @internal Tests only: `router.prerender()` warms through `router.fetch`,
|
|
15
|
+
* and the rsc Vitest project cannot run `import.meta.viteRsc.loadModule`.
|
|
16
|
+
*/
|
|
17
|
+
export function setDefaultSSRModuleLoaderForTests(
|
|
18
|
+
loader: LoadSSRModule | undefined,
|
|
19
|
+
): void {
|
|
20
|
+
testLoader = loader;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @internal Undefined everywhere but in tests. */
|
|
24
|
+
export function defaultSSRModuleLoaderForTests(): LoadSSRModule | undefined {
|
|
25
|
+
return testLoader;
|
|
26
|
+
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -97,6 +97,14 @@ export interface RscPayload {
|
|
|
97
97
|
locationState?: Record<string, unknown>;
|
|
98
98
|
};
|
|
99
99
|
returnValue?: { ok: boolean; data: unknown };
|
|
100
|
+
/**
|
|
101
|
+
* useActionState form state of a progressive-enhancement POST re-render
|
|
102
|
+
* (the value the SSR render received). Present only on that document; the
|
|
103
|
+
* browser entry passes it to hydrateRoot so the hydrated hook matches the
|
|
104
|
+
* server HTML instead of resetting to its initial state. A promise so a
|
|
105
|
+
* state Flight cannot serialize rejects this slot alone.
|
|
106
|
+
*/
|
|
107
|
+
formState?: Promise<ReactFormState>;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a RouteContentWrapper receives as `content`.
|
|
7
|
+
*
|
|
8
|
+
* Browser: hand over the settled value. React treats a promise as fulfilled
|
|
9
|
+
* only after it has read it, and a boundary on screen handed a promise it has
|
|
10
|
+
* not read, in a render that cannot wait, shows its fallback. A component that
|
|
11
|
+
* is already a promise (a Flight-streamed segment) is returned as is, since
|
|
12
|
+
* it can really be pending.
|
|
13
|
+
*
|
|
14
|
+
* Server: a fresh `Promise.resolve(component)` per call, so Suspense emits the
|
|
15
|
+
* fallback in the streamed HTML. A shared resolved promise would carry React's
|
|
16
|
+
* `.status` across requests and skip it.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function getBoundaryContent(component: ReactNode): ReactNode {
|
|
21
|
+
if (component instanceof Promise || IS_BROWSER) {
|
|
22
|
+
return component;
|
|
23
|
+
}
|
|
24
|
+
return Promise.resolve(component);
|
|
25
|
+
}
|
|
@@ -2,36 +2,30 @@ import type { ResolvedSegment } from "./types.js";
|
|
|
2
2
|
import { INTERNAL_RANGO_DEBUG } from "./internal-debug.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* loaders that resolve to primitive data are memoized too. The per-key array
|
|
12
|
-
* is capped (MAX_ENTRIES_PER_KEY, oldest evicted) so a long session under a
|
|
13
|
-
* stable first-ref does not grow it without bound.
|
|
5
|
+
* Browser: a boundary is handed the settled value. React treats a promise as
|
|
6
|
+
* fulfilled only after it has read it, and a boundary on screen handed a
|
|
7
|
+
* promise it has not read, in a render that cannot wait, shows its fallback.
|
|
8
|
+
* So a cache entry records its array once the aggregate fulfils and later
|
|
9
|
+
* calls return that same array; until then (and when it rejects) they return
|
|
10
|
+
* the promise. A boundary with no loaders gets one shared empty array.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* (object keys only).
|
|
12
|
+
* Server: a fresh promise per call, so Suspense emits the fallback in the
|
|
13
|
+
* streamed HTML. A shared resolved promise would carry React's `.status`
|
|
14
|
+
* across requests and skip it.
|
|
19
15
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* and skip the fallback. The zero-loader case is especially prone because
|
|
25
|
-
* every empty-loader site would otherwise share one promise across requests.
|
|
16
|
+
* Entries are keyed on the first loader's `loaderData` reference (object refs
|
|
17
|
+
* in a WeakMap, primitives in a Map), so reconciliation's fresh segment
|
|
18
|
+
* objects keep hitting them. Each key's entry list is capped (see
|
|
19
|
+
* MAX_ENTRIES_PER_KEY).
|
|
26
20
|
*/
|
|
27
21
|
const IS_BROWSER = typeof window !== "undefined";
|
|
28
22
|
|
|
29
23
|
interface LoaderCacheEntry {
|
|
30
24
|
sources: any[];
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// (Promise<any[]> | any[]) to mirror its siblings.
|
|
25
|
+
// Resolves to the same array it records in `value`; a rejection passes
|
|
26
|
+
// through untouched.
|
|
34
27
|
promise: Promise<any[]>;
|
|
28
|
+
value?: any[];
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
// Cap the per-key entries array. A stable first-ref (e.g. a layout loader whose
|
|
@@ -51,12 +45,8 @@ const primitiveLoaderCache = IS_BROWSER
|
|
|
51
45
|
? new Map<unknown, LoaderCacheEntry[]>()
|
|
52
46
|
: null;
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// state across renders. On the server it would leak `.status = "fulfilled"`
|
|
57
|
-
// across requests and skip the Suspense fallback, so we rebuild on each call.
|
|
58
|
-
const SHARED_EMPTY_LOADER_PROMISE: Promise<any[]> | null = IS_BROWSER
|
|
59
|
-
? Promise.resolve([])
|
|
48
|
+
const SHARED_EMPTY_LOADERS: any[] | null = IS_BROWSER
|
|
49
|
+
? (Object.freeze([]) as unknown as any[])
|
|
60
50
|
: null;
|
|
61
51
|
|
|
62
52
|
function hasSameReferences(a: any[], b: any[]): boolean {
|
|
@@ -117,12 +107,8 @@ function isObjectLike(value: unknown): value is object {
|
|
|
117
107
|
}
|
|
118
108
|
|
|
119
109
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* loader.loaderData ref changes — keeps React's `use()` in "known fulfilled"
|
|
123
|
-
* state and prevents a fresh Promise.all from suspending (and briefly
|
|
124
|
-
* committing the Suspense fallback) on every partial update that doesn't
|
|
125
|
-
* actually change loader data.
|
|
110
|
+
* The aggregate for a set of loader segments, memoized on their `loaderData`
|
|
111
|
+
* refs: the settled array once it has fulfilled, the promise before that.
|
|
126
112
|
*
|
|
127
113
|
* @internal
|
|
128
114
|
*/
|
|
@@ -130,7 +116,7 @@ export function getMemoizedLoaderPromise(
|
|
|
130
116
|
loaders: ResolvedSegment[],
|
|
131
117
|
): Promise<any[]> | any[] {
|
|
132
118
|
if (loaders.length === 0) {
|
|
133
|
-
return
|
|
119
|
+
return SHARED_EMPTY_LOADERS ?? buildLoaderPromise(loaders);
|
|
134
120
|
}
|
|
135
121
|
if (!objectLoaderCache || !primitiveLoaderCache) {
|
|
136
122
|
return buildLoaderPromise(loaders);
|
|
@@ -145,13 +131,17 @@ export function getMemoizedLoaderPromise(
|
|
|
145
131
|
if (entries) {
|
|
146
132
|
for (const entry of entries) {
|
|
147
133
|
if (hasSameReferences(entry.sources, sources)) {
|
|
148
|
-
return entry.promise;
|
|
134
|
+
return entry.value ?? entry.promise;
|
|
149
135
|
}
|
|
150
136
|
}
|
|
151
137
|
}
|
|
152
138
|
|
|
153
|
-
const
|
|
154
|
-
const
|
|
139
|
+
const newEntry = { sources } as LoaderCacheEntry;
|
|
140
|
+
const promise = buildLoaderPromise(loaders).then((values) => {
|
|
141
|
+
newEntry.value = values;
|
|
142
|
+
return values;
|
|
143
|
+
});
|
|
144
|
+
newEntry.promise = promise;
|
|
155
145
|
if (entries) {
|
|
156
146
|
// Bound the array: drop the oldest entry before appending when at the cap.
|
|
157
147
|
if (entries.length >= MAX_ENTRIES_PER_KEY) {
|