@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +293 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- 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 +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2508 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +24 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-snapshot.ts +368 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +222 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1113 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +173 -35
- package/src/index.ts +241 -73
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +527 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +897 -0
- package/src/rsc/shell-serve.ts +124 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +459 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +848 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +443 -135
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -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 +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -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 +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -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 +76 -98
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +44 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +280 -0
- package/src/urls/pattern-types.ts +160 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -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 +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -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";
|
|
@@ -111,16 +106,6 @@ import {
|
|
|
111
106
|
withSegmentResolution,
|
|
112
107
|
} from "./match-middleware/index.js";
|
|
113
108
|
|
|
114
|
-
/**
|
|
115
|
-
* Compose multiple async generator middleware into a single middleware
|
|
116
|
-
*
|
|
117
|
-
* Middleware are applied in reverse order (rightmost runs first, innermost).
|
|
118
|
-
* For the pipeline:
|
|
119
|
-
* compose(A, B, C)(source)
|
|
120
|
-
*
|
|
121
|
-
* The flow is: source -> C -> B -> A -> output
|
|
122
|
-
* Where C is the innermost (runs first on input) and A is outermost (runs last).
|
|
123
|
-
*/
|
|
124
109
|
export function compose<T>(
|
|
125
110
|
...middleware: GeneratorMiddleware<T>[]
|
|
126
111
|
): GeneratorMiddleware<T> {
|
|
@@ -131,83 +116,22 @@ export function compose<T>(
|
|
|
131
116
|
return middleware[0];
|
|
132
117
|
}
|
|
133
118
|
return (source) => {
|
|
134
|
-
// Apply middleware in reverse order (rightmost first)
|
|
135
119
|
return middleware.reduceRight((prev, fn) => fn(prev), source);
|
|
136
120
|
};
|
|
137
121
|
}
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
* Create an empty async generator (source for pipeline)
|
|
141
|
-
*/
|
|
142
|
-
export async function* empty<T>(): AsyncGenerator<T> {
|
|
143
|
-
// Yields nothing - used as the initial source for the pipeline
|
|
144
|
-
}
|
|
123
|
+
export async function* empty<T>(): AsyncGenerator<T> {}
|
|
145
124
|
|
|
146
|
-
/**
|
|
147
|
-
* Create the match partial pipeline
|
|
148
|
-
*
|
|
149
|
-
* Pipeline order (innermost to outermost):
|
|
150
|
-
* 1. cache-lookup - Check cache first, yield cached segments if hit
|
|
151
|
-
* 2. segment-resolution - Resolve segments if cache miss
|
|
152
|
-
* 3. intercept-resolution - Resolve intercept segments
|
|
153
|
-
* 4. cache-store - Store segments in cache
|
|
154
|
-
* 5. background-revalidation - Trigger SWR if cache was stale
|
|
155
|
-
*
|
|
156
|
-
* Data flow:
|
|
157
|
-
* - empty() produces no segments
|
|
158
|
-
* - cache-lookup either yields cached segments OR passes through to segment-resolution
|
|
159
|
-
* - segment-resolution resolves fresh segments on cache miss
|
|
160
|
-
* - intercept-resolution adds intercept segments
|
|
161
|
-
* - cache-store observes and caches segments
|
|
162
|
-
* - background-revalidation triggers SWR revalidation if needed
|
|
163
|
-
*/
|
|
164
125
|
export function createMatchPartialPipeline<TEnv>(
|
|
165
126
|
ctx: MatchContext<TEnv>,
|
|
166
|
-
state: MatchPipelineState
|
|
127
|
+
state: MatchPipelineState,
|
|
167
128
|
): AsyncGenerator<ResolvedSegment> {
|
|
168
|
-
// Build the middleware chain
|
|
169
129
|
const pipeline = compose<ResolvedSegment>(
|
|
170
|
-
// Outermost - observes segments and triggers background revalidation
|
|
171
130
|
withBackgroundRevalidation(ctx, state),
|
|
172
|
-
// Observes and stores segments in cache
|
|
173
|
-
withCacheStore(ctx, state),
|
|
174
|
-
// Adds intercept segments after main segments
|
|
175
|
-
withInterceptResolution(ctx, state),
|
|
176
|
-
// Resolves segments on cache miss
|
|
177
|
-
withSegmentResolution(ctx, state),
|
|
178
|
-
// Innermost - checks cache first
|
|
179
|
-
withCacheLookup(ctx, state)
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// Start with empty source - cache lookup or segment resolution will produce segments
|
|
183
|
-
return pipeline(empty());
|
|
184
|
-
}
|
|
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
131
|
withCacheStore(ctx, state),
|
|
208
132
|
withInterceptResolution(ctx, state),
|
|
209
133
|
withSegmentResolution(ctx, state),
|
|
210
|
-
withCacheLookup(ctx, state)
|
|
134
|
+
withCacheLookup(ctx, state),
|
|
211
135
|
);
|
|
212
136
|
|
|
213
137
|
return pipeline(empty());
|
|
@@ -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
|
|
@@ -108,14 +109,11 @@
|
|
|
108
109
|
*/
|
|
109
110
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
110
111
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
111
|
-
import { generateServerTiming, logMetrics } from "./metrics.js";
|
|
112
112
|
import { debugLog } from "./logging.js";
|
|
113
|
+
import { appendMetric } from "./metrics.js";
|
|
113
114
|
|
|
114
|
-
/**
|
|
115
|
-
* Collect all segments from an async generator
|
|
116
|
-
*/
|
|
117
115
|
export async function collectSegments(
|
|
118
|
-
generator: AsyncGenerator<ResolvedSegment
|
|
116
|
+
generator: AsyncGenerator<ResolvedSegment>,
|
|
119
117
|
): Promise<ResolvedSegment[]> {
|
|
120
118
|
const segments: ResolvedSegment[] = [];
|
|
121
119
|
for await (const segment of generator) {
|
|
@@ -125,88 +123,161 @@ export async function collectSegments(
|
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
/**
|
|
128
|
-
*
|
|
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.
|
|
129
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
|
+
|
|
130
193
|
export function buildMatchResult<TEnv>(
|
|
131
194
|
allSegments: ResolvedSegment[],
|
|
132
195
|
ctx: MatchContext<TEnv>,
|
|
133
|
-
state: MatchPipelineState
|
|
196
|
+
state: MatchPipelineState,
|
|
134
197
|
): MatchResult {
|
|
135
|
-
const logPrefix = ctx.isFullMatch
|
|
198
|
+
const logPrefix = ctx.isFullMatch
|
|
199
|
+
? "[Router.match]"
|
|
200
|
+
: "[Router.matchPartial]";
|
|
136
201
|
|
|
137
202
|
let allIds: string[];
|
|
138
203
|
let segmentsToRender: ResolvedSegment[];
|
|
139
204
|
|
|
140
205
|
if (ctx.isFullMatch) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
206
|
+
const seen = new Set<string>();
|
|
207
|
+
segmentsToRender = [];
|
|
208
|
+
for (const s of allSegments) {
|
|
209
|
+
if (!seen.has(s.id)) {
|
|
210
|
+
seen.add(s.id);
|
|
211
|
+
segmentsToRender.push(s);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
allIds = segmentsToRender.map((s) => s.id);
|
|
144
215
|
} else {
|
|
145
|
-
// Partial match (navigation) - filter and handle intercepts
|
|
146
|
-
// When intercepting, tell browser to keep its current segments + add modal
|
|
147
|
-
// This prevents the browser from discarding the current page content
|
|
148
|
-
// If client sent empty segments (HMR recovery), use segment IDs from allSegments
|
|
149
216
|
allIds = ctx.interceptResult
|
|
150
217
|
? ctx.clientSegmentIds.length > 0
|
|
151
218
|
? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
|
|
152
|
-
: allSegments.map((s) => s.id)
|
|
219
|
+
: allSegments.map((s) => s.id)
|
|
153
220
|
: [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
|
|
154
221
|
|
|
155
|
-
|
|
156
|
-
|
|
222
|
+
allIds = [...new Set(allIds)];
|
|
223
|
+
|
|
224
|
+
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
157
225
|
segmentsToRender = allSegments.filter(
|
|
158
|
-
(s) =>
|
|
226
|
+
(s) =>
|
|
227
|
+
s.component !== null || s.type === "loader" || !clientIdSet.has(s.id),
|
|
159
228
|
);
|
|
160
229
|
}
|
|
161
230
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
hasComponent: s.component !== null,
|
|
167
|
-
})),
|
|
168
|
-
});
|
|
169
|
-
debugLog(logPrefix, "segments to render", {
|
|
170
|
-
segmentIds: segmentsToRender.map((s) => s.id),
|
|
171
|
-
});
|
|
231
|
+
const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
|
|
232
|
+
segmentsToRender,
|
|
233
|
+
logPrefix,
|
|
234
|
+
);
|
|
172
235
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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;
|
|
247
|
+
});
|
|
179
248
|
|
|
180
249
|
return {
|
|
181
|
-
segments:
|
|
182
|
-
matched:
|
|
183
|
-
diff:
|
|
250
|
+
segments: cleanedSegments,
|
|
251
|
+
matched: matchedIds,
|
|
252
|
+
diff: cleanedSegments.map((s) => s.id),
|
|
253
|
+
resolvedIds,
|
|
184
254
|
params: ctx.matched.params,
|
|
185
255
|
routeName: ctx.routeKey,
|
|
186
|
-
serverTiming,
|
|
187
256
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
|
188
257
|
routeMiddleware:
|
|
189
258
|
ctx.routeMiddleware.length > 0 ? ctx.routeMiddleware : undefined,
|
|
190
259
|
};
|
|
191
260
|
}
|
|
192
261
|
|
|
193
|
-
/**
|
|
194
|
-
* Collect segments from pipeline and build MatchResult
|
|
195
|
-
*
|
|
196
|
-
* This is the main entry point for building the final result after
|
|
197
|
-
* the pipeline has processed all segments.
|
|
198
|
-
*/
|
|
199
262
|
export async function collectMatchResult<TEnv>(
|
|
200
263
|
pipeline: AsyncGenerator<ResolvedSegment>,
|
|
201
264
|
ctx: MatchContext<TEnv>,
|
|
202
|
-
state: MatchPipelineState
|
|
265
|
+
state: MatchPipelineState,
|
|
203
266
|
): Promise<MatchResult> {
|
|
204
267
|
const allSegments = await collectSegments(pipeline);
|
|
205
268
|
|
|
206
|
-
|
|
269
|
+
const buildStart = performance.now();
|
|
270
|
+
|
|
207
271
|
if (state.segments.length === 0) {
|
|
208
272
|
state.segments = allSegments;
|
|
209
273
|
}
|
|
210
274
|
|
|
211
|
-
|
|
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;
|
|
212
283
|
}
|
package/src/router/metrics.ts
CHANGED
|
@@ -1,61 +1,252 @@
|
|
|
1
|
-
|
|
2
|
-
* Router Metrics Utilities
|
|
3
|
-
*
|
|
4
|
-
* Performance metrics collection and reporting for RSC Router.
|
|
5
|
-
*/
|
|
1
|
+
import type { MetricsStore, PerformanceMetric } from "../server/context";
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
const BASE_INDENT = 2;
|
|
4
|
+
const DEPTH_INDENT = 2;
|
|
5
|
+
const TIMELINE_WIDTH = 40;
|
|
6
|
+
|
|
7
|
+
function formatMs(value: number): string {
|
|
8
|
+
return `${value.toFixed(2)}ms`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
|
|
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
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Span {
|
|
20
|
+
startTime: number;
|
|
21
|
+
duration: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderTimeline(spans: Span[], total: number): string {
|
|
25
|
+
if (TIMELINE_WIDTH <= 0) {
|
|
26
|
+
return "||";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cells = Array(TIMELINE_WIDTH).fill(".");
|
|
30
|
+
|
|
31
|
+
if (!(total > 0)) {
|
|
32
|
+
cells[0] = "#";
|
|
33
|
+
return `|${cells.join("")}|`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const span of spans) {
|
|
37
|
+
const start = Math.max(0, span.startTime);
|
|
38
|
+
const end = Math.max(start, span.startTime + span.duration);
|
|
39
|
+
const startColumn = Math.min(
|
|
40
|
+
TIMELINE_WIDTH - 1,
|
|
41
|
+
Math.floor((start / total) * TIMELINE_WIDTH),
|
|
42
|
+
);
|
|
43
|
+
const endColumn = Math.max(
|
|
44
|
+
startColumn + 1,
|
|
45
|
+
Math.min(
|
|
46
|
+
TIMELINE_WIDTH,
|
|
47
|
+
Math.ceil((Math.min(total, end) / total) * TIMELINE_WIDTH),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
cells.fill("#", startColumn, endColumn);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `|${cells.join("")}|`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createTimelineAxis(total: number): string {
|
|
58
|
+
const totalLabel = formatMs(total);
|
|
59
|
+
return `0ms${" ".repeat(
|
|
60
|
+
Math.max(1, TIMELINE_WIDTH - "0ms".length - totalLabel.length),
|
|
61
|
+
)}${totalLabel}`;
|
|
62
|
+
}
|
|
8
63
|
|
|
9
|
-
/**
|
|
10
|
-
* Create a metrics store for the request if debugPerformance is enabled
|
|
11
|
-
*/
|
|
12
64
|
export function createMetricsStore(
|
|
13
|
-
debugPerformance: boolean
|
|
65
|
+
debugPerformance: boolean,
|
|
66
|
+
requestStart?: number,
|
|
14
67
|
): MetricsStore | undefined {
|
|
15
68
|
if (!debugPerformance) return undefined;
|
|
16
69
|
return {
|
|
17
70
|
enabled: true,
|
|
18
|
-
requestStart: performance.now(),
|
|
71
|
+
requestStart: requestStart ?? performance.now(),
|
|
19
72
|
metrics: [],
|
|
20
73
|
};
|
|
21
74
|
}
|
|
22
75
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
76
|
+
export function appendMetric(
|
|
77
|
+
metricsStore: MetricsStore | undefined,
|
|
78
|
+
label: string,
|
|
79
|
+
start: number,
|
|
80
|
+
duration: number,
|
|
81
|
+
depth?: number,
|
|
82
|
+
): void {
|
|
83
|
+
if (!metricsStore) return;
|
|
84
|
+
metricsStore.metrics.push({
|
|
85
|
+
label,
|
|
86
|
+
duration,
|
|
87
|
+
startTime: start - metricsStore.requestStart,
|
|
88
|
+
depth,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildMetricsTiming(
|
|
93
|
+
method: string,
|
|
94
|
+
pathname: string,
|
|
95
|
+
metricsStore: MetricsStore | undefined,
|
|
96
|
+
): string | undefined {
|
|
97
|
+
if (!metricsStore) return undefined;
|
|
98
|
+
logMetrics(method, pathname, metricsStore);
|
|
99
|
+
return generateServerTiming(metricsStore) || undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface DisplayRow {
|
|
103
|
+
label: string;
|
|
104
|
+
startTime: number;
|
|
105
|
+
duration: number;
|
|
106
|
+
depth: number | undefined;
|
|
107
|
+
spans: Span[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
|
|
111
|
+
const preMap = new Map<string, PerformanceMetric>();
|
|
112
|
+
const postMap = new Map<string, PerformanceMetric>();
|
|
113
|
+
const consumed = new Set<PerformanceMetric>();
|
|
114
|
+
|
|
115
|
+
for (const m of sorted) {
|
|
116
|
+
if (m.label.endsWith(":pre")) {
|
|
117
|
+
preMap.set(m.label.slice(0, -4), m);
|
|
118
|
+
} else if (m.label.endsWith(":post")) {
|
|
119
|
+
postMap.set(m.label.slice(0, -5), m);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const rows: DisplayRow[] = [];
|
|
124
|
+
|
|
125
|
+
for (const m of sorted) {
|
|
126
|
+
if (consumed.has(m)) continue;
|
|
127
|
+
|
|
128
|
+
if (m.label.endsWith(":pre")) {
|
|
129
|
+
const base = m.label.slice(0, -4);
|
|
130
|
+
const post = postMap.get(base);
|
|
131
|
+
if (post) {
|
|
132
|
+
// Merge into a single row with two disjoint spans
|
|
133
|
+
consumed.add(m);
|
|
134
|
+
consumed.add(post);
|
|
135
|
+
rows.push({
|
|
136
|
+
label: base,
|
|
137
|
+
startTime: m.startTime,
|
|
138
|
+
duration: m.duration + post.duration,
|
|
139
|
+
depth: m.depth,
|
|
140
|
+
spans: [
|
|
141
|
+
{ startTime: m.startTime, duration: m.duration },
|
|
142
|
+
{ startTime: post.startTime, duration: post.duration },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// Lone :pre — display with base label
|
|
148
|
+
consumed.add(m);
|
|
149
|
+
rows.push({
|
|
150
|
+
label: base,
|
|
151
|
+
startTime: m.startTime,
|
|
152
|
+
duration: m.duration,
|
|
153
|
+
depth: m.depth,
|
|
154
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (m.label.endsWith(":post")) {
|
|
160
|
+
const base = m.label.slice(0, -5);
|
|
161
|
+
if (preMap.has(base)) {
|
|
162
|
+
// Already consumed as part of the pair above
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Lone :post — display with base label
|
|
166
|
+
consumed.add(m);
|
|
167
|
+
rows.push({
|
|
168
|
+
label: base,
|
|
169
|
+
startTime: m.startTime,
|
|
170
|
+
duration: m.duration,
|
|
171
|
+
depth: m.depth,
|
|
172
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Regular metric
|
|
178
|
+
rows.push({
|
|
179
|
+
label: m.label,
|
|
180
|
+
startTime: m.startTime,
|
|
181
|
+
duration: m.duration,
|
|
182
|
+
depth: m.depth,
|
|
183
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return rows;
|
|
188
|
+
}
|
|
189
|
+
|
|
26
190
|
export function logMetrics(
|
|
27
191
|
method: string,
|
|
28
192
|
pathname: string,
|
|
29
|
-
metricsStore: MetricsStore
|
|
193
|
+
metricsStore: MetricsStore,
|
|
30
194
|
): void {
|
|
31
195
|
const total = performance.now() - metricsStore.requestStart;
|
|
32
196
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
197
|
+
const sorted = sortMetrics(metricsStore.metrics);
|
|
198
|
+
const displayRows = buildDisplayRows(sorted);
|
|
199
|
+
|
|
200
|
+
const labels = displayRows.map(
|
|
201
|
+
(r) =>
|
|
202
|
+
`${" ".repeat(BASE_INDENT + (r.depth ?? 0) * DEPTH_INDENT)}${r.label}`,
|
|
203
|
+
);
|
|
204
|
+
const startValues = displayRows.map((r) => formatMs(r.startTime));
|
|
205
|
+
const durationValues = displayRows.map((r) => formatMs(r.duration));
|
|
206
|
+
const startWidth = Math.max(
|
|
207
|
+
"start".length,
|
|
208
|
+
...startValues.map((v) => v.length),
|
|
209
|
+
);
|
|
210
|
+
const durationWidth = Math.max(
|
|
211
|
+
"dur".length,
|
|
212
|
+
...durationValues.map((v) => v.length),
|
|
213
|
+
);
|
|
214
|
+
const spanWidth = Math.max(
|
|
215
|
+
"span".length,
|
|
216
|
+
...labels.map((label) => label.length),
|
|
217
|
+
22,
|
|
218
|
+
);
|
|
219
|
+
const timelinePadding = " ".repeat(
|
|
220
|
+
startWidth + 2 + durationWidth + 2 + spanWidth + 2,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
console.log(`[RSC Perf] ${method} ${pathname} (${total.toFixed(2)}ms)`);
|
|
224
|
+
console.log(
|
|
225
|
+
`${"start".padStart(startWidth)} ${"dur".padStart(durationWidth)} ${"span".padEnd(spanWidth)} timeline`,
|
|
37
226
|
);
|
|
227
|
+
console.log(`${timelinePadding}${createTimelineAxis(total)}`);
|
|
38
228
|
|
|
39
|
-
|
|
229
|
+
for (let index = 0; index < displayRows.length; index++) {
|
|
230
|
+
const row = displayRows[index];
|
|
231
|
+
const label = labels[index].padEnd(spanWidth);
|
|
232
|
+
const start = formatMs(row.startTime).padStart(startWidth);
|
|
233
|
+
const duration = formatMs(row.duration).padStart(durationWidth);
|
|
40
234
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
235
|
+
console.log(
|
|
236
|
+
`${start} ${duration} ${label} ${renderTimeline(row.spans, total)}`,
|
|
237
|
+
);
|
|
44
238
|
}
|
|
45
239
|
}
|
|
46
240
|
|
|
47
|
-
/**
|
|
48
|
-
* Generate Server-Timing header value from metrics
|
|
49
|
-
* Format: metric-name;dur=X.XX
|
|
50
|
-
*/
|
|
51
241
|
export function generateServerTiming(metricsStore: MetricsStore): string {
|
|
52
242
|
return metricsStore.metrics
|
|
53
243
|
.map((m) => {
|
|
54
244
|
// Convert label to valid Server-Timing name (alphanumeric and hyphens)
|
|
55
|
-
const
|
|
245
|
+
const base = m.label
|
|
56
246
|
.replace(/:/g, "-")
|
|
57
247
|
.replace(/[^a-zA-Z0-9-]/g, "")
|
|
58
248
|
.toLowerCase();
|
|
249
|
+
const name = m.depth ? `d${m.depth}-${base}` : base;
|
|
59
250
|
return `${name};dur=${m.duration.toFixed(2)}`;
|
|
60
251
|
})
|
|
61
252
|
.join(", ");
|