@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81
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 +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +5091 -941
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +61 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +340 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +91 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +393 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +358 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +977 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/client.tsx
CHANGED
|
@@ -13,13 +13,91 @@ import {
|
|
|
13
13
|
type ClientErrorBoundaryFallbackProps,
|
|
14
14
|
type ErrorInfo,
|
|
15
15
|
type LoaderDefinition,
|
|
16
|
-
type LoaderFn,
|
|
17
16
|
type ResolvedSegment,
|
|
18
17
|
} from "./types";
|
|
19
18
|
import {
|
|
20
19
|
RouteContentWrapper,
|
|
21
20
|
LoaderBoundary,
|
|
22
21
|
} from "./route-content-wrapper.js";
|
|
22
|
+
import { OutletProvider } from "./outlet-provider.js";
|
|
23
|
+
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
24
|
+
import { getMemoizedContentPromise } from "./segment-content-promise.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render the content for a named parallel/intercept slot segment.
|
|
28
|
+
*
|
|
29
|
+
* Shared by Outlet (with `name` prop) and ParallelOutlet — both resolve a
|
|
30
|
+
* segment from context.parallel by slot name and then render it through the
|
|
31
|
+
* same layout/loader/mountPath wrapping pipeline.
|
|
32
|
+
*/
|
|
33
|
+
function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
34
|
+
if (!segment) return null;
|
|
35
|
+
|
|
36
|
+
const content: ReactNode =
|
|
37
|
+
segment.loading || segment.component instanceof Promise ? (
|
|
38
|
+
<RouteContentWrapper
|
|
39
|
+
content={getMemoizedContentPromise(segment.component)}
|
|
40
|
+
fallback={segment.loading}
|
|
41
|
+
segmentId={segment.id}
|
|
42
|
+
/>
|
|
43
|
+
) : (
|
|
44
|
+
(segment.component ?? null)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const hasOwnLoaders = !!(segment.loaderDataPromise && segment.loaderIds);
|
|
48
|
+
const loaderWrapped = hasOwnLoaders ? (
|
|
49
|
+
<LoaderBoundary
|
|
50
|
+
loaderDataPromise={segment.loaderDataPromise!}
|
|
51
|
+
loaderIds={segment.loaderIds!}
|
|
52
|
+
fallback={segment.loading}
|
|
53
|
+
outletKey={segment.id + "-loader"}
|
|
54
|
+
outletContent={null}
|
|
55
|
+
segment={segment}
|
|
56
|
+
>
|
|
57
|
+
{content}
|
|
58
|
+
</LoaderBoundary>
|
|
59
|
+
) : null;
|
|
60
|
+
|
|
61
|
+
let result: ReactNode;
|
|
62
|
+
if (segment.layout) {
|
|
63
|
+
// Layout renders immediately; if loaders exist, the LoaderBoundary becomes
|
|
64
|
+
// the outlet content so layout's <Outlet /> suspends until loaders resolve.
|
|
65
|
+
result = (
|
|
66
|
+
<OutletProvider
|
|
67
|
+
content={hasOwnLoaders ? loaderWrapped : content}
|
|
68
|
+
segment={segment}
|
|
69
|
+
>
|
|
70
|
+
{segment.layout}
|
|
71
|
+
</OutletProvider>
|
|
72
|
+
);
|
|
73
|
+
} else if (hasOwnLoaders) {
|
|
74
|
+
// No layout but has loaders — wrap content with LoaderBoundary for useLoader context.
|
|
75
|
+
// Common for intercept routes that use useLoader without a custom layout.
|
|
76
|
+
result = loaderWrapped;
|
|
77
|
+
} else {
|
|
78
|
+
result = content;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (segment.mountPath) {
|
|
82
|
+
return (
|
|
83
|
+
<MountContextProvider value={segment.mountPath}>
|
|
84
|
+
{result}
|
|
85
|
+
</MountContextProvider>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function useSlotSegment(
|
|
93
|
+
context: OutletContextValue | null,
|
|
94
|
+
name: `@${string}` | undefined,
|
|
95
|
+
): ResolvedSegment | null {
|
|
96
|
+
return useMemo(() => {
|
|
97
|
+
if (!name || !context?.parallel) return null;
|
|
98
|
+
return context.parallel.find((seg) => seg.slot === name) ?? null;
|
|
99
|
+
}, [context, name]);
|
|
100
|
+
}
|
|
23
101
|
|
|
24
102
|
/**
|
|
25
103
|
* Outlet component - renders child content in layouts
|
|
@@ -60,84 +138,10 @@ import {
|
|
|
60
138
|
*/
|
|
61
139
|
export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
62
140
|
const context = useContext(OutletContext);
|
|
141
|
+
const namedSegment = useSlotSegment(context, name);
|
|
63
142
|
|
|
64
|
-
// If name provided, render parallel/intercept content for that slot
|
|
65
143
|
if (name) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!segment) return null;
|
|
69
|
-
|
|
70
|
-
// Determine the content to render
|
|
71
|
-
let content: ReactNode;
|
|
72
|
-
if (segment.loading || segment.component instanceof Promise) {
|
|
73
|
-
// Use RouteContentWrapper to handle Suspense wrapping properly
|
|
74
|
-
content = (
|
|
75
|
-
<RouteContentWrapper
|
|
76
|
-
content={
|
|
77
|
-
segment.component instanceof Promise
|
|
78
|
-
? segment.component
|
|
79
|
-
: Promise.resolve(segment.component)
|
|
80
|
-
}
|
|
81
|
-
fallback={segment.loading}
|
|
82
|
-
segmentId={segment.id}
|
|
83
|
-
/>
|
|
84
|
-
);
|
|
85
|
-
} else {
|
|
86
|
-
content = segment.component ?? null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// If segment has a layout, wrap appropriately
|
|
90
|
-
if (segment.layout) {
|
|
91
|
-
// Check if this segment has loaders that need streaming
|
|
92
|
-
// The layout renders immediately, LoaderBoundary becomes the outlet content
|
|
93
|
-
// When layout renders <Outlet />, it gets the LoaderBoundary which suspends
|
|
94
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
95
|
-
const loaderAwareContent = (
|
|
96
|
-
<LoaderBoundary
|
|
97
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
98
|
-
loaderIds={segment.loaderIds}
|
|
99
|
-
fallback={segment.loading}
|
|
100
|
-
outletKey={segment.id + "-loader"}
|
|
101
|
-
outletContent={null}
|
|
102
|
-
segment={segment}
|
|
103
|
-
>
|
|
104
|
-
{content}
|
|
105
|
-
</LoaderBoundary>
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
110
|
-
{segment.layout}
|
|
111
|
-
</OutletProvider>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
116
|
-
return (
|
|
117
|
-
<OutletProvider content={content} segment={segment}>
|
|
118
|
-
{segment.layout}
|
|
119
|
-
</OutletProvider>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
124
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
125
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
126
|
-
return (
|
|
127
|
-
<LoaderBoundary
|
|
128
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
129
|
-
loaderIds={segment.loaderIds}
|
|
130
|
-
fallback={segment.loading}
|
|
131
|
-
outletKey={segment.id + "-loader"}
|
|
132
|
-
outletContent={null}
|
|
133
|
-
segment={segment}
|
|
134
|
-
>
|
|
135
|
-
{content}
|
|
136
|
-
</LoaderBoundary>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return content;
|
|
144
|
+
return renderSlotContent(namedSegment);
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
// Default: render child content
|
|
@@ -151,6 +155,7 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
151
155
|
|
|
152
156
|
return content;
|
|
153
157
|
}
|
|
158
|
+
|
|
154
159
|
/**
|
|
155
160
|
* ParallelOutlet component - renders content for a named parallel slot
|
|
156
161
|
*
|
|
@@ -175,124 +180,16 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
175
180
|
*/
|
|
176
181
|
export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
177
182
|
const context = useContext(OutletContext);
|
|
178
|
-
const segment =
|
|
179
|
-
if (!context?.parallel) return null;
|
|
180
|
-
return context.parallel.find((seg) => seg.slot === name) ?? null;
|
|
181
|
-
}, [context, name]);
|
|
182
|
-
|
|
183
|
-
if (!segment) return null;
|
|
183
|
+
const segment = useSlotSegment(context, name);
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
let content: ReactNode;
|
|
187
|
-
if (segment.loading || segment.component instanceof Promise) {
|
|
188
|
-
// Use RouteContentWrapper to handle Suspense wrapping properly
|
|
189
|
-
content = (
|
|
190
|
-
<RouteContentWrapper
|
|
191
|
-
content={
|
|
192
|
-
segment.component instanceof Promise
|
|
193
|
-
? segment.component
|
|
194
|
-
: Promise.resolve(segment.component)
|
|
195
|
-
}
|
|
196
|
-
fallback={segment.loading}
|
|
197
|
-
segmentId={segment.id}
|
|
198
|
-
/>
|
|
199
|
-
);
|
|
200
|
-
} else {
|
|
201
|
-
content = segment.component ?? null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// If segment has a layout, wrap appropriately
|
|
205
|
-
if (segment.layout) {
|
|
206
|
-
// Check if this segment has loaders that need streaming
|
|
207
|
-
// The layout renders immediately, LoaderBoundary becomes the outlet content
|
|
208
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
209
|
-
const loaderAwareContent = (
|
|
210
|
-
<LoaderBoundary
|
|
211
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
212
|
-
loaderIds={segment.loaderIds}
|
|
213
|
-
fallback={segment.loading}
|
|
214
|
-
outletKey={segment.id + "-loader"}
|
|
215
|
-
outletContent={null}
|
|
216
|
-
segment={segment}
|
|
217
|
-
>
|
|
218
|
-
{content}
|
|
219
|
-
</LoaderBoundary>
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
224
|
-
{segment.layout}
|
|
225
|
-
</OutletProvider>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
230
|
-
return (
|
|
231
|
-
<OutletProvider content={content} segment={segment}>
|
|
232
|
-
{segment.layout}
|
|
233
|
-
</OutletProvider>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
238
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
239
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
240
|
-
return (
|
|
241
|
-
<LoaderBoundary
|
|
242
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
243
|
-
loaderIds={segment.loaderIds}
|
|
244
|
-
fallback={segment.loading}
|
|
245
|
-
outletKey={segment.id + "-loader"}
|
|
246
|
-
outletContent={null}
|
|
247
|
-
segment={segment}
|
|
248
|
-
>
|
|
249
|
-
{content}
|
|
250
|
-
</LoaderBoundary>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return content;
|
|
185
|
+
return renderSlotContent(segment);
|
|
255
186
|
}
|
|
256
187
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
* component, Outlet will wrap content with Suspense using that as fallback.
|
|
263
|
-
*/
|
|
264
|
-
export function OutletProvider({
|
|
265
|
-
content,
|
|
266
|
-
parallel,
|
|
267
|
-
segment,
|
|
268
|
-
loaderData,
|
|
269
|
-
children,
|
|
270
|
-
}: {
|
|
271
|
-
content: ReactNode;
|
|
272
|
-
parallel?: ResolvedSegment[];
|
|
273
|
-
segment?: ResolvedSegment;
|
|
274
|
-
loaderData?: Record<string, any>;
|
|
275
|
-
children: ReactNode;
|
|
276
|
-
}): ReactNode {
|
|
277
|
-
// Get parent context to enable walking up the chain for loader lookups
|
|
278
|
-
const parentContext = useContext(OutletContext);
|
|
279
|
-
|
|
280
|
-
const value = useMemo(
|
|
281
|
-
() => ({
|
|
282
|
-
content,
|
|
283
|
-
parallel,
|
|
284
|
-
segment,
|
|
285
|
-
loaderData,
|
|
286
|
-
parent: parentContext,
|
|
287
|
-
loading: segment?.loading,
|
|
288
|
-
}),
|
|
289
|
-
[content, parallel, segment, loaderData, parentContext]
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
return (
|
|
293
|
-
<OutletContext.Provider value={value}>{children}</OutletContext.Provider>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
188
|
+
// OutletProvider is defined in outlet-provider.tsx to break a circular
|
|
189
|
+
// dependency between client.tsx and route-content-wrapper.tsx.
|
|
190
|
+
// Imported at the top of this file for local use in Outlet/ParallelOutlet,
|
|
191
|
+
// and re-exported here for backwards compatibility.
|
|
192
|
+
export { OutletProvider };
|
|
296
193
|
|
|
297
194
|
/**
|
|
298
195
|
* Hook to access outlet content programmatically
|
|
@@ -323,103 +220,6 @@ export {
|
|
|
323
220
|
type UseLoaderOptions,
|
|
324
221
|
} from "./use-loader.js";
|
|
325
222
|
|
|
326
|
-
/**
|
|
327
|
-
* Hook to access all loader data in the current context
|
|
328
|
-
*
|
|
329
|
-
* Returns a record of all loader data available in the current outlet context
|
|
330
|
-
* and all parent contexts. Useful for debugging or when you need access to
|
|
331
|
-
* multiple loaders.
|
|
332
|
-
*
|
|
333
|
-
* @returns Record of loader name to data, or empty object if no loaders
|
|
334
|
-
*
|
|
335
|
-
* @example
|
|
336
|
-
* ```tsx
|
|
337
|
-
* "use client";
|
|
338
|
-
* import { useLoaderData } from "rsc-router/client";
|
|
339
|
-
*
|
|
340
|
-
* export function DebugPanel() {
|
|
341
|
-
* const loaderData = useLoaderData();
|
|
342
|
-
* return <pre>{JSON.stringify(loaderData, null, 2)}</pre>;
|
|
343
|
-
* }
|
|
344
|
-
* ```
|
|
345
|
-
*/
|
|
346
|
-
export function useLoaderData(): Record<string, any> {
|
|
347
|
-
const context = useContext(OutletContext);
|
|
348
|
-
|
|
349
|
-
// Collect all loader data from the context chain
|
|
350
|
-
// Child loaders override parent loaders with the same name
|
|
351
|
-
const result: Record<string, any> = {};
|
|
352
|
-
const stack: OutletContextValue[] = [];
|
|
353
|
-
|
|
354
|
-
// Build stack from current to root
|
|
355
|
-
let current: OutletContextValue | null | undefined = context;
|
|
356
|
-
while (current) {
|
|
357
|
-
stack.push(current);
|
|
358
|
-
current = current.parent;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Apply from root to current (so children override parents)
|
|
362
|
-
for (let i = stack.length - 1; i >= 0; i--) {
|
|
363
|
-
const ctx = stack[i];
|
|
364
|
-
if (ctx.loaderData) {
|
|
365
|
-
Object.assign(result, ctx.loaderData);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return result;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Client-safe createLoader factory
|
|
374
|
-
*
|
|
375
|
-
* Creates a loader definition that can be used with useLoader().
|
|
376
|
-
* This is the client-side version that only stores the $$id - the function
|
|
377
|
-
* is ignored since loaders only execute on the server.
|
|
378
|
-
*
|
|
379
|
-
* The $$id is injected by the exposeLoaderId Vite plugin. In most cases,
|
|
380
|
-
* you should import the loader directly from the server file rather than
|
|
381
|
-
* creating a reference manually.
|
|
382
|
-
*
|
|
383
|
-
* @param fn - Loader function (ignored on client, kept for API compatibility)
|
|
384
|
-
* @param _fetchable - Optional fetchable flag (ignored on client)
|
|
385
|
-
* @param __injectedId - $$id injected by Vite plugin
|
|
386
|
-
*
|
|
387
|
-
* @example
|
|
388
|
-
* ```tsx
|
|
389
|
-
* "use client";
|
|
390
|
-
* import { useLoader } from "rsc-router/client";
|
|
391
|
-
* import { CartLoader } from "../loaders/cart"; // Import from server file
|
|
392
|
-
*
|
|
393
|
-
* export function CartIcon() {
|
|
394
|
-
* const cart = useLoader(CartLoader);
|
|
395
|
-
* return <span>Cart ({cart?.items.length ?? 0})</span>;
|
|
396
|
-
* }
|
|
397
|
-
* ```
|
|
398
|
-
*/
|
|
399
|
-
// Overload 1: With function only (not fetchable)
|
|
400
|
-
export function createLoader<T>(
|
|
401
|
-
fn: LoaderFn<T, Record<string, string | undefined>, any>
|
|
402
|
-
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
403
|
-
|
|
404
|
-
// Overload 2: With function and fetchable flag
|
|
405
|
-
export function createLoader<T>(
|
|
406
|
-
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
407
|
-
fetchable: true
|
|
408
|
-
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
409
|
-
|
|
410
|
-
// Implementation - function is ignored at runtime on client
|
|
411
|
-
// The $$id is injected by Vite plugin as hidden third parameter
|
|
412
|
-
export function createLoader(
|
|
413
|
-
_fn: LoaderFn<any, Record<string, string | undefined>, any>,
|
|
414
|
-
_fetchable?: true,
|
|
415
|
-
__injectedId?: string
|
|
416
|
-
): LoaderDefinition<any, Record<string, string | undefined>> {
|
|
417
|
-
return {
|
|
418
|
-
__brand: "loader",
|
|
419
|
-
$$id: __injectedId || "",
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
223
|
/**
|
|
424
224
|
* Props for the ErrorBoundary component
|
|
425
225
|
*/
|
|
@@ -534,11 +334,16 @@ export class ErrorBoundary extends Component<
|
|
|
534
334
|
// ============================================================================
|
|
535
335
|
|
|
536
336
|
// Navigation hooks
|
|
537
|
-
export {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
} from "./browser/react/use-
|
|
337
|
+
export { useNavigation } from "./browser/react/use-navigation.js";
|
|
338
|
+
export { useRouter } from "./browser/react/use-router.js";
|
|
339
|
+
export { usePathname } from "./browser/react/use-pathname.js";
|
|
340
|
+
export { useSearchParams } from "./browser/react/use-search-params.js";
|
|
341
|
+
export { useParams } from "./browser/react/use-params.js";
|
|
342
|
+
export type {
|
|
343
|
+
RouterInstance,
|
|
344
|
+
RouterNavigateOptions,
|
|
345
|
+
ReadonlyURLSearchParams,
|
|
346
|
+
} from "./browser/types.js";
|
|
542
347
|
|
|
543
348
|
// Action state tracking hook
|
|
544
349
|
export {
|
|
@@ -585,16 +390,15 @@ export {
|
|
|
585
390
|
type ScrollRestorationProps,
|
|
586
391
|
} from "./browser/react/ScrollRestoration.js";
|
|
587
392
|
|
|
588
|
-
// Handle
|
|
589
|
-
export {
|
|
590
|
-
|
|
591
|
-
// Handle data hook
|
|
393
|
+
// Handle data hook (client-side only — createHandle/isHandle are server APIs from the root export)
|
|
394
|
+
export { type Handle } from "./handle.js";
|
|
592
395
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
593
396
|
|
|
594
397
|
// Built-in handles
|
|
595
398
|
export { Meta } from "./handles/meta.js";
|
|
596
399
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
597
400
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
401
|
+
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
598
402
|
|
|
599
403
|
// Location state - type-safe navigation state
|
|
600
404
|
export {
|
|
@@ -602,10 +406,40 @@ export {
|
|
|
602
406
|
useLocationState,
|
|
603
407
|
type LocationStateDefinition,
|
|
604
408
|
type LocationStateEntry,
|
|
409
|
+
type LocationStateOptions,
|
|
605
410
|
} from "./browser/react/location-state.js";
|
|
606
411
|
|
|
607
412
|
// Type-safe href for client-side path validation
|
|
608
|
-
export {
|
|
413
|
+
export {
|
|
414
|
+
href,
|
|
415
|
+
type ValidPaths,
|
|
416
|
+
type PatternToPath,
|
|
417
|
+
type PathResponse,
|
|
418
|
+
} from "./href-client.js";
|
|
419
|
+
|
|
420
|
+
// Response envelope types for consuming JSON response routes
|
|
421
|
+
export type { ResponseEnvelope, ResponseError } from "./urls.js";
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Type guard for checking if a response envelope contains an error.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
|
|
429
|
+
* if (isResponseError(result)) {
|
|
430
|
+
* console.log(result.error.message, result.error.code);
|
|
431
|
+
* return;
|
|
432
|
+
* }
|
|
433
|
+
* result.data // fully typed as Product
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
export function isResponseError<T>(
|
|
437
|
+
result: import("./urls.js").ResponseEnvelope<T>,
|
|
438
|
+
): result is import("./urls.js").ResponseEnvelope<T> & {
|
|
439
|
+
error: import("./urls.js").ResponseError;
|
|
440
|
+
} {
|
|
441
|
+
return result.error !== undefined;
|
|
442
|
+
}
|
|
609
443
|
|
|
610
444
|
// Mount context for include() scoped components
|
|
611
445
|
export { useMount } from "./browser/react/use-mount.js";
|
|
@@ -614,8 +448,8 @@ export { MountContext } from "./browser/react/mount-context.js";
|
|
|
614
448
|
// Mount-aware href hook - auto-prefixes paths with include() mount
|
|
615
449
|
export { useHref } from "./browser/react/use-href.js";
|
|
616
450
|
|
|
617
|
-
// Type-safe scoped
|
|
618
|
-
export type {
|
|
451
|
+
// Type-safe scoped reverse function for scopedReverse<typeof patterns>()
|
|
452
|
+
export type { ScopedReverseFunction } from "./reverse.js";
|
|
619
453
|
|
|
620
454
|
// Loader definition type - for typing loader props in client components
|
|
621
455
|
export type { LoaderDefinition } from "./types.js";
|
package/src/component-utils.ts
CHANGED
|
@@ -33,7 +33,7 @@ const CLIENT_REFERENCE = Symbol.for("react.client.reference");
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
export function isClientComponent(
|
|
36
|
-
component: ComponentType<unknown> | unknown
|
|
36
|
+
component: ComponentType<unknown> | unknown,
|
|
37
37
|
): boolean {
|
|
38
38
|
if (typeof component !== "function") {
|
|
39
39
|
return false;
|
|
@@ -52,13 +52,13 @@ export function isClientComponent(
|
|
|
52
52
|
*/
|
|
53
53
|
export function assertClientComponent(
|
|
54
54
|
component: ComponentType<unknown> | unknown,
|
|
55
|
-
name: string
|
|
55
|
+
name: string,
|
|
56
56
|
): asserts component is ComponentType<unknown> {
|
|
57
57
|
if (typeof component !== "function") {
|
|
58
58
|
throw new Error(
|
|
59
59
|
`${name} must be a client component function with "use client" directive. ` +
|
|
60
60
|
`Make sure to pass the component itself, not a JSX element: ` +
|
|
61
|
-
`${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)
|
|
61
|
+
`${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)`,
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ export function assertClientComponent(
|
|
|
66
66
|
throw new Error(
|
|
67
67
|
`${name} must be a client component with "use client" directive at the top of the file. ` +
|
|
68
68
|
`Server components cannot be used as the ${name} because their function reference ` +
|
|
69
|
-
`cannot be serialized in the RSC payload. Add "use client" to your ${name} file
|
|
69
|
+
`cannot be serialized in the RSC payload. Add "use client" to your ${name} file.`,
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -11,7 +11,11 @@ import { MetaTags } from "../handles/MetaTags.js";
|
|
|
11
11
|
* Uses suppressHydrationWarning on <html> because the theme script
|
|
12
12
|
* may modify class/style attributes before React hydrates.
|
|
13
13
|
*/
|
|
14
|
-
export function DefaultDocument({
|
|
14
|
+
export function DefaultDocument({
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}): ReactElement {
|
|
15
19
|
return (
|
|
16
20
|
<html lang="en" suppressHydrationWarning>
|
|
17
21
|
<head>
|