@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -25,15 +25,18 @@ function parsePathname(pathname: string): string[] {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Build segments state from event controller
|
|
28
|
+
* Build segments state from event controller. `segmentIds` is the
|
|
29
|
+
* route-only list (parallels and loaders stripped) — distinct from the
|
|
30
|
+
* controller's `segmentOrder` which drives handle collection and includes
|
|
31
|
+
* parallel slot ids.
|
|
29
32
|
*/
|
|
30
33
|
function buildSegmentsState(
|
|
31
34
|
location: URL,
|
|
32
|
-
|
|
35
|
+
routeSegmentIds: string[],
|
|
33
36
|
): SegmentsState {
|
|
34
37
|
return {
|
|
35
38
|
path: parsePathname(location.pathname),
|
|
36
|
-
segmentIds:
|
|
39
|
+
segmentIds: routeSegmentIds,
|
|
37
40
|
location,
|
|
38
41
|
};
|
|
39
42
|
}
|
|
@@ -74,7 +77,7 @@ export function useSegments<T>(
|
|
|
74
77
|
const handleState = ctx.eventController.getHandleState();
|
|
75
78
|
const segmentsState = buildSegmentsState(
|
|
76
79
|
location as URL,
|
|
77
|
-
handleState.
|
|
80
|
+
handleState.routeSegmentIds,
|
|
78
81
|
);
|
|
79
82
|
return selector ? selector(segmentsState) : segmentsState;
|
|
80
83
|
});
|
|
@@ -83,47 +86,36 @@ export function useSegments<T>(
|
|
|
83
86
|
const selectorRef = useRef(selector);
|
|
84
87
|
selectorRef.current = selector;
|
|
85
88
|
|
|
86
|
-
// Track selector identity to detect when the selector function changes.
|
|
87
|
-
// Only then do we eagerly recompute during render to avoid staleness.
|
|
88
|
-
// Without this guard, no-selector mode causes infinite re-renders because
|
|
89
|
-
// buildSegmentsState creates fresh arrays that fail Object.is checks.
|
|
90
89
|
const prevSelectorIdentity = useRef(selector);
|
|
91
90
|
|
|
92
|
-
// Cache SegmentsState to stabilize nested references (path, segmentIds
|
|
93
|
-
// arrays) so selectors returning composite values don't cause spurious
|
|
94
|
-
// render-time setState calls.
|
|
95
91
|
const segmentsCache = useRef<{
|
|
96
92
|
location: URL;
|
|
97
|
-
|
|
93
|
+
routeSegmentIds: string[];
|
|
98
94
|
state: SegmentsState;
|
|
99
95
|
} | null>(null);
|
|
100
96
|
|
|
101
|
-
// Recompute selected value from current store state and apply selector.
|
|
102
|
-
// Shared by the render-time eager check and the subscription callback.
|
|
103
97
|
function recompute(
|
|
104
98
|
sel: ((state: SegmentsState) => T) | undefined,
|
|
105
99
|
): T | SegmentsState {
|
|
106
100
|
const location = ctx!.eventController.getLocation();
|
|
107
101
|
const handleState = ctx!.eventController.getHandleState();
|
|
108
102
|
|
|
109
|
-
// Reuse cached state when inputs haven't changed by reference,
|
|
110
|
-
// keeping array/object references stable for composite selectors.
|
|
111
103
|
const cache = segmentsCache.current;
|
|
112
104
|
let segmentsState: SegmentsState;
|
|
113
105
|
if (
|
|
114
106
|
cache &&
|
|
115
107
|
cache.location === location &&
|
|
116
|
-
cache.
|
|
108
|
+
cache.routeSegmentIds === handleState.routeSegmentIds
|
|
117
109
|
) {
|
|
118
110
|
segmentsState = cache.state;
|
|
119
111
|
} else {
|
|
120
112
|
segmentsState = buildSegmentsState(
|
|
121
113
|
location as URL,
|
|
122
|
-
handleState.
|
|
114
|
+
handleState.routeSegmentIds,
|
|
123
115
|
);
|
|
124
116
|
segmentsCache.current = {
|
|
125
117
|
location: location as URL,
|
|
126
|
-
|
|
118
|
+
routeSegmentIds: handleState.routeSegmentIds,
|
|
127
119
|
state: segmentsState,
|
|
128
120
|
};
|
|
129
121
|
}
|
|
@@ -162,8 +154,6 @@ export function useSegments<T>(
|
|
|
162
154
|
unsubscribeNav();
|
|
163
155
|
unsubscribeHandles();
|
|
164
156
|
};
|
|
165
|
-
// Stable subscription: selector changes are handled via selectorRef,
|
|
166
|
-
// state comparison uses prevState ref. No re-subscribe needed.
|
|
167
157
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
158
|
}, []);
|
|
169
159
|
|
|
@@ -24,6 +24,51 @@ export function emptyResponse(): Response {
|
|
|
24
24
|
return new Response(null, { status: 200 });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Whether an RSC content response carries a server-stamped router identity
|
|
29
|
+
* (`X-RSC-Router-Id`) that DIFFERS from the id this client expects (its own
|
|
30
|
+
* routerId, also sent as `_rsc_rid`). Pre-decode integrity check: lets a caller
|
|
31
|
+
* refuse a foreign app's payload before `createFromFetch` imports its chunks.
|
|
32
|
+
*
|
|
33
|
+
* True ONLY when both the header and the expected id are present and differ. An
|
|
34
|
+
* absent header (control-only reload/redirect responses are not stamped) or an
|
|
35
|
+
* absent expected id (e.g. before the client is seeded) is a pass-through —
|
|
36
|
+
* never a false reject.
|
|
37
|
+
*/
|
|
38
|
+
export function isForeignRouterId(
|
|
39
|
+
response: Response,
|
|
40
|
+
expectedId: string | undefined,
|
|
41
|
+
): boolean {
|
|
42
|
+
const got = response.headers.get("X-RSC-Router-Id");
|
|
43
|
+
if (!got || !expectedId) return false;
|
|
44
|
+
return got !== expectedId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handle the X-RSC-Reload control header (server requests a full page reload on
|
|
49
|
+
* a version mismatch). Returns a short-circuit response when the header is
|
|
50
|
+
* present -- emptyResponse() if the URL was blocked by origin validation, or a
|
|
51
|
+
* never-resolving promise while the page reloads -- and null when absent, so
|
|
52
|
+
* the caller continues processing (e.g. the X-RSC-Redirect check). Scoped to
|
|
53
|
+
* X-RSC-Reload only; redirect handling differs between callers.
|
|
54
|
+
*/
|
|
55
|
+
export function handleReloadHeader(
|
|
56
|
+
response: Response,
|
|
57
|
+
opts: { onBlocked: () => void; onReload: (url: string) => void },
|
|
58
|
+
): Response | Promise<Response> | null {
|
|
59
|
+
const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
|
|
60
|
+
if (reload === "blocked") {
|
|
61
|
+
opts.onBlocked();
|
|
62
|
+
return emptyResponse();
|
|
63
|
+
}
|
|
64
|
+
if (reload) {
|
|
65
|
+
opts.onReload(reload.url);
|
|
66
|
+
window.location.href = reload.url;
|
|
67
|
+
return new Promise<Response>(() => {});
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
27
72
|
/**
|
|
28
73
|
* Tee a response body for RSC parsing and stream completion tracking.
|
|
29
74
|
* Returns a new Response with one branch; the other is consumed to detect
|
|
@@ -31,11 +76,17 @@ export function emptyResponse(): Response {
|
|
|
31
76
|
*
|
|
32
77
|
* If the response has no body, onComplete fires synchronously.
|
|
33
78
|
* If signal is provided, an abort cancels the tracking reader.
|
|
79
|
+
*
|
|
80
|
+
* `silent` suppresses the stream-error log. Prefetch passes it: a speculative,
|
|
81
|
+
* low-priority prefetch that is aborted or never consumed can error its stream
|
|
82
|
+
* benignly, which is not worth surfacing. The fresh-navigation path keeps the
|
|
83
|
+
* log (default), where a stream error reflects a real failed navigation.
|
|
34
84
|
*/
|
|
35
85
|
export function teeWithCompletion(
|
|
36
86
|
response: Response,
|
|
37
87
|
onComplete: () => void,
|
|
38
88
|
signal?: AbortSignal,
|
|
89
|
+
silent = false,
|
|
39
90
|
): Response {
|
|
40
91
|
if (!response.body) {
|
|
41
92
|
onComplete();
|
|
@@ -59,7 +110,7 @@ export function teeWithCompletion(
|
|
|
59
110
|
onComplete();
|
|
60
111
|
}
|
|
61
112
|
})().catch((error) => {
|
|
62
|
-
if (!signal?.aborted) {
|
|
113
|
+
if (!silent && !signal?.aborted) {
|
|
63
114
|
console.error("[Browser] Error reading tracking stream:", error);
|
|
64
115
|
}
|
|
65
116
|
onComplete();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
generateHistoryKey,
|
|
9
9
|
} from "./navigation-store.js";
|
|
10
10
|
import { createEventController } from "./event-controller.js";
|
|
11
|
+
import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
11
12
|
import { createNavigationClient } from "./navigation-client.js";
|
|
12
13
|
import { createServerActionBridge } from "./server-action-bridge.js";
|
|
13
14
|
import { createNavigationBridge } from "./navigation-bridge.js";
|
|
@@ -22,11 +23,15 @@ import type {
|
|
|
22
23
|
import type { EventController } from "./event-controller.js";
|
|
23
24
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
25
|
import { initRangoState } from "./rango-state.js";
|
|
26
|
+
import { registerNavigationStore } from "./navigation-store-handle.js";
|
|
25
27
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
28
|
+
import { setPrefetchDecoder } from "./prefetch/fetch.js";
|
|
29
|
+
import { setAppVersion } from "./app-version.js";
|
|
26
30
|
import {
|
|
27
31
|
isInterceptSegment,
|
|
28
32
|
splitInterceptSegments,
|
|
29
33
|
} from "./intercept-utils.js";
|
|
34
|
+
import { createAppShellRef } from "./app-shell.js";
|
|
30
35
|
|
|
31
36
|
// Vite HMR types are provided by vite/client
|
|
32
37
|
|
|
@@ -113,13 +118,22 @@ export interface BrowserAppContext {
|
|
|
113
118
|
warmupEnabled?: boolean;
|
|
114
119
|
/** App version for prefetch version mismatch detection */
|
|
115
120
|
version?: string;
|
|
121
|
+
/**
|
|
122
|
+
* App-shell ref, read through on each render so renderSegments and the
|
|
123
|
+
* NavigationProvider see rootLayout/basename/version without closing over a
|
|
124
|
+
* stale snapshot. Set once from the initial payload and not swapped within a
|
|
125
|
+
* session: a cross-app navigation is a full document load (X-RSC-Reload), so
|
|
126
|
+
* the target app establishes its own shell on load. Theme, warmup, and
|
|
127
|
+
* prefetch TTL are document-lifetime too (see AppShell).
|
|
128
|
+
*/
|
|
129
|
+
appShellRef?: import("./app-shell.js").AppShellRef;
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// Module-level state for the initialized app
|
|
119
133
|
let browserAppContext: BrowserAppContext | null = null;
|
|
120
134
|
|
|
121
135
|
/**
|
|
122
|
-
* Initialize the browser app. Must be called before rendering
|
|
136
|
+
* Initialize the browser app. Must be called before rendering Rango.
|
|
123
137
|
*
|
|
124
138
|
* This function:
|
|
125
139
|
* - Loads the initial RSC payload from the stream
|
|
@@ -139,7 +153,6 @@ export async function initBrowserApp(
|
|
|
139
153
|
initialTheme,
|
|
140
154
|
} = options;
|
|
141
155
|
|
|
142
|
-
// Load initial payload from SSR-injected __FLIGHT_DATA__
|
|
143
156
|
const initialPayload =
|
|
144
157
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
145
158
|
|
|
@@ -164,6 +177,18 @@ export async function initBrowserApp(
|
|
|
164
177
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
165
178
|
});
|
|
166
179
|
|
|
180
|
+
// Register the active store on the module-level handle and wire the
|
|
181
|
+
// jar-divergence observer before any getRangoState() read can detect a
|
|
182
|
+
// cross-tab/server rotation. There is no global store singleton, so this
|
|
183
|
+
// handle is the live reference.
|
|
184
|
+
registerNavigationStore(store);
|
|
185
|
+
|
|
186
|
+
// Seed router identity from the initial SSR payload so the first
|
|
187
|
+
// cross-app SPA navigation can detect the app switch.
|
|
188
|
+
if (initialPayload.metadata?.routerId) {
|
|
189
|
+
store.setRouterId?.(initialPayload.metadata.routerId);
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
// Create event controller for reactive state management
|
|
168
193
|
const eventController = createEventController({
|
|
169
194
|
initialLocation: new URL(window.location.href),
|
|
@@ -198,13 +223,25 @@ export async function initBrowserApp(
|
|
|
198
223
|
// Create composable utilities
|
|
199
224
|
const client = createNavigationClient(deps);
|
|
200
225
|
|
|
201
|
-
//
|
|
202
|
-
|
|
226
|
+
// Capture the per-router app-shell. rootLayout, basename, and version live
|
|
227
|
+
// here and are read through the ref at call time rather than closed over.
|
|
228
|
+
// It is set once from the initial payload and not swapped within a session:
|
|
229
|
+
// a cross-app navigation is a full document load (X-RSC-Reload), so the
|
|
230
|
+
// target app establishes its own shell on load.
|
|
203
231
|
const version = initialPayload.metadata?.version;
|
|
232
|
+
const appShellRef = createAppShellRef({
|
|
233
|
+
routerId: initialPayload.metadata?.routerId,
|
|
234
|
+
rootLayout: initialPayload.metadata?.rootLayout,
|
|
235
|
+
basename: initialPayload.metadata?.basename,
|
|
236
|
+
version,
|
|
237
|
+
});
|
|
204
238
|
|
|
205
|
-
// Initialize the
|
|
206
|
-
//
|
|
207
|
-
|
|
239
|
+
// Initialize the rango state cookie for cache invalidation. The build version
|
|
240
|
+
// busts cached prefetches on deploy; the server-resolved cookie name
|
|
241
|
+
// namespaces the cookie so sibling apps on the same origin don't collide
|
|
242
|
+
// (falls back to the bare default prefix if metadata lacks the name).
|
|
243
|
+
initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
|
|
244
|
+
setAppVersion(version);
|
|
208
245
|
|
|
209
246
|
// Initialize the in-memory prefetch cache TTL from server config.
|
|
210
247
|
// A value of 0 disables the cache; undefined falls back to the module default.
|
|
@@ -213,11 +250,22 @@ export async function initBrowserApp(
|
|
|
213
250
|
initPrefetchCache(prefetchCacheTTL);
|
|
214
251
|
}
|
|
215
252
|
|
|
216
|
-
//
|
|
253
|
+
// Wire the RSC decoder so prefetches decode eagerly and warm the route's
|
|
254
|
+
// client chunks (same createFromFetch the navigation client uses).
|
|
255
|
+
setPrefetchDecoder((response) => deps.createFromFetch<RscPayload>(response));
|
|
256
|
+
|
|
257
|
+
// Create a bound renderSegments that reads rootLayout through the shell ref.
|
|
258
|
+
// The shell is set once at init and not swapped within a session (a cross-app
|
|
259
|
+
// navigation is a full document load), so this always renders this app's
|
|
260
|
+
// Document; reading through the ref just avoids closing over a stale value.
|
|
217
261
|
const renderSegments = (
|
|
218
262
|
segments: ResolvedSegment[],
|
|
219
263
|
options?: RenderSegmentsOptions,
|
|
220
|
-
) =>
|
|
264
|
+
) =>
|
|
265
|
+
baseRenderSegments(segments, {
|
|
266
|
+
...options,
|
|
267
|
+
rootLayout: appShellRef.get().rootLayout,
|
|
268
|
+
});
|
|
221
269
|
|
|
222
270
|
// Lazy reference for navigation bridge — the action bridge is created first
|
|
223
271
|
// but may need to trigger SPA navigation for action redirects.
|
|
@@ -231,10 +279,15 @@ export async function initBrowserApp(
|
|
|
231
279
|
deps,
|
|
232
280
|
onUpdate: (update) => store.emitUpdate(update),
|
|
233
281
|
renderSegments,
|
|
234
|
-
version,
|
|
235
282
|
onNavigate: (url, options) => {
|
|
236
283
|
if (!navigateFn) {
|
|
237
|
-
|
|
284
|
+
// Navigation bridge not wired yet: hard-navigate, but re-validate
|
|
285
|
+
// same-origin defensively so this init-window fallback cannot become an
|
|
286
|
+
// open redirect (the normal path validates inside the navigation bridge).
|
|
287
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
288
|
+
if (safe) {
|
|
289
|
+
window.location.href = safe;
|
|
290
|
+
}
|
|
238
291
|
return Promise.resolve();
|
|
239
292
|
}
|
|
240
293
|
return navigateFn(url, options);
|
|
@@ -249,7 +302,7 @@ export async function initBrowserApp(
|
|
|
249
302
|
client,
|
|
250
303
|
onUpdate: (update) => store.emitUpdate(update),
|
|
251
304
|
renderSegments,
|
|
252
|
-
version,
|
|
305
|
+
version: version,
|
|
253
306
|
});
|
|
254
307
|
|
|
255
308
|
// Connect action redirect → navigation bridge (now that both are initialized)
|
|
@@ -263,75 +316,157 @@ export async function initBrowserApp(
|
|
|
263
316
|
// Build initial tree with rootLayout
|
|
264
317
|
const initialTree = renderSegments(initialPayload.metadata!.segments);
|
|
265
318
|
|
|
266
|
-
// Setup HMR
|
|
319
|
+
// Setup HMR with debounce — burst saves (format-on-save, rapid edits)
|
|
320
|
+
// fire many rsc:update events in quick succession. Without debouncing,
|
|
321
|
+
// each event triggers a fetchPartial() which on slow routes can pile up
|
|
322
|
+
// and overwhelm the worker (cross-request promise issues, 500s).
|
|
267
323
|
if (import.meta.hot) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
324
|
+
let hmrTimer: ReturnType<typeof setTimeout> | null = null;
|
|
325
|
+
let hmrAbort: AbortController | null = null;
|
|
326
|
+
|
|
327
|
+
import.meta.hot.on("rsc:update", () => {
|
|
328
|
+
// Cancel any pending debounce timer
|
|
329
|
+
if (hmrTimer !== null) {
|
|
330
|
+
clearTimeout(hmrTimer);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Abort any in-flight HMR fetch so it doesn't race with the next one
|
|
334
|
+
if (hmrAbort) {
|
|
335
|
+
hmrAbort.abort();
|
|
336
|
+
hmrAbort = null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Debounce: wait 200ms of quiet before fetching
|
|
340
|
+
hmrTimer = setTimeout(async () => {
|
|
341
|
+
hmrTimer = null;
|
|
342
|
+
|
|
343
|
+
// Don't interrupt an active user navigation — startNavigation()
|
|
344
|
+
// would abort it and refetch the old URL (window.location.href
|
|
345
|
+
// hasn't updated yet). The user's navigation will pick up the
|
|
346
|
+
// new server code when it completes. isNavigating covers the
|
|
347
|
+
// full lifecycle (fetching + streaming, before commit) without
|
|
348
|
+
// blocking on server actions.
|
|
349
|
+
if (eventController.getState().isNavigating) {
|
|
350
|
+
console.log("[Rango] HMR: Skipping — navigation in progress");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log("[Rango] HMR: Server update, refetching RSC");
|
|
355
|
+
|
|
356
|
+
const abort = new AbortController();
|
|
357
|
+
hmrAbort = abort;
|
|
358
|
+
|
|
359
|
+
const handle = eventController.startNavigation(window.location.href, {
|
|
360
|
+
replace: true,
|
|
285
361
|
});
|
|
362
|
+
const streamingToken = handle.startStreaming();
|
|
363
|
+
|
|
364
|
+
const interceptSourceUrl = store.getInterceptSourceUrl();
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const { payload, streamComplete } = await client.fetchPartial({
|
|
368
|
+
targetUrl: window.location.href,
|
|
369
|
+
segmentIds: [],
|
|
370
|
+
previousUrl: store.getSegmentState().currentUrl,
|
|
371
|
+
interceptSourceUrl: interceptSourceUrl || undefined,
|
|
372
|
+
routerId: store.getRouterId?.(),
|
|
373
|
+
hmr: true,
|
|
374
|
+
signal: abort.signal,
|
|
375
|
+
});
|
|
286
376
|
|
|
287
|
-
|
|
288
|
-
const segments = payload.metadata.segments || [];
|
|
289
|
-
const matched = payload.metadata.matched || [];
|
|
377
|
+
if (abort.signal.aborted) return;
|
|
290
378
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
379
|
+
// If the server returned a non-RSC response (404, 500 without
|
|
380
|
+
// error boundary), the payload won't have valid metadata.
|
|
381
|
+
// Reload to recover rather than leaving the page stale.
|
|
382
|
+
if (!payload.metadata) {
|
|
383
|
+
throw new Error("HMR refetch returned invalid payload");
|
|
384
|
+
}
|
|
295
385
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
386
|
+
// Update version BEFORE rebuilding state so that
|
|
387
|
+
// clearHistoryCache() runs first, then the fresh segment
|
|
388
|
+
// cache entry we create below survives.
|
|
389
|
+
//
|
|
390
|
+
// Compare against the bridge's live version, not the init-time
|
|
391
|
+
// `version` const: after the first HMR bump the const is stale, so a
|
|
392
|
+
// later update with an unchanged version would otherwise re-clear the
|
|
393
|
+
// cache and re-broadcast across tabs/apps. The live read fires only
|
|
394
|
+
// on a genuine version change.
|
|
395
|
+
const newVersion = payload.metadata.version;
|
|
396
|
+
const currentVersion = navigationBridge.getVersion();
|
|
397
|
+
if (newVersion && newVersion !== currentVersion) {
|
|
398
|
+
console.log(
|
|
399
|
+
"[Rango] HMR: version changed",
|
|
400
|
+
currentVersion,
|
|
401
|
+
"→",
|
|
402
|
+
newVersion,
|
|
403
|
+
"clearing caches",
|
|
404
|
+
);
|
|
405
|
+
navigationBridge.updateVersion(newVersion);
|
|
299
406
|
}
|
|
300
407
|
|
|
301
|
-
|
|
302
|
-
|
|
408
|
+
// Apply only partial segment updates. A non-partial payload during
|
|
409
|
+
// HMR is transient: the worker route table is still rebuilding after
|
|
410
|
+
// the edit, so the URL momentarily resolves to not-found/catch-all.
|
|
411
|
+
// Skip it -- the debounced follow-up refetch returns the settled
|
|
412
|
+
// route's partial payload and renders it below. We never reload here:
|
|
413
|
+
// a paramless document GET would run the SSR path and surface the
|
|
414
|
+
// not-found page during that same transient.
|
|
415
|
+
if (payload.metadata?.isPartial) {
|
|
416
|
+
const segments = payload.metadata.segments || [];
|
|
417
|
+
const matched = payload.metadata.matched || [];
|
|
418
|
+
|
|
419
|
+
// Derive intercept state from the returned payload, not the
|
|
420
|
+
// pre-fetch store snapshot. If the HMR edit removed intercept
|
|
421
|
+
// behavior, the response won't contain intercept segments.
|
|
422
|
+
const responseIsIntercept = segments.some(isInterceptSegment);
|
|
423
|
+
|
|
424
|
+
// Sync store intercept state with what the server returned
|
|
425
|
+
if (!responseIsIntercept && interceptSourceUrl) {
|
|
426
|
+
store.setInterceptSourceUrl(null);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
store.setSegmentIds(matched);
|
|
430
|
+
store.setCurrentUrl(window.location.href);
|
|
431
|
+
|
|
432
|
+
const historyKey = generateHistoryKey(window.location.href, {
|
|
433
|
+
intercept: responseIsIntercept,
|
|
434
|
+
});
|
|
435
|
+
store.setHistoryKey(historyKey);
|
|
436
|
+
const currentHandleData = eventController.getHandleState().data;
|
|
437
|
+
store.cacheSegmentsForHistory(
|
|
438
|
+
historyKey,
|
|
439
|
+
segments,
|
|
440
|
+
currentHandleData,
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const { main, intercept } = splitInterceptSegments(segments);
|
|
444
|
+
store.emitUpdate({
|
|
445
|
+
root: renderSegments(main, {
|
|
446
|
+
interceptSegments: intercept.length > 0 ? intercept : undefined,
|
|
447
|
+
}),
|
|
448
|
+
metadata: payload.metadata,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
303
451
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
store.emitUpdate({
|
|
317
|
-
root: renderSegments(main, {
|
|
318
|
-
interceptSegments: intercept.length > 0 ? intercept : undefined,
|
|
319
|
-
}),
|
|
320
|
-
metadata: payload.metadata,
|
|
321
|
-
});
|
|
452
|
+
await streamComplete;
|
|
453
|
+
handle.complete(new URL(window.location.href));
|
|
454
|
+
console.log("[Rango] HMR: RSC stream complete");
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (abort.signal.aborted) return;
|
|
457
|
+
console.warn("[Rango] HMR: Refetch failed, reloading page", err);
|
|
458
|
+
window.location.reload();
|
|
459
|
+
return;
|
|
460
|
+
} finally {
|
|
461
|
+
if (hmrAbort === abort) hmrAbort = null;
|
|
462
|
+
streamingToken.end();
|
|
463
|
+
handle[Symbol.dispose]();
|
|
322
464
|
}
|
|
323
|
-
|
|
324
|
-
await streamComplete;
|
|
325
|
-
handle.complete(new URL(window.location.href));
|
|
326
|
-
console.log("[RSCRouter] HMR: RSC stream complete");
|
|
327
|
-
} finally {
|
|
328
|
-
streamingToken.end();
|
|
329
|
-
handle[Symbol.dispose]();
|
|
330
|
-
}
|
|
465
|
+
}, 200);
|
|
331
466
|
});
|
|
332
467
|
}
|
|
333
468
|
|
|
334
|
-
// Store context for
|
|
469
|
+
// Store context for Rango component
|
|
335
470
|
const context: BrowserAppContext = {
|
|
336
471
|
store,
|
|
337
472
|
eventController,
|
|
@@ -342,6 +477,7 @@ export async function initBrowserApp(
|
|
|
342
477
|
initialTheme: effectiveInitialTheme,
|
|
343
478
|
warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
|
|
344
479
|
version,
|
|
480
|
+
appShellRef,
|
|
345
481
|
};
|
|
346
482
|
browserAppContext = context;
|
|
347
483
|
|
|
@@ -354,7 +490,7 @@ export async function initBrowserApp(
|
|
|
354
490
|
export function getBrowserAppContext(): BrowserAppContext {
|
|
355
491
|
if (!browserAppContext) {
|
|
356
492
|
throw new Error(
|
|
357
|
-
"
|
|
493
|
+
"Rango: initBrowserApp() must be called before rendering Rango",
|
|
358
494
|
);
|
|
359
495
|
}
|
|
360
496
|
return browserAppContext;
|
|
@@ -368,18 +504,18 @@ export function resetBrowserAppContext(): void {
|
|
|
368
504
|
}
|
|
369
505
|
|
|
370
506
|
/**
|
|
371
|
-
* Props for the
|
|
507
|
+
* Props for the Rango component
|
|
372
508
|
*/
|
|
373
|
-
export interface
|
|
509
|
+
export interface RangoProps {}
|
|
374
510
|
|
|
375
511
|
/**
|
|
376
|
-
*
|
|
512
|
+
* Rango component - renders the RSC router with all internal wiring.
|
|
377
513
|
*
|
|
378
514
|
* Must be called after initBrowserApp() has completed.
|
|
379
515
|
*
|
|
380
516
|
* @example
|
|
381
517
|
* ```tsx
|
|
382
|
-
* import { initBrowserApp,
|
|
518
|
+
* import { initBrowserApp, Rango } from "rsc-router/browser";
|
|
383
519
|
* import { rscStream } from "rsc-html-stream/client";
|
|
384
520
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
385
521
|
*
|
|
@@ -389,14 +525,14 @@ export interface RSCRouterProps {}
|
|
|
389
525
|
* hydrateRoot(
|
|
390
526
|
* document,
|
|
391
527
|
* <React.StrictMode>
|
|
392
|
-
* <
|
|
528
|
+
* <Rango />
|
|
393
529
|
* </React.StrictMode>
|
|
394
530
|
* );
|
|
395
531
|
* }
|
|
396
532
|
* main();
|
|
397
533
|
* ```
|
|
398
534
|
*/
|
|
399
|
-
export function
|
|
535
|
+
export function Rango(_props: RangoProps): React.ReactElement {
|
|
400
536
|
const {
|
|
401
537
|
store,
|
|
402
538
|
eventController,
|
|
@@ -407,6 +543,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
407
543
|
initialTheme,
|
|
408
544
|
warmupEnabled,
|
|
409
545
|
version,
|
|
546
|
+
appShellRef,
|
|
410
547
|
} = getBrowserAppContext();
|
|
411
548
|
|
|
412
549
|
// Signal that the React tree has hydrated. useEffect only fires after
|
|
@@ -426,6 +563,8 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
426
563
|
initialTheme={initialTheme}
|
|
427
564
|
warmupEnabled={warmupEnabled}
|
|
428
565
|
version={version}
|
|
566
|
+
basename={initialPayload.metadata?.basename}
|
|
567
|
+
appShellRef={appShellRef}
|
|
429
568
|
/>
|
|
430
569
|
);
|
|
431
570
|
}
|