@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +426 -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 +2500 -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 +29 -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-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -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 +177 -35
- package/src/index.ts +255 -71
- 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 +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -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 +452 -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/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- 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 +40 -72
- 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 +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -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
package/src/server.ts
CHANGED
|
@@ -9,15 +9,22 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
// Router registry (used by Vite plugin for build-time discovery)
|
|
12
|
+
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
|
|
13
|
+
|
|
14
|
+
// Host router registry (used by Vite plugin for host-router lazy discovery)
|
|
12
15
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./router.js";
|
|
16
|
+
HostRouterRegistry,
|
|
17
|
+
type HostRouterRegistryEntry,
|
|
18
|
+
} from "./host/router.js";
|
|
16
19
|
|
|
17
20
|
// Route map builder (Vite plugin injects these via virtual modules)
|
|
18
21
|
export {
|
|
19
22
|
registerRouteMap,
|
|
20
23
|
setCachedManifest,
|
|
24
|
+
clearCachedManifest,
|
|
25
|
+
clearAllRouterData,
|
|
26
|
+
getGlobalRouteMap,
|
|
27
|
+
getRouterManifest,
|
|
21
28
|
setPrecomputedEntries,
|
|
22
29
|
setRouteTrie,
|
|
23
30
|
setManifestReadyPromise,
|
|
@@ -29,7 +36,10 @@ export {
|
|
|
29
36
|
} from "./route-map-builder.js";
|
|
30
37
|
|
|
31
38
|
// Loader registry (Vite plugin registers lazy loader imports)
|
|
32
|
-
export {
|
|
39
|
+
export {
|
|
40
|
+
registerLoaderById,
|
|
41
|
+
setLoaderImports,
|
|
42
|
+
} from "./server/loader-registry.js";
|
|
33
43
|
|
|
34
44
|
// Request context creation (used by RSC handler, not user-facing)
|
|
35
45
|
export {
|
|
@@ -38,7 +48,4 @@ export {
|
|
|
38
48
|
} from "./server/request-context.js";
|
|
39
49
|
|
|
40
50
|
// Component utilities (used internally for server/client boundary checks)
|
|
41
|
-
export {
|
|
42
|
-
isClientComponent,
|
|
43
|
-
assertClientComponent,
|
|
44
|
-
} from "./component-utils.js";
|
|
51
|
+
export { isClientComponent, assertClientComponent } from "./component-utils.js";
|
package/src/ssr/index.tsx
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { initSegmentsSync } from "../browser/react/use-segments.js";
|
|
4
|
-
import { initThemeConfigSync } from "../theme/theme-context.js";
|
|
5
|
-
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
6
|
-
import { NavigationStoreContext } from "../browser/react/context.js";
|
|
7
|
-
import type { NavigationStoreContextValue } from "../browser/react/context.js";
|
|
8
|
-
import type { HandleData } from "../browser/types.js";
|
|
2
|
+
import { createSsrRootComponent } from "./ssr-root.js";
|
|
9
3
|
import type { ErrorPhase } from "../types.js";
|
|
10
|
-
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
11
|
-
import type { EventController, DerivedNavigationState } from "../browser/event-controller.js";
|
|
12
4
|
|
|
13
5
|
/**
|
|
14
6
|
* Options for injectRSCPayload
|
|
@@ -29,6 +21,58 @@ interface RenderToReadableStreamOptions {
|
|
|
29
21
|
formState?: unknown;
|
|
30
22
|
}
|
|
31
23
|
|
|
24
|
+
/**
|
|
25
|
+
* ReadableStream with the allReady promise added by react-dom/server.edge.
|
|
26
|
+
*/
|
|
27
|
+
interface ReactDOMReadableStream extends ReadableStream<Uint8Array> {
|
|
28
|
+
allReady: Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for prerender from react-dom/static.edge
|
|
33
|
+
*/
|
|
34
|
+
interface PrerenderOptions {
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
bootstrapScriptContent?: string;
|
|
37
|
+
onError?: (error: unknown) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Result of prerender from react-dom/static.edge. `postponed` is React's
|
|
42
|
+
* opaque resume state — non-null when the render was aborted with pending
|
|
43
|
+
* holes, null when the shell completed with nothing left to stream.
|
|
44
|
+
*/
|
|
45
|
+
interface PrerenderResult {
|
|
46
|
+
prelude: ReadableStream<Uint8Array>;
|
|
47
|
+
postponed: unknown;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* prerender from react-dom/static.edge
|
|
52
|
+
*/
|
|
53
|
+
type PrerenderFn = (
|
|
54
|
+
element: React.ReactNode,
|
|
55
|
+
options?: PrerenderOptions,
|
|
56
|
+
) => Promise<PrerenderResult>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Options for resume from react-dom/server.edge
|
|
60
|
+
*/
|
|
61
|
+
interface ResumeOptions {
|
|
62
|
+
onError?: (error: unknown) => void;
|
|
63
|
+
nonce?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* resume from react-dom/server.edge — continues a prerendered render, emitting
|
|
68
|
+
* only the postponed holes.
|
|
69
|
+
*/
|
|
70
|
+
type ResumeFn = (
|
|
71
|
+
element: React.ReactNode,
|
|
72
|
+
postponedState: unknown,
|
|
73
|
+
options?: ResumeOptions,
|
|
74
|
+
) => Promise<ReactDOMReadableStream>;
|
|
75
|
+
|
|
32
76
|
/**
|
|
33
77
|
* Options for the renderHTML function
|
|
34
78
|
*/
|
|
@@ -45,6 +89,14 @@ export interface SSRRenderOptions {
|
|
|
45
89
|
* Nonce for Content Security Policy (CSP)
|
|
46
90
|
*/
|
|
47
91
|
nonce?: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* SSR stream mode.
|
|
95
|
+
*
|
|
96
|
+
* - `"stream"` (default) — start flushing HTML immediately.
|
|
97
|
+
* - `"allReady"` — await `stream.allReady` before returning.
|
|
98
|
+
*/
|
|
99
|
+
streamMode?: import("../router/router-options.js").SSRStreamMode;
|
|
48
100
|
}
|
|
49
101
|
|
|
50
102
|
/**
|
|
@@ -52,24 +104,26 @@ export interface SSRRenderOptions {
|
|
|
52
104
|
*/
|
|
53
105
|
export interface SSRDependencies<TEnv = unknown> {
|
|
54
106
|
/**
|
|
55
|
-
* createFromReadableStream from @
|
|
107
|
+
* createFromReadableStream from @rangojs/router/internal/deps/ssr
|
|
56
108
|
*/
|
|
57
|
-
createFromReadableStream: <T>(
|
|
109
|
+
createFromReadableStream: <T>(
|
|
110
|
+
stream: ReadableStream<Uint8Array>,
|
|
111
|
+
) => Promise<T>;
|
|
58
112
|
|
|
59
113
|
/**
|
|
60
114
|
* renderToReadableStream from react-dom/server.edge
|
|
61
115
|
*/
|
|
62
116
|
renderToReadableStream: (
|
|
63
117
|
element: React.ReactNode,
|
|
64
|
-
options?: RenderToReadableStreamOptions
|
|
65
|
-
) => Promise<
|
|
118
|
+
options?: RenderToReadableStreamOptions,
|
|
119
|
+
) => Promise<ReactDOMReadableStream>;
|
|
66
120
|
|
|
67
121
|
/**
|
|
68
|
-
* injectRSCPayload from
|
|
122
|
+
* injectRSCPayload from @rangojs/router/internal/deps/html-stream-server
|
|
69
123
|
*/
|
|
70
124
|
injectRSCPayload: (
|
|
71
125
|
rscStream: ReadableStream<Uint8Array>,
|
|
72
|
-
options?: InjectRSCPayloadOptions
|
|
126
|
+
options?: InjectRSCPayloadOptions,
|
|
73
127
|
) => TransformStream<Uint8Array, Uint8Array>;
|
|
74
128
|
|
|
75
129
|
/**
|
|
@@ -78,6 +132,18 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
78
132
|
*/
|
|
79
133
|
loadBootstrapScriptContent: () => Promise<string>;
|
|
80
134
|
|
|
135
|
+
/**
|
|
136
|
+
* prerender from react-dom/static.edge. Optional; required only by
|
|
137
|
+
* {@link createShellCaptureHandler} for PPR shell capture.
|
|
138
|
+
*/
|
|
139
|
+
prerender?: PrerenderFn;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* resume from react-dom/server.edge. Optional; required only by
|
|
143
|
+
* {@link createShellResumeHandler} for resuming a postponed shell.
|
|
144
|
+
*/
|
|
145
|
+
resume?: ResumeFn;
|
|
146
|
+
|
|
81
147
|
/**
|
|
82
148
|
* Optional callback invoked when an error occurs during SSR rendering.
|
|
83
149
|
*
|
|
@@ -98,73 +164,142 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
98
164
|
}
|
|
99
165
|
|
|
100
166
|
/**
|
|
101
|
-
*
|
|
167
|
+
* Default guard for how long capture waits on the caller's `quiesce` signal
|
|
168
|
+
* before forcing the abort that freezes the shell. This is the ONLY wall-clock
|
|
169
|
+
* on the capture path and it is a pathological guard — it should never fire once
|
|
170
|
+
* the caller's `quiesce` is a task-quantized, frozen-byte signal (the capture
|
|
171
|
+
* gate in shell-capture.ts). See docs/design/ppr-shell-resume.md.
|
|
102
172
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
173
|
+
const DEFAULT_SHELL_CAPTURE_MAX_WAIT_MS = 5000;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fixed number of macrotask hops between `quiesce` resolving and the abort. By
|
|
177
|
+
* the time `quiesce` resolves the Flight input is byte-quiet and frozen, so
|
|
178
|
+
* these hops are deterministic: they only give React's fizz worker turns to
|
|
179
|
+
* flush the settled shell and mark still-pending boundaries as POSTPONED (rather
|
|
180
|
+
* than errored) before controller.abort() lands. Not a wall-clock wait.
|
|
181
|
+
*/
|
|
182
|
+
const POST_QUIESCE_TASK_HOPS = 2;
|
|
113
183
|
|
|
114
184
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
185
|
+
* Route an SSR error through the deps.onError notification callback with the
|
|
186
|
+
* "rendering" phase. Swallows callback failures so a broken reporter never
|
|
187
|
+
* masks the original error. Shared by renderHTML, capture, and resume so the
|
|
188
|
+
* onError contract is identical across all three handlers.
|
|
117
189
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
190
|
+
function reportRenderError(
|
|
191
|
+
onError: SSRDependencies["onError"],
|
|
192
|
+
error: unknown,
|
|
193
|
+
): void {
|
|
194
|
+
if (onError) {
|
|
195
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
196
|
+
try {
|
|
197
|
+
onError(errorObj, { phase: "rendering" });
|
|
198
|
+
} catch (callbackError) {
|
|
199
|
+
console.error("[SSRHandler.onError] Callback error:", callbackError);
|
|
200
|
+
}
|
|
124
201
|
}
|
|
125
|
-
return lastData;
|
|
126
202
|
}
|
|
127
203
|
|
|
128
204
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
205
|
+
* Yield one macrotask. Used by capture to let React's fizz worker flush the
|
|
206
|
+
* shell and mark still-pending boundaries as postponed before the abort.
|
|
131
207
|
*/
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
state: "idle",
|
|
136
|
-
isStreaming: false,
|
|
137
|
-
location,
|
|
138
|
-
pendingUrl: null,
|
|
139
|
-
inflightActions: [],
|
|
140
|
-
};
|
|
208
|
+
function macrotask(): Promise<void> {
|
|
209
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
210
|
+
}
|
|
141
211
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
/**
|
|
213
|
+
* A timeout promise paired with a cancel() so the pending timer is cleared once
|
|
214
|
+
* the race is decided — otherwise the maxWait timer keeps the event loop alive
|
|
215
|
+
* for the full duration even after `quiesce` won.
|
|
216
|
+
*/
|
|
217
|
+
function createCancelableTimeout(ms: number): {
|
|
218
|
+
promise: Promise<void>;
|
|
219
|
+
cancel: () => void;
|
|
220
|
+
} {
|
|
221
|
+
let id: ReturnType<typeof setTimeout> | undefined;
|
|
222
|
+
const promise = new Promise<void>((resolve) => {
|
|
223
|
+
id = setTimeout(resolve, ms);
|
|
224
|
+
});
|
|
225
|
+
return { promise, cancel: () => clearTimeout(id) };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Drain a ReadableStream fully into a single Uint8Array. Capture buffers the
|
|
230
|
+
* whole prelude so it can be stored and later prepended byte-for-byte.
|
|
231
|
+
*/
|
|
232
|
+
async function readStreamToUint8Array(
|
|
233
|
+
stream: ReadableStream<Uint8Array>,
|
|
234
|
+
): Promise<Uint8Array> {
|
|
235
|
+
const reader = stream.getReader();
|
|
236
|
+
const chunks: Uint8Array[] = [];
|
|
237
|
+
let total = 0;
|
|
238
|
+
while (true) {
|
|
239
|
+
const { done, value } = await reader.read();
|
|
240
|
+
if (done) break;
|
|
241
|
+
chunks.push(value);
|
|
242
|
+
total += value.length;
|
|
243
|
+
}
|
|
244
|
+
const out = new Uint8Array(total);
|
|
245
|
+
let offset = 0;
|
|
246
|
+
for (const chunk of chunks) {
|
|
247
|
+
out.set(chunk, offset);
|
|
248
|
+
offset += chunk.length;
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A minimal HTML stream for the resume DATA variant: one empty chunk, then
|
|
255
|
+
* close.
|
|
256
|
+
*
|
|
257
|
+
* injectRSCPayload only resolves its internal flight-data promise (and thus
|
|
258
|
+
* only writes the Flight payload <script> pushes) from inside transform()'s
|
|
259
|
+
* scheduled callback. A stream that closes without ever emitting a chunk never
|
|
260
|
+
* runs transform, so its flush() awaits a promise that is never resolved and
|
|
261
|
+
* the output deadlocks. Emitting a single empty chunk runs transform once,
|
|
262
|
+
* which is enough for the payload to be written and the trailer appended.
|
|
263
|
+
*/
|
|
264
|
+
function createDataVariantHtmlStream(): ReadableStream<Uint8Array> {
|
|
265
|
+
return new ReadableStream({
|
|
266
|
+
start(controller) {
|
|
267
|
+
controller.enqueue(new Uint8Array(0));
|
|
268
|
+
controller.close();
|
|
163
269
|
},
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Options for the captureShellHTML function returned by
|
|
275
|
+
* {@link createShellCaptureHandler}.
|
|
276
|
+
*/
|
|
277
|
+
interface ShellCaptureOptions {
|
|
278
|
+
/** Caller-provided promise that resolves once the cached content settled. */
|
|
279
|
+
quiesce: Promise<void>;
|
|
280
|
+
/** Upper bound on how long to wait for `quiesce`. Default 5000ms. */
|
|
281
|
+
maxWaitMs?: number;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Result of a successful shell capture. `prelude` is the raw prelude bytes;
|
|
286
|
+
* `postponed` is React's resume state serialized to JSON, or null when the
|
|
287
|
+
* shell completed with no holes (the DATA variant).
|
|
288
|
+
*/
|
|
289
|
+
interface ShellCaptureResult {
|
|
290
|
+
prelude: Uint8Array;
|
|
291
|
+
postponed: string | null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Options for the resumeShellHTML function returned by
|
|
296
|
+
* {@link createShellResumeHandler}.
|
|
297
|
+
*/
|
|
298
|
+
interface ShellResumeOptions {
|
|
299
|
+
/** JSON from capture; null selects the DATA variant (no fizz). */
|
|
300
|
+
postponed: string | null;
|
|
301
|
+
/** Nonce for CSP. */
|
|
302
|
+
nonce?: string;
|
|
168
303
|
}
|
|
169
304
|
|
|
170
305
|
/**
|
|
@@ -172,10 +307,10 @@ function createSsrEventController(pathname: string): EventController {
|
|
|
172
307
|
*
|
|
173
308
|
* @example
|
|
174
309
|
* ```tsx
|
|
175
|
-
* import { createSSRHandler } from "
|
|
176
|
-
* import { createFromReadableStream } from "@
|
|
310
|
+
* import { createSSRHandler } from "@rangojs/router/ssr";
|
|
311
|
+
* import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
|
|
177
312
|
* import { renderToReadableStream } from "react-dom/server.edge";
|
|
178
|
-
* import { injectRSCPayload } from "
|
|
313
|
+
* import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
|
|
179
314
|
*
|
|
180
315
|
* export const renderHTML = createSSRHandler({
|
|
181
316
|
* createFromReadableStream,
|
|
@@ -203,9 +338,9 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
203
338
|
*/
|
|
204
339
|
return async function renderHTML(
|
|
205
340
|
rscStream: ReadableStream<Uint8Array>,
|
|
206
|
-
options?: SSRRenderOptions
|
|
341
|
+
options?: SSRRenderOptions,
|
|
207
342
|
): Promise<ReadableStream<Uint8Array>> {
|
|
208
|
-
const { nonce, formState } = options ?? {};
|
|
343
|
+
const { nonce, formState, streamMode } = options ?? {};
|
|
209
344
|
|
|
210
345
|
try {
|
|
211
346
|
// Tee the stream:
|
|
@@ -213,58 +348,11 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
213
348
|
// - rscStream2: For browser hydration (inject as __FLIGHT_DATA__)
|
|
214
349
|
const [rscStream1, rscStream2] = rscStream.tee();
|
|
215
350
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
payload ??= createFromReadableStream<RscPayload>(rscStream1);
|
|
222
|
-
const resolved = React.use(payload);
|
|
223
|
-
|
|
224
|
-
// Initialize segments state before children render (for useSegments hook)
|
|
225
|
-
initSegmentsSync(resolved.metadata?.matched, resolved.metadata?.pathname);
|
|
226
|
-
|
|
227
|
-
// Initialize theme config for MetaTags to render theme script
|
|
228
|
-
const themeConfig = resolved.metadata?.themeConfig ?? null;
|
|
229
|
-
initThemeConfigSync(themeConfig);
|
|
230
|
-
|
|
231
|
-
// Await handles and initialize state before children render
|
|
232
|
-
// The handles property is an async generator that yields on each push
|
|
233
|
-
// Memoize the promise since async generators can only be iterated once
|
|
234
|
-
if (resolved.metadata?.handles) {
|
|
235
|
-
handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
|
|
236
|
-
const handleData = React.use(handlesPromise);
|
|
237
|
-
initHandleDataSync(handleData, resolved.metadata.matched);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Create SSR context with correct pathname for useNavigation
|
|
241
|
-
ssrContextValue ??= {
|
|
242
|
-
store: null as any,
|
|
243
|
-
eventController: createSsrEventController(resolved.metadata?.pathname ?? "/"),
|
|
244
|
-
navigate: async () => {},
|
|
245
|
-
refresh: async () => {},
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// Build content tree with all necessary providers
|
|
249
|
-
// Order must match NavigationProvider: NavigationStoreContext > ThemeProvider > content
|
|
250
|
-
let content: React.ReactNode = resolved.root;
|
|
251
|
-
|
|
252
|
-
// Wrap content with ThemeProvider if theme is enabled
|
|
253
|
-
if (themeConfig) {
|
|
254
|
-
content = (
|
|
255
|
-
<ThemeProvider config={themeConfig} initialTheme={resolved.metadata?.initialTheme}>
|
|
256
|
-
{content}
|
|
257
|
-
</ThemeProvider>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Wrap with NavigationStoreContext for useNavigation hook
|
|
262
|
-
return (
|
|
263
|
-
<NavigationStoreContext.Provider value={ssrContextValue}>
|
|
264
|
-
{content}
|
|
265
|
-
</NavigationStoreContext.Provider>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
351
|
+
const SsrRoot = createSsrRootComponent({
|
|
352
|
+
createFromReadableStream,
|
|
353
|
+
rscStream: rscStream1,
|
|
354
|
+
nonce,
|
|
355
|
+
});
|
|
268
356
|
|
|
269
357
|
// Get bootstrap script content
|
|
270
358
|
const bootstrapScriptContent = await loadBootstrapScriptContent();
|
|
@@ -278,18 +366,206 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
278
366
|
nonce,
|
|
279
367
|
});
|
|
280
368
|
|
|
369
|
+
// Wait for all Suspense boundaries to resolve when streamMode is "allReady".
|
|
370
|
+
// This buffers the entire HTML before flushing — used for bots that
|
|
371
|
+
// cannot process streamed HTML.
|
|
372
|
+
if (streamMode === "allReady") {
|
|
373
|
+
await htmlStream.allReady;
|
|
374
|
+
}
|
|
375
|
+
|
|
281
376
|
// Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
|
|
282
377
|
return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
|
|
283
378
|
} catch (error) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
379
|
+
reportRenderError(onError, error);
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Create the PPR shell capture handler.
|
|
387
|
+
*
|
|
388
|
+
* captureShellHTML prerenders the shell over the (cached, loader-masked) Flight
|
|
389
|
+
* stream, aborts once the shell settles, and returns the prelude bytes plus the
|
|
390
|
+
* postponed resume state for storage. The stored pair is later served by
|
|
391
|
+
* {@link createShellResumeHandler}. See docs/design/ppr-shell-resume.md.
|
|
392
|
+
*
|
|
393
|
+
* Throws at creation if `deps.prerender` is missing — capture cannot run
|
|
394
|
+
* without react-dom/static.edge's prerender.
|
|
395
|
+
*/
|
|
396
|
+
export function createShellCaptureHandler<TEnv = unknown>(
|
|
397
|
+
deps: SSRDependencies<TEnv>,
|
|
398
|
+
) {
|
|
399
|
+
const { createFromReadableStream, loadBootstrapScriptContent, prerender } =
|
|
400
|
+
deps;
|
|
401
|
+
|
|
402
|
+
if (!prerender) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
"[createShellCaptureHandler] Missing `prerender` dependency (react-dom/static.edge). " +
|
|
405
|
+
"PPR shell capture requires the prerender export; wire it in the SSR virtual entry.",
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Prerender the shell and return the stored artifacts, or null when the
|
|
411
|
+
* shell degraded (root postpone / hung handles) and must not be cached.
|
|
412
|
+
*
|
|
413
|
+
* @param rscStream - Flight stream to render the shell over. Not teed and not
|
|
414
|
+
* piped through injectRSCPayload: the hydration payload is produced fresh
|
|
415
|
+
* per request by the resume/serve pass.
|
|
416
|
+
* @param opts - quiesce signal and maxWait guard.
|
|
417
|
+
*/
|
|
418
|
+
return async function captureShellHTML(
|
|
419
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
420
|
+
opts: ShellCaptureOptions,
|
|
421
|
+
): Promise<ShellCaptureResult | null> {
|
|
422
|
+
const maxWaitMs = opts.maxWaitMs ?? DEFAULT_SHELL_CAPTURE_MAX_WAIT_MS;
|
|
423
|
+
|
|
424
|
+
// No nonce (nonce'd requests never reach capture); no formState.
|
|
425
|
+
const SsrRoot = createSsrRootComponent({
|
|
426
|
+
createFromReadableStream,
|
|
427
|
+
rscStream,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const bootstrapScriptContent = await loadBootstrapScriptContent();
|
|
431
|
+
|
|
432
|
+
// Start prerender first, then run the abort schedule concurrently. When
|
|
433
|
+
// holes are pending, prerender's promise settles only after abort(); when
|
|
434
|
+
// the shell completes with no holes it settles on its own and the later
|
|
435
|
+
// abort() is a harmless no-op (the DATA variant).
|
|
436
|
+
const controller = new AbortController();
|
|
437
|
+
const prerenderPromise = prerender(<SsrRoot />, {
|
|
438
|
+
signal: controller.signal,
|
|
439
|
+
bootstrapScriptContent,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Wait for the caller's quiesce signal. By the time it resolves the Flight
|
|
443
|
+
// input is byte-quiet and FROZEN by the capture gate (shell-capture.ts
|
|
444
|
+
// gateFlightForCapture), so there is no wall-clock debounce here — maxWaitMs
|
|
445
|
+
// is only the pathological guard for a shell that never goes quiet (a root
|
|
446
|
+
// postpone / hung handle), and should never fire in tests.
|
|
447
|
+
const timer = createCancelableTimeout(maxWaitMs);
|
|
448
|
+
try {
|
|
449
|
+
await Promise.race([opts.quiesce, timer.promise]);
|
|
450
|
+
} finally {
|
|
451
|
+
timer.cancel();
|
|
452
|
+
}
|
|
453
|
+
// Fixed task hops before the abort: give React's fizz worker turns to flush
|
|
454
|
+
// the now-complete shell and mark the still-pending boundaries as POSTPONED
|
|
455
|
+
// rather than errored. Deterministic (the byte set is already frozen), so a
|
|
456
|
+
// fixed count of turns suffices — no wall-clock.
|
|
457
|
+
for (let i = 0; i < POST_QUIESCE_TASK_HOPS; i++) {
|
|
458
|
+
await macrotask();
|
|
459
|
+
}
|
|
460
|
+
controller.abort();
|
|
461
|
+
|
|
462
|
+
// A hard prerender rejection (fatal shell error) propagates. Expected
|
|
463
|
+
// degradation surfaces three ways and all return null: a trivial prelude
|
|
464
|
+
// (sanity gate below), the prerender REJECTING with an AbortError, or the
|
|
465
|
+
// prelude STREAM erroring with the abort reason mid-read — both abort
|
|
466
|
+
// shapes happen when our own abort lands before the shell completed (seen
|
|
467
|
+
// on dev cold paths, where module transform / first-render latency
|
|
468
|
+
// outlasts flight quiesce; a later request re-captures against warm
|
|
469
|
+
// modules and succeeds).
|
|
470
|
+
let prelude: Uint8Array;
|
|
471
|
+
let postponed: unknown;
|
|
472
|
+
try {
|
|
473
|
+
const result = await prerenderPromise;
|
|
474
|
+
prelude = await readStreamToUint8Array(result.prelude);
|
|
475
|
+
postponed = result.postponed;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// Name-based match: the rejection is a DOMException on workerd/Node,
|
|
478
|
+
// which is not an Error subclass there, so instanceof Error would let
|
|
479
|
+
// the abort escape as a spurious reported error.
|
|
480
|
+
if (
|
|
481
|
+
controller.signal.aborted &&
|
|
482
|
+
(error as { name?: string } | null)?.name === "AbortError"
|
|
483
|
+
) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Sanity gate: a prelude with no `<body` is the no-shell failure mode.
|
|
490
|
+
// Return null and store nothing; the request falls back to axis 1 and a
|
|
491
|
+
// later request re-captures. The dominant real-world cause is a loader
|
|
492
|
+
// route WITHOUT a route-level loading() boundary: renderSegments' loading-
|
|
493
|
+
// less branch awaits loader data at TREE-BUILD, so the masked loader pins
|
|
494
|
+
// the whole tree above <body> (root postpone). Root-postponing layouts and
|
|
495
|
+
// hung handles degrade the same way. shell-capture.ts logs a once-per-key
|
|
496
|
+
// warning so the eternal-MISS shape is diagnosable.
|
|
497
|
+
if (!new TextDecoder().decode(prelude).includes("<body")) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
prelude,
|
|
503
|
+
postponed: postponed == null ? null : JSON.stringify(postponed),
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Create the PPR shell resume handler.
|
|
510
|
+
*
|
|
511
|
+
* resumeShellHTML produces the per-request live portion of the document: for a
|
|
512
|
+
* postponed shell it resumes fizz over a fresh SsrRoot to emit only the holes;
|
|
513
|
+
* for the DATA variant it emits only the fresh Flight payload scripts. The
|
|
514
|
+
* caller (shell-cache middleware) prepends the stored prelude bytes to form the
|
|
515
|
+
* composite response. See docs/design/ppr-shell-resume.md.
|
|
516
|
+
*/
|
|
517
|
+
export function createShellResumeHandler<TEnv = unknown>(
|
|
518
|
+
deps: SSRDependencies<TEnv>,
|
|
519
|
+
) {
|
|
520
|
+
const { createFromReadableStream, injectRSCPayload, resume, onError } = deps;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* @param rscStream - Fresh full Flight stream for this request.
|
|
524
|
+
* @param opts - postponed state (null = DATA variant) and optional nonce.
|
|
525
|
+
*/
|
|
526
|
+
return async function resumeShellHTML(
|
|
527
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
528
|
+
opts: ShellResumeOptions,
|
|
529
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
530
|
+
const { postponed, nonce } = opts;
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
if (postponed === null) {
|
|
534
|
+
// DATA variant: the stored prelude is the complete shell. No fizz runs;
|
|
535
|
+
// feed injectRSCPayload a minimal HTML stream so its flush appends the
|
|
536
|
+
// fresh Flight payload scripts after the shell. The stream must emit at
|
|
537
|
+
// least one chunk — see createDataVariantHtmlStream.
|
|
538
|
+
return createDataVariantHtmlStream().pipeThrough(
|
|
539
|
+
injectRSCPayload(rscStream, { nonce }),
|
|
540
|
+
);
|
|
292
541
|
}
|
|
542
|
+
|
|
543
|
+
if (!resume) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
"[createShellResumeHandler] Missing `resume` dependency (react-dom/server.edge). " +
|
|
546
|
+
"Resuming a postponed shell requires the resume export; wire it in the SSR virtual entry.",
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Tee: one branch deserializes into the SsrRoot VDOM that resume() replays
|
|
551
|
+
// (a fresh instance is fine — replay matches structure, not identity), the
|
|
552
|
+
// other feeds the fresh hydration payload to injectRSCPayload.
|
|
553
|
+
const [rscStream1, rscStream2] = rscStream.tee();
|
|
554
|
+
|
|
555
|
+
const SsrRoot = createSsrRootComponent({
|
|
556
|
+
createFromReadableStream,
|
|
557
|
+
rscStream: rscStream1,
|
|
558
|
+
nonce,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const resumed = await resume(<SsrRoot />, JSON.parse(postponed), {
|
|
562
|
+
onError: (error) => reportRenderError(onError, error),
|
|
563
|
+
nonce,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
return resumed.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
|
|
567
|
+
} catch (error) {
|
|
568
|
+
reportRenderError(onError, error);
|
|
293
569
|
throw error;
|
|
294
570
|
}
|
|
295
571
|
};
|