@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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 +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -67,10 +67,11 @@
|
|
|
67
67
|
* Keep if:
|
|
68
68
|
* - component !== null (needs rendering)
|
|
69
69
|
* - type === "loader" (carries data even with null component)
|
|
70
|
+
* - client doesn't have the segment (structurally required parent node)
|
|
70
71
|
*
|
|
71
72
|
* Skip if:
|
|
72
|
-
* - component === null AND type !== "loader"
|
|
73
|
-
* - (
|
|
73
|
+
* - component === null AND type !== "loader" AND client has it cached
|
|
74
|
+
* - (Revalidation skip — client already has this segment's UI)
|
|
74
75
|
*
|
|
75
76
|
*
|
|
76
77
|
* INTERCEPT HANDLING
|
|
@@ -109,10 +110,8 @@
|
|
|
109
110
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
110
111
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
111
112
|
import { debugLog } from "./logging.js";
|
|
113
|
+
import { appendMetric } from "./metrics.js";
|
|
112
114
|
|
|
113
|
-
/**
|
|
114
|
-
* Collect all segments from an async generator
|
|
115
|
-
*/
|
|
116
115
|
export async function collectSegments(
|
|
117
116
|
generator: AsyncGenerator<ResolvedSegment>,
|
|
118
117
|
): Promise<ResolvedSegment[]> {
|
|
@@ -124,8 +123,73 @@ export async function collectSegments(
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
/**
|
|
127
|
-
*
|
|
126
|
+
* Deduplicate inherited loader segments by loaderId.
|
|
127
|
+
*
|
|
128
|
+
* When a route has loaders and a child layout has parallel slots, the same
|
|
129
|
+
* loader is resolved twice: once for the route and once inherited into the
|
|
130
|
+
* layout (tagged with `_inherited`). The inherited copy is only needed when
|
|
131
|
+
* the route uses `loading()` — in that case, the loader data is inside a
|
|
132
|
+
* LoaderBoundary/Suspense that parallel slots can't reach through. Without
|
|
133
|
+
* loading(), useLoader() traverses parent contexts and finds the data.
|
|
128
134
|
*/
|
|
135
|
+
function deduplicateLoaderSegments(
|
|
136
|
+
segments: ResolvedSegment[],
|
|
137
|
+
logPrefix: string,
|
|
138
|
+
): { segments: ResolvedSegment[]; removedIds: Set<string> } {
|
|
139
|
+
// Single pass: original (non-inherited) loaderIds, all loaderIds grouped by
|
|
140
|
+
// namespace, and namespaces of segments that declare loading().
|
|
141
|
+
const originalLoaders = new Set<string>();
|
|
142
|
+
const loaderIdsByNamespace = new Map<string, string[]>();
|
|
143
|
+
const namespacesWithLoading = new Set<string>();
|
|
144
|
+
for (const s of segments) {
|
|
145
|
+
if (s.type === "loader" && s.loaderId) {
|
|
146
|
+
if (!s._inherited) originalLoaders.add(s.loaderId);
|
|
147
|
+
const ids = loaderIdsByNamespace.get(s.namespace);
|
|
148
|
+
if (ids) ids.push(s.loaderId);
|
|
149
|
+
else loaderIdsByNamespace.set(s.namespace, [s.loaderId]);
|
|
150
|
+
} else if (
|
|
151
|
+
s.type !== "loader" &&
|
|
152
|
+
s.loading !== undefined &&
|
|
153
|
+
s.loading !== false
|
|
154
|
+
) {
|
|
155
|
+
namespacesWithLoading.add(s.namespace);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const loadersWithLoading = new Set<string>();
|
|
160
|
+
for (const ns of namespacesWithLoading) {
|
|
161
|
+
for (const id of loaderIdsByNamespace.get(ns) ?? []) {
|
|
162
|
+
loadersWithLoading.add(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result: ResolvedSegment[] = [];
|
|
167
|
+
const removedIds = new Set<string>();
|
|
168
|
+
|
|
169
|
+
for (const s of segments) {
|
|
170
|
+
if (
|
|
171
|
+
s.type === "loader" &&
|
|
172
|
+
s.loaderId &&
|
|
173
|
+
s._inherited &&
|
|
174
|
+
originalLoaders.has(s.loaderId) &&
|
|
175
|
+
!loadersWithLoading.has(s.loaderId)
|
|
176
|
+
) {
|
|
177
|
+
removedIds.add(s.id);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
result.push(s);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (removedIds.size > 0) {
|
|
184
|
+
debugLog(
|
|
185
|
+
logPrefix,
|
|
186
|
+
`deduped ${removedIds.size} inherited loader segment(s)`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { segments: result, removedIds };
|
|
191
|
+
}
|
|
192
|
+
|
|
129
193
|
export function buildMatchResult<TEnv>(
|
|
130
194
|
allSegments: ResolvedSegment[],
|
|
131
195
|
ctx: MatchContext<TEnv>,
|
|
@@ -139,11 +203,6 @@ export function buildMatchResult<TEnv>(
|
|
|
139
203
|
let segmentsToRender: ResolvedSegment[];
|
|
140
204
|
|
|
141
205
|
if (ctx.isFullMatch) {
|
|
142
|
-
// Full match (document request) - all segments are rendered
|
|
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
206
|
const seen = new Set<string>();
|
|
148
207
|
segmentsToRender = [];
|
|
149
208
|
for (const s of allSegments) {
|
|
@@ -154,41 +213,44 @@ export function buildMatchResult<TEnv>(
|
|
|
154
213
|
}
|
|
155
214
|
allIds = segmentsToRender.map((s) => s.id);
|
|
156
215
|
} else {
|
|
157
|
-
// Partial match (navigation) - filter and handle intercepts
|
|
158
|
-
// When intercepting, tell browser to keep its current segments + add modal
|
|
159
|
-
// This prevents the browser from discarding the current page content
|
|
160
|
-
// If client sent empty segments (HMR recovery), use segment IDs from allSegments
|
|
161
216
|
allIds = ctx.interceptResult
|
|
162
217
|
? ctx.clientSegmentIds.length > 0
|
|
163
218
|
? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
|
|
164
|
-
: allSegments.map((s) => s.id)
|
|
219
|
+
: allSegments.map((s) => s.id)
|
|
165
220
|
: [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
|
|
166
221
|
|
|
167
|
-
// Deduplicate allIds (defense-in-depth for partial match path)
|
|
168
222
|
allIds = [...new Set(allIds)];
|
|
169
223
|
|
|
170
|
-
|
|
171
|
-
// BUT always include loader segments - they carry data even with null component
|
|
224
|
+
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
172
225
|
segmentsToRender = allSegments.filter(
|
|
173
|
-
(s) =>
|
|
226
|
+
(s) =>
|
|
227
|
+
s.component !== null || s.type === "loader" || !clientIdSet.has(s.id),
|
|
174
228
|
);
|
|
175
229
|
}
|
|
176
230
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
231
|
+
const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
|
|
232
|
+
segmentsToRender,
|
|
233
|
+
logPrefix,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const matchedIds =
|
|
237
|
+
removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
|
|
238
|
+
|
|
239
|
+
const resolvedIds = ctx.isFullMatch
|
|
240
|
+
? allSegments.map((s) => s.id)
|
|
241
|
+
: allSegments.filter((s) => s._handlerRan).map((s) => s.id);
|
|
242
|
+
|
|
243
|
+
const cleanedSegments = dedupedSegments.map((s) => {
|
|
244
|
+
if (s._handlerRan === undefined) return s;
|
|
245
|
+
const { _handlerRan: _drop, ...rest } = s;
|
|
246
|
+
return rest as ResolvedSegment;
|
|
186
247
|
});
|
|
187
248
|
|
|
188
249
|
return {
|
|
189
|
-
segments:
|
|
190
|
-
matched:
|
|
191
|
-
diff:
|
|
250
|
+
segments: cleanedSegments,
|
|
251
|
+
matched: matchedIds,
|
|
252
|
+
diff: cleanedSegments.map((s) => s.id),
|
|
253
|
+
resolvedIds,
|
|
192
254
|
params: ctx.matched.params,
|
|
193
255
|
routeName: ctx.routeKey,
|
|
194
256
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
|
@@ -197,12 +259,6 @@ export function buildMatchResult<TEnv>(
|
|
|
197
259
|
};
|
|
198
260
|
}
|
|
199
261
|
|
|
200
|
-
/**
|
|
201
|
-
* Collect segments from pipeline and build MatchResult
|
|
202
|
-
*
|
|
203
|
-
* This is the main entry point for building the final result after
|
|
204
|
-
* the pipeline has processed all segments.
|
|
205
|
-
*/
|
|
206
262
|
export async function collectMatchResult<TEnv>(
|
|
207
263
|
pipeline: AsyncGenerator<ResolvedSegment>,
|
|
208
264
|
ctx: MatchContext<TEnv>,
|
|
@@ -210,10 +266,18 @@ export async function collectMatchResult<TEnv>(
|
|
|
210
266
|
): Promise<MatchResult> {
|
|
211
267
|
const allSegments = await collectSegments(pipeline);
|
|
212
268
|
|
|
213
|
-
|
|
269
|
+
const buildStart = performance.now();
|
|
270
|
+
|
|
214
271
|
if (state.segments.length === 0) {
|
|
215
272
|
state.segments = allSegments;
|
|
216
273
|
}
|
|
217
274
|
|
|
218
|
-
|
|
275
|
+
const result = buildMatchResult(allSegments, ctx, state);
|
|
276
|
+
appendMetric(
|
|
277
|
+
ctx.metricsStore,
|
|
278
|
+
"collect-result",
|
|
279
|
+
buildStart,
|
|
280
|
+
performance.now() - buildStart,
|
|
281
|
+
);
|
|
282
|
+
return result;
|
|
219
283
|
}
|
package/src/router/metrics.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router Metrics Utilities
|
|
3
|
-
*
|
|
4
|
-
* Performance metrics collection and reporting for RSC Router.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { MetricsStore, PerformanceMetric } from "../server/context";
|
|
8
2
|
|
|
9
3
|
const BASE_INDENT = 2;
|
|
@@ -15,7 +9,11 @@ function formatMs(value: number): string {
|
|
|
15
9
|
}
|
|
16
10
|
|
|
17
11
|
function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
|
|
18
|
-
return [...metrics].sort((a, b) =>
|
|
12
|
+
return [...metrics].sort((a, b) => {
|
|
13
|
+
if (a.label === "handler:total") return 1;
|
|
14
|
+
if (b.label === "handler:total") return -1;
|
|
15
|
+
return a.startTime - b.startTime;
|
|
16
|
+
});
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
interface Span {
|
|
@@ -63,11 +61,6 @@ function createTimelineAxis(total: number): string {
|
|
|
63
61
|
)}${totalLabel}`;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
/**
|
|
67
|
-
* Create a metrics store for the request if debugPerformance is enabled.
|
|
68
|
-
* An optional `requestStart` timestamp can anchor the store to an earlier
|
|
69
|
-
* point (e.g. handler start) so that handler:total has startTime=0.
|
|
70
|
-
*/
|
|
71
64
|
export function createMetricsStore(
|
|
72
65
|
debugPerformance: boolean,
|
|
73
66
|
requestStart?: number,
|
|
@@ -80,9 +73,6 @@ export function createMetricsStore(
|
|
|
80
73
|
};
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
/**
|
|
84
|
-
* Append a metric to the request store using an absolute start timestamp.
|
|
85
|
-
*/
|
|
86
76
|
export function appendMetric(
|
|
87
77
|
metricsStore: MetricsStore | undefined,
|
|
88
78
|
label: string,
|
|
@@ -99,9 +89,6 @@ export function appendMetric(
|
|
|
99
89
|
});
|
|
100
90
|
}
|
|
101
91
|
|
|
102
|
-
/**
|
|
103
|
-
* Log the current request metrics and return the corresponding Server-Timing value.
|
|
104
|
-
*/
|
|
105
92
|
export function buildMetricsTiming(
|
|
106
93
|
method: string,
|
|
107
94
|
pathname: string,
|
|
@@ -112,7 +99,6 @@ export function buildMetricsTiming(
|
|
|
112
99
|
return generateServerTiming(metricsStore) || undefined;
|
|
113
100
|
}
|
|
114
101
|
|
|
115
|
-
/** Display row produced by merging :pre/:post metric pairs. */
|
|
116
102
|
interface DisplayRow {
|
|
117
103
|
label: string;
|
|
118
104
|
startTime: number;
|
|
@@ -121,12 +107,7 @@ interface DisplayRow {
|
|
|
121
107
|
spans: Span[];
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
/**
|
|
125
|
-
* Build display rows from sorted metrics, merging :pre/:post pairs into
|
|
126
|
-
* a single row with disjoint timeline segments.
|
|
127
|
-
*/
|
|
128
110
|
function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
|
|
129
|
-
// Index :pre and :post metrics by their base label
|
|
130
111
|
const preMap = new Map<string, PerformanceMetric>();
|
|
131
112
|
const postMap = new Map<string, PerformanceMetric>();
|
|
132
113
|
const consumed = new Set<PerformanceMetric>();
|
|
@@ -206,11 +187,6 @@ function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
|
|
|
206
187
|
return rows;
|
|
207
188
|
}
|
|
208
189
|
|
|
209
|
-
/**
|
|
210
|
-
* Log metrics to console in a formatted way.
|
|
211
|
-
* Uses a shared-axis timeline so overlapping work stays visible.
|
|
212
|
-
* Merges :pre/:post pairs onto one row with disjoint timeline segments.
|
|
213
|
-
*/
|
|
214
190
|
export function logMetrics(
|
|
215
191
|
method: string,
|
|
216
192
|
pathname: string,
|
|
@@ -262,11 +238,6 @@ export function logMetrics(
|
|
|
262
238
|
}
|
|
263
239
|
}
|
|
264
240
|
|
|
265
|
-
/**
|
|
266
|
-
* Generate Server-Timing header value from metrics
|
|
267
|
-
* Format: metric-name;dur=X.XX
|
|
268
|
-
* Depth is encoded as a "d{N}-" prefix for nested metrics.
|
|
269
|
-
*/
|
|
270
241
|
export function generateServerTiming(metricsStore: MetricsStore): string {
|
|
271
242
|
return metricsStore.metrics
|
|
272
243
|
.map((m) => {
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions and interfaces for the middleware system.
|
|
5
|
-
* Separated from execution logic for cleaner imports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import type { ContextVar } from "../context-var.js";
|
|
9
2
|
import type {
|
|
10
3
|
DefaultReverseRouteMap,
|
|
@@ -14,26 +7,22 @@ import type {
|
|
|
14
7
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
15
8
|
import type { Theme } from "../theme/types.js";
|
|
16
9
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
10
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
17
11
|
|
|
18
|
-
/**
|
|
19
|
-
* Get variable function type
|
|
20
|
-
*/
|
|
21
12
|
type GetVariableFn = {
|
|
22
13
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
23
14
|
<K extends keyof DefaultVars>(key: K): DefaultVars[K];
|
|
24
15
|
};
|
|
25
16
|
|
|
26
|
-
/**
|
|
27
|
-
* Set variable function type
|
|
28
|
-
*/
|
|
29
17
|
type SetVariableFn = {
|
|
30
|
-
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
31
|
-
<K extends keyof DefaultVars>(
|
|
18
|
+
<T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
|
|
19
|
+
<K extends keyof DefaultVars>(
|
|
20
|
+
key: K,
|
|
21
|
+
value: DefaultVars[K],
|
|
22
|
+
options?: { cache?: boolean },
|
|
23
|
+
): void;
|
|
32
24
|
};
|
|
33
25
|
|
|
34
|
-
/**
|
|
35
|
-
* Cookie options for setting cookies
|
|
36
|
-
*/
|
|
37
26
|
export interface CookieOptions {
|
|
38
27
|
domain?: string;
|
|
39
28
|
path?: string;
|
|
@@ -44,178 +33,60 @@ export interface CookieOptions {
|
|
|
44
33
|
sameSite?: "strict" | "lax" | "none";
|
|
45
34
|
}
|
|
46
35
|
|
|
47
|
-
/**
|
|
48
|
-
* Context passed to middleware
|
|
49
|
-
*
|
|
50
|
-
* @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
|
|
51
|
-
* @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
|
|
52
|
-
*/
|
|
53
36
|
export interface MiddlewareContext<
|
|
54
37
|
TEnv = any,
|
|
55
|
-
TParams = Record<string, string>,
|
|
56
|
-
> {
|
|
57
|
-
/** Original request */
|
|
58
|
-
request: Request;
|
|
59
|
-
|
|
60
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
61
|
-
url: URL;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The original request URL with all parameters intact, including
|
|
65
|
-
* internal `_rsc*` transport params.
|
|
66
|
-
*/
|
|
67
|
-
originalUrl: URL;
|
|
68
|
-
|
|
69
|
-
/** URL pathname */
|
|
70
|
-
pathname: string;
|
|
71
|
-
|
|
72
|
-
/** URL search params */
|
|
73
|
-
searchParams: URLSearchParams;
|
|
74
|
-
|
|
75
|
-
/** Platform bindings (Cloudflare, etc.) */
|
|
76
|
-
env: TEnv;
|
|
77
|
-
|
|
78
|
-
/** URL params extracted from route/middleware pattern */
|
|
38
|
+
TParams = Record<string, string | undefined>,
|
|
39
|
+
> extends RequestScope<TEnv> {
|
|
79
40
|
params: TParams;
|
|
80
41
|
|
|
81
|
-
/**
|
|
82
|
-
* Response headers.
|
|
83
|
-
* Before `next()`, returns headers from the shared response stub.
|
|
84
|
-
* After `next()`, returns headers from the downstream response.
|
|
85
|
-
*/
|
|
86
42
|
readonly headers: Headers;
|
|
87
43
|
|
|
88
|
-
/** Get a context variable (shared with route handlers) */
|
|
89
44
|
get: GetVariableFn;
|
|
90
45
|
|
|
91
|
-
/** Set a context variable (shared with route handlers) */
|
|
92
46
|
set: SetVariableFn;
|
|
93
47
|
|
|
94
|
-
/**
|
|
95
|
-
* Middleware-injected variables.
|
|
96
|
-
* Same shared dictionary as `ctx.get()`/`ctx.set()`.
|
|
97
|
-
*/
|
|
98
|
-
var: DefaultVars;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Set a response header - can be called before or after `next()`.
|
|
102
|
-
*
|
|
103
|
-
* When called before `next()`, headers are queued and merged into the final response.
|
|
104
|
-
* When called after `next()`, headers are set directly on the response.
|
|
105
|
-
*/
|
|
106
48
|
header(name: string, value: string): void;
|
|
107
49
|
|
|
108
|
-
/**
|
|
109
|
-
* The matched route name, if available and the route has an explicit name.
|
|
110
|
-
* Undefined for global middleware (runs before route matching) or unnamed routes.
|
|
111
|
-
*/
|
|
112
50
|
routeName?: DefaultRouteName;
|
|
113
51
|
|
|
114
|
-
/**
|
|
115
|
-
* Enable performance metrics for this request.
|
|
116
|
-
* When called, granular timing breakdown is logged to console and
|
|
117
|
-
* included in the Server-Timing response header, regardless of the
|
|
118
|
-
* router-level `debugPerformance` option.
|
|
119
|
-
*
|
|
120
|
-
* Call **before** `await next()` so the metrics store exists when
|
|
121
|
-
* downstream phases (route matching, rendering, SSR) record their
|
|
122
|
-
* spans. Calling after `next()` returns still emits `handler:total`
|
|
123
|
-
* but misses all upstream metrics.
|
|
124
|
-
*/
|
|
125
52
|
debugPerformance(): void;
|
|
126
53
|
|
|
127
|
-
/**
|
|
128
|
-
* Current theme (from cookie or default).
|
|
129
|
-
* Only available when theme is enabled in router config.
|
|
130
|
-
*/
|
|
131
54
|
theme?: Theme;
|
|
132
55
|
|
|
133
|
-
/**
|
|
134
|
-
* Set the theme (only available when theme is enabled in router config).
|
|
135
|
-
* Sets a cookie with the new theme value.
|
|
136
|
-
*/
|
|
137
56
|
setTheme?: (theme: Theme) => void;
|
|
138
57
|
|
|
139
|
-
/**
|
|
140
|
-
* Attach location state entries to this response.
|
|
141
|
-
* State is delivered to the client via history.pushState and accessible
|
|
142
|
-
* through the useLocationState() hook.
|
|
143
|
-
*/
|
|
144
58
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
145
59
|
|
|
146
|
-
/**
|
|
147
|
-
* Generate URLs from route names.
|
|
148
|
-
* - `name` — global route, from the named-routes definition
|
|
149
|
-
*/
|
|
150
60
|
reverse: ScopedReverseFunction<
|
|
151
61
|
Record<string, string>,
|
|
152
62
|
DefaultReverseRouteMap
|
|
153
63
|
>;
|
|
154
64
|
}
|
|
155
65
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
* @template TParams - URL params type (typed for route middleware)
|
|
161
|
-
*
|
|
162
|
-
* When using middleware with global augmentation (RSCRouter.Env), explicitly
|
|
163
|
-
* annotate your middleware functions, or the types will be inferred from context:
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* ```typescript
|
|
167
|
-
* // With explicit annotation (recommended for reusable middleware)
|
|
168
|
-
* const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
|
|
169
|
-
*
|
|
170
|
-
* // Types inferred from router.use() call
|
|
171
|
-
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
|
|
66
|
+
export type MiddlewareFn<
|
|
67
|
+
TEnv = any,
|
|
68
|
+
TParams = Record<string, string | undefined>,
|
|
69
|
+
> = (
|
|
175
70
|
ctx: MiddlewareContext<TEnv, TParams>,
|
|
176
71
|
next: () => Promise<Response>,
|
|
177
72
|
) => Response | void | Promise<Response | void>;
|
|
178
73
|
|
|
179
|
-
/**
|
|
180
|
-
* Stored middleware entry with pattern matching info
|
|
181
|
-
* @internal - uses any for internal flexibility
|
|
182
|
-
*/
|
|
183
74
|
export interface MiddlewareEntry<TEnv = any> {
|
|
184
|
-
/** Original pattern string */
|
|
185
75
|
pattern: string | null;
|
|
186
|
-
|
|
187
|
-
/** Compiled regex for matching */
|
|
188
76
|
regex: RegExp | null;
|
|
189
|
-
|
|
190
|
-
/** Param names extracted from pattern */
|
|
191
77
|
paramNames: string[];
|
|
192
|
-
|
|
193
|
-
/** The middleware function */
|
|
194
78
|
handler: MiddlewareFn<TEnv>;
|
|
195
|
-
|
|
196
|
-
/** Mount prefix this middleware is scoped to (null = global) */
|
|
197
|
-
mountPrefix: string | null;
|
|
198
79
|
}
|
|
199
80
|
|
|
200
|
-
/**
|
|
201
|
-
* Mutable response holder - tracks the current response through the middleware chain.
|
|
202
|
-
*/
|
|
203
81
|
export interface ResponseHolder {
|
|
204
82
|
response: Response | null;
|
|
205
83
|
}
|
|
206
84
|
|
|
207
|
-
/**
|
|
208
|
-
* Entry type for middleware collection
|
|
209
|
-
* Matches the shape of EntryData used in router.ts
|
|
210
|
-
*/
|
|
211
85
|
export interface MiddlewareCollectableEntry {
|
|
212
86
|
middleware?: MiddlewareFn<any, any>[];
|
|
213
87
|
layout?: MiddlewareCollectableEntry[];
|
|
214
88
|
}
|
|
215
89
|
|
|
216
|
-
/**
|
|
217
|
-
* Collected route middleware with params
|
|
218
|
-
*/
|
|
219
90
|
export interface CollectedMiddleware {
|
|
220
91
|
handler: MiddlewareFn<any, any>;
|
|
221
92
|
params: Record<string, string>;
|