@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rsc-router/server
|
|
3
|
+
*
|
|
4
|
+
* Server-only exports for route definition and building
|
|
5
|
+
* These should only be imported in server-side handler files
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Route definition helpers (server-only)
|
|
9
|
+
export {
|
|
10
|
+
createLoader,
|
|
11
|
+
redirect,
|
|
12
|
+
type RouteHelpers,
|
|
13
|
+
type RouteHandlers,
|
|
14
|
+
} from "./route-definition.js";
|
|
15
|
+
|
|
16
|
+
// Django-style URL patterns (server-only)
|
|
17
|
+
export {
|
|
18
|
+
urls,
|
|
19
|
+
RESPONSE_TYPE,
|
|
20
|
+
type PathHelpers,
|
|
21
|
+
type PathOptions,
|
|
22
|
+
type UrlPatterns,
|
|
23
|
+
type IncludeOptions,
|
|
24
|
+
type ResponseHandler,
|
|
25
|
+
type ResponseHandlerContext,
|
|
26
|
+
type JsonResponseHandler,
|
|
27
|
+
type TextResponseHandler,
|
|
28
|
+
type JsonValue,
|
|
29
|
+
type ResponsePathFn,
|
|
30
|
+
type JsonResponsePathFn,
|
|
31
|
+
type TextResponsePathFn,
|
|
32
|
+
type RouteResponse,
|
|
33
|
+
type ResponseError,
|
|
34
|
+
type ResponseEnvelope,
|
|
35
|
+
} from "./urls.js";
|
|
36
|
+
|
|
37
|
+
// Re-export IncludeItem from route-types
|
|
38
|
+
export type { IncludeItem } from "./route-types.js";
|
|
39
|
+
|
|
40
|
+
// Core router (server-only)
|
|
41
|
+
export {
|
|
42
|
+
createRouter,
|
|
43
|
+
RSC_ROUTER_BRAND,
|
|
44
|
+
RouterRegistry,
|
|
45
|
+
type RSCRouter,
|
|
46
|
+
type RSCRouterOptions,
|
|
47
|
+
type RootLayoutProps,
|
|
48
|
+
} from "./router.js";
|
|
49
|
+
|
|
50
|
+
// Type-safe reverse utilities (Django-style URL reversal)
|
|
51
|
+
export {
|
|
52
|
+
createReverse,
|
|
53
|
+
type ReverseFunction,
|
|
54
|
+
type PrefixedRoutes,
|
|
55
|
+
type PrefixRoutePatterns,
|
|
56
|
+
type ParamsFor,
|
|
57
|
+
type SanitizePrefix,
|
|
58
|
+
type MergeRoutes,
|
|
59
|
+
} from "./reverse.js";
|
|
60
|
+
|
|
61
|
+
// Segment system (server-only)
|
|
62
|
+
export { renderSegments } from "./segment-system.js";
|
|
63
|
+
|
|
64
|
+
// Performance tracking (server-only)
|
|
65
|
+
export { track } from "./server/context.js";
|
|
66
|
+
|
|
67
|
+
// Handle API (works in both server and client contexts)
|
|
68
|
+
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
69
|
+
|
|
70
|
+
// Pre-render handler API
|
|
71
|
+
export {
|
|
72
|
+
createPrerenderHandler,
|
|
73
|
+
isPrerenderHandler,
|
|
74
|
+
type PrerenderHandlerDefinition,
|
|
75
|
+
type PrerenderOptions,
|
|
76
|
+
type BuildContext,
|
|
77
|
+
} from "./prerender.js";
|
|
78
|
+
|
|
79
|
+
// Built-in handles
|
|
80
|
+
export { Meta } from "./handles/meta.js";
|
|
81
|
+
|
|
82
|
+
// Loader registry (for GET-based loader fetching)
|
|
83
|
+
export { registerLoaderById, setLoaderImports } from "./server/loader-registry.js";
|
|
84
|
+
|
|
85
|
+
// Route map builder (for build-time manifest registration)
|
|
86
|
+
export { registerRouteMap, setCachedManifest, setPrecomputedEntries, setRouteTrie, setManifestReadyPromise } from "./route-map-builder.js";
|
|
87
|
+
|
|
88
|
+
// Request context (for accessing request data in server components/actions)
|
|
89
|
+
export {
|
|
90
|
+
getRequestContext,
|
|
91
|
+
requireRequestContext,
|
|
92
|
+
createRequestContext,
|
|
93
|
+
type RequestContext,
|
|
94
|
+
type CreateRequestContextOptions,
|
|
95
|
+
} from "./server/request-context.js";
|
|
96
|
+
|
|
97
|
+
// Meta types
|
|
98
|
+
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
99
|
+
|
|
100
|
+
// Middleware context types (Middleware type is exported from types.ts)
|
|
101
|
+
export type {
|
|
102
|
+
MiddlewareContext,
|
|
103
|
+
CookieOptions,
|
|
104
|
+
} from "./router/middleware.js";
|
|
105
|
+
|
|
106
|
+
// Error classes and utilities
|
|
107
|
+
export {
|
|
108
|
+
RouteNotFoundError,
|
|
109
|
+
DataNotFoundError,
|
|
110
|
+
notFound,
|
|
111
|
+
MiddlewareError,
|
|
112
|
+
HandlerError,
|
|
113
|
+
BuildError,
|
|
114
|
+
InvalidHandlerError,
|
|
115
|
+
sanitizeError,
|
|
116
|
+
RouterError,
|
|
117
|
+
} from "./errors.js";
|
|
118
|
+
|
|
119
|
+
// Component utilities
|
|
120
|
+
export {
|
|
121
|
+
isClientComponent,
|
|
122
|
+
assertClientComponent,
|
|
123
|
+
} from "./component-utils.js";
|
|
124
|
+
|
|
125
|
+
// Debug utilities for route matching (development only)
|
|
126
|
+
export {
|
|
127
|
+
enableMatchDebug,
|
|
128
|
+
getMatchDebugStats,
|
|
129
|
+
} from "./router/pattern-matching.js";
|
|
130
|
+
|
|
131
|
+
// Types (re-exported for convenience - user-facing only)
|
|
132
|
+
export type {
|
|
133
|
+
// Configuration types
|
|
134
|
+
RouterEnv,
|
|
135
|
+
DefaultEnv,
|
|
136
|
+
RouteDefinition,
|
|
137
|
+
RouteConfig,
|
|
138
|
+
RouteDefinitionOptions,
|
|
139
|
+
TrailingSlashMode,
|
|
140
|
+
// Handler types
|
|
141
|
+
Handler, // Supports params object, path pattern, or route name
|
|
142
|
+
HandlerContext,
|
|
143
|
+
ExtractParams,
|
|
144
|
+
GenericParams,
|
|
145
|
+
// Middleware types (also exported from router/middleware.js above)
|
|
146
|
+
Middleware, // Supports env type and optional route name for params
|
|
147
|
+
// Revalidation types
|
|
148
|
+
RevalidateParams,
|
|
149
|
+
Revalidate,
|
|
150
|
+
RouteKeys,
|
|
151
|
+
// Loader types
|
|
152
|
+
LoaderDefinition,
|
|
153
|
+
LoaderFn,
|
|
154
|
+
LoaderContext,
|
|
155
|
+
// Error boundary types
|
|
156
|
+
ErrorInfo,
|
|
157
|
+
ErrorBoundaryFallbackProps,
|
|
158
|
+
ErrorBoundaryHandler,
|
|
159
|
+
ClientErrorBoundaryFallbackProps,
|
|
160
|
+
// NotFound boundary types
|
|
161
|
+
NotFoundInfo,
|
|
162
|
+
NotFoundBoundaryFallbackProps,
|
|
163
|
+
NotFoundBoundaryHandler,
|
|
164
|
+
// Error handling callback types
|
|
165
|
+
ErrorPhase,
|
|
166
|
+
OnErrorContext,
|
|
167
|
+
OnErrorCallback,
|
|
168
|
+
} from "./types.js";
|
|
169
|
+
|
|
170
|
+
// Path-based response type lookup from RegisteredRoutes
|
|
171
|
+
export type { PathResponse } from "./href-client.js";
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { initHandleDataSync } from "../browser/react/use-handle.js";
|
|
3
|
+
import { initSegmentsSync } from "../browser/react/use-segments.js";
|
|
4
|
+
import { initThemeConfigSync } from "../theme/theme-context.js";
|
|
5
|
+
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
6
|
+
import { NavigationStoreContext } from "../browser/react/context.js";
|
|
7
|
+
import type { NavigationStoreContextValue } from "../browser/react/context.js";
|
|
8
|
+
import type { HandleData } from "../browser/types.js";
|
|
9
|
+
import type { ErrorPhase } from "../types.js";
|
|
10
|
+
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
11
|
+
import type { EventController, DerivedNavigationState } from "../browser/event-controller.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for injectRSCPayload
|
|
15
|
+
*/
|
|
16
|
+
export interface InjectRSCPayloadOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Nonce for Content Security Policy (CSP)
|
|
19
|
+
*/
|
|
20
|
+
nonce?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for renderToReadableStream from react-dom/server
|
|
25
|
+
*/
|
|
26
|
+
interface RenderToReadableStreamOptions {
|
|
27
|
+
bootstrapScriptContent?: string;
|
|
28
|
+
nonce?: string;
|
|
29
|
+
formState?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for the renderHTML function
|
|
34
|
+
*/
|
|
35
|
+
export interface SSRRenderOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Form state for useActionState progressive enhancement.
|
|
38
|
+
* This is the result of decodeFormState() and should be passed to
|
|
39
|
+
* react-dom's renderToReadableStream to enable useActionState to
|
|
40
|
+
* receive the action result during SSR.
|
|
41
|
+
*/
|
|
42
|
+
formState?: unknown;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Nonce for Content Security Policy (CSP)
|
|
46
|
+
*/
|
|
47
|
+
nonce?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* SSR dependencies from external packages
|
|
52
|
+
*/
|
|
53
|
+
export interface SSRDependencies<TEnv = unknown> {
|
|
54
|
+
/**
|
|
55
|
+
* createFromReadableStream from @vitejs/plugin-rsc/ssr
|
|
56
|
+
*/
|
|
57
|
+
createFromReadableStream: <T>(stream: ReadableStream<Uint8Array>) => Promise<T>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* renderToReadableStream from react-dom/server.edge
|
|
61
|
+
*/
|
|
62
|
+
renderToReadableStream: (
|
|
63
|
+
element: React.ReactNode,
|
|
64
|
+
options?: RenderToReadableStreamOptions
|
|
65
|
+
) => Promise<ReadableStream<Uint8Array>>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* injectRSCPayload from rsc-html-stream/server
|
|
69
|
+
*/
|
|
70
|
+
injectRSCPayload: (
|
|
71
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
72
|
+
options?: InjectRSCPayloadOptions
|
|
73
|
+
) => TransformStream<Uint8Array, Uint8Array>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Function to load bootstrap script content
|
|
77
|
+
* Typically: () => import.meta.viteRsc.loadBootstrapScriptContent("index")
|
|
78
|
+
*/
|
|
79
|
+
loadBootstrapScriptContent: () => Promise<string>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional callback invoked when an error occurs during SSR rendering.
|
|
83
|
+
*
|
|
84
|
+
* This callback is for notification/logging purposes.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* export const renderHTML = createSSRHandler({
|
|
89
|
+
* // ... other deps
|
|
90
|
+
* onError: (error, context) => {
|
|
91
|
+
* console.error('[SSR] Rendering error:', error);
|
|
92
|
+
* Sentry.captureException(error);
|
|
93
|
+
* },
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
onError?: (error: Error, context: { phase: ErrorPhase }) => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* RSC payload type (minimal interface for SSR)
|
|
102
|
+
*/
|
|
103
|
+
interface RscPayload {
|
|
104
|
+
root: React.ReactNode;
|
|
105
|
+
metadata?: {
|
|
106
|
+
handles?: AsyncGenerator<HandleData, void, unknown>;
|
|
107
|
+
matched?: string[];
|
|
108
|
+
pathname?: string;
|
|
109
|
+
themeConfig?: ResolvedThemeConfig | null;
|
|
110
|
+
initialTheme?: Theme;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Consume an async generator and return a Promise that resolves with the final value.
|
|
116
|
+
* Used for SSR where we need to await all handle data before rendering.
|
|
117
|
+
*/
|
|
118
|
+
async function consumeAsyncGenerator(
|
|
119
|
+
generator: AsyncGenerator<HandleData, void, unknown>
|
|
120
|
+
): Promise<HandleData> {
|
|
121
|
+
let lastData: HandleData = {};
|
|
122
|
+
for await (const data of generator) {
|
|
123
|
+
lastData = data;
|
|
124
|
+
}
|
|
125
|
+
return lastData;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create a minimal event controller for SSR.
|
|
130
|
+
* This provides the correct pathname so useNavigation returns the right value during SSR.
|
|
131
|
+
*/
|
|
132
|
+
function createSsrEventController(pathname: string): EventController {
|
|
133
|
+
const location = new URL(pathname, "http://localhost");
|
|
134
|
+
const state: DerivedNavigationState = {
|
|
135
|
+
state: "idle",
|
|
136
|
+
isStreaming: false,
|
|
137
|
+
location,
|
|
138
|
+
pendingUrl: null,
|
|
139
|
+
inflightActions: [],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
getState: () => state,
|
|
144
|
+
subscribe: () => () => {},
|
|
145
|
+
getActionState: () => ({
|
|
146
|
+
state: "idle",
|
|
147
|
+
actionId: null,
|
|
148
|
+
payload: null,
|
|
149
|
+
error: null,
|
|
150
|
+
result: null,
|
|
151
|
+
}),
|
|
152
|
+
subscribeToAction: () => () => {},
|
|
153
|
+
subscribeToHandles: () => () => {},
|
|
154
|
+
setHandleData: () => {},
|
|
155
|
+
getHandleState: () => ({ data: {}, segmentOrder: [] }),
|
|
156
|
+
setLocation: () => {},
|
|
157
|
+
startNavigation: () => {
|
|
158
|
+
throw new Error("Navigation not supported during SSR");
|
|
159
|
+
},
|
|
160
|
+
abortNavigation: () => {},
|
|
161
|
+
startAction: () => {
|
|
162
|
+
throw new Error("Actions not supported during SSR");
|
|
163
|
+
},
|
|
164
|
+
abortAllActions: () => {},
|
|
165
|
+
getCurrentNavigation: () => null,
|
|
166
|
+
getInflightActions: () => new Map(),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Create an SSR handler that converts RSC streams to HTML.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* import { createSSRHandler } from "rsc-router/ssr";
|
|
176
|
+
* import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
|
|
177
|
+
* import { renderToReadableStream } from "react-dom/server.edge";
|
|
178
|
+
* import { injectRSCPayload } from "rsc-html-stream/server";
|
|
179
|
+
*
|
|
180
|
+
* export const renderHTML = createSSRHandler({
|
|
181
|
+
* createFromReadableStream,
|
|
182
|
+
* renderToReadableStream,
|
|
183
|
+
* injectRSCPayload,
|
|
184
|
+
* loadBootstrapScriptContent: () =>
|
|
185
|
+
* import.meta.viteRsc.loadBootstrapScriptContent("index"),
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
190
|
+
const {
|
|
191
|
+
createFromReadableStream,
|
|
192
|
+
renderToReadableStream,
|
|
193
|
+
injectRSCPayload,
|
|
194
|
+
loadBootstrapScriptContent,
|
|
195
|
+
onError,
|
|
196
|
+
} = deps;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render RSC stream to HTML stream
|
|
200
|
+
*
|
|
201
|
+
* @param rscStream - The RSC stream to render
|
|
202
|
+
* @param options - Optional render options including formState for useActionState and nonce for CSP
|
|
203
|
+
*/
|
|
204
|
+
return async function renderHTML(
|
|
205
|
+
rscStream: ReadableStream<Uint8Array>,
|
|
206
|
+
options?: SSRRenderOptions
|
|
207
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
208
|
+
const { nonce, formState } = options ?? {};
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Tee the stream:
|
|
212
|
+
// - rscStream1: For SSR rendering (deserialize to React VDOM)
|
|
213
|
+
// - rscStream2: For browser hydration (inject as __FLIGHT_DATA__)
|
|
214
|
+
const [rscStream1, rscStream2] = rscStream.tee();
|
|
215
|
+
|
|
216
|
+
// Deserialize RSC stream to React tree
|
|
217
|
+
let payload: Promise<RscPayload> | undefined;
|
|
218
|
+
let handlesPromise: Promise<HandleData> | undefined;
|
|
219
|
+
let ssrContextValue: NavigationStoreContextValue | undefined;
|
|
220
|
+
function SsrRoot() {
|
|
221
|
+
payload ??= createFromReadableStream<RscPayload>(rscStream1);
|
|
222
|
+
const resolved = React.use(payload);
|
|
223
|
+
|
|
224
|
+
// Initialize segments state before children render (for useSegments hook)
|
|
225
|
+
initSegmentsSync(resolved.metadata?.matched, resolved.metadata?.pathname);
|
|
226
|
+
|
|
227
|
+
// Initialize theme config for MetaTags to render theme script
|
|
228
|
+
const themeConfig = resolved.metadata?.themeConfig ?? null;
|
|
229
|
+
initThemeConfigSync(themeConfig);
|
|
230
|
+
|
|
231
|
+
// Await handles and initialize state before children render
|
|
232
|
+
// The handles property is an async generator that yields on each push
|
|
233
|
+
// Memoize the promise since async generators can only be iterated once
|
|
234
|
+
if (resolved.metadata?.handles) {
|
|
235
|
+
handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
|
|
236
|
+
const handleData = React.use(handlesPromise);
|
|
237
|
+
initHandleDataSync(handleData, resolved.metadata.matched);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Create SSR context with correct pathname for useNavigation
|
|
241
|
+
ssrContextValue ??= {
|
|
242
|
+
store: null as any,
|
|
243
|
+
eventController: createSsrEventController(resolved.metadata?.pathname ?? "/"),
|
|
244
|
+
navigate: async () => {},
|
|
245
|
+
refresh: async () => {},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Build content tree with all necessary providers
|
|
249
|
+
// Order must match NavigationProvider: NavigationStoreContext > ThemeProvider > content
|
|
250
|
+
let content: React.ReactNode = resolved.root;
|
|
251
|
+
|
|
252
|
+
// Wrap content with ThemeProvider if theme is enabled
|
|
253
|
+
if (themeConfig) {
|
|
254
|
+
content = (
|
|
255
|
+
<ThemeProvider config={themeConfig} initialTheme={resolved.metadata?.initialTheme}>
|
|
256
|
+
{content}
|
|
257
|
+
</ThemeProvider>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Wrap with NavigationStoreContext for useNavigation hook
|
|
262
|
+
return (
|
|
263
|
+
<NavigationStoreContext.Provider value={ssrContextValue}>
|
|
264
|
+
{content}
|
|
265
|
+
</NavigationStoreContext.Provider>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Get bootstrap script content
|
|
270
|
+
const bootstrapScriptContent = await loadBootstrapScriptContent();
|
|
271
|
+
|
|
272
|
+
// Render React tree to HTML stream
|
|
273
|
+
// Pass formState for useActionState progressive enhancement if provided
|
|
274
|
+
// Pass nonce for CSP if provided
|
|
275
|
+
const htmlStream = await renderToReadableStream(<SsrRoot />, {
|
|
276
|
+
bootstrapScriptContent,
|
|
277
|
+
formState,
|
|
278
|
+
nonce,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
|
|
282
|
+
return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Invoke onError callback if provided
|
|
285
|
+
if (onError) {
|
|
286
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
287
|
+
try {
|
|
288
|
+
onError(errorObj, { phase: "rendering" });
|
|
289
|
+
} catch (callbackError) {
|
|
290
|
+
console.error("[SSRHandler.onError] Callback error:", callbackError);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|