@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430
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 +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -867
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- 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/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -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/event-controller.ts +87 -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 +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -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 +32 -79
- 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 +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -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 +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +109 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -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 +411 -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 +469 -0
- package/src/build/route-types/scan-filter.ts +78 -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 +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -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 +153 -19
- package/src/index.ts +211 -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 +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +211 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -35
- package/src/router/match-api.ts +620 -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 +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -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 +289 -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 +77 -3
- package/src/router.ts +692 -4257
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -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 +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -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 +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1133
- package/src/vite/plugin-types.ts +131 -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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -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 +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -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 +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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 +189 -0
- package/src/vite/utils/shared-utils.ts +169 -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
|
@@ -105,6 +105,7 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
107
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
|
+
import { debugLog } from "../logging.js";
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
111
|
* Creates intercept resolution middleware
|
|
@@ -117,11 +118,14 @@ import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
|
117
118
|
*/
|
|
118
119
|
export function withInterceptResolution<TEnv>(
|
|
119
120
|
ctx: MatchContext<TEnv>,
|
|
120
|
-
state: MatchPipelineState
|
|
121
|
+
state: MatchPipelineState,
|
|
121
122
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
122
123
|
return async function* (
|
|
123
|
-
source: AsyncGenerator<ResolvedSegment
|
|
124
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
124
125
|
): AsyncGenerator<ResolvedSegment> {
|
|
126
|
+
const pipelineStart = performance.now();
|
|
127
|
+
const ms = ctx.metricsStore;
|
|
128
|
+
|
|
125
129
|
// First, yield all segments from the source (main segment resolution or cache)
|
|
126
130
|
const segments: ResolvedSegment[] = [];
|
|
127
131
|
for await (const segment of source) {
|
|
@@ -131,6 +135,13 @@ export function withInterceptResolution<TEnv>(
|
|
|
131
135
|
|
|
132
136
|
// Skip intercept resolution for full match (document requests don't have intercepts)
|
|
133
137
|
if (ctx.isFullMatch) {
|
|
138
|
+
if (ms) {
|
|
139
|
+
ms.metrics.push({
|
|
140
|
+
label: "pipeline:intercept",
|
|
141
|
+
duration: performance.now() - pipelineStart,
|
|
142
|
+
startTime: pipelineStart - ms.requestStart,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
134
145
|
return;
|
|
135
146
|
}
|
|
136
147
|
|
|
@@ -149,6 +160,13 @@ export function withInterceptResolution<TEnv>(
|
|
|
149
160
|
if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
|
|
150
161
|
await handleCacheHitIntercept(ctx, state, segments);
|
|
151
162
|
}
|
|
163
|
+
if (ms) {
|
|
164
|
+
ms.metrics.push({
|
|
165
|
+
label: "pipeline:intercept",
|
|
166
|
+
duration: performance.now() - pipelineStart,
|
|
167
|
+
startTime: pipelineStart - ms.requestStart,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
152
170
|
return;
|
|
153
171
|
}
|
|
154
172
|
|
|
@@ -156,9 +174,10 @@ export function withInterceptResolution<TEnv>(
|
|
|
156
174
|
const { resolveInterceptEntry } = getRouterContext<TEnv>();
|
|
157
175
|
|
|
158
176
|
const slotName = ctx.interceptResult!.intercept.slotName;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
debugLog("matchPartial.intercept", "intercept resolved", {
|
|
178
|
+
routeName: ctx.localRouteName,
|
|
179
|
+
slotName,
|
|
180
|
+
});
|
|
162
181
|
|
|
163
182
|
// Resolve intercept entry (middleware, loaders, handler)
|
|
164
183
|
const Store = ctx.Store;
|
|
@@ -178,8 +197,8 @@ export function withInterceptResolution<TEnv>(
|
|
|
178
197
|
routeKey: ctx.routeKey,
|
|
179
198
|
actionContext: ctx.actionContext,
|
|
180
199
|
stale: ctx.stale,
|
|
181
|
-
}
|
|
182
|
-
)
|
|
200
|
+
},
|
|
201
|
+
),
|
|
183
202
|
);
|
|
184
203
|
|
|
185
204
|
// Update state
|
|
@@ -193,6 +212,14 @@ export function withInterceptResolution<TEnv>(
|
|
|
193
212
|
for (const segment of interceptSegments) {
|
|
194
213
|
yield segment;
|
|
195
214
|
}
|
|
215
|
+
|
|
216
|
+
if (ms) {
|
|
217
|
+
ms.metrics.push({
|
|
218
|
+
label: "pipeline:intercept",
|
|
219
|
+
duration: performance.now() - pipelineStart,
|
|
220
|
+
startTime: pipelineStart - ms.requestStart,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
196
223
|
};
|
|
197
224
|
}
|
|
198
225
|
|
|
@@ -204,7 +231,7 @@ export function withInterceptResolution<TEnv>(
|
|
|
204
231
|
async function handleCacheHitIntercept<TEnv>(
|
|
205
232
|
ctx: MatchContext<TEnv>,
|
|
206
233
|
state: MatchPipelineState,
|
|
207
|
-
segments: ResolvedSegment[]
|
|
234
|
+
segments: ResolvedSegment[],
|
|
208
235
|
): Promise<void> {
|
|
209
236
|
if (!ctx.interceptResult) return;
|
|
210
237
|
|
|
@@ -214,7 +241,7 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
214
241
|
|
|
215
242
|
// Find intercept segments from cached segments (namespace starts with "intercept:")
|
|
216
243
|
const interceptSegments = segments.filter((s) =>
|
|
217
|
-
s.namespace?.startsWith("intercept:")
|
|
244
|
+
s.namespace?.startsWith("intercept:"),
|
|
218
245
|
);
|
|
219
246
|
state.interceptSegments = interceptSegments;
|
|
220
247
|
|
|
@@ -238,25 +265,36 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
238
265
|
routeKey: ctx.routeKey,
|
|
239
266
|
actionContext: ctx.actionContext,
|
|
240
267
|
stale: ctx.stale,
|
|
241
|
-
}
|
|
242
|
-
)
|
|
268
|
+
},
|
|
269
|
+
),
|
|
243
270
|
);
|
|
244
271
|
|
|
245
272
|
// Update intercept segment's loaderDataPromise with fresh data
|
|
246
273
|
if (freshLoaderResult) {
|
|
247
274
|
const interceptMainSegment = interceptSegments.find(
|
|
248
|
-
(s) => s.type === "parallel" && s.slot
|
|
275
|
+
(s) => s.type === "parallel" && s.slot,
|
|
249
276
|
);
|
|
250
277
|
if (interceptMainSegment) {
|
|
251
|
-
interceptMainSegment.loaderDataPromise =
|
|
278
|
+
interceptMainSegment.loaderDataPromise =
|
|
279
|
+
freshLoaderResult.loaderDataPromise;
|
|
252
280
|
interceptMainSegment.loaderIds = freshLoaderResult.loaderIds;
|
|
253
|
-
|
|
254
|
-
|
|
281
|
+
debugLog(
|
|
282
|
+
"matchPartial.intercept",
|
|
283
|
+
"cache hit with fresh intercept loaders",
|
|
284
|
+
{
|
|
285
|
+
routeName: ctx.localRouteName,
|
|
286
|
+
slotName,
|
|
287
|
+
},
|
|
255
288
|
);
|
|
256
289
|
}
|
|
257
290
|
} else {
|
|
258
|
-
|
|
259
|
-
|
|
291
|
+
debugLog(
|
|
292
|
+
"matchPartial.intercept",
|
|
293
|
+
"cache hit without intercept loader revalidation",
|
|
294
|
+
{
|
|
295
|
+
routeName: ctx.localRouteName,
|
|
296
|
+
slotName,
|
|
297
|
+
},
|
|
260
298
|
);
|
|
261
299
|
}
|
|
262
300
|
}
|
|
@@ -99,11 +99,14 @@ import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
|
99
99
|
*/
|
|
100
100
|
export function withSegmentResolution<TEnv>(
|
|
101
101
|
ctx: MatchContext<TEnv>,
|
|
102
|
-
state: MatchPipelineState
|
|
102
|
+
state: MatchPipelineState,
|
|
103
103
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
104
104
|
return async function* (
|
|
105
|
-
source: AsyncGenerator<ResolvedSegment
|
|
105
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
106
106
|
): AsyncGenerator<ResolvedSegment> {
|
|
107
|
+
const pipelineStart = performance.now();
|
|
108
|
+
const ms = ctx.metricsStore;
|
|
109
|
+
|
|
107
110
|
// IMPORTANT: Always iterate source first to give cache-lookup a chance
|
|
108
111
|
// to run and set state.cacheHit. Without this, cache-lookup never executes!
|
|
109
112
|
for await (const segment of source) {
|
|
@@ -112,6 +115,13 @@ export function withSegmentResolution<TEnv>(
|
|
|
112
115
|
|
|
113
116
|
// If cache hit, segments were already yielded by cache lookup
|
|
114
117
|
if (state.cacheHit) {
|
|
118
|
+
if (ms) {
|
|
119
|
+
ms.metrics.push({
|
|
120
|
+
label: "pipeline:segment-resolve",
|
|
121
|
+
duration: performance.now() - pipelineStart,
|
|
122
|
+
startTime: pipelineStart - ms.requestStart,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
115
125
|
return;
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -128,8 +138,8 @@ export function withSegmentResolution<TEnv>(
|
|
|
128
138
|
ctx.routeKey,
|
|
129
139
|
ctx.matched.params,
|
|
130
140
|
ctx.handlerContext,
|
|
131
|
-
ctx.loaderPromises
|
|
132
|
-
)
|
|
141
|
+
ctx.loaderPromises,
|
|
142
|
+
),
|
|
133
143
|
);
|
|
134
144
|
|
|
135
145
|
// Update state with resolved segments
|
|
@@ -157,8 +167,8 @@ export function withSegmentResolution<TEnv>(
|
|
|
157
167
|
ctx.actionContext,
|
|
158
168
|
ctx.interceptResult,
|
|
159
169
|
ctx.localRouteName,
|
|
160
|
-
ctx.pathname
|
|
161
|
-
)
|
|
170
|
+
ctx.pathname,
|
|
171
|
+
),
|
|
162
172
|
);
|
|
163
173
|
|
|
164
174
|
// Update state with resolved segments
|
|
@@ -170,5 +180,13 @@ export function withSegmentResolution<TEnv>(
|
|
|
170
180
|
yield segment;
|
|
171
181
|
}
|
|
172
182
|
}
|
|
183
|
+
|
|
184
|
+
if (ms) {
|
|
185
|
+
ms.metrics.push({
|
|
186
|
+
label: "pipeline:segment-resolve",
|
|
187
|
+
duration: performance.now() - pipelineStart,
|
|
188
|
+
startTime: pipelineStart - ms.requestStart,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
173
191
|
};
|
|
174
192
|
}
|
|
@@ -86,19 +86,14 @@
|
|
|
86
86
|
* -> output: cached segments + fresh loader data
|
|
87
87
|
*
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* 2. createMatchPartialPipeline (Partial Match)
|
|
98
|
-
* - Used for client-side navigation
|
|
99
|
-
* - Includes revalidation for SWR
|
|
100
|
-
* - Compares with previous params/URL
|
|
101
|
-
* - Supports intercepts (soft navigation modals)
|
|
89
|
+
* PIPELINE VARIANT
|
|
90
|
+
* ================
|
|
91
|
+
*
|
|
92
|
+
* createMatchPartialPipeline handles both full (document) and partial
|
|
93
|
+
* (navigation) requests. The middleware steps adapt based on ctx.isFullMatch:
|
|
94
|
+
* - cache-lookup/store work for both
|
|
95
|
+
* - background-revalidation is a no-op for full matches (no stale state)
|
|
96
|
+
* - intercept-resolution is a no-op for full matches (no previous navigation)
|
|
102
97
|
*/
|
|
103
98
|
import type { ResolvedSegment } from "../types.js";
|
|
104
99
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
@@ -163,7 +158,7 @@ export async function* empty<T>(): AsyncGenerator<T> {
|
|
|
163
158
|
*/
|
|
164
159
|
export function createMatchPartialPipeline<TEnv>(
|
|
165
160
|
ctx: MatchContext<TEnv>,
|
|
166
|
-
state: MatchPipelineState
|
|
161
|
+
state: MatchPipelineState,
|
|
167
162
|
): AsyncGenerator<ResolvedSegment> {
|
|
168
163
|
// Build the middleware chain
|
|
169
164
|
const pipeline = compose<ResolvedSegment>(
|
|
@@ -176,39 +171,9 @@ export function createMatchPartialPipeline<TEnv>(
|
|
|
176
171
|
// Resolves segments on cache miss
|
|
177
172
|
withSegmentResolution(ctx, state),
|
|
178
173
|
// Innermost - checks cache first
|
|
179
|
-
withCacheLookup(ctx, state)
|
|
174
|
+
withCacheLookup(ctx, state),
|
|
180
175
|
);
|
|
181
176
|
|
|
182
177
|
// Start with empty source - cache lookup or segment resolution will produce segments
|
|
183
178
|
return pipeline(empty());
|
|
184
179
|
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Create the full match pipeline (simpler, no revalidation)
|
|
188
|
-
*
|
|
189
|
-
* Used for document requests (initial page load) where we don't need
|
|
190
|
-
* revalidation logic since there's no previous state to compare against.
|
|
191
|
-
*/
|
|
192
|
-
export function createMatchPipeline<TEnv>(
|
|
193
|
-
ctx: MatchContext<TEnv>,
|
|
194
|
-
state: MatchPipelineState
|
|
195
|
-
): AsyncGenerator<ResolvedSegment> {
|
|
196
|
-
// For full match, we only need:
|
|
197
|
-
// 1. Cache lookup
|
|
198
|
-
// 2. Segment resolution (without revalidation)
|
|
199
|
-
// 3. Intercept resolution
|
|
200
|
-
// 4. Cache store
|
|
201
|
-
|
|
202
|
-
// Note: Full match uses different resolution logic (resolveAllSegments instead of
|
|
203
|
-
// resolveAllSegmentsWithRevalidation). This will be handled by the segment resolution
|
|
204
|
-
// middleware checking ctx.isFullMatch or similar flag.
|
|
205
|
-
|
|
206
|
-
const pipeline = compose<ResolvedSegment>(
|
|
207
|
-
withCacheStore(ctx, state),
|
|
208
|
-
withInterceptResolution(ctx, state),
|
|
209
|
-
withSegmentResolution(ctx, state),
|
|
210
|
-
withCacheLookup(ctx, state)
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return pipeline(empty());
|
|
214
|
-
}
|
|
@@ -108,13 +108,13 @@
|
|
|
108
108
|
*/
|
|
109
109
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
110
110
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
111
|
-
import {
|
|
111
|
+
import { debugLog } from "./logging.js";
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Collect all segments from an async generator
|
|
115
115
|
*/
|
|
116
116
|
export async function collectSegments(
|
|
117
|
-
generator: AsyncGenerator<ResolvedSegment
|
|
117
|
+
generator: AsyncGenerator<ResolvedSegment>,
|
|
118
118
|
): Promise<ResolvedSegment[]> {
|
|
119
119
|
const segments: ResolvedSegment[] = [];
|
|
120
120
|
for await (const segment of generator) {
|
|
@@ -129,17 +129,30 @@ export async function collectSegments(
|
|
|
129
129
|
export function buildMatchResult<TEnv>(
|
|
130
130
|
allSegments: ResolvedSegment[],
|
|
131
131
|
ctx: MatchContext<TEnv>,
|
|
132
|
-
state: MatchPipelineState
|
|
132
|
+
state: MatchPipelineState,
|
|
133
133
|
): MatchResult {
|
|
134
|
-
const logPrefix = ctx.isFullMatch
|
|
134
|
+
const logPrefix = ctx.isFullMatch
|
|
135
|
+
? "[Router.match]"
|
|
136
|
+
: "[Router.matchPartial]";
|
|
135
137
|
|
|
136
138
|
let allIds: string[];
|
|
137
139
|
let segmentsToRender: ResolvedSegment[];
|
|
138
140
|
|
|
139
141
|
if (ctx.isFullMatch) {
|
|
140
142
|
// Full match (document request) - all segments are rendered
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
// Deduplicate by segment ID (defense-in-depth). The primary dedup is in
|
|
144
|
+
// resolveAllSegments, but this guards against any path that bypasses it.
|
|
145
|
+
// include() scopes can produce entries that resolve the same shared layout,
|
|
146
|
+
// and duplicate IDs change the client's React tree depth causing remounts.
|
|
147
|
+
const seen = new Set<string>();
|
|
148
|
+
segmentsToRender = [];
|
|
149
|
+
for (const s of allSegments) {
|
|
150
|
+
if (!seen.has(s.id)) {
|
|
151
|
+
seen.add(s.id);
|
|
152
|
+
segmentsToRender.push(s);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
allIds = segmentsToRender.map((s) => s.id);
|
|
143
156
|
} else {
|
|
144
157
|
// Partial match (navigation) - filter and handle intercepts
|
|
145
158
|
// When intercepting, tell browser to keep its current segments + add modal
|
|
@@ -151,32 +164,26 @@ export function buildMatchResult<TEnv>(
|
|
|
151
164
|
: allSegments.map((s) => s.id) // Use actual segments, not matchedIds
|
|
152
165
|
: [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
|
|
153
166
|
|
|
167
|
+
// Deduplicate allIds (defense-in-depth for partial match path)
|
|
168
|
+
allIds = [...new Set(allIds)];
|
|
169
|
+
|
|
154
170
|
// Filter out segments with null components (client already has them)
|
|
155
171
|
// BUT always include loader segments - they carry data even with null component
|
|
156
172
|
segmentsToRender = allSegments.filter(
|
|
157
|
-
(s) => s.component !== null || s.type === "loader"
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (process.env.NODE_ENV === "development") {
|
|
162
|
-
console.log(
|
|
163
|
-
`${logPrefix} All segments:`,
|
|
164
|
-
allSegments
|
|
165
|
-
.map((s) => `${s.id}(${s.type}, component=${s.component !== null})`)
|
|
166
|
-
.join(", ")
|
|
167
|
-
);
|
|
168
|
-
console.log(
|
|
169
|
-
`${logPrefix} Segments to render:`,
|
|
170
|
-
segmentsToRender.map((s) => s.id).join(", ")
|
|
173
|
+
(s) => s.component !== null || s.type === "loader",
|
|
171
174
|
);
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
debugLog(logPrefix, "all segments", {
|
|
178
|
+
segments: allSegments.map((s) => ({
|
|
179
|
+
id: s.id,
|
|
180
|
+
type: s.type,
|
|
181
|
+
hasComponent: s.component !== null,
|
|
182
|
+
})),
|
|
183
|
+
});
|
|
184
|
+
debugLog(logPrefix, "segments to render", {
|
|
185
|
+
segmentIds: segmentsToRender.map((s) => s.id),
|
|
186
|
+
});
|
|
180
187
|
|
|
181
188
|
return {
|
|
182
189
|
segments: segmentsToRender,
|
|
@@ -184,7 +191,6 @@ export function buildMatchResult<TEnv>(
|
|
|
184
191
|
diff: segmentsToRender.map((s) => s.id),
|
|
185
192
|
params: ctx.matched.params,
|
|
186
193
|
routeName: ctx.routeKey,
|
|
187
|
-
serverTiming,
|
|
188
194
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
|
189
195
|
routeMiddleware:
|
|
190
196
|
ctx.routeMiddleware.length > 0 ? ctx.routeMiddleware : undefined,
|
|
@@ -200,7 +206,7 @@ export function buildMatchResult<TEnv>(
|
|
|
200
206
|
export async function collectMatchResult<TEnv>(
|
|
201
207
|
pipeline: AsyncGenerator<ResolvedSegment>,
|
|
202
208
|
ctx: MatchContext<TEnv>,
|
|
203
|
-
state: MatchPipelineState
|
|
209
|
+
state: MatchPipelineState,
|
|
204
210
|
): Promise<MatchResult> {
|
|
205
211
|
const allSegments = await collectSegments(pipeline);
|
|
206
212
|
|