@rangojs/router 0.20.0 → 0.22.0
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/dist/bin/rango.js +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -35,11 +35,19 @@
|
|
|
35
35
|
* useRouter().replace kind "replace", refresh() kind "revalidate".
|
|
36
36
|
* Back/forward, action and optimistic clientUrls() swap decisions are
|
|
37
37
|
* e2e territory.
|
|
38
|
+
* - Location state reaches readers the way it does in production:
|
|
39
|
+
* NavigationProvider holds the committed entry's state next to the
|
|
40
|
+
* payload, so a navigation whose loader the test holds keeps the entry
|
|
41
|
+
* being left on screen with its own state. A `popstate` event is a
|
|
42
|
+
* back/forward onto the entry history.state holds; the tree has one
|
|
43
|
+
* location, so no page is restored with it. A server action's state, a
|
|
44
|
+
* redirect's and an intercept's are e2e territory.
|
|
38
45
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
39
46
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
40
47
|
* useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
|
|
41
48
|
* descendant pending state, useLoader/useFetchLoader (seeded data),
|
|
42
|
-
* useLocationState (seeded), and useHandle
|
|
49
|
+
* useLocationState (seeded, or written by a navigation), and useHandle
|
|
50
|
+
* (seeded).
|
|
43
51
|
* Basename-mounted apps: pass the `basename` option so useRouter().basename,
|
|
44
52
|
* <Link> prefixing, and useMount/useHref resolve against the mount prefix
|
|
45
53
|
* (without it they resolve at the root "/"). For an include("/shop", ...)
|
|
@@ -48,7 +56,12 @@
|
|
|
48
56
|
*/
|
|
49
57
|
|
|
50
58
|
import { decideTransitionGatedOff } from "../browser/transition-when.js";
|
|
51
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
StrictMode,
|
|
61
|
+
useEffect,
|
|
62
|
+
type ReactNode,
|
|
63
|
+
type ComponentType,
|
|
64
|
+
} from "react";
|
|
52
65
|
import type { RenderResult } from "@testing-library/react";
|
|
53
66
|
import { renderSegments } from "../segment-system.js";
|
|
54
67
|
import {
|
|
@@ -62,6 +75,7 @@ import {
|
|
|
62
75
|
shouldStartViewTransition,
|
|
63
76
|
} from "../browser/partial-update.js";
|
|
64
77
|
import type {
|
|
78
|
+
NavigateOptions,
|
|
65
79
|
NavigateOptionsInternal,
|
|
66
80
|
NavigationStore,
|
|
67
81
|
NavigationBridge,
|
|
@@ -80,10 +94,13 @@ import type {
|
|
|
80
94
|
TransitionConfig,
|
|
81
95
|
TransitionWhenKind,
|
|
82
96
|
} from "../types.js";
|
|
83
|
-
import
|
|
97
|
+
import {
|
|
98
|
+
setLocationStateVersion,
|
|
99
|
+
type LocationStateDefinition,
|
|
100
|
+
} from "../browser/react/location-state-shared.js";
|
|
84
101
|
import {
|
|
85
102
|
buildHistoryState,
|
|
86
|
-
|
|
103
|
+
clearLocationStateOnDocumentLoad,
|
|
87
104
|
pushHistoryWithIdx,
|
|
88
105
|
resolveNavigationState,
|
|
89
106
|
} from "../browser/history-state.js";
|
|
@@ -93,9 +110,9 @@ import type { ThemeConfig } from "../theme/types.js";
|
|
|
93
110
|
import { resolveThemeConfig } from "../theme/constants.js";
|
|
94
111
|
import { isUnderTestRunner } from "../runtime-env.js";
|
|
95
112
|
import { setupNavigationBridgeDelegatedPrefetch } from "../browser/navigation-bridge.js";
|
|
96
|
-
import { resetAdaptiveStrategyForTesting } from "../browser/prefetch/default-strategy.js";
|
|
97
113
|
import { resetPrefetchObserverForTesting } from "../browser/prefetch/observer.js";
|
|
98
114
|
import type { PrefetchStrategy } from "../router/prefetch-default.js";
|
|
115
|
+
import { auditTreeCause } from "../suspense-audit.js";
|
|
99
116
|
|
|
100
117
|
const TEST_ORIGIN = "http://localhost";
|
|
101
118
|
let activePrefetchRegistrations = 0;
|
|
@@ -230,6 +247,8 @@ export interface RenderRouteOptions {
|
|
|
230
247
|
* `useLocationState(StateDef)`. Like loaders, a real `createLocationState()`
|
|
231
248
|
* handle has an empty injected key in a bare test, so pass `[def, value]`
|
|
232
249
|
* pairs; renderRoute assigns a synthetic key and writes it to `history.state`.
|
|
250
|
+
* With `hydrate: true` a `clearOnReload` definition's seed is removed before
|
|
251
|
+
* hydration, as on a document load.
|
|
233
252
|
*
|
|
234
253
|
* @example
|
|
235
254
|
* renderRoute([{ path: "/", Component: FlashBanner }], {
|
|
@@ -265,6 +284,24 @@ export interface RenderRouteOptions {
|
|
|
265
284
|
* computes the segment id for you). Merged with `handles`.
|
|
266
285
|
*/
|
|
267
286
|
handle?: HandleDataSeed;
|
|
287
|
+
/**
|
|
288
|
+
* Handle values a document delivers after hydration, as a loader's push
|
|
289
|
+
* that is not in the document's handle snapshot is (a push made after the
|
|
290
|
+
* handler output settled, or any loader push on a PPR shell HIT). Each
|
|
291
|
+
* entry is `[handle, values]`: the handle's values after the update, which
|
|
292
|
+
* replace its `handles` seed once the root has hydrated (or mounted). With
|
|
293
|
+
* `hydrate: true` the server HTML is rendered from `handles`, and a reader
|
|
294
|
+
* inside a `<Suspense>` boundary hydrates after the update was applied, as
|
|
295
|
+
* a boundary that hydrates after the root does in production.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* const { serverHtml, recoverableErrors } = await renderRoute(routes, {
|
|
299
|
+
* hydrate: true,
|
|
300
|
+
* handles: [[Notes, ["from-handler"]]],
|
|
301
|
+
* lateHandles: [[Notes, ["from-handler", "from-loader"]]],
|
|
302
|
+
* });
|
|
303
|
+
*/
|
|
304
|
+
lateHandles?: ReadonlyArray<readonly [Handle<any, any>, unknown[]]>;
|
|
268
305
|
/**
|
|
269
306
|
* Route name -> pattern map. Informational for parity with the server test
|
|
270
307
|
* context; client useReverse takes its map directly as an argument, so this
|
|
@@ -326,6 +363,25 @@ export interface RenderRouteOptions {
|
|
|
326
363
|
* not override a `"none"` default.
|
|
327
364
|
*/
|
|
328
365
|
defaultPrefetch?: PrefetchStrategy;
|
|
366
|
+
/**
|
|
367
|
+
* Hydrate instead of mounting fresh: the same element is rendered to HTML
|
|
368
|
+
* (react-dom/server `renderToString`), placed in the container and hydrated.
|
|
369
|
+
* The result gains `serverHtml` and `recoverableErrors`
|
|
370
|
+
* (RenderRouteHydrateResult). `data-hydrated` is set on `<html>` after
|
|
371
|
+
* hydration, as production's root does, and removed on unmount.
|
|
372
|
+
*
|
|
373
|
+
* Limits:
|
|
374
|
+
* - The server pass runs without `window` and `document`, and only those. A
|
|
375
|
+
* hook that reads a global that stays defined there (`history`,
|
|
376
|
+
* `localStorage`, `navigator`) outside a server snapshot renders the same
|
|
377
|
+
* in both passes here and can still mismatch in real SSR.
|
|
378
|
+
* - Content that suspends in the server pass (`lazy`, a pending or untracked
|
|
379
|
+
* `use()` promise) is emitted as its fallback and client-rendered, not
|
|
380
|
+
* hydrated; React reports the boundary in `recoverableErrors`. Outside any
|
|
381
|
+
* `<Suspense>` it rejects.
|
|
382
|
+
* - Needs @testing-library/react >= 16.2.0; throws on older versions.
|
|
383
|
+
*/
|
|
384
|
+
hydrate?: boolean;
|
|
329
385
|
}
|
|
330
386
|
|
|
331
387
|
/**
|
|
@@ -356,10 +412,20 @@ export interface TestRouterHandle {
|
|
|
356
412
|
* `options.transition: false` is the per-navigation opt-out
|
|
357
413
|
* (`router.push(url, { transition: false })`, `<Link transition={false}>`):
|
|
358
414
|
* the commit is urgent and no transition({ when }) predicate is called.
|
|
415
|
+
*
|
|
416
|
+
* `options.state` and `options.replace: true` write the history entry the
|
|
417
|
+
* way `useRouter().push(url, { state })` and `.replace(url, { state })` do,
|
|
418
|
+
* so a test can carry location state on a navigation whose loader it
|
|
419
|
+
* holds: `state` pushes an entry with it, `replace: true` replaces the
|
|
420
|
+
* current entry (with `state`, or with none, which drops the entry's
|
|
421
|
+
* location state as a production replace does). With neither, history is
|
|
422
|
+
* left alone and readers keep the state they have: `navigate(url)` and
|
|
423
|
+
* `navigate(url, { replace: false })` are the same call.
|
|
359
424
|
*/
|
|
360
425
|
navigate(
|
|
361
426
|
url: string,
|
|
362
|
-
options?: Pick<RenderRouteOptions, "loaders"> &
|
|
427
|
+
options?: Pick<RenderRouteOptions, "loaders"> &
|
|
428
|
+
Pick<NavigateOptions, "state" | "replace" | "transition">,
|
|
363
429
|
): Promise<void>;
|
|
364
430
|
/**
|
|
365
431
|
* Re-render the current location, as router.refresh() does: a spec's
|
|
@@ -379,6 +445,21 @@ export interface TestRouterHandle {
|
|
|
379
445
|
/** Result of renderRoute: RTL's render result plus the router handle. */
|
|
380
446
|
export type RenderRouteResult = RenderResult & { router: TestRouterHandle };
|
|
381
447
|
|
|
448
|
+
/** Result of renderRoute with `hydrate: true`. */
|
|
449
|
+
export type RenderRouteHydrateResult = RenderRouteResult & {
|
|
450
|
+
/** The HTML the server pass produced, as placed in the container. */
|
|
451
|
+
serverHtml: string;
|
|
452
|
+
/**
|
|
453
|
+
* Messages of the errors React recovered from (`onRecoverableError`): a
|
|
454
|
+
* hydration mismatch that made it re-render on the client, or a boundary the
|
|
455
|
+
* server pass could not finish. Empty means the tree hydrated as rendered.
|
|
456
|
+
* Live: React appends for as long as the root is mounted. An attribute-only
|
|
457
|
+
* mismatch is NOT here — React keeps the server attribute and only logs it
|
|
458
|
+
* with `console.error` in development.
|
|
459
|
+
*/
|
|
460
|
+
recoverableErrors: string[];
|
|
461
|
+
};
|
|
462
|
+
|
|
382
463
|
interface ResolvedMatch {
|
|
383
464
|
params: Record<string, string>;
|
|
384
465
|
pathname: string;
|
|
@@ -410,7 +491,6 @@ function DelegatedPrefetchRegistration({
|
|
|
410
491
|
}
|
|
411
492
|
pendingPrefetchReset = undefined;
|
|
412
493
|
resetPrefetchObserverForTesting();
|
|
413
|
-
resetAdaptiveStrategyForTesting();
|
|
414
494
|
});
|
|
415
495
|
}
|
|
416
496
|
}
|
|
@@ -419,6 +499,46 @@ function DelegatedPrefetchRegistration({
|
|
|
419
499
|
return null;
|
|
420
500
|
}
|
|
421
501
|
|
|
502
|
+
/**
|
|
503
|
+
* initBrowserApp calls the bridge's registerLinkInterception, which is where
|
|
504
|
+
* production listens for `popstate` (navigation-bridge.ts); a rendered tree
|
|
505
|
+
* has no initBrowserApp.
|
|
506
|
+
*/
|
|
507
|
+
function LinkInterceptionRegistration({
|
|
508
|
+
bridge,
|
|
509
|
+
}: {
|
|
510
|
+
bridge: NavigationBridge;
|
|
511
|
+
}): null {
|
|
512
|
+
useEffect(() => bridge.registerLinkInterception(), [bridge]);
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* The marker production's root sets from its effect after hydration
|
|
518
|
+
* (browser/rsc-router.tsx Rango), so a boundary that hydrates in a later pass
|
|
519
|
+
* finds it set, as in production. Removed on unmount: a test unmounts its
|
|
520
|
+
* root, production never does.
|
|
521
|
+
*/
|
|
522
|
+
function HydratedMarker(): null {
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
document.documentElement.dataset.hydrated = "";
|
|
525
|
+
return () => document.documentElement.removeAttribute("data-hydrated");
|
|
526
|
+
}, []);
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Runs `run` from an effect after the tree's. That is where production's late
|
|
532
|
+
* handle channel writes a push that arrives after the root hydrated (the store
|
|
533
|
+
* holds it while the hydration window is open, EventController.lockHydration),
|
|
534
|
+
* and where the document has been revealed (browser/rsc-router.tsx
|
|
535
|
+
* documentRevealed).
|
|
536
|
+
*/
|
|
537
|
+
function AfterTree({ run }: { run: () => void }): null {
|
|
538
|
+
useEffect(run, [run]);
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
422
542
|
function matchLeaf(
|
|
423
543
|
pattern: string,
|
|
424
544
|
pathname: string,
|
|
@@ -504,6 +624,14 @@ function buildSegments(
|
|
|
504
624
|
return segments;
|
|
505
625
|
}
|
|
506
626
|
|
|
627
|
+
export function renderRoute(
|
|
628
|
+
routes: RenderRouteSpec[],
|
|
629
|
+
options: RenderRouteOptions & { hydrate: true },
|
|
630
|
+
): Promise<RenderRouteHydrateResult>;
|
|
631
|
+
export function renderRoute(
|
|
632
|
+
routes: RenderRouteSpec[],
|
|
633
|
+
options?: RenderRouteOptions,
|
|
634
|
+
): Promise<RenderRouteResult>;
|
|
507
635
|
export async function renderRoute(
|
|
508
636
|
routes: RenderRouteSpec[],
|
|
509
637
|
options: RenderRouteOptions = {},
|
|
@@ -539,11 +667,17 @@ export async function renderRoute(
|
|
|
539
667
|
const loaderData = seedLoaders(options.loaderData ?? {}, options.loaders);
|
|
540
668
|
|
|
541
669
|
if (typeof window !== "undefined") {
|
|
670
|
+
// The tree has no app version (its bridge reports none), so a seed needs
|
|
671
|
+
// none either. Reset what an initBrowserApp in the same file may have left.
|
|
672
|
+
setLocationStateVersion(undefined);
|
|
542
673
|
const stateObj: Record<string, unknown> = {};
|
|
543
674
|
for (const [def, value] of options.locationState ?? []) {
|
|
544
675
|
stateObj[withLocationStateKey(def).__rsc_ls_key] = value;
|
|
545
676
|
}
|
|
546
677
|
window.history.replaceState(stateObj, "");
|
|
678
|
+
// Hydrate mode is a document load of the seeded entry: run start-up's
|
|
679
|
+
// step (initBrowserApp), not a copy of it.
|
|
680
|
+
if (options.hydrate) clearLocationStateOnDocumentLoad();
|
|
547
681
|
}
|
|
548
682
|
|
|
549
683
|
const resolve = (pathname: string): ResolvedMatch => {
|
|
@@ -609,14 +743,40 @@ export async function renderRoute(
|
|
|
609
743
|
|
|
610
744
|
const eventController = createEventController({ initialLocation: url });
|
|
611
745
|
eventController.setParams(initialMatch.params);
|
|
746
|
+
// The seeded entry, as initBrowserApp commits the document's.
|
|
747
|
+
eventController.commitLocationState(window.history.state);
|
|
612
748
|
// Resolve-by-default: resolve any deferred (Promise) seeded handle values
|
|
613
749
|
// before applying, so the seeded handles reach collect/useHandle resolved —
|
|
614
750
|
// matching what the server/client do in a real app.
|
|
615
751
|
const resolvedSeed = await resolveDeferredHandleValues(handleSeed);
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
752
|
+
const initialSegmentIds = initialSegments.map((s) => s.id);
|
|
753
|
+
eventController.setHandleData(resolvedSeed, initialSegmentIds);
|
|
754
|
+
// As initBrowserApp before hydrateRoot: until the provider's barrier commit,
|
|
755
|
+
// handle reads return this state and later writes wait.
|
|
756
|
+
if (options.hydrate) eventController.lockHydration();
|
|
757
|
+
let resolveDocumentRevealed: (() => void) | undefined;
|
|
758
|
+
const documentRevealed = options.hydrate
|
|
759
|
+
? new Promise<void>((resolve) => {
|
|
760
|
+
resolveDocumentRevealed = resolve;
|
|
761
|
+
})
|
|
762
|
+
: undefined;
|
|
763
|
+
// A late update carries the full state: the seed with these handles' values
|
|
764
|
+
// replaced, resolved as the browser resolves a late yield before applying.
|
|
765
|
+
let applyLateHandles: (() => void) | undefined;
|
|
766
|
+
if (options.lateHandles && leafRouteSegmentId !== undefined) {
|
|
767
|
+
const lateSeed = cloneHandleSeed(resolvedSeed);
|
|
768
|
+
for (const [handle, values] of options.lateHandles) {
|
|
769
|
+
const id = (handle as unknown as { $$id: string }).$$id;
|
|
770
|
+
(lateSeed[id] ??= {})[leafRouteSegmentId] = values;
|
|
771
|
+
}
|
|
772
|
+
const lateState = await resolveDeferredHandleValues(lateSeed);
|
|
773
|
+
// Flushed, not left to the notify timer: renderRoute's act() has
|
|
774
|
+
// returned by then.
|
|
775
|
+
applyLateHandles = () => {
|
|
776
|
+
eventController.setHandleData(lateState, initialSegmentIds);
|
|
777
|
+
eventController.flushRouteState();
|
|
778
|
+
};
|
|
779
|
+
}
|
|
620
780
|
|
|
621
781
|
let warnedNavLifecycle = false;
|
|
622
782
|
const navigate = async (
|
|
@@ -633,10 +793,11 @@ export async function renderRoute(
|
|
|
633
793
|
// A useRouter().push/replace or <Link> navigation (`history`) writes its
|
|
634
794
|
// entry the way production does (navigation-bridge.ts navigate ->
|
|
635
795
|
// navigation-transaction.ts commit): the dev state check, typed entries
|
|
636
|
-
// spread onto history.state, the idx stamp, and the
|
|
637
|
-
//
|
|
638
|
-
// put: renderRoute tracks location on the event
|
|
639
|
-
// window.location. router.navigate()
|
|
796
|
+
// spread onto history.state, the idx stamp, and the entry state committed
|
|
797
|
+
// on the event controller, which the provider takes with the payload.
|
|
798
|
+
// The URL stays put: renderRoute tracks location on the event
|
|
799
|
+
// controller, not window.location. router.navigate() without `state` or
|
|
800
|
+
// `replace: true`, and refresh(), leave history alone.
|
|
640
801
|
const history = navOptions?.history;
|
|
641
802
|
const historyState = history
|
|
642
803
|
? buildHistoryState(
|
|
@@ -702,30 +863,28 @@ export async function renderRoute(
|
|
|
702
863
|
eventController.setParams(match.params);
|
|
703
864
|
store.setCurrentUrl(nextUrl.href);
|
|
704
865
|
store.setSegmentIds(segments.map((s) => s.id));
|
|
705
|
-
let notifyLocationState = false;
|
|
706
866
|
if (history) {
|
|
707
|
-
notifyLocationState =
|
|
708
|
-
hasLocationState(window.history.state) ||
|
|
709
|
-
hasLocationState(historyState);
|
|
710
867
|
pushHistoryWithIdx(
|
|
711
868
|
historyState ?? null,
|
|
712
869
|
window.location.href,
|
|
713
870
|
history.replace ?? false,
|
|
714
871
|
);
|
|
872
|
+
eventController.commitLocationState(historyState);
|
|
715
873
|
}
|
|
716
874
|
const emit: UpdateSubscriber = (update) => store.emitUpdate(update);
|
|
717
875
|
await act(async () => {
|
|
876
|
+
// As production's emitters name their cause (suspense-audit.ts I6).
|
|
877
|
+
auditTreeCause("navigation");
|
|
718
878
|
// Production's transition() lane (browser/partial-update.ts). Its
|
|
719
879
|
// same-structure / fully-prefetched / optimistic hold lanes are not
|
|
720
880
|
// modeled: without transition() the commit stays urgent.
|
|
721
881
|
if (shouldStartViewTransition(segments, gatedOff)) {
|
|
722
|
-
commitInTransition(emit, segments, { root, metadata }, [
|
|
882
|
+
commitInTransition(store.loaders, emit, segments, { root, metadata }, [
|
|
883
|
+
"navigation",
|
|
884
|
+
]);
|
|
723
885
|
} else {
|
|
724
886
|
emit({ root, metadata });
|
|
725
887
|
}
|
|
726
|
-
if (notifyLocationState) {
|
|
727
|
-
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
728
|
-
}
|
|
729
888
|
});
|
|
730
889
|
};
|
|
731
890
|
|
|
@@ -739,8 +898,20 @@ export async function renderRoute(
|
|
|
739
898
|
navigate: (target, navigateOptions) =>
|
|
740
899
|
navigate(target, { history: navigateOptions ?? {} }),
|
|
741
900
|
refresh: () => refresh(),
|
|
742
|
-
|
|
743
|
-
|
|
901
|
+
// Back/forward onto the entry history.state holds. The tree has one
|
|
902
|
+
// location, so nothing is restored: a treeless commit of that entry, as
|
|
903
|
+
// production's traversal that keeps every segment
|
|
904
|
+
// (navigation-transaction.ts commit). Flushed, not left to the notify
|
|
905
|
+
// timer: the dispatching test's act() has returned by then.
|
|
906
|
+
handlePopstate: async () => {
|
|
907
|
+
eventController.commitLocationState(window.history.state, true);
|
|
908
|
+
eventController.flushRouteState();
|
|
909
|
+
},
|
|
910
|
+
registerLinkInterception: () => {
|
|
911
|
+
const onPopstate = (): void => void bridge.handlePopstate();
|
|
912
|
+
window.addEventListener("popstate", onPopstate);
|
|
913
|
+
return () => window.removeEventListener("popstate", onPopstate);
|
|
914
|
+
},
|
|
744
915
|
registerDelegatedPrefetch: () =>
|
|
745
916
|
setupNavigationBridgeDelegatedPrefetch(
|
|
746
917
|
store,
|
|
@@ -764,6 +935,54 @@ export async function renderRoute(
|
|
|
764
935
|
outletPending: options.outletPending,
|
|
765
936
|
});
|
|
766
937
|
|
|
938
|
+
// The marker sits after the provider so its effect runs after the tree's,
|
|
939
|
+
// like the effect of production's root component.
|
|
940
|
+
const ui = (
|
|
941
|
+
<>
|
|
942
|
+
<NavigationProvider
|
|
943
|
+
store={store}
|
|
944
|
+
eventController={eventController}
|
|
945
|
+
initialPayload={{ root: initialTree, metadata: initialMetadata }}
|
|
946
|
+
bridge={bridge}
|
|
947
|
+
basename={basename}
|
|
948
|
+
themeConfig={
|
|
949
|
+
options.theme === undefined ? null : resolveThemeConfig(options.theme)
|
|
950
|
+
}
|
|
951
|
+
nonce={options.nonce}
|
|
952
|
+
hydration={documentRevealed ? { settled: documentRevealed } : undefined}
|
|
953
|
+
/>
|
|
954
|
+
<DelegatedPrefetchRegistration bridge={bridge} />
|
|
955
|
+
<LinkInterceptionRegistration bridge={bridge} />
|
|
956
|
+
{options.hydrate && <HydratedMarker />}
|
|
957
|
+
{applyLateHandles && <AfterTree run={applyLateHandles} />}
|
|
958
|
+
{resolveDocumentRevealed && <AfterTree run={resolveDocumentRevealed} />}
|
|
959
|
+
</>
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
let serverHtml: string | undefined;
|
|
963
|
+
const recoverableErrors: string[] = [];
|
|
964
|
+
if (options.hydrate) {
|
|
965
|
+
// RTL forwards onRecoverableError to hydrateRoot from 16.2.0. 16.0-16.1
|
|
966
|
+
// are inside the peer range and drop it (their render() never names the
|
|
967
|
+
// option), which would leave `recoverableErrors` empty on a real mismatch.
|
|
968
|
+
if (!String(render).includes("onRecoverableError")) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
"renderRoute: `hydrate` needs @testing-library/react >= 16.2.0. Older " +
|
|
971
|
+
"versions do not pass onRecoverableError to hydrateRoot, so a " +
|
|
972
|
+
"hydration mismatch would go unreported.",
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const { getConfig } = await import("@testing-library/react");
|
|
976
|
+
const { renderToString } = await import("react-dom/server");
|
|
977
|
+
// RTL wraps the hydrated element the same way.
|
|
978
|
+
const serverUi = getConfig().reactStrictMode ? (
|
|
979
|
+
<StrictMode>{ui}</StrictMode>
|
|
980
|
+
) : (
|
|
981
|
+
ui
|
|
982
|
+
);
|
|
983
|
+
serverHtml = withoutBrowserGlobals(() => renderToString(serverUi));
|
|
984
|
+
}
|
|
985
|
+
|
|
767
986
|
// Wrap render in an awaited async act so a tree that suspends (async loaders,
|
|
768
987
|
// loading states, deferred handle entries that arrive as a Promise) settles its
|
|
769
988
|
// Suspense within act — otherwise React orphans the resolution ("a component
|
|
@@ -772,30 +991,32 @@ export async function renderRoute(
|
|
|
772
991
|
let result!: Awaited<ReturnType<typeof render>>;
|
|
773
992
|
const container = document.body.appendChild(document.createElement("div"));
|
|
774
993
|
prefetchRoot = container;
|
|
994
|
+
if (serverHtml !== undefined) container.innerHTML = serverHtml;
|
|
775
995
|
await act(async () => {
|
|
776
|
-
result = render(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
nonce={options.nonce}
|
|
790
|
-
/>
|
|
791
|
-
<DelegatedPrefetchRegistration bridge={bridge} />
|
|
792
|
-
</>,
|
|
793
|
-
{ baseElement: document.body, container },
|
|
794
|
-
);
|
|
996
|
+
result = render(ui, {
|
|
997
|
+
baseElement: document.body,
|
|
998
|
+
container,
|
|
999
|
+
...(serverHtml !== undefined && {
|
|
1000
|
+
hydrate: true,
|
|
1001
|
+
onRecoverableError: (error: unknown) => {
|
|
1002
|
+
recoverableErrors.push(
|
|
1003
|
+
error instanceof Error ? error.message : String(error),
|
|
1004
|
+
);
|
|
1005
|
+
},
|
|
1006
|
+
}),
|
|
1007
|
+
});
|
|
795
1008
|
});
|
|
796
1009
|
|
|
797
1010
|
const router: TestRouterHandle = {
|
|
798
|
-
navigate,
|
|
1011
|
+
navigate: (target, navOptions) =>
|
|
1012
|
+
navigate(target, {
|
|
1013
|
+
loaders: navOptions?.loaders,
|
|
1014
|
+
transition: navOptions?.transition,
|
|
1015
|
+
history:
|
|
1016
|
+
navOptions?.state !== undefined || navOptions?.replace
|
|
1017
|
+
? navOptions
|
|
1018
|
+
: undefined,
|
|
1019
|
+
}),
|
|
799
1020
|
refresh,
|
|
800
1021
|
pathname: () => new URL(eventController.getLocation().href).pathname,
|
|
801
1022
|
params: () => eventController.getParams(),
|
|
@@ -803,7 +1024,33 @@ export async function renderRoute(
|
|
|
803
1024
|
eventController,
|
|
804
1025
|
};
|
|
805
1026
|
|
|
806
|
-
return Object.assign(result, { router });
|
|
1027
|
+
if (serverHtml === undefined) return Object.assign(result, { router });
|
|
1028
|
+
return Object.assign(result, { router, serverHtml, recoverableErrors });
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Runs the server pass without `window` and `document`, the two globals
|
|
1033
|
+
* SSR-aware code probes. A DOM test environment defines both, so with them in
|
|
1034
|
+
* place a `typeof window` branch takes its client side, renders the client
|
|
1035
|
+
* HTML, and the mismatch it causes in production hydrates clean. `fn` must be
|
|
1036
|
+
* synchronous (renderToString, not a streaming entry): nothing else may run
|
|
1037
|
+
* while they are gone.
|
|
1038
|
+
*/
|
|
1039
|
+
function withoutBrowserGlobals<T>(fn: () => T): T {
|
|
1040
|
+
const scope = globalThis as { window?: unknown; document?: unknown };
|
|
1041
|
+
const windowDescriptor = Object.getOwnPropertyDescriptor(scope, "window")!;
|
|
1042
|
+
const documentDescriptor = Object.getOwnPropertyDescriptor(
|
|
1043
|
+
scope,
|
|
1044
|
+
"document",
|
|
1045
|
+
)!;
|
|
1046
|
+
try {
|
|
1047
|
+
delete scope.window;
|
|
1048
|
+
delete scope.document;
|
|
1049
|
+
return fn();
|
|
1050
|
+
} finally {
|
|
1051
|
+
Object.defineProperty(scope, "window", windowDescriptor);
|
|
1052
|
+
Object.defineProperty(scope, "document", documentDescriptor);
|
|
1053
|
+
}
|
|
807
1054
|
}
|
|
808
1055
|
|
|
809
1056
|
function makeMetadata(
|