@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- 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 +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- 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 +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -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 +2170 -377
- 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 +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
package/src/prerender.ts
CHANGED
|
@@ -33,11 +33,13 @@ import type {
|
|
|
33
33
|
ExtractParams,
|
|
34
34
|
} from "./types.js";
|
|
35
35
|
import type { Handle } from "./handle.js";
|
|
36
|
+
import type { HandlePush } from "./defer.js";
|
|
36
37
|
import type { ContextVar } from "./context-var.js";
|
|
37
38
|
import type { ReverseFunction } from "./reverse.js";
|
|
38
39
|
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
39
40
|
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
40
41
|
import { isCachedFunction } from "./cache/taint.js";
|
|
42
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
41
43
|
|
|
42
44
|
// -- Named route resolution types -------------------------------------------
|
|
43
45
|
|
|
@@ -164,8 +166,14 @@ export interface BuildContext<TParams> {
|
|
|
164
166
|
(key: string, value: any): void;
|
|
165
167
|
};
|
|
166
168
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Push handle data (frozen into pre-rendered output at build time). Returns
|
|
171
|
+
* the full push function, including `.defer()` — a deferred slot resolved by
|
|
172
|
+
* a deep async component during the prerender render is awaited before the
|
|
173
|
+
* artifact is baked (resolve-by-default), so the baked output holds the
|
|
174
|
+
* resolved value.
|
|
175
|
+
*/
|
|
176
|
+
use: <T>(handle: Handle<T>) => HandlePush<T>;
|
|
169
177
|
|
|
170
178
|
/** Synthetic URL built from pattern + params (no real request). */
|
|
171
179
|
url: URL;
|
|
@@ -221,8 +229,14 @@ export interface StaticBuildContext {
|
|
|
221
229
|
(key: string, value: any): void;
|
|
222
230
|
};
|
|
223
231
|
|
|
224
|
-
/**
|
|
225
|
-
|
|
232
|
+
/**
|
|
233
|
+
* Push handle data (frozen into pre-rendered output at build time). Returns
|
|
234
|
+
* the full push function, including `.defer()` — a deferred slot resolved by
|
|
235
|
+
* a deep async component during the prerender render is awaited before the
|
|
236
|
+
* artifact is baked (resolve-by-default), so the baked output holds the
|
|
237
|
+
* resolved value.
|
|
238
|
+
*/
|
|
239
|
+
use: <T>(handle: Handle<T>) => HandlePush<T>;
|
|
226
240
|
|
|
227
241
|
/** URL generation by route name. */
|
|
228
242
|
reverse: BuildReverseFunction;
|
|
@@ -273,6 +287,11 @@ export interface PrerenderHandlerDefinition<
|
|
|
273
287
|
use?: () => UseItems<HandlerUseItem>;
|
|
274
288
|
}
|
|
275
289
|
|
|
290
|
+
// Process-stable fallback id counter (mirrors createHandle / createLoader). Only
|
|
291
|
+
// assigned in a bare unit test where the Vite plugin did not inject an id; never
|
|
292
|
+
// fires in a real build (the plugin always injects).
|
|
293
|
+
let runtimePrerenderIdCounter = 0;
|
|
294
|
+
|
|
276
295
|
// -- Overloads --------------------------------------------------------------
|
|
277
296
|
//
|
|
278
297
|
// T accepts: named route string (global or .local) OR explicit param object.
|
|
@@ -376,12 +395,27 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
376
395
|
);
|
|
377
396
|
}
|
|
378
397
|
|
|
379
|
-
|
|
398
|
+
// Throw unless under a test runner. The plugin always injects $$id for a
|
|
399
|
+
// supported `export const` Prerender on every build, so a missing id means
|
|
400
|
+
// either no plugin (a bare test — fall back below) or an UNSUPPORTED shape the
|
|
401
|
+
// plugin silently skipped (dev OR a real build — fail loud; a synthetic id
|
|
402
|
+
// would degrade to a silent prerender miss). The message is already small (no
|
|
403
|
+
// stack-parsing diagnostic), so it ships as-is. isUnderTestRunner() is
|
|
404
|
+
// runtime-safe — never a bare `process.env` access.
|
|
405
|
+
if (!id && !isUnderTestRunner()) {
|
|
380
406
|
throw new Error(
|
|
381
|
-
"[rango] Prerender: missing $$id. " +
|
|
382
|
-
"
|
|
407
|
+
"[rango] Prerender: missing $$id. Use `export const X = Prerender(...)` " +
|
|
408
|
+
"and ensure the exposeInternalIds Vite plugin is configured.",
|
|
383
409
|
);
|
|
384
410
|
}
|
|
411
|
+
// Under vitest with no plugin id: assign a process-stable runtime id so a
|
|
412
|
+
// whole-app router with Prerender routes constructs in a bare test (for
|
|
413
|
+
// dispatch / assertGeneratedRoutesMatch). Never reached in a real build (the
|
|
414
|
+
// throw above fires there); prerender storage/lookup keys on routeName +
|
|
415
|
+
// paramHash, never $$id (mirrors createHandle / createLoader).
|
|
416
|
+
if (!id) {
|
|
417
|
+
id = `__rango_runtime_prerender_${runtimePrerenderIdCounter++}`;
|
|
418
|
+
}
|
|
385
419
|
|
|
386
420
|
return {
|
|
387
421
|
__brand: "prerenderHandler" as const,
|
|
@@ -421,6 +455,40 @@ export function isPrerenderPassthrough(
|
|
|
421
455
|
);
|
|
422
456
|
}
|
|
423
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Detect whether any resolved segment carries the passthrough sentinel.
|
|
460
|
+
*
|
|
461
|
+
* A build handler signals passthrough by returning `ctx.passthrough()` (the
|
|
462
|
+
* PRERENDER_PASSTHROUGH sentinel), which lands on the segment's `component`.
|
|
463
|
+
* But when the route declares `loading()`, the handler result is deferred
|
|
464
|
+
* upstream (segment-resolution/fresh.ts), so `component` is a thenable resolving
|
|
465
|
+
* to the sentinel rather than the sentinel itself — a synchronous
|
|
466
|
+
* `isPrerenderPassthrough(component)` on the Promise returns false and the build
|
|
467
|
+
* bakes a corrupt artifact instead of deferring. Resolve thenables first.
|
|
468
|
+
*
|
|
469
|
+
* Rejections are swallowed here: a throwing build handler resurfaces during
|
|
470
|
+
* segment serialization, preserving the prior error-handling behavior.
|
|
471
|
+
*/
|
|
472
|
+
export async function detectPrerenderPassthrough(
|
|
473
|
+
segments: ReadonlyArray<{ component: unknown }>,
|
|
474
|
+
): Promise<boolean> {
|
|
475
|
+
for (const seg of segments) {
|
|
476
|
+
let component: unknown = seg.component;
|
|
477
|
+
if (
|
|
478
|
+
component &&
|
|
479
|
+
typeof (component as { then?: unknown }).then === "function"
|
|
480
|
+
) {
|
|
481
|
+
try {
|
|
482
|
+
component = await component;
|
|
483
|
+
} catch {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (isPrerenderPassthrough(component)) return true;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
424
492
|
// -- Type guards ------------------------------------------------------------
|
|
425
493
|
|
|
426
494
|
/**
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral same-origin redirect rule.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the client redirect guard (`browser/validate-redirect-origin.ts`,
|
|
5
|
+
* which validates redirect targets the client JS is about to navigate to) and
|
|
6
|
+
* the server outgoing-redirect guard (`rsc/redirect-guard.ts`, which validates
|
|
7
|
+
* every browser-followed `Location` header before it leaves the handler). Kept
|
|
8
|
+
* at the `src/` root so both layers import the ONE rule and cannot drift -- a
|
|
9
|
+
* cross-origin target blocked on the JS/fetch path is blocked identically on the
|
|
10
|
+
* no-JS (PE) and full-page document paths.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a redirect target against the current origin.
|
|
15
|
+
*
|
|
16
|
+
* Returns the canonical (normalized) same-origin href -- which also collapses
|
|
17
|
+
* protocol-relative (`//evil.com`) and other ambiguous forms -- or `null` when
|
|
18
|
+
* the target resolves to a different origin or is unparseable. Pure: no logging,
|
|
19
|
+
* no side effects.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveSameOriginRedirect(
|
|
22
|
+
url: string,
|
|
23
|
+
currentOrigin: string,
|
|
24
|
+
): string | null {
|
|
25
|
+
try {
|
|
26
|
+
const target = new URL(url, currentOrigin);
|
|
27
|
+
if (target.origin !== currentOrigin) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return target.href;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate an explicit off-origin redirect target (`redirect(url, { external:
|
|
38
|
+
* true })`).
|
|
39
|
+
*
|
|
40
|
+
* `external` opts out of the same-origin rule, but NOT out of scheme safety:
|
|
41
|
+
* only `http:`/`https:` targets are allowed. A redirect ultimately reaches the
|
|
42
|
+
* browser via `window.location.assign()` on the SPA/action client paths, so a
|
|
43
|
+
* forged or mistaken `redirect("javascript:...", { external: true })` would be a
|
|
44
|
+
* scriptable navigation if the scheme were not checked here. Returns the
|
|
45
|
+
* normalized href for an http(s) target (same- or cross-origin), or `null`
|
|
46
|
+
* otherwise. Pure: no logging, no side effects.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveExternalRedirect(
|
|
49
|
+
url: string,
|
|
50
|
+
currentOrigin: string,
|
|
51
|
+
): string | null {
|
|
52
|
+
try {
|
|
53
|
+
const target = new URL(url, currentOrigin);
|
|
54
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return target.href;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The safe same-origin landing for a blocked redirect.
|
|
65
|
+
*
|
|
66
|
+
* Every guard that neutralizes a cross-origin/unsafe redirect target sends the
|
|
67
|
+
* browser here instead: the app's basename root, or `"/"` when unset. Kept
|
|
68
|
+
* beside the resolvers so the "where does a blocked redirect go" answer lives
|
|
69
|
+
* in ONE place -- the server 3xx guard (`rsc/redirect-guard.ts`) and the
|
|
70
|
+
* shell-HIT degradation path (`rsc/rsc-rendering.ts`) must agree, or a blocked
|
|
71
|
+
* redirect lands differently depending on which exit it took.
|
|
72
|
+
*/
|
|
73
|
+
export function safeSameOriginLanding(basename: string | undefined): string {
|
|
74
|
+
return basename && basename !== "/" ? basename : "/";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Out-of-band brand for `redirect(url, { external: true })`.
|
|
79
|
+
*
|
|
80
|
+
* The external opt-in MUST be settable only by app code calling `redirect(...,
|
|
81
|
+
* { external: true })`, never by an attacker. An earlier design carried the
|
|
82
|
+
* opt-in as a wire header (`x-rango-redirect-external`), but a wire header is
|
|
83
|
+
* forgeable: a proxy-style response route that copies an attacker-controlled
|
|
84
|
+
* upstream response's headers would let `302 Location: https://evil` plus that
|
|
85
|
+
* header bypass the same-origin guard without app code ever opting in. So the
|
|
86
|
+
* opt-in is now an out-of-band brand on the Response object itself, tracked in a
|
|
87
|
+
* `WeakSet` that cannot cross the wire. `redirect()` brands the Response; the
|
|
88
|
+
* small set of internal redirect-rebuild paths (middleware `mergeResponse`,
|
|
89
|
+
* `carryOverRedirectHeaders`, the response-route rewrap) transfer the brand onto
|
|
90
|
+
* the rebuilt Response; the guard and the SPA intercept read it. An upstream
|
|
91
|
+
* Response an app proxies is never branded, so its forged header is inert.
|
|
92
|
+
*
|
|
93
|
+
* Fail-closed: if a rebuild path ever drops the brand, the redirect is
|
|
94
|
+
* neutralized to the app root rather than allowed off-host.
|
|
95
|
+
*/
|
|
96
|
+
const externalRedirects = new WeakSet<Response>();
|
|
97
|
+
|
|
98
|
+
/** Brand a Response as an explicit `{ external: true }` redirect (out-of-band). */
|
|
99
|
+
export function markExternalRedirect(response: Response): void {
|
|
100
|
+
externalRedirects.add(response);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Read the out-of-band `{ external: true }` brand off a Response. */
|
|
104
|
+
export function isExternalRedirect(response: Response): boolean {
|
|
105
|
+
return externalRedirects.has(response);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reserved internal header name. No longer a trust signal -- the external
|
|
110
|
+
* opt-in is the out-of-band brand above. It is kept only so the redirect-rebuild
|
|
111
|
+
* paths and the guard can defensively strip any value (e.g. one forged by a
|
|
112
|
+
* proxied upstream) and guarantee it never reaches the browser.
|
|
113
|
+
*/
|
|
114
|
+
export const EXTERNAL_REDIRECT_MARKER: string = "x-rango-redirect-external";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a string for literal use inside a RegExp. Single source of truth for
|
|
3
|
+
* the router runtime (matching) and the vite build (transform/scan); a pure,
|
|
4
|
+
* dependency-free leaf so both environments can share it.
|
|
5
|
+
*/
|
|
6
|
+
export function escapeRegExp(input: string): string {
|
|
7
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
interface RenderErrorThrowerProps {
|
|
6
|
+
error: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Client component that throws the given error during render, so the nearest
|
|
11
|
+
* error boundary catches it. Errors thrown during render are caught by error
|
|
12
|
+
* boundaries; async errors (rejected promises) are not -- which is why the
|
|
13
|
+
* navigation bridge funnels processing failures through this component instead
|
|
14
|
+
* of letting them surface as uncaught rejections.
|
|
15
|
+
*/
|
|
16
|
+
export function RenderErrorThrower({
|
|
17
|
+
error,
|
|
18
|
+
}: RenderErrorThrowerProps): ReactNode {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
package/src/response-utils.ts
CHANGED
|
@@ -27,6 +27,31 @@ export function isWebSocketUpgradeResponse(response: Response): boolean {
|
|
|
27
27
|
);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Append `Accept` to a response's `Vary` header without duplicating it.
|
|
32
|
+
*
|
|
33
|
+
* Content-negotiated responses already carry `Vary: Accept` from the
|
|
34
|
+
* upstream layer (response-route-handler's callHandlerWithVary, or
|
|
35
|
+
* handleRscRendering baking `accept` into its vary list). The negotiated
|
|
36
|
+
* post-append in the handler would otherwise emit `Vary: Accept, Accept`,
|
|
37
|
+
* a redundant token some proxies/CDNs treat as a distinct cache key.
|
|
38
|
+
* Token match is case-insensitive (HTTP field tokens are case-insensitive)
|
|
39
|
+
* and whitespace-tolerant.
|
|
40
|
+
*/
|
|
41
|
+
export function appendVaryAccept(response: Response): void {
|
|
42
|
+
const existing = response.headers.get("Vary");
|
|
43
|
+
if (!existing) {
|
|
44
|
+
response.headers.set("Vary", "Accept");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const hasAccept = existing
|
|
48
|
+
.split(",")
|
|
49
|
+
.some((token) => token.trim().toLowerCase() === "accept");
|
|
50
|
+
if (!hasAccept) {
|
|
51
|
+
response.headers.append("Vary", "Accept");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
// Location truthiness (not presence) so an empty `Location: ""` is not a redirect.
|
|
31
56
|
export function isRedirectResponse(response: Response): boolean {
|
|
32
57
|
return (
|
|
@@ -3,26 +3,17 @@
|
|
|
3
3
|
import { Component, useState, type ReactNode } from "react";
|
|
4
4
|
import type { ClientErrorBoundaryFallbackProps } from "./types.js";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Check if an error is a network-related error
|
|
8
|
-
*/
|
|
9
6
|
function isNetworkError(error: Error): boolean {
|
|
10
7
|
return error.name === "NetworkError";
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/**
|
|
14
|
-
* Network error fallback UI with retry functionality
|
|
15
|
-
* Shows a connection-specific message and allows retrying via page refresh
|
|
16
|
-
*/
|
|
17
10
|
function NetworkErrorFallback({
|
|
18
11
|
error,
|
|
19
|
-
reset,
|
|
20
12
|
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
21
13
|
const [isRetrying, setIsRetrying] = useState(false);
|
|
22
14
|
|
|
23
15
|
const handleRetry = (): void => {
|
|
24
16
|
setIsRetrying(true);
|
|
25
|
-
// Refresh the page to retry the request
|
|
26
17
|
window.location.reload();
|
|
27
18
|
};
|
|
28
19
|
|
|
@@ -42,7 +33,6 @@ function NetworkErrorFallback({
|
|
|
42
33
|
marginBottom: "1rem",
|
|
43
34
|
}}
|
|
44
35
|
>
|
|
45
|
-
{/* Simple cloud with x icon using CSS */}
|
|
46
36
|
<span style={{ color: "#9ca3af" }}>☁</span>
|
|
47
37
|
</div>
|
|
48
38
|
<h1
|
|
@@ -101,10 +91,6 @@ function NetworkErrorFallback({
|
|
|
101
91
|
);
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
/**
|
|
105
|
-
* Default fallback UI for root error boundary
|
|
106
|
-
* This is shown when an unhandled error bubbles up to the root
|
|
107
|
-
*/
|
|
108
94
|
function RootErrorFallback({
|
|
109
95
|
error,
|
|
110
96
|
reset,
|
|
@@ -230,7 +216,6 @@ export class RootErrorBoundary extends Component<
|
|
|
230
216
|
}
|
|
231
217
|
|
|
232
218
|
componentDidMount(): void {
|
|
233
|
-
// Listen for popstate (back/forward navigation) to reset error state
|
|
234
219
|
window.addEventListener("popstate", this.handlePopState);
|
|
235
220
|
}
|
|
236
221
|
|
|
@@ -247,15 +232,13 @@ export class RootErrorBoundary extends Component<
|
|
|
247
232
|
}
|
|
248
233
|
|
|
249
234
|
componentDidUpdate(prevProps: { children: ReactNode }): void {
|
|
250
|
-
// Reset error
|
|
251
|
-
// This allows the app to recover after navigation away from an errored route
|
|
235
|
+
// Reset error on children change (navigation).
|
|
252
236
|
if (this.state.hasError && prevProps.children !== this.props.children) {
|
|
253
237
|
this.setState({ hasError: false, error: null });
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
|
|
257
241
|
handlePopState = (): void => {
|
|
258
|
-
// Reset error state on back/forward navigation
|
|
259
242
|
if (this.state.hasError) {
|
|
260
243
|
this.setState({ hasError: false, error: null });
|
|
261
244
|
}
|
|
@@ -276,7 +259,6 @@ export class RootErrorBoundary extends Component<
|
|
|
276
259
|
segmentType: "route" as const,
|
|
277
260
|
};
|
|
278
261
|
|
|
279
|
-
// Use specialized fallback for network errors
|
|
280
262
|
if (isNetworkError(this.state.error)) {
|
|
281
263
|
return <NetworkErrorFallback error={errorInfo} reset={this.reset} />;
|
|
282
264
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { Suspense, use
|
|
4
|
-
import { invariant } from "./errors";
|
|
3
|
+
import { Suspense, use } from "react";
|
|
5
4
|
import { OutletProvider } from "./outlet-provider.js";
|
|
6
5
|
import type { ResolvedSegment } from "./types.js";
|
|
7
6
|
import { decodeLoaderResults } from "./decode-loader-results.js";
|
|
@@ -22,7 +21,9 @@ export function RouteContentWrapper({
|
|
|
22
21
|
fallback,
|
|
23
22
|
segmentId,
|
|
24
23
|
}: {
|
|
25
|
-
|
|
24
|
+
// Normally a pending promise (use() suspends -> fallback). forceAwait paths
|
|
25
|
+
// pass an already-resolved node so Suspender renders it without suspending.
|
|
26
|
+
content: Promise<ReactNode> | ReactNode;
|
|
26
27
|
fallback?: ReactNode;
|
|
27
28
|
segmentId?: string;
|
|
28
29
|
}): ReactNode {
|
|
@@ -36,57 +37,20 @@ export function RouteContentWrapper({
|
|
|
36
37
|
);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
export function RouteContentWrapperCallback<T>({
|
|
40
|
-
resolve,
|
|
41
|
-
fallback,
|
|
42
|
-
children,
|
|
43
|
-
}: {
|
|
44
|
-
resolve: Promise<T> | T;
|
|
45
|
-
fallback?: ReactNode;
|
|
46
|
-
children: (data: T) => ReactNode;
|
|
47
|
-
}): ReactNode {
|
|
48
|
-
const id = useId();
|
|
49
|
-
invariant(children, "RouteContentWrapperCallback requires children");
|
|
50
|
-
invariant(
|
|
51
|
-
typeof children === "function",
|
|
52
|
-
"RouteContentWrapperCallback requires children to be a function",
|
|
53
|
-
);
|
|
54
|
-
invariant(
|
|
55
|
-
resolve !== undefined,
|
|
56
|
-
"RouteContentWrapperCallback requires resolve",
|
|
57
|
-
);
|
|
58
|
-
return (
|
|
59
|
-
<Suspense
|
|
60
|
-
fallback={fallback ?? null}
|
|
61
|
-
key={"route-content-suspense-callback-" + id}
|
|
62
|
-
>
|
|
63
|
-
<SuspenderCallback resolve={resolve} key={id}>
|
|
64
|
-
{children}
|
|
65
|
-
</SuspenderCallback>
|
|
66
|
-
</Suspense>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
40
|
const Suspender = ({
|
|
71
41
|
content,
|
|
72
42
|
}: {
|
|
73
43
|
content: Promise<ReactNode> | ReactNode;
|
|
74
44
|
}): ReactNode => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
resolve: Promise<T> | T;
|
|
85
|
-
children: (data: T) => ReactNode;
|
|
86
|
-
}): ReactNode => {
|
|
87
|
-
return resolve instanceof Promise
|
|
88
|
-
? children(use(resolve))
|
|
89
|
-
: children(resolve);
|
|
45
|
+
// Normally content is a pending promise -> use() suspends and the wrapping
|
|
46
|
+
// Suspense shows the loading() fallback. forceAwait paths (popstate,
|
|
47
|
+
// stale-revalidation, fully-prefetched nav) instead pass the ALREADY-RESOLVED
|
|
48
|
+
// node so first render does not suspend for a microtask and flash the loading()
|
|
49
|
+
// fallback on a NORMAL (non-transition) commit. The wrapper tree
|
|
50
|
+
// (RouteContentWrapper > Suspense > Suspender) is identical either way, so this
|
|
51
|
+
// preserves tree structure (see docs/tree-structure.md) — only whether use()
|
|
52
|
+
// suspends differs, exactly like LoaderResolver's resolved-data branch.
|
|
53
|
+
return content instanceof Promise ? use(content) : content;
|
|
90
54
|
};
|
|
91
55
|
|
|
92
56
|
/**
|