@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81
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/AGENTS.md +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +5091 -941
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +61 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +340 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +91 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +393 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +358 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +977 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -70,9 +70,11 @@
|
|
|
70
70
|
* - No segments yielded from this middleware
|
|
71
71
|
*
|
|
72
72
|
* Loaders:
|
|
73
|
-
* - NEVER cached
|
|
73
|
+
* - NEVER cached in the segment cache
|
|
74
74
|
* - Always resolved fresh on every request
|
|
75
75
|
* - Ensures data freshness even with cached UI components
|
|
76
|
+
* - Segment cache staleness does NOT propagate to loader revalidation;
|
|
77
|
+
* loaders use their own revalidation rules (actionId, user-defined)
|
|
76
78
|
*
|
|
77
79
|
*
|
|
78
80
|
* REVALIDATION RULES
|
|
@@ -92,12 +94,199 @@
|
|
|
92
94
|
import type { ResolvedSegment } from "../../types.js";
|
|
93
95
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
94
96
|
import { getRouterContext } from "../router-context.js";
|
|
97
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
98
|
+
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
99
|
+
import { treeHasStreaming } from "./segment-resolution.js";
|
|
100
|
+
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
101
|
+
import type { HandleStore } from "../../server/handle-store.js";
|
|
102
|
+
import {
|
|
103
|
+
getRequestContext,
|
|
104
|
+
_getRequestContext,
|
|
105
|
+
} from "../../server/request-context.js";
|
|
106
|
+
|
|
107
|
+
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
108
|
+
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
109
|
+
// top-level, which breaks vitest (only URLs with file:, data:, node: schemes).
|
|
110
|
+
let prerenderStoreInstance: PrerenderStore | null | undefined;
|
|
111
|
+
let _deserializeSegments:
|
|
112
|
+
| typeof import("../../cache/segment-codec.js").deserializeSegments
|
|
113
|
+
| undefined;
|
|
114
|
+
let _restoreHandles:
|
|
115
|
+
| typeof import("../../cache/handle-snapshot.js").restoreHandles
|
|
116
|
+
| undefined;
|
|
117
|
+
let _hashParams:
|
|
118
|
+
| typeof import("../../prerender/param-hash.js").hashParams
|
|
119
|
+
| undefined;
|
|
120
|
+
let _lazyGetRequestContext:
|
|
121
|
+
| typeof import("../../server/request-context.js").getRequestContext
|
|
122
|
+
| undefined;
|
|
123
|
+
|
|
124
|
+
function paramsEqual(
|
|
125
|
+
a: Record<string, string>,
|
|
126
|
+
b: Record<string, string>,
|
|
127
|
+
): boolean {
|
|
128
|
+
if (a === b) return true;
|
|
129
|
+
|
|
130
|
+
const keysA = Object.keys(a);
|
|
131
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
132
|
+
|
|
133
|
+
for (const key of keysA) {
|
|
134
|
+
if (a[key] !== b[key]) return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function ensurePrerenderDeps() {
|
|
141
|
+
if (!_deserializeSegments) {
|
|
142
|
+
const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
|
|
143
|
+
import("../../cache/segment-codec.js"),
|
|
144
|
+
import("../../cache/handle-snapshot.js"),
|
|
145
|
+
import("../../prerender/param-hash.js"),
|
|
146
|
+
import("../../server/request-context.js"),
|
|
147
|
+
import("../../prerender/store.js"),
|
|
148
|
+
]);
|
|
149
|
+
_deserializeSegments = codec.deserializeSegments;
|
|
150
|
+
_restoreHandles = snapshot.restoreHandles;
|
|
151
|
+
_hashParams = paramHash.hashParams;
|
|
152
|
+
_lazyGetRequestContext = reqCtx.getRequestContext;
|
|
153
|
+
if (prerenderStoreInstance === undefined) {
|
|
154
|
+
prerenderStoreInstance = store.createPrerenderStore();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Shared yield logic for prerender and static handler store entries.
|
|
161
|
+
* Deserializes segments, replays handle data, yields segments with partial
|
|
162
|
+
* navigation nullification, and resolves fresh loaders.
|
|
163
|
+
*/
|
|
164
|
+
async function* yieldFromStore<TEnv>(
|
|
165
|
+
entry: PrerenderEntry,
|
|
166
|
+
ctx: MatchContext<TEnv>,
|
|
167
|
+
state: MatchPipelineState,
|
|
168
|
+
pipelineStart: number,
|
|
169
|
+
handleStoreRef?: HandleStore,
|
|
170
|
+
): AsyncGenerator<ResolvedSegment> {
|
|
171
|
+
const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
|
|
172
|
+
getRouterContext<TEnv>();
|
|
173
|
+
|
|
174
|
+
if (
|
|
175
|
+
!_deserializeSegments ||
|
|
176
|
+
!_restoreHandles ||
|
|
177
|
+
!_hashParams ||
|
|
178
|
+
!_lazyGetRequestContext
|
|
179
|
+
) {
|
|
180
|
+
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const segments = await _deserializeSegments(entry.segments);
|
|
184
|
+
|
|
185
|
+
// Replay handle data (same as runtime cache hit path).
|
|
186
|
+
// Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
|
|
187
|
+
const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
|
|
188
|
+
if (handleStore) {
|
|
189
|
+
_restoreHandles(entry.handles, handleStore);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
state.cacheHit = true;
|
|
193
|
+
state.cacheSource = "prerender";
|
|
194
|
+
state.cachedSegments = segments;
|
|
195
|
+
state.cachedMatchedIds = segments.map((s) => s.id);
|
|
196
|
+
|
|
197
|
+
// Set streaming flag (once) and resolve render barrier.
|
|
198
|
+
const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
|
|
199
|
+
const barrierReqCtx = reqCtx ?? _getRequestContext();
|
|
200
|
+
if (barrierReqCtx) {
|
|
201
|
+
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
202
|
+
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
203
|
+
}
|
|
204
|
+
barrierReqCtx._resolveRenderBarrier(segments);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// For partial navigation, nullify components the client already has
|
|
208
|
+
// so parent layouts stay live (client keeps its existing versions).
|
|
209
|
+
// When params changed (e.g., different guide slug), the segments have
|
|
210
|
+
// different content, so we must NOT nullify.
|
|
211
|
+
const paramsChanged =
|
|
212
|
+
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
213
|
+
for (const segment of segments) {
|
|
214
|
+
if (
|
|
215
|
+
!ctx.isFullMatch &&
|
|
216
|
+
!paramsChanged &&
|
|
217
|
+
ctx.clientSegmentSet.has(segment.id)
|
|
218
|
+
) {
|
|
219
|
+
segment.component = null;
|
|
220
|
+
segment.loading = undefined;
|
|
221
|
+
}
|
|
222
|
+
yield segment;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Resolve loaders fresh (loaders are never pre-rendered/cached)
|
|
226
|
+
const ms = ctx.metricsStore;
|
|
227
|
+
const loaderStart = performance.now();
|
|
228
|
+
|
|
229
|
+
if (ctx.isFullMatch) {
|
|
230
|
+
if (resolveLoadersOnly) {
|
|
231
|
+
const loaderSegments = await ctx.Store.run(() =>
|
|
232
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
233
|
+
);
|
|
234
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
235
|
+
for (const segment of loaderSegments) {
|
|
236
|
+
yield segment;
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
if (resolveLoadersOnlyWithRevalidation) {
|
|
243
|
+
const loaderResult = await ctx.Store.run(() =>
|
|
244
|
+
resolveLoadersOnlyWithRevalidation(
|
|
245
|
+
ctx.entries,
|
|
246
|
+
ctx.handlerContext,
|
|
247
|
+
ctx.clientSegmentSet,
|
|
248
|
+
ctx.prevParams,
|
|
249
|
+
ctx.request,
|
|
250
|
+
ctx.prevUrl,
|
|
251
|
+
ctx.url,
|
|
252
|
+
ctx.routeKey,
|
|
253
|
+
ctx.actionContext,
|
|
254
|
+
ctx.stale || undefined,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
state.matchedIds = [
|
|
258
|
+
...state.cachedMatchedIds!,
|
|
259
|
+
...loaderResult.matchedIds,
|
|
260
|
+
];
|
|
261
|
+
for (const segment of loaderResult.segments) {
|
|
262
|
+
yield segment;
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (ms) {
|
|
270
|
+
const loaderEnd = performance.now();
|
|
271
|
+
ms.metrics.push({
|
|
272
|
+
label: "pipeline:loader-resolve",
|
|
273
|
+
duration: loaderEnd - loaderStart,
|
|
274
|
+
startTime: loaderStart - ms.requestStart,
|
|
275
|
+
depth: 1,
|
|
276
|
+
});
|
|
277
|
+
ms.metrics.push({
|
|
278
|
+
label: "pipeline:cache-hit",
|
|
279
|
+
duration: loaderEnd - pipelineStart,
|
|
280
|
+
startTime: pipelineStart - ms.requestStart,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
95
284
|
|
|
96
285
|
/**
|
|
97
286
|
* Async generator middleware type
|
|
98
287
|
*/
|
|
99
288
|
export type GeneratorMiddleware<T> = (
|
|
100
|
-
source: AsyncGenerator<T
|
|
289
|
+
source: AsyncGenerator<T>,
|
|
101
290
|
) => AsyncGenerator<T>;
|
|
102
291
|
|
|
103
292
|
/**
|
|
@@ -115,11 +304,21 @@ export type GeneratorMiddleware<T> = (
|
|
|
115
304
|
*/
|
|
116
305
|
export function withCacheLookup<TEnv>(
|
|
117
306
|
ctx: MatchContext<TEnv>,
|
|
118
|
-
state: MatchPipelineState
|
|
307
|
+
state: MatchPipelineState,
|
|
119
308
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
120
309
|
return async function* (
|
|
121
|
-
source: AsyncGenerator<ResolvedSegment
|
|
310
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
122
311
|
): AsyncGenerator<ResolvedSegment> {
|
|
312
|
+
const pipelineStart = performance.now();
|
|
313
|
+
const ms = ctx.metricsStore;
|
|
314
|
+
|
|
315
|
+
// Eagerly capture the HandleStore before any async operations.
|
|
316
|
+
// In workerd/Cloudflare, dynamic imports and fetch() inside the pipeline
|
|
317
|
+
// can disrupt AsyncLocalStorage, causing getRequestContext() to return
|
|
318
|
+
// undefined afterward. Capturing the reference early ensures handle replay
|
|
319
|
+
// and handler handle-push work regardless of ALS state.
|
|
320
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
321
|
+
|
|
123
322
|
const {
|
|
124
323
|
evaluateRevalidation,
|
|
125
324
|
buildEntryRevalidateMap,
|
|
@@ -127,10 +326,144 @@ export function withCacheLookup<TEnv>(
|
|
|
127
326
|
resolveLoadersOnly,
|
|
128
327
|
} = getRouterContext<TEnv>();
|
|
129
328
|
|
|
329
|
+
// Prerender lookup: check build-time cached data before runtime cache.
|
|
330
|
+
// Prerender data is available regardless of runtime cache configuration.
|
|
331
|
+
// Skip for HMR requests — the dev prerender endpoint reads from a stale
|
|
332
|
+
// RouterRegistry snapshot; rendering fresh ensures edits are visible.
|
|
333
|
+
const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
|
|
334
|
+
if (!ctx.isAction && !isHmr && ctx.matched.pr) {
|
|
335
|
+
await ensurePrerenderDeps();
|
|
336
|
+
if (prerenderStoreInstance) {
|
|
337
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
338
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
339
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (ctx.isIntercept) {
|
|
343
|
+
// Intercept navigation: try intercept-specific prerender entry
|
|
344
|
+
const entry = await prerenderStoreInstance.get(
|
|
345
|
+
ctx.matched.routeKey,
|
|
346
|
+
paramHash + "/i",
|
|
347
|
+
{
|
|
348
|
+
pathname: ctx.pathname,
|
|
349
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
350
|
+
},
|
|
351
|
+
);
|
|
352
|
+
if (entry) {
|
|
353
|
+
yield* yieldFromStore(
|
|
354
|
+
entry,
|
|
355
|
+
ctx,
|
|
356
|
+
state,
|
|
357
|
+
pipelineStart,
|
|
358
|
+
handleStoreRef,
|
|
359
|
+
);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
363
|
+
// (skip non-intercept prerender to let intercept-resolution run)
|
|
364
|
+
} else {
|
|
365
|
+
// Normal navigation: existing behavior
|
|
366
|
+
const entry = await prerenderStoreInstance.get(
|
|
367
|
+
ctx.matched.routeKey,
|
|
368
|
+
paramHash,
|
|
369
|
+
{
|
|
370
|
+
pathname: ctx.pathname,
|
|
371
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
if (entry) {
|
|
375
|
+
yield* yieldFromStore(
|
|
376
|
+
entry,
|
|
377
|
+
ctx,
|
|
378
|
+
state,
|
|
379
|
+
pipelineStart,
|
|
380
|
+
handleStoreRef,
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Dev-mode static handler interception for non-Node.js runtimes.
|
|
389
|
+
// __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
|
|
390
|
+
// lacks a Node.js module runner (e.g. workerd, Deno workers). In those
|
|
391
|
+
// runtimes, handlers that depend on Node APIs like node:fs can't run
|
|
392
|
+
// in-process. We redirect them to the /__rsc_prerender endpoint which
|
|
393
|
+
// resolves segments in a Node.js temp server, same as prerender routes.
|
|
394
|
+
// In Node.js dev mode this variable is undefined -- handlers run
|
|
395
|
+
// in-process where Node APIs work, so no interception is needed.
|
|
396
|
+
if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
|
|
397
|
+
const hasStatic = ctx.entries.some(
|
|
398
|
+
(e) =>
|
|
399
|
+
(e.type === "layout" ||
|
|
400
|
+
e.type === "route" ||
|
|
401
|
+
e.type === "parallel") &&
|
|
402
|
+
e.isStaticPrerender,
|
|
403
|
+
);
|
|
404
|
+
if (hasStatic) {
|
|
405
|
+
await ensurePrerenderDeps();
|
|
406
|
+
if (prerenderStoreInstance) {
|
|
407
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
408
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
409
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (ctx.isIntercept) {
|
|
413
|
+
const entry = await prerenderStoreInstance.get(
|
|
414
|
+
ctx.matched.routeKey,
|
|
415
|
+
paramHash + "/i",
|
|
416
|
+
{
|
|
417
|
+
pathname: ctx.pathname,
|
|
418
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
419
|
+
},
|
|
420
|
+
);
|
|
421
|
+
if (entry) {
|
|
422
|
+
yield* yieldFromStore(
|
|
423
|
+
entry,
|
|
424
|
+
ctx,
|
|
425
|
+
state,
|
|
426
|
+
pipelineStart,
|
|
427
|
+
handleStoreRef,
|
|
428
|
+
);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
432
|
+
} else {
|
|
433
|
+
const entry = await prerenderStoreInstance.get(
|
|
434
|
+
ctx.matched.routeKey,
|
|
435
|
+
paramHash,
|
|
436
|
+
{
|
|
437
|
+
pathname: ctx.pathname,
|
|
438
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
if (entry) {
|
|
442
|
+
yield* yieldFromStore(
|
|
443
|
+
entry,
|
|
444
|
+
ctx,
|
|
445
|
+
state,
|
|
446
|
+
pipelineStart,
|
|
447
|
+
handleStoreRef,
|
|
448
|
+
);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
130
456
|
// Skip cache during actions
|
|
131
457
|
if (ctx.isAction || !ctx.cacheScope?.enabled) {
|
|
132
458
|
// Cache miss - pass through to segment resolution
|
|
133
459
|
yield* source;
|
|
460
|
+
if (ms) {
|
|
461
|
+
ms.metrics.push({
|
|
462
|
+
label: "pipeline:cache-miss",
|
|
463
|
+
duration: performance.now() - pipelineStart,
|
|
464
|
+
startTime: pipelineStart - ms.requestStart,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
134
467
|
return;
|
|
135
468
|
}
|
|
136
469
|
|
|
@@ -138,27 +471,54 @@ export function withCacheLookup<TEnv>(
|
|
|
138
471
|
const cacheResult = await ctx.cacheScope.lookupRoute(
|
|
139
472
|
ctx.pathname,
|
|
140
473
|
ctx.matched.params,
|
|
141
|
-
ctx.isIntercept
|
|
474
|
+
ctx.isIntercept,
|
|
142
475
|
);
|
|
143
476
|
|
|
144
477
|
if (!cacheResult) {
|
|
145
478
|
// Cache miss - pass through to segment resolution
|
|
146
479
|
yield* source;
|
|
480
|
+
if (ms) {
|
|
481
|
+
ms.metrics.push({
|
|
482
|
+
label: "pipeline:cache-miss",
|
|
483
|
+
duration: performance.now() - pipelineStart,
|
|
484
|
+
startTime: pipelineStart - ms.requestStart,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
147
487
|
return;
|
|
148
488
|
}
|
|
149
489
|
|
|
150
490
|
// Cache HIT
|
|
151
491
|
state.cacheHit = true;
|
|
492
|
+
state.cacheSource = "runtime";
|
|
152
493
|
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
153
494
|
state.cachedSegments = cacheResult.segments;
|
|
154
495
|
state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
|
|
155
496
|
|
|
156
|
-
// Apply revalidation to cached segments
|
|
157
|
-
|
|
497
|
+
// Apply revalidation to cached segments.
|
|
498
|
+
// For full matches or empty client segment sets, this map is unnecessary:
|
|
499
|
+
// we never run segment-level revalidation and can stream segments directly.
|
|
500
|
+
const canCheckSegmentRevalidation =
|
|
501
|
+
!ctx.isFullMatch &&
|
|
502
|
+
ctx.clientSegmentSet.size > 0 &&
|
|
503
|
+
!!buildEntryRevalidateMap;
|
|
504
|
+
const entryRevalidateMap = canCheckSegmentRevalidation
|
|
505
|
+
? buildEntryRevalidateMap(ctx.entries)
|
|
506
|
+
: undefined;
|
|
158
507
|
|
|
159
508
|
for (const segment of cacheResult.segments) {
|
|
160
509
|
// Skip segments client doesn't have - they need their component
|
|
161
510
|
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
511
|
+
if (isTraceActive()) {
|
|
512
|
+
pushRevalidationTraceEntry({
|
|
513
|
+
segmentId: segment.id,
|
|
514
|
+
segmentType: segment.type,
|
|
515
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
516
|
+
source: "cache-hit",
|
|
517
|
+
defaultShouldRevalidate: true,
|
|
518
|
+
finalShouldRevalidate: true,
|
|
519
|
+
reason: "new-segment",
|
|
520
|
+
});
|
|
521
|
+
}
|
|
162
522
|
yield segment;
|
|
163
523
|
continue;
|
|
164
524
|
}
|
|
@@ -171,8 +531,53 @@ export function withCacheLookup<TEnv>(
|
|
|
171
531
|
|
|
172
532
|
// Look up revalidation rules for this segment
|
|
173
533
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
534
|
+
|
|
535
|
+
// Even without explicit revalidation rules, route segments and their
|
|
536
|
+
// children must re-render when params or search params change — the
|
|
537
|
+
// handler reads ctx.params/ctx.searchParams so different values produce
|
|
538
|
+
// different content. Matches evaluateRevalidation's default logic.
|
|
539
|
+
const searchChanged = ctx.prevUrl.search !== ctx.url.search;
|
|
540
|
+
const routeParamsChanged = !paramsEqual(
|
|
541
|
+
ctx.matched.params,
|
|
542
|
+
ctx.prevParams,
|
|
543
|
+
);
|
|
544
|
+
const shouldDefaultRevalidate =
|
|
545
|
+
(searchChanged || routeParamsChanged) &&
|
|
546
|
+
(segment.type === "route" ||
|
|
547
|
+
(segment.belongsToRoute &&
|
|
548
|
+
(segment.type === "layout" || segment.type === "parallel")));
|
|
549
|
+
|
|
174
550
|
if (!entryInfo || entryInfo.revalidate.length === 0) {
|
|
551
|
+
if (shouldDefaultRevalidate) {
|
|
552
|
+
// Params or search params changed — must re-render even without custom rules
|
|
553
|
+
if (isTraceActive()) {
|
|
554
|
+
pushRevalidationTraceEntry({
|
|
555
|
+
segmentId: segment.id,
|
|
556
|
+
segmentType: segment.type,
|
|
557
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
558
|
+
source: "cache-hit",
|
|
559
|
+
defaultShouldRevalidate: true,
|
|
560
|
+
finalShouldRevalidate: true,
|
|
561
|
+
reason: routeParamsChanged
|
|
562
|
+
? "cached-params-changed"
|
|
563
|
+
: "cached-search-changed",
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
yield segment;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
175
569
|
// No revalidation rules, use default behavior (skip if client has)
|
|
570
|
+
if (isTraceActive()) {
|
|
571
|
+
pushRevalidationTraceEntry({
|
|
572
|
+
segmentId: segment.id,
|
|
573
|
+
segmentType: segment.type,
|
|
574
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
575
|
+
source: "cache-hit",
|
|
576
|
+
defaultShouldRevalidate: false,
|
|
577
|
+
finalShouldRevalidate: false,
|
|
578
|
+
reason: "cached-no-rules",
|
|
579
|
+
});
|
|
580
|
+
}
|
|
176
581
|
segment.component = null;
|
|
177
582
|
segment.loading = undefined;
|
|
178
583
|
yield segment;
|
|
@@ -194,8 +599,24 @@ export function withCacheLookup<TEnv>(
|
|
|
194
599
|
routeKey: ctx.routeKey,
|
|
195
600
|
context: ctx.handlerContext,
|
|
196
601
|
actionContext: ctx.actionContext,
|
|
602
|
+
stale: cacheResult.shouldRevalidate || ctx.stale || undefined,
|
|
603
|
+
traceSource: "cache-hit",
|
|
197
604
|
});
|
|
198
605
|
|
|
606
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
607
|
+
if (routerCtx.telemetry) {
|
|
608
|
+
const tSink = resolveSink(routerCtx.telemetry);
|
|
609
|
+
safeEmit(tSink, {
|
|
610
|
+
type: "revalidation.decision",
|
|
611
|
+
timestamp: performance.now(),
|
|
612
|
+
requestId: routerCtx.requestId,
|
|
613
|
+
segmentId: segment.id,
|
|
614
|
+
pathname: ctx.pathname,
|
|
615
|
+
routeKey: ctx.routeKey,
|
|
616
|
+
shouldRevalidate,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
199
620
|
if (!shouldRevalidate) {
|
|
200
621
|
// Client has it, no revalidation needed
|
|
201
622
|
segment.component = null;
|
|
@@ -205,15 +626,25 @@ export function withCacheLookup<TEnv>(
|
|
|
205
626
|
yield segment;
|
|
206
627
|
}
|
|
207
628
|
|
|
629
|
+
// Set streaming flag (once) and resolve render barrier.
|
|
630
|
+
const barrierReqCtx = _getRequestContext();
|
|
631
|
+
if (barrierReqCtx) {
|
|
632
|
+
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
633
|
+
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
634
|
+
}
|
|
635
|
+
barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
|
|
636
|
+
}
|
|
637
|
+
|
|
208
638
|
// Resolve loaders fresh (loaders are NOT cached by default)
|
|
209
639
|
// This ensures fresh data even on cache hit
|
|
210
640
|
const Store = ctx.Store;
|
|
641
|
+
const loaderStart = performance.now();
|
|
211
642
|
|
|
212
643
|
if (ctx.isFullMatch) {
|
|
213
644
|
// Full match (document request) - simple loader resolution without revalidation
|
|
214
645
|
if (resolveLoadersOnly) {
|
|
215
646
|
const loaderSegments = await Store.run(() =>
|
|
216
|
-
resolveLoadersOnly(ctx.entries, ctx.handlerContext)
|
|
647
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
217
648
|
);
|
|
218
649
|
|
|
219
650
|
// Update state - full match doesn't track matchedIds separately
|
|
@@ -239,8 +670,13 @@ export function withCacheLookup<TEnv>(
|
|
|
239
670
|
ctx.prevUrl,
|
|
240
671
|
ctx.url,
|
|
241
672
|
ctx.routeKey,
|
|
242
|
-
ctx.actionContext
|
|
243
|
-
|
|
673
|
+
ctx.actionContext,
|
|
674
|
+
// Loaders are never cached in the segment cache, so segment
|
|
675
|
+
// staleness (cacheResult.shouldRevalidate) must not propagate.
|
|
676
|
+
// But browser-sent staleness (ctx.stale) — indicating an action
|
|
677
|
+
// happened in this or another tab — must still reach loaders.
|
|
678
|
+
ctx.stale || undefined,
|
|
679
|
+
),
|
|
244
680
|
);
|
|
245
681
|
|
|
246
682
|
// Update state with fresh loader matchedIds
|
|
@@ -257,5 +693,19 @@ export function withCacheLookup<TEnv>(
|
|
|
257
693
|
state.matchedIds = state.cachedMatchedIds!;
|
|
258
694
|
}
|
|
259
695
|
}
|
|
696
|
+
if (ms) {
|
|
697
|
+
const loaderEnd = performance.now();
|
|
698
|
+
ms.metrics.push({
|
|
699
|
+
label: "pipeline:loader-resolve",
|
|
700
|
+
duration: loaderEnd - loaderStart,
|
|
701
|
+
startTime: loaderStart - ms.requestStart,
|
|
702
|
+
depth: 1,
|
|
703
|
+
});
|
|
704
|
+
ms.metrics.push({
|
|
705
|
+
label: "pipeline:cache-hit",
|
|
706
|
+
duration: loaderEnd - pipelineStart,
|
|
707
|
+
startTime: pipelineStart - ms.requestStart,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
260
710
|
};
|
|
261
711
|
}
|