@voltro/web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +347 -0
- package/dist/defaultFallbacks-Bg_X-9Wv.js +541 -0
- package/dist/frameworkBoot-DBOFujAI.js +596 -0
- package/dist/frameworkBoot.d.ts +64 -0
- package/dist/frameworkBoot.js +2 -0
- package/dist/hooks.d.ts +28 -0
- package/dist/hooks.js +14 -0
- package/dist/index.d.ts +1077 -0
- package/dist/index.js +141 -0
- package/dist/mount-BuFzlw1e.js +74 -0
- package/dist/mount.d.ts +70 -0
- package/dist/mount.js +2 -0
- package/dist/serverContext-D5-Dmh8d.js +741 -0
- package/dist/ssr.d.ts +322 -0
- package/dist/ssr.js +76 -0
- package/package.json +68 -0
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { PipeableStream } from 'react-dom/server';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
declare interface ErrorBoundaryProps {
|
|
6
|
+
readonly error: unknown;
|
|
7
|
+
readonly reset: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* How the client-side JS hydrates a server-rendered page.
|
|
12
|
+
*
|
|
13
|
+
* - 'full' — Hydrate the entire page tree as one React root.
|
|
14
|
+
* Default for `'spa'` pages and the safe default for
|
|
15
|
+
* `'static'` pages that haven't been thought through.
|
|
16
|
+
* Subscriptions + interactivity work everywhere.
|
|
17
|
+
* - 'islands' — Skip the full-tree hydration. The client runtime
|
|
18
|
+
* only hydrates `<div data-voltro-island>` markers
|
|
19
|
+
* emitted by `island()`. Other parts of the page stay
|
|
20
|
+
* pure static HTML with no React lifecycle running.
|
|
21
|
+
* Best perf for content-heavy pages with isolated
|
|
22
|
+
* interactive zones.
|
|
23
|
+
*
|
|
24
|
+
* Defaults to `'full'` when not specified.
|
|
25
|
+
*/
|
|
26
|
+
declare type InteractiveMode = 'full' | 'islands' | 'none';
|
|
27
|
+
|
|
28
|
+
declare interface LoaderContext {
|
|
29
|
+
readonly params: Readonly<Record<string, string>>;
|
|
30
|
+
readonly pathname: string;
|
|
31
|
+
/** Aborted when the user navigates away before the loader resolves. */
|
|
32
|
+
readonly signal: AbortSignal;
|
|
33
|
+
/** Request headers when the loader runs server-side via `voltro
|
|
34
|
+
* start`. Lowercased keys. Empty `{}` for client-side loader
|
|
35
|
+
* invocations (browser-fetch goes through the rpc layer, not the
|
|
36
|
+
* loader). Use this for tenant resolution, auth headers, etc.
|
|
37
|
+
* until the auth slice ships a typed Subject. */
|
|
38
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
39
|
+
/** Server-side one-shot rpc fetch. Invokes any backend rpc (queries
|
|
40
|
+
* included — the FIRST snapshot of a streaming query is resolved and
|
|
41
|
+
* returned) over the api's HTTP rpc surface (`POST /rpc`), forwarding
|
|
42
|
+
* the request's `cookie` header so the api resolves the same Subject +
|
|
43
|
+
* tenant the WS path would. Bound to the app's single / first api.
|
|
44
|
+
*
|
|
45
|
+
* Present ONLY when the loader runs server-side (`voltro start` /
|
|
46
|
+
* `voltro dev` SSR). `undefined` for client-side loader invocations —
|
|
47
|
+
* in the browser, use `useSubscription` in the component for live data
|
|
48
|
+
* instead; the loader's `query` is for SSR first-paint + `meta`. */
|
|
49
|
+
readonly query?: <T = unknown>(tag: string, input?: Record<string, unknown>) => Promise<T>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare type LoaderFn<T = unknown> = (ctx: LoaderContext) => Promise<T> | T;
|
|
53
|
+
|
|
54
|
+
declare interface PageDescriptor<TLoaderData = unknown> {
|
|
55
|
+
/** URL pattern, e.g. `/`, `/about`, `/users/[id]`, `/docs/[...slug]`. */
|
|
56
|
+
readonly pattern: string;
|
|
57
|
+
/** The page component. Present on eager routes; on a LAZY route (see
|
|
58
|
+
* `load`) it's filled by the Router once the page chunk has loaded. */
|
|
59
|
+
readonly Component?: ComponentType | undefined;
|
|
60
|
+
/** LAZY route: a thunk that dynamically imports the page module. When set,
|
|
61
|
+
* the page chunk is fetched only when this route first matches, so the
|
|
62
|
+
* initial bundle never imports every page. The Router resolves
|
|
63
|
+
* `Component` / `loader` / `meta` / … from the loaded module; the layout
|
|
64
|
+
* `chain` stays eager. */
|
|
65
|
+
readonly load?: (() => Promise<Record<string, unknown>>) | undefined;
|
|
66
|
+
/** Static meta, or a function of `{ params, loaderData, locale }` —
|
|
67
|
+
* so a document title can read what the page loader fetched (the
|
|
68
|
+
* dynamic metadata case) AND can localise per-locale at SSG time
|
|
69
|
+
* for URL-prefix i18n routing. `loaderData` is the page loader's
|
|
70
|
+
* result; `locale` is the active i18n locale during pre-render
|
|
71
|
+
* (driven by `params.locale` for `[locale]/...` routes, falling
|
|
72
|
+
* back to the app's defaultLocale). On the client, `locale` is
|
|
73
|
+
* the URL-derived locale when the route is locale-prefixed,
|
|
74
|
+
* otherwise the framework's resolved locale (cookie / Accept-
|
|
75
|
+
* Language / default). */
|
|
76
|
+
readonly meta?: PageMeta | ((ctx: {
|
|
77
|
+
readonly params: Readonly<Record<string, string>>;
|
|
78
|
+
readonly loaderData: unknown;
|
|
79
|
+
readonly locale: string;
|
|
80
|
+
}) => PageMeta) | undefined;
|
|
81
|
+
/** Async data preload; result available via useLoaderData<T>(). */
|
|
82
|
+
readonly loader?: LoaderFn<TLoaderData> | undefined;
|
|
83
|
+
/** Catches render + loader errors for THIS leaf. Tighter scope than `chain[*].Error`. */
|
|
84
|
+
readonly ErrorBoundary?: ComponentType<ErrorBoundaryProps> | undefined;
|
|
85
|
+
/** Shown while the loader is in flight (first navigation to the route). */
|
|
86
|
+
readonly Pending?: ComponentType | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Outer-to-inner layer chain. Each segment maps to one directory level
|
|
89
|
+
* (or route group) on the way from the pages root down to this leaf.
|
|
90
|
+
* Codegen produces this from the discovery walk; user code rarely
|
|
91
|
+
* writes it by hand.
|
|
92
|
+
*/
|
|
93
|
+
readonly chain?: ReadonlyArray<RouteSegment> | undefined;
|
|
94
|
+
/** How the page's HTML is produced. Defaults to 'static' so pages
|
|
95
|
+
* pre-render by default — SEO + first-paint speed are wins users
|
|
96
|
+
* pay nothing for. Opt out with `'spa'` for pages that need a
|
|
97
|
+
* per-request render. */
|
|
98
|
+
readonly renderMode?: RenderMode | undefined;
|
|
99
|
+
/** How the page's client-side JS hydrates. Defaults to `'full'`
|
|
100
|
+
* (whole tree). Pages designed around the islands pattern should
|
|
101
|
+
* set this to `'islands'` so only marked interactive zones
|
|
102
|
+
* hydrate; the rest stays pure static HTML. */
|
|
103
|
+
readonly interactive?: InteractiveMode | undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare interface PageMeta {
|
|
107
|
+
readonly title?: string;
|
|
108
|
+
readonly description?: string;
|
|
109
|
+
/** Canonical URL for this page. Emitted as `<link rel="canonical">`.
|
|
110
|
+
* Relative paths are accepted — search engines resolve them
|
|
111
|
+
* against the document's base URL. Use this on duplicated routes
|
|
112
|
+
* (locale variants, alternate URL forms) to declare the preferred
|
|
113
|
+
* one indexable URL. */
|
|
114
|
+
readonly canonical?: string;
|
|
115
|
+
/** Arbitrary additional meta tags. `name` OR `property` is required. */
|
|
116
|
+
readonly tags?: ReadonlyArray<{
|
|
117
|
+
readonly name?: string;
|
|
118
|
+
readonly property?: string;
|
|
119
|
+
readonly content: string;
|
|
120
|
+
}>;
|
|
121
|
+
/** Arbitrary additional `<link>` tags. Use for `alternate` hreflang,
|
|
122
|
+
* prev/next pagination, RSS feeds, manifest, etc. (For the common
|
|
123
|
+
* canonical case, prefer the dedicated `canonical` field above.) */
|
|
124
|
+
readonly links?: ReadonlyArray<{
|
|
125
|
+
readonly rel: string;
|
|
126
|
+
readonly href: string;
|
|
127
|
+
readonly hreflang?: string;
|
|
128
|
+
readonly type?: string;
|
|
129
|
+
readonly title?: string;
|
|
130
|
+
}>;
|
|
131
|
+
/** JSON-LD structured-data payloads. Each entry is emitted as a
|
|
132
|
+
* separate `<script type="application/ld+json">` so search engines
|
|
133
|
+
* and LLM crawlers can pick up rich metadata (Article, Product,
|
|
134
|
+
* SoftwareApplication, BreadcrumbList, FAQPage, etc.). Anything
|
|
135
|
+
* JSON-serialisable is accepted; `@context` + `@type` are
|
|
136
|
+
* authored, not synthesised. */
|
|
137
|
+
readonly jsonLd?: ReadonlyArray<Record<string, unknown>>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Helper for the build pipeline: stringify the resolved meta into a
|
|
144
|
+
* `<head>`-ready HTML fragment. Returns an empty string when no meta
|
|
145
|
+
* is set so the caller can blindly inject the result.
|
|
146
|
+
*/
|
|
147
|
+
export declare const renderMetaToHtml: (meta: PageMeta | null) => string;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* How a page's HTML is produced.
|
|
151
|
+
*
|
|
152
|
+
* - 'static' — Pre-rendered to flat HTML at build time. Default.
|
|
153
|
+
* Shipped as `dist/<route>/index.html`. JS bundle
|
|
154
|
+
* still loads to hydrate islands + handle SPA-style
|
|
155
|
+
* navigation, but the first paint is instant + SEO
|
|
156
|
+
* friendly. Loaders MUST be side-effect-free + must
|
|
157
|
+
* not require per-request context (subject, headers).
|
|
158
|
+
* - 'spa' — Client-only rendering. No HTML pre-render; the
|
|
159
|
+
* shipped HTML is a shell that mounts on the
|
|
160
|
+
* client. Useful for pages that genuinely need a
|
|
161
|
+
* fresh client render every load (most reactive
|
|
162
|
+
* dashboards).
|
|
163
|
+
*
|
|
164
|
+
* - 'ssr' — Rendered on every request, server-side, via `voltro
|
|
165
|
+
* start`. Loaders run per request with access to
|
|
166
|
+
* `headers` + `query` (the api HTTP rpc); the resolved
|
|
167
|
+
* HTML is sent fresh each time. Use for personalised /
|
|
168
|
+
* tenant-scoped first paint.
|
|
169
|
+
* - 'isr' — Like 'ssr' on the first request, then cached and
|
|
170
|
+
* revalidated in the background on a TTL.
|
|
171
|
+
*/
|
|
172
|
+
declare type RenderMode = 'static' | 'spa' | 'ssr' | 'isr';
|
|
173
|
+
|
|
174
|
+
export declare interface RenderPageOptions {
|
|
175
|
+
/** Descriptor produced by codegen (`pageRoute(...)`). */
|
|
176
|
+
readonly descriptor: PageDescriptor;
|
|
177
|
+
/** URL pattern's resolved params. `{}` for static, no-param routes. */
|
|
178
|
+
readonly params: Readonly<Record<string, string>>;
|
|
179
|
+
/** Pathname this render resolves under. Used by `useLocation()` on
|
|
180
|
+
* the server side. */
|
|
181
|
+
readonly pathname: string;
|
|
182
|
+
/** Page (leaf) loader data — pre-resolved by the caller. The build /
|
|
183
|
+
* SSR pipeline runs the descriptor's `loader` (if any) and passes the
|
|
184
|
+
* result here; reachable via `useLoaderData()` inside the page. */
|
|
185
|
+
readonly loaderData: unknown;
|
|
186
|
+
/** Per-layout loader data, keyed by chain-segment index. The pipeline
|
|
187
|
+
* runs each `chain[i].loader` and passes the results here; each layout
|
|
188
|
+
* reads ITS own via `useLoaderData()`. Omit when no layout loaders. */
|
|
189
|
+
readonly segmentLoaderData?: Readonly<Record<number, unknown>>;
|
|
190
|
+
/** Request snapshot (cookies + headers) exposed to the rendered tree
|
|
191
|
+
* via `useServerRequest()`. Optional: omit on builds (static
|
|
192
|
+
* prerender) where no per-request data exists; provide on
|
|
193
|
+
* per-request SSR so consumers can resolve cookie-driven preferences
|
|
194
|
+
* like locale or theme. */
|
|
195
|
+
readonly requestContext?: ServerRequestContextValue;
|
|
196
|
+
/** Outer wrapper to apply around the rendered tree. The build and
|
|
197
|
+
* the SSR runtime pass an `<I18nProvider>` wrapper here when the
|
|
198
|
+
* consumer app has `locales` configured — without it, any page
|
|
199
|
+
* calling `useT()` would crash at render time ("Could not find
|
|
200
|
+
* required `intl` object"). The wrapper is generic on purpose:
|
|
201
|
+
* any cross-cutting provider (i18n, theme, feature-flag context)
|
|
202
|
+
* rides this slot so `@voltro/web` stays free of optional deps. */
|
|
203
|
+
readonly outerWrap?: (children: ReactNode) => ReactNode;
|
|
204
|
+
/** Active locale for this render. Drives meta resolution so a page's
|
|
205
|
+
* `meta` function can localise title / description / canonical /
|
|
206
|
+
* hreflang per locale. The build passes `params.locale ?? defaultLocale`
|
|
207
|
+
* so locale-prefixed routes (`[locale]/...`) get their per-locale
|
|
208
|
+
* catalog at meta-resolve time. Optional: when absent, page
|
|
209
|
+
* `meta(ctx)` callbacks see `ctx.locale = 'en'` as a safe default. */
|
|
210
|
+
readonly locale?: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export declare interface RenderPageResult {
|
|
214
|
+
/** HTML for the page body — goes inside `<div id="root">…</div>`. */
|
|
215
|
+
readonly html: string;
|
|
216
|
+
/** Resolved page meta (after running the descriptor's `meta`
|
|
217
|
+
* function with `params` if present). Caller renders this into
|
|
218
|
+
* the document head. */
|
|
219
|
+
readonly meta: PageMeta | null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export declare interface RenderPageStreamOptions extends RenderPageOptions {
|
|
223
|
+
/** Called once React commits the shell (all Suspense boundaries that
|
|
224
|
+
* weren't deferred have resolved). Flush the `<head>` (with `meta`) +
|
|
225
|
+
* opening `<body>` here, then pipe the returned stream into the response.
|
|
226
|
+
* The build pipeline uses this to send bytes before the whole tree is
|
|
227
|
+
* ready — the streaming-SSR win over `renderToString`. */
|
|
228
|
+
readonly onShellReady?: () => void;
|
|
229
|
+
/** Called if the shell itself fails to render (before any bytes flushed) —
|
|
230
|
+
* the pipeline should fall back to an error page / a 500. */
|
|
231
|
+
readonly onShellError?: (error: unknown) => void;
|
|
232
|
+
/** Called when the ENTIRE tree (incl. deferred Suspense content) has
|
|
233
|
+
* streamed. Close the response's trailing `</body></html>` here. */
|
|
234
|
+
readonly onAllReady?: () => void;
|
|
235
|
+
/** Bootstrap `<script>` module URLs injected by React at the end of the
|
|
236
|
+
* shell — the client bundle that hydrates the streamed HTML. */
|
|
237
|
+
readonly bootstrapModules?: ReadonlyArray<string>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export declare interface RenderPageStreamResult {
|
|
241
|
+
/** The React pipeable stream. `pipe(res)` into a Node writable to flush the
|
|
242
|
+
* page body; `abort()` to cancel a slow render. */
|
|
243
|
+
readonly stream: PipeableStream;
|
|
244
|
+
/** Resolved page meta — available synchronously (meta runs before render),
|
|
245
|
+
* so the caller can write `<head>` in `onShellReady` before piping. */
|
|
246
|
+
readonly meta: PageMeta | null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Render a page to an HTML string using its layout chain + loader data. The
|
|
251
|
+
* returned `html` is the body fragment — the caller composes the surrounding
|
|
252
|
+
* document. Renders synchronously via `renderToString`; for the streaming
|
|
253
|
+
* path (flush the shell before the whole tree is ready) use
|
|
254
|
+
* {@link renderPageToStream}.
|
|
255
|
+
*/
|
|
256
|
+
export declare const renderPageToHtml: (options: RenderPageOptions) => RenderPageResult;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Streaming SSR — the richer path alongside {@link renderPageToHtml}. Renders
|
|
260
|
+
* the page via React's `renderToPipeableStream` so the server can flush the
|
|
261
|
+
* shell (and each Suspense boundary as it resolves) instead of buffering the
|
|
262
|
+
* whole document into one string. Same tree, same providers, same meta as the
|
|
263
|
+
* sync path; the difference is time-to-first-byte and native Suspense
|
|
264
|
+
* streaming.
|
|
265
|
+
*
|
|
266
|
+
* The caller drives the response: write the doctype + `<head>` (using the
|
|
267
|
+
* synchronously-returned `meta`) + opening `<body><div id="root">` in
|
|
268
|
+
* `onShellReady`, `stream.pipe(res)`, then close the document in `onAllReady`.
|
|
269
|
+
*/
|
|
270
|
+
export declare const renderPageToStream: (options: RenderPageStreamOptions) => RenderPageStreamResult;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* One layer in a page's nesting chain. Each represents a directory level
|
|
274
|
+
* in the file-convention layout (or a route group).
|
|
275
|
+
*
|
|
276
|
+
* Layouts wrap their children — outer layers wrap inner ones. Errors are
|
|
277
|
+
* caught by the deepest layer with an `Error` component; if none, the
|
|
278
|
+
* error bubbles up the chain. NotFound applies when an URL is under this
|
|
279
|
+
* layer's scope but no concrete page matches.
|
|
280
|
+
*
|
|
281
|
+
* The chain persists across navigation within its scope — React preserves
|
|
282
|
+
* the layout component instances because their position in the tree is
|
|
283
|
+
* stable for any leaf within that subtree.
|
|
284
|
+
*/
|
|
285
|
+
declare interface RouteSegment {
|
|
286
|
+
/** Outer-vs-inner ordering label (debug only). */
|
|
287
|
+
readonly id?: string | undefined;
|
|
288
|
+
readonly Layout?: ComponentType<{
|
|
289
|
+
children: ReactNode;
|
|
290
|
+
}> | undefined;
|
|
291
|
+
readonly Error?: ComponentType<ErrorBoundaryProps> | undefined;
|
|
292
|
+
readonly Pending?: ComponentType | undefined;
|
|
293
|
+
readonly NotFound?: ComponentType | undefined;
|
|
294
|
+
/** Async data preload for THIS layout level. Runs in parallel with the
|
|
295
|
+
* page loader (and the other layouts') before mount; the result is
|
|
296
|
+
* reachable via `useLoaderData<T>()` from INSIDE this layout (each
|
|
297
|
+
* level sees its own loader's data). A `layout.tsx` exports it like a
|
|
298
|
+
* page does — codegen wires it onto the segment. Server-side it
|
|
299
|
+
* receives request `headers` (session/tenant resolution). */
|
|
300
|
+
readonly loader?: LoaderFn | undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Serialise loader data into a `<script>` payload safe to embed in
|
|
305
|
+
* HTML. The result is wrapped in `<script type="application/json"
|
|
306
|
+
* id="__voltro_state__">` by the caller; here we just produce the
|
|
307
|
+
* JSON body with `</` sequences escaped (the well-known SSR XSS
|
|
308
|
+
* vector).
|
|
309
|
+
*/
|
|
310
|
+
export declare const serialiseStateForInlining: (state: unknown) => string;
|
|
311
|
+
|
|
312
|
+
declare interface ServerRequestContextValue {
|
|
313
|
+
readonly cookies: Readonly<Record<string, string>>;
|
|
314
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
315
|
+
/** Raw request URL as it came off the wire (path + query). Useful
|
|
316
|
+
* for SSR pages that need to read `?q=…` style search params
|
|
317
|
+
* without touching anything client-only. Empty string for build-
|
|
318
|
+
* time SSG renders where there is no incoming request. */
|
|
319
|
+
readonly url: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export { }
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { c as e, d as t, r as n, t as r, x as i } from "./serverContext-D5-Dmh8d.js";
|
|
2
|
+
import { createElement as a } from "react";
|
|
3
|
+
import { renderToPipeableStream as o, renderToString as s } from "react-dom/server";
|
|
4
|
+
//#region src/ssr.tsx
|
|
5
|
+
var c = (t, n, r, i) => {
|
|
6
|
+
let o = a(e.Provider, { value: r }, a(t));
|
|
7
|
+
if (!n) return o;
|
|
8
|
+
for (let t = n.length - 1; t >= 0; t--) {
|
|
9
|
+
let r = n[t];
|
|
10
|
+
if (r.Layout) {
|
|
11
|
+
let n = r.Layout;
|
|
12
|
+
o = a(e.Provider, { value: i(t) }, a(n, { children: o }));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return o;
|
|
16
|
+
}, l = (e) => {
|
|
17
|
+
let { descriptor: n, params: o, pathname: s, loaderData: l, segmentLoaderData: u, requestContext: d, outerWrap: f, locale: p } = e, m = i(n.meta, o, l, p), h = {
|
|
18
|
+
pathname: s,
|
|
19
|
+
search: "",
|
|
20
|
+
params: o,
|
|
21
|
+
loaderData: l,
|
|
22
|
+
navigate: () => {
|
|
23
|
+
throw Error("navigate() is not supported during server-side rendering. Trigger navigations on the client only.");
|
|
24
|
+
},
|
|
25
|
+
prefetch: () => {},
|
|
26
|
+
registerBlocker: () => () => {},
|
|
27
|
+
blocked: null
|
|
28
|
+
}, g = n.Component;
|
|
29
|
+
if (!g) throw Error(`SSR received a descriptor with no Component for "${s}". Lazy page routes are a client-only optimization and must not be used on the server.`);
|
|
30
|
+
let _ = a(t.Provider, { value: h }, c(g, n.chain, l, (e) => u?.[e]));
|
|
31
|
+
return d && (_ = a(r.Provider, { value: d }, _)), f && (_ = f(_)), {
|
|
32
|
+
tree: _,
|
|
33
|
+
meta: m
|
|
34
|
+
};
|
|
35
|
+
}, u = (e) => {
|
|
36
|
+
let { tree: t, meta: n } = l(e);
|
|
37
|
+
return {
|
|
38
|
+
html: s(t),
|
|
39
|
+
meta: n
|
|
40
|
+
};
|
|
41
|
+
}, d = (e) => {
|
|
42
|
+
let { tree: t, meta: n } = l(e);
|
|
43
|
+
return {
|
|
44
|
+
stream: o(t, {
|
|
45
|
+
...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
|
|
46
|
+
...e.onShellReady ? { onShellReady: e.onShellReady } : {},
|
|
47
|
+
...e.onShellError ? { onShellError: e.onShellError } : {},
|
|
48
|
+
...e.onAllReady ? { onAllReady: e.onAllReady } : {}
|
|
49
|
+
}),
|
|
50
|
+
meta: n
|
|
51
|
+
};
|
|
52
|
+
}, f = (e) => {
|
|
53
|
+
if (!e) return "";
|
|
54
|
+
let t = [];
|
|
55
|
+
if (typeof e.title == "string" && t.push(`<title>${m(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${h(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${h(e.canonical)}" />`), e.tags) for (let n of e.tags) {
|
|
56
|
+
let e = n.name ? `name="${h(n.name)}"` : n.property ? `property="${h(n.property)}"` : "";
|
|
57
|
+
e && t.push(`<meta ${e} content="${h(n.content)}" />`);
|
|
58
|
+
}
|
|
59
|
+
if (e.links) for (let n of e.links) {
|
|
60
|
+
let e = [
|
|
61
|
+
`rel="${h(n.rel)}"`,
|
|
62
|
+
`href="${h(n.href)}"`,
|
|
63
|
+
n.hreflang ? `hreflang="${h(n.hreflang)}"` : "",
|
|
64
|
+
n.type ? `type="${h(n.type)}"` : "",
|
|
65
|
+
n.title ? `title="${h(n.title)}"` : ""
|
|
66
|
+
].filter(Boolean).join(" ");
|
|
67
|
+
t.push(`<link ${e} />`);
|
|
68
|
+
}
|
|
69
|
+
if (e.jsonLd) for (let n of e.jsonLd) {
|
|
70
|
+
let e = JSON.stringify(n).replace(/<\/(script)/gi, "<\\/$1").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
71
|
+
t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
|
|
72
|
+
}
|
|
73
|
+
return t.join("\n ");
|
|
74
|
+
}, p = (e) => JSON.stringify(e ?? null).replace(/</g, "\\u003c"), m = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"), h = (e) => e.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
75
|
+
//#endregion
|
|
76
|
+
export { n as parseCookieHeader, f as renderMetaToHtml, u as renderPageToHtml, d as renderPageToStream, p as serialiseStateForInlining };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Voltro web framework — file-based routing, render modes (SSR / SSG / islands), the page-export contract, data hooks, and the browser mount.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./mount": {
|
|
27
|
+
"types": "./dist/mount.d.ts",
|
|
28
|
+
"import": "./dist/mount.js",
|
|
29
|
+
"default": "./dist/mount.js"
|
|
30
|
+
},
|
|
31
|
+
"./hooks": {
|
|
32
|
+
"types": "./dist/hooks.d.ts",
|
|
33
|
+
"import": "./dist/hooks.js",
|
|
34
|
+
"default": "./dist/hooks.js"
|
|
35
|
+
},
|
|
36
|
+
"./runtime": {
|
|
37
|
+
"types": "./dist/frameworkBoot.d.ts",
|
|
38
|
+
"import": "./dist/frameworkBoot.js",
|
|
39
|
+
"default": "./dist/frameworkBoot.js"
|
|
40
|
+
},
|
|
41
|
+
"./ssr": {
|
|
42
|
+
"types": "./dist/ssr.d.ts",
|
|
43
|
+
"import": "./dist/ssr.js",
|
|
44
|
+
"default": "./dist/ssr.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"main": "./dist/index.js",
|
|
48
|
+
"module": "./dist/index.js",
|
|
49
|
+
"types": "./dist/index.d.ts",
|
|
50
|
+
"sideEffects": false,
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=24.0.0"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@voltro/client": "0.1.0",
|
|
56
|
+
"@voltro/ui": "0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@effect/platform": "^0.96.2",
|
|
60
|
+
"@effect/rpc": "^0.75.1",
|
|
61
|
+
"effect": "^3.21.4",
|
|
62
|
+
"react": "^19.0.0",
|
|
63
|
+
"react-dom": "^19.0.0"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|