@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
package/src/client.rsc.tsx
CHANGED
|
@@ -14,60 +14,43 @@
|
|
|
14
14
|
export {
|
|
15
15
|
Outlet,
|
|
16
16
|
ParallelOutlet,
|
|
17
|
-
OutletProvider,
|
|
18
17
|
useOutlet,
|
|
19
18
|
useLoader,
|
|
20
19
|
ErrorBoundary,
|
|
21
20
|
type ErrorBoundaryProps,
|
|
22
21
|
} from "./client.js";
|
|
23
22
|
|
|
24
|
-
// Re-export the server's createLoader for RSC context
|
|
25
|
-
// This version includes the actual loader function
|
|
26
23
|
export { createLoader } from "./route-definition.js";
|
|
27
24
|
|
|
28
|
-
// Re-export Link component (can be used in server components)
|
|
29
25
|
export {
|
|
30
26
|
Link,
|
|
31
27
|
type LinkProps,
|
|
32
28
|
type PrefetchStrategy,
|
|
33
29
|
} from "./browser/react/Link.js";
|
|
34
30
|
|
|
35
|
-
// Re-export ScrollRestoration (can be used in server components)
|
|
36
31
|
export {
|
|
37
32
|
ScrollRestoration,
|
|
38
33
|
type ScrollRestorationProps,
|
|
39
34
|
} from "./browser/react/ScrollRestoration.js";
|
|
40
35
|
|
|
41
|
-
// Re-export NavigationProvider (needed for setup)
|
|
42
36
|
export {
|
|
43
37
|
NavigationProvider,
|
|
44
38
|
type NavigationProviderProps,
|
|
45
39
|
} from "./browser/react/NavigationProvider.js";
|
|
46
40
|
|
|
47
|
-
// Re-export href function (can be used in server components)
|
|
48
41
|
export { href } from "./href-client.js";
|
|
49
42
|
|
|
50
|
-
// Mount context re-exports (useMount is client-only, but MountContext can be referenced)
|
|
51
43
|
export { MountContext } from "./browser/react/mount-context.js";
|
|
52
44
|
|
|
53
|
-
//
|
|
54
|
-
// because they use client-side state and should only be used in client components
|
|
45
|
+
// useNavigation and useAction are NOT re-exported here because they use client-side state
|
|
55
46
|
|
|
56
|
-
// Handle API - for accumulating data across route segments
|
|
57
|
-
// Works in both RSC and client contexts
|
|
58
47
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
59
48
|
|
|
60
|
-
// Built-in handles
|
|
61
|
-
// Meta handle works in RSC context
|
|
62
49
|
export { Meta } from "./handles/meta.js";
|
|
63
|
-
// MetaTags is a "use client" component that can be imported from RSC
|
|
64
50
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
65
51
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
66
|
-
// Breadcrumbs handle works in RSC context
|
|
67
52
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
68
53
|
|
|
69
|
-
// Location state - createLocationState works in RSC (just creates definition)
|
|
70
|
-
// useLocationState is NOT exported here as it uses client hooks
|
|
71
54
|
export {
|
|
72
55
|
createLocationState,
|
|
73
56
|
type LocationStateDefinition,
|
|
@@ -75,11 +58,13 @@ export {
|
|
|
75
58
|
type LocationStateOptions,
|
|
76
59
|
} from "./browser/react/location-state-shared.js";
|
|
77
60
|
|
|
78
|
-
// Re-export useHref - it's a "use client" hook
|
|
79
61
|
export { useHref } from "./browser/react/use-href.js";
|
|
80
62
|
|
|
81
|
-
|
|
63
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
64
|
+
|
|
82
65
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
66
|
+
// Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
|
|
67
|
+
// (a `ctx.use(Handle).defer()` slot) until it resolves.
|
|
68
|
+
export type { DeferredHandleEntry } from "./defer.js";
|
|
83
69
|
|
|
84
|
-
// Re-export useLocationState - it's a "use client" hook
|
|
85
70
|
export { useLocationState } from "./browser/react/location-state.js";
|
package/src/client.tsx
CHANGED
|
@@ -13,7 +13,6 @@ 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 {
|
|
@@ -22,6 +21,83 @@ import {
|
|
|
22
21
|
} from "./route-content-wrapper.js";
|
|
23
22
|
import { OutletProvider } from "./outlet-provider.js";
|
|
24
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
|
+
}
|
|
25
101
|
|
|
26
102
|
/**
|
|
27
103
|
* Outlet component - renders child content in layouts
|
|
@@ -35,6 +111,11 @@ import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
|
35
111
|
* the parallel segment with that slot name instead of the default content.
|
|
36
112
|
* This is used for parallel routes and intercepting routes.
|
|
37
113
|
*
|
|
114
|
+
* For a named slot, `<Outlet name="@x" />` is equivalent to
|
|
115
|
+
* `<ParallelOutlet name="@x" />` — both run the same resolution + wrapping
|
|
116
|
+
* pipeline. Convention: use bare `<Outlet />` for default content and
|
|
117
|
+
* `<ParallelOutlet name="@x" />` for named slots.
|
|
118
|
+
*
|
|
38
119
|
* @param name - Optional slot name for parallel/intercept content (must start with @)
|
|
39
120
|
*
|
|
40
121
|
* @example
|
|
@@ -62,95 +143,10 @@ import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
|
62
143
|
*/
|
|
63
144
|
export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
64
145
|
const context = useContext(OutletContext);
|
|
146
|
+
const namedSegment = useSlotSegment(context, name);
|
|
65
147
|
|
|
66
|
-
// If name provided, render parallel/intercept content for that slot
|
|
67
148
|
if (name) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!segment) return null;
|
|
71
|
-
|
|
72
|
-
// Determine the content to render
|
|
73
|
-
let content: ReactNode;
|
|
74
|
-
if (segment.loading || segment.component instanceof Promise) {
|
|
75
|
-
// Use RouteContentWrapper to handle Suspense wrapping properly
|
|
76
|
-
content = (
|
|
77
|
-
<RouteContentWrapper
|
|
78
|
-
content={
|
|
79
|
-
segment.component instanceof Promise
|
|
80
|
-
? segment.component
|
|
81
|
-
: Promise.resolve(segment.component)
|
|
82
|
-
}
|
|
83
|
-
fallback={segment.loading}
|
|
84
|
-
segmentId={segment.id}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
} else {
|
|
88
|
-
content = segment.component ?? null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let result: ReactNode;
|
|
92
|
-
|
|
93
|
-
// If segment has a layout, wrap appropriately
|
|
94
|
-
if (segment.layout) {
|
|
95
|
-
// Check if this segment has loaders that need streaming
|
|
96
|
-
// The layout renders immediately, LoaderBoundary becomes the outlet content
|
|
97
|
-
// When layout renders <Outlet />, it gets the LoaderBoundary which suspends
|
|
98
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
99
|
-
const loaderAwareContent = (
|
|
100
|
-
<LoaderBoundary
|
|
101
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
102
|
-
loaderIds={segment.loaderIds}
|
|
103
|
-
fallback={segment.loading}
|
|
104
|
-
outletKey={segment.id + "-loader"}
|
|
105
|
-
outletContent={null}
|
|
106
|
-
segment={segment}
|
|
107
|
-
>
|
|
108
|
-
{content}
|
|
109
|
-
</LoaderBoundary>
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
result = (
|
|
113
|
-
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
114
|
-
{segment.layout}
|
|
115
|
-
</OutletProvider>
|
|
116
|
-
);
|
|
117
|
-
} else {
|
|
118
|
-
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
119
|
-
result = (
|
|
120
|
-
<OutletProvider content={content} segment={segment}>
|
|
121
|
-
{segment.layout}
|
|
122
|
-
</OutletProvider>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
} else if (segment.loaderDataPromise && segment.loaderIds) {
|
|
126
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
127
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
128
|
-
result = (
|
|
129
|
-
<LoaderBoundary
|
|
130
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
131
|
-
loaderIds={segment.loaderIds}
|
|
132
|
-
fallback={segment.loading}
|
|
133
|
-
outletKey={segment.id + "-loader"}
|
|
134
|
-
outletContent={null}
|
|
135
|
-
segment={segment}
|
|
136
|
-
>
|
|
137
|
-
{content}
|
|
138
|
-
</LoaderBoundary>
|
|
139
|
-
);
|
|
140
|
-
} else {
|
|
141
|
-
result = content;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Wrap with MountContextProvider for include() scoped parallel/intercept slots
|
|
145
|
-
if (segment.mountPath) {
|
|
146
|
-
return (
|
|
147
|
-
<MountContextProvider value={segment.mountPath}>
|
|
148
|
-
{result}
|
|
149
|
-
</MountContextProvider>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return result;
|
|
149
|
+
return renderSlotContent(namedSegment);
|
|
154
150
|
}
|
|
155
151
|
|
|
156
152
|
// Default: render child content
|
|
@@ -164,6 +160,7 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
164
160
|
|
|
165
161
|
return content;
|
|
166
162
|
}
|
|
163
|
+
|
|
167
164
|
/**
|
|
168
165
|
* ParallelOutlet component - renders content for a named parallel slot
|
|
169
166
|
*
|
|
@@ -171,6 +168,9 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
171
168
|
* is wrapped in Suspense with the loading component as fallback.
|
|
172
169
|
* This enables streaming and navigation loading states for parallels.
|
|
173
170
|
*
|
|
171
|
+
* Equivalent to `<Outlet name="@x" />` for a named slot; ParallelOutlet
|
|
172
|
+
* requires `name` and is named-slot-only, which reads clearer at the call site.
|
|
173
|
+
*
|
|
174
174
|
* @param name - The slot name (must start with @, e.g., "@modal", "@sidebar")
|
|
175
175
|
*
|
|
176
176
|
* @example
|
|
@@ -188,101 +188,15 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
188
188
|
*/
|
|
189
189
|
export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
190
190
|
const context = useContext(OutletContext);
|
|
191
|
-
const segment =
|
|
192
|
-
if (!context?.parallel) return null;
|
|
193
|
-
return context.parallel.find((seg) => seg.slot === name) ?? null;
|
|
194
|
-
}, [context, name]);
|
|
195
|
-
|
|
196
|
-
if (!segment) return null;
|
|
197
|
-
|
|
198
|
-
// Determine the content to render
|
|
199
|
-
let content: ReactNode;
|
|
200
|
-
if (segment.loading || segment.component instanceof Promise) {
|
|
201
|
-
// Use RouteContentWrapper to handle Suspense wrapping properly
|
|
202
|
-
content = (
|
|
203
|
-
<RouteContentWrapper
|
|
204
|
-
content={
|
|
205
|
-
segment.component instanceof Promise
|
|
206
|
-
? segment.component
|
|
207
|
-
: Promise.resolve(segment.component)
|
|
208
|
-
}
|
|
209
|
-
fallback={segment.loading}
|
|
210
|
-
segmentId={segment.id}
|
|
211
|
-
/>
|
|
212
|
-
);
|
|
213
|
-
} else {
|
|
214
|
-
content = segment.component ?? null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let result: ReactNode;
|
|
218
|
-
|
|
219
|
-
// If segment has a layout, wrap appropriately
|
|
220
|
-
if (segment.layout) {
|
|
221
|
-
// Check if this segment has loaders that need streaming
|
|
222
|
-
// The layout renders immediately, LoaderBoundary becomes the outlet content
|
|
223
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
224
|
-
const loaderAwareContent = (
|
|
225
|
-
<LoaderBoundary
|
|
226
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
227
|
-
loaderIds={segment.loaderIds}
|
|
228
|
-
fallback={segment.loading}
|
|
229
|
-
outletKey={segment.id + "-loader"}
|
|
230
|
-
outletContent={null}
|
|
231
|
-
segment={segment}
|
|
232
|
-
>
|
|
233
|
-
{content}
|
|
234
|
-
</LoaderBoundary>
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
result = (
|
|
238
|
-
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
239
|
-
{segment.layout}
|
|
240
|
-
</OutletProvider>
|
|
241
|
-
);
|
|
242
|
-
} else {
|
|
243
|
-
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
244
|
-
result = (
|
|
245
|
-
<OutletProvider content={content} segment={segment}>
|
|
246
|
-
{segment.layout}
|
|
247
|
-
</OutletProvider>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
} else if (segment.loaderDataPromise && segment.loaderIds) {
|
|
251
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
252
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
253
|
-
result = (
|
|
254
|
-
<LoaderBoundary
|
|
255
|
-
loaderDataPromise={segment.loaderDataPromise}
|
|
256
|
-
loaderIds={segment.loaderIds}
|
|
257
|
-
fallback={segment.loading}
|
|
258
|
-
outletKey={segment.id + "-loader"}
|
|
259
|
-
outletContent={null}
|
|
260
|
-
segment={segment}
|
|
261
|
-
>
|
|
262
|
-
{content}
|
|
263
|
-
</LoaderBoundary>
|
|
264
|
-
);
|
|
265
|
-
} else {
|
|
266
|
-
result = content;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Wrap with MountContextProvider for include() scoped parallel/intercept slots
|
|
270
|
-
if (segment.mountPath) {
|
|
271
|
-
return (
|
|
272
|
-
<MountContextProvider value={segment.mountPath}>
|
|
273
|
-
{result}
|
|
274
|
-
</MountContextProvider>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
191
|
+
const segment = useSlotSegment(context, name);
|
|
277
192
|
|
|
278
|
-
return
|
|
193
|
+
return renderSlotContent(segment);
|
|
279
194
|
}
|
|
280
195
|
|
|
281
196
|
// OutletProvider is defined in outlet-provider.tsx to break a circular
|
|
282
|
-
// dependency between client.tsx and route-content-wrapper.tsx.
|
|
283
|
-
//
|
|
284
|
-
// and
|
|
285
|
-
export { OutletProvider };
|
|
197
|
+
// dependency between client.tsx and route-content-wrapper.tsx. It is imported
|
|
198
|
+
// at the top of this file for local use in Outlet/ParallelOutlet only; it is an
|
|
199
|
+
// internal component and is intentionally not part of the public ./client API.
|
|
286
200
|
|
|
287
201
|
/**
|
|
288
202
|
* Hook to access outlet content programmatically
|
|
@@ -303,67 +217,16 @@ export function useOutlet(): ReactNode {
|
|
|
303
217
|
return context?.content ?? null;
|
|
304
218
|
}
|
|
305
219
|
|
|
306
|
-
// Loader hooks - re-exported from dedicated file
|
|
307
220
|
export {
|
|
308
221
|
useLoader,
|
|
309
222
|
useFetchLoader,
|
|
223
|
+
useRefreshLoaders,
|
|
310
224
|
type LoadFunction,
|
|
311
225
|
type UseLoaderResult,
|
|
312
226
|
type UseFetchLoaderResult,
|
|
313
227
|
type UseLoaderOptions,
|
|
314
228
|
} from "./use-loader.js";
|
|
315
229
|
|
|
316
|
-
/**
|
|
317
|
-
* Client-safe createLoader factory
|
|
318
|
-
*
|
|
319
|
-
* Creates a loader definition that can be used with useLoader().
|
|
320
|
-
* This is the client-side version that only stores the $$id - the function
|
|
321
|
-
* is ignored since loaders only execute on the server.
|
|
322
|
-
*
|
|
323
|
-
* The $$id is injected by the exposeLoaderId Vite plugin. In most cases,
|
|
324
|
-
* you should import the loader directly from the server file rather than
|
|
325
|
-
* creating a reference manually.
|
|
326
|
-
*
|
|
327
|
-
* @param fn - Loader function (ignored on client, kept for API compatibility)
|
|
328
|
-
* @param _fetchable - Optional fetchable flag (ignored on client)
|
|
329
|
-
* @param __injectedId - $$id injected by Vite plugin
|
|
330
|
-
*
|
|
331
|
-
* @example
|
|
332
|
-
* ```tsx
|
|
333
|
-
* "use client";
|
|
334
|
-
* import { useLoader } from "rsc-router/client";
|
|
335
|
-
* import { CartLoader } from "../loaders/cart"; // Import from server file
|
|
336
|
-
*
|
|
337
|
-
* export function CartIcon() {
|
|
338
|
-
* const cart = useLoader(CartLoader);
|
|
339
|
-
* return <span>Cart ({cart?.items.length ?? 0})</span>;
|
|
340
|
-
* }
|
|
341
|
-
* ```
|
|
342
|
-
*/
|
|
343
|
-
// Overload 1: With function only (not fetchable)
|
|
344
|
-
export function createLoader<T>(
|
|
345
|
-
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
346
|
-
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
347
|
-
|
|
348
|
-
// Overload 2: With function and fetchable flag
|
|
349
|
-
export function createLoader<T>(
|
|
350
|
-
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
351
|
-
fetchable: true,
|
|
352
|
-
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
353
|
-
|
|
354
|
-
// Implementation - function is ignored at runtime on client
|
|
355
|
-
// The $$id is injected by Vite plugin as hidden third parameter
|
|
356
|
-
export function createLoader(
|
|
357
|
-
_fn: LoaderFn<any, Record<string, string | undefined>, any>,
|
|
358
|
-
_fetchable?: true,
|
|
359
|
-
__injectedId?: string,
|
|
360
|
-
): LoaderDefinition<any, Record<string, string | undefined>> {
|
|
361
|
-
return {
|
|
362
|
-
__brand: "loader",
|
|
363
|
-
$$id: __injectedId || "",
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
230
|
/**
|
|
368
231
|
* Props for the ErrorBoundary component
|
|
369
232
|
*/
|
|
@@ -472,12 +335,6 @@ export class ErrorBoundary extends Component<
|
|
|
472
335
|
}
|
|
473
336
|
}
|
|
474
337
|
|
|
475
|
-
// ============================================================================
|
|
476
|
-
// Re-exports from browser/react for convenience
|
|
477
|
-
// These are the most commonly used client-side navigation utilities
|
|
478
|
-
// ============================================================================
|
|
479
|
-
|
|
480
|
-
// Navigation hooks
|
|
481
338
|
export { useNavigation } from "./browser/react/use-navigation.js";
|
|
482
339
|
export { useRouter } from "./browser/react/use-router.js";
|
|
483
340
|
export { usePathname } from "./browser/react/use-pathname.js";
|
|
@@ -487,66 +344,55 @@ export type {
|
|
|
487
344
|
RouterInstance,
|
|
488
345
|
RouterNavigateOptions,
|
|
489
346
|
ReadonlyURLSearchParams,
|
|
347
|
+
ActionState,
|
|
348
|
+
ActionLifecycleState,
|
|
490
349
|
} from "./browser/types.js";
|
|
491
350
|
|
|
492
|
-
// Action state tracking hook
|
|
493
351
|
export {
|
|
494
352
|
useAction,
|
|
495
353
|
type ServerActionFunction,
|
|
496
354
|
} from "./browser/react/use-action.js";
|
|
497
355
|
|
|
498
|
-
// Segments state hook
|
|
499
356
|
export {
|
|
500
357
|
useSegments,
|
|
501
358
|
type SegmentsState,
|
|
502
359
|
} from "./browser/react/use-segments.js";
|
|
503
360
|
|
|
504
|
-
// Client cache controls hook
|
|
505
|
-
export {
|
|
506
|
-
useClientCache,
|
|
507
|
-
type ClientCacheControls,
|
|
508
|
-
} from "./browser/react/use-client-cache.js";
|
|
509
|
-
|
|
510
|
-
// Provider
|
|
511
361
|
export {
|
|
512
362
|
NavigationProvider,
|
|
513
363
|
type NavigationProviderProps,
|
|
514
364
|
} from "./browser/react/NavigationProvider.js";
|
|
515
365
|
|
|
516
|
-
// Link component
|
|
517
366
|
export {
|
|
518
367
|
Link,
|
|
519
368
|
type LinkProps,
|
|
520
369
|
type PrefetchStrategy,
|
|
521
370
|
type StateOrGetter,
|
|
371
|
+
type LinkState,
|
|
522
372
|
} from "./browser/react/Link.js";
|
|
523
373
|
|
|
524
|
-
// Link status hook
|
|
525
374
|
export {
|
|
526
375
|
useLinkStatus,
|
|
527
376
|
type LinkStatus,
|
|
528
377
|
} from "./browser/react/use-link-status.js";
|
|
529
378
|
|
|
530
|
-
// Scroll restoration
|
|
531
379
|
export {
|
|
532
380
|
ScrollRestoration,
|
|
533
381
|
useScrollRestoration,
|
|
534
382
|
type ScrollRestorationProps,
|
|
535
383
|
} from "./browser/react/ScrollRestoration.js";
|
|
536
384
|
|
|
537
|
-
|
|
538
|
-
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
539
|
-
|
|
540
|
-
// Handle data hook
|
|
385
|
+
export { type Handle } from "./handle.js";
|
|
541
386
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
387
|
+
// Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
|
|
388
|
+
// (a `ctx.use(Handle).defer()` slot) until it resolves.
|
|
389
|
+
export type { DeferredHandleEntry } from "./defer.js";
|
|
542
390
|
|
|
543
|
-
// Built-in handles
|
|
544
391
|
export { Meta } from "./handles/meta.js";
|
|
545
392
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
546
393
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
547
394
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
548
395
|
|
|
549
|
-
// Location state - type-safe navigation state
|
|
550
396
|
export {
|
|
551
397
|
createLocationState,
|
|
552
398
|
useLocationState,
|
|
@@ -555,47 +401,19 @@ export {
|
|
|
555
401
|
type LocationStateOptions,
|
|
556
402
|
} from "./browser/react/location-state.js";
|
|
557
403
|
|
|
558
|
-
//
|
|
559
|
-
export {
|
|
560
|
-
href,
|
|
561
|
-
type ValidPaths,
|
|
562
|
-
type PatternToPath,
|
|
563
|
-
type PathResponse,
|
|
564
|
-
} from "./href-client.js";
|
|
404
|
+
// Ambient Rango.Path / Rango.PathResponse types (declared in href-client.ts)
|
|
405
|
+
export { href, type PatternToPath } from "./href-client.js";
|
|
565
406
|
|
|
566
|
-
//
|
|
567
|
-
export type {
|
|
407
|
+
// RFC 9457 error type for JSON response routes
|
|
408
|
+
export type { ProblemDetails } from "./urls.js";
|
|
568
409
|
|
|
569
|
-
/**
|
|
570
|
-
* Type guard for checking if a response envelope contains an error.
|
|
571
|
-
*
|
|
572
|
-
* @example
|
|
573
|
-
* ```typescript
|
|
574
|
-
* const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
|
|
575
|
-
* if (isResponseError(result)) {
|
|
576
|
-
* console.log(result.error.message, result.error.code);
|
|
577
|
-
* return;
|
|
578
|
-
* }
|
|
579
|
-
* result.data // fully typed as Product
|
|
580
|
-
* ```
|
|
581
|
-
*/
|
|
582
|
-
export function isResponseError<T>(
|
|
583
|
-
result: import("./urls.js").ResponseEnvelope<T>,
|
|
584
|
-
): result is import("./urls.js").ResponseEnvelope<T> & {
|
|
585
|
-
error: import("./urls.js").ResponseError;
|
|
586
|
-
} {
|
|
587
|
-
return result.error !== undefined;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Mount context for include() scoped components
|
|
591
410
|
export { useMount } from "./browser/react/use-mount.js";
|
|
592
411
|
export { MountContext } from "./browser/react/mount-context.js";
|
|
593
412
|
|
|
594
|
-
// Mount-aware href hook - auto-prefixes paths with include() mount
|
|
595
413
|
export { useHref } from "./browser/react/use-href.js";
|
|
596
414
|
|
|
597
|
-
|
|
598
|
-
|
|
415
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
416
|
+
|
|
417
|
+
export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
|
|
599
418
|
|
|
600
|
-
// Loader definition type - for typing loader props in client components
|
|
601
419
|
export type { LoaderDefinition } from "./types.js";
|
package/src/component-utils.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ComponentType } from "react";
|
|
8
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Symbol used by React to mark client component references.
|
|
@@ -48,11 +49,21 @@ export function isClientComponent(
|
|
|
48
49
|
*
|
|
49
50
|
* @param component - The component to check
|
|
50
51
|
* @param name - Name to use in error message (e.g., "document")
|
|
52
|
+
* @param opts.allowServerInTest - When true AND running under a test runner
|
|
53
|
+
* (`isUnderTestRunner()`), relax ONLY the "use client" requirement: a server
|
|
54
|
+
* component is accepted. The plugin's "use client" transform does not run in a
|
|
55
|
+
* bare unit test, so a real exported `document` (almost every app sets one) has
|
|
56
|
+
* no client marker and would otherwise throw at `createRouter`, blocking
|
|
57
|
+
* `dispatch`/`assertGeneratedRoutesMatch` against the real router. The document
|
|
58
|
+
* reference is irrelevant to those (no Flight render). The "not a JSX element"
|
|
59
|
+
* guard still fires, and a real dev/build still throws (mirrors the runtime
|
|
60
|
+
* fallback-id gating in handle.ts/loader.ts).
|
|
51
61
|
* @throws Error if the component is not a client component
|
|
52
62
|
*/
|
|
53
63
|
export function assertClientComponent(
|
|
54
64
|
component: ComponentType<unknown> | unknown,
|
|
55
65
|
name: string,
|
|
66
|
+
opts?: { allowServerInTest?: boolean },
|
|
56
67
|
): asserts component is ComponentType<unknown> {
|
|
57
68
|
if (typeof component !== "function") {
|
|
58
69
|
throw new Error(
|
|
@@ -62,6 +73,14 @@ export function assertClientComponent(
|
|
|
62
73
|
);
|
|
63
74
|
}
|
|
64
75
|
|
|
76
|
+
// Under a test runner the "use client" transform did not run, so a real
|
|
77
|
+
// server-rendered `document` has no client marker; accept it (the reference is
|
|
78
|
+
// never serialized in dispatch/route-map checks). Outside a test runner this
|
|
79
|
+
// still throws — the build-time safety net is preserved.
|
|
80
|
+
if (opts?.allowServerInTest && isUnderTestRunner()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
if (!isClientComponent(component)) {
|
|
66
85
|
throw new Error(
|
|
67
86
|
`${name} must be a client component with "use client" directive at the top of the file. ` +
|