@voltro/web 0.7.0 → 0.9.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 +40 -0
- package/dist/index.d.ts +88 -1
- package/dist/index.js +68 -45
- package/dist/mount-Gsn3ouYx.js +76 -0
- package/dist/mount.js +1 -1
- package/dist/routerState-DpUqogK8.js +101 -0
- package/dist/{serverContext-DSValgbm.js → serverContext-COzippNt.js} +271 -224
- package/dist/ssr.d.ts +189 -14
- package/dist/ssr.js +41 -40
- package/package.json +3 -3
- package/dist/mount-DhGdfjSE.js +0 -74
package/dist/ssr.d.ts
CHANGED
|
@@ -2,11 +2,99 @@ import { ComponentType } from 'react';
|
|
|
2
2
|
import { PipeableStream } from 'react-dom/server';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Throw unless this page can actually stream. Verified against React 19.2.7
|
|
7
|
+
* rather than assumed — each rejected combination FAILS SILENTLY otherwise,
|
|
8
|
+
* which is worse than not shipping the feature:
|
|
9
|
+
*
|
|
10
|
+
* - `renderMode: 'static'` — the prerender uses `renderToString`, which does
|
|
11
|
+
* not support Suspense: it emits an ERRORED boundary (`<!--$!-->`) plus a
|
|
12
|
+
* `<template data-msg="Switched to client rendering...">` and no warning.
|
|
13
|
+
* The artefact would ship a permanent client-rendered fallback.
|
|
14
|
+
* - `renderMode: 'isr'` — ISR caches a completed HTML STRING. Awaiting the
|
|
15
|
+
* deferred values to fill that string would make `defer()` a silent no-op
|
|
16
|
+
* that still reads like it streams; caching a stream is different
|
|
17
|
+
* machinery entirely.
|
|
18
|
+
* - `interactive: 'none'` — revealing a boundary needs React's inline
|
|
19
|
+
* `$RC`/`$RV` scripts, and this mode ships zero JS. The fallback would be
|
|
20
|
+
* permanent.
|
|
21
|
+
* - `interactive: 'islands'` — `mount()` returns after `hydrateIslandsOnPage()`
|
|
22
|
+
* and never hydrates the root, so nothing ever consumes the registry
|
|
23
|
+
* promises. Islands take their props from their own serialised payload, not
|
|
24
|
+
* from loader data, so there is no value in wiring them in.
|
|
25
|
+
*/
|
|
26
|
+
export declare const assertDeferralSupported: (target: DeferralTarget) => void;
|
|
27
|
+
|
|
28
|
+
/** Render modes / interactive modes a page may combine with `defer()`. */
|
|
29
|
+
export declare interface DeferralTarget {
|
|
30
|
+
/** Route pattern or source file — named in the error so it is actionable. */
|
|
31
|
+
readonly page: string;
|
|
32
|
+
readonly renderMode: string;
|
|
33
|
+
readonly interactive: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Brand key on the object `defer()` returns. A single explicit container
|
|
37
|
+
* check — NOT a scan of arbitrary loader results for markers by shape. */
|
|
38
|
+
declare const DEFERRED_RESULT_TAG: "__voltroDeferredLoaderResult";
|
|
39
|
+
|
|
40
|
+
/** What a loader returns when it defers part of its data. Opaque to user
|
|
41
|
+
* code: build it with {@link defer}, read it with `useLoaderData()`. */
|
|
42
|
+
declare interface DeferredLoaderResult<TEager extends object = object, TDeferred extends Record<string, Promise<unknown>> = Record<string, Promise<unknown>>> {
|
|
43
|
+
readonly [DEFERRED_RESULT_TAG]: true;
|
|
44
|
+
/** Awaited before the shell renders — present in the SSR markup AND in the
|
|
45
|
+
* inlined `__voltro_state__` payload. */
|
|
46
|
+
readonly eager: TEager;
|
|
47
|
+
/** Streamed after the shell — each key becomes a promise on
|
|
48
|
+
* `useLoaderData()`, to be rendered through `<Await>`. */
|
|
49
|
+
readonly deferred: TDeferred;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Build the wire payload from what the server render was given. */
|
|
53
|
+
export declare const encodeRouterState: (input: RouterStateInput) => InlinedRouterState;
|
|
54
|
+
|
|
5
55
|
declare interface ErrorBoundaryProps {
|
|
6
56
|
readonly error: unknown;
|
|
7
57
|
readonly reset: () => void;
|
|
8
58
|
}
|
|
9
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The wire shape. JSON cannot express `undefined`, which is why `ran` exists
|
|
62
|
+
* as a separate presence record: a loader that returned `undefined` is
|
|
63
|
+
* otherwise indistinguishable from one that never ran, and the client would
|
|
64
|
+
* re-run it on hydration (the flash this whole payload exists to remove).
|
|
65
|
+
*/
|
|
66
|
+
export declare interface InlinedRouterState {
|
|
67
|
+
/** The page (leaf) loader's result — exactly the value the server render
|
|
68
|
+
* provided to the page's `LoaderDataContext`. */
|
|
69
|
+
readonly page?: unknown;
|
|
70
|
+
/** Per-layout loader results keyed by CHAIN INDEX (as a JSON string key).
|
|
71
|
+
* The index is the same one `descriptor.chain[i]` uses on both sides —
|
|
72
|
+
* see `wrapInChain` in ssr.tsx and in router.tsx. */
|
|
73
|
+
readonly segments?: Readonly<Record<string, unknown>>;
|
|
74
|
+
/** Which loaders actually RAN server-side. Drives cache seeding (i.e.
|
|
75
|
+
* "don't re-run this on hydration"); the values above drive the render. */
|
|
76
|
+
readonly ran?: {
|
|
77
|
+
readonly page?: boolean;
|
|
78
|
+
readonly segments?: ReadonlyArray<number>;
|
|
79
|
+
};
|
|
80
|
+
/** True when the server rendered the layout chain but left the page leaf as
|
|
81
|
+
* an EMPTY slot — the `renderMode:'spa'`-under-an-SSR-layout case. The
|
|
82
|
+
* server ran only the layout loaders; the page is client-only. The client's
|
|
83
|
+
* FIRST render must reproduce the identical empty slot so the layout chain
|
|
84
|
+
* hydrates without a mismatch, then mount the real page after commit. Absent
|
|
85
|
+
* (falsy) for every full-page SSR/ISR/static document. */
|
|
86
|
+
readonly pageClientOnly?: boolean;
|
|
87
|
+
/** Deferred (streamed) loader fields: field name -> client-registry id, for
|
|
88
|
+
* the page loader and per chain index. The VALUES are not here — they
|
|
89
|
+
* arrive later, published by the settle `<script>` each `<Await>` boundary
|
|
90
|
+
* emits as its promise resolves. Absent entirely when nothing deferred, so
|
|
91
|
+
* a non-deferring page's payload is byte-identical to what it always was. */
|
|
92
|
+
readonly deferred?: {
|
|
93
|
+
readonly page?: Readonly<Record<string, string>>;
|
|
94
|
+
readonly segments?: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
10
98
|
/**
|
|
11
99
|
* How the client-side JS hydrates a server-rendered page.
|
|
12
100
|
*
|
|
@@ -25,6 +113,9 @@ declare interface ErrorBoundaryProps {
|
|
|
25
113
|
*/
|
|
26
114
|
declare type InteractiveMode = 'full' | 'islands' | 'none';
|
|
27
115
|
|
|
116
|
+
/** True for exactly the object {@link defer} returns. */
|
|
117
|
+
export declare const isDeferredLoaderResult: (value: unknown) => value is DeferredLoaderResult;
|
|
118
|
+
|
|
28
119
|
declare interface LoaderContext {
|
|
29
120
|
readonly params: Readonly<Record<string, string>>;
|
|
30
121
|
readonly pathname: string;
|
|
@@ -139,6 +230,33 @@ declare interface PageMeta {
|
|
|
139
230
|
|
|
140
231
|
export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
|
|
141
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Split one loader result for a streaming render. `scope` is `'page'` for the
|
|
235
|
+
* page loader or the layout's chain index, and only feeds the registry id.
|
|
236
|
+
*/
|
|
237
|
+
export declare const prepareDeferredLoaderData: (result: unknown, scope: "page" | number) => PreparedLoaderData;
|
|
238
|
+
|
|
239
|
+
/** One loader's result, prepared for a streaming SSR render. */
|
|
240
|
+
export declare interface PreparedLoaderData {
|
|
241
|
+
/** What the RENDERER gets: eager fields plus one tagged promise per
|
|
242
|
+
* deferred field. Identical to the plain result when nothing deferred. */
|
|
243
|
+
readonly renderData: unknown;
|
|
244
|
+
/** What the `__voltro_state__` payload gets: eager fields only. A promise
|
|
245
|
+
* serialises to `{}`, so the deferred half must never reach it. */
|
|
246
|
+
readonly stateData: unknown;
|
|
247
|
+
/** Deferred field name -> registry id, for the state payload. `undefined`
|
|
248
|
+
* when the loader did not defer — the wire shape is then byte-identical
|
|
249
|
+
* to what a non-deferring page has always emitted. */
|
|
250
|
+
readonly deferredIds: Readonly<Record<string, string>> | undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The `<script>` every streaming SSR emitter puts in `<head>`, before the
|
|
255
|
+
* shell. Must precede any settle script — which is automatic, since settle
|
|
256
|
+
* scripts ride in the body.
|
|
257
|
+
*/
|
|
258
|
+
export declare const renderDeferredRegistryScript: () => string;
|
|
259
|
+
|
|
142
260
|
/**
|
|
143
261
|
* Helper for the build pipeline: stringify the resolved meta into a
|
|
144
262
|
* `<head>`-ready HTML fragment. Returns an empty string when no meta
|
|
@@ -208,6 +326,14 @@ export declare interface RenderPageOptions {
|
|
|
208
326
|
* catalog at meta-resolve time. Optional: when absent, page
|
|
209
327
|
* `meta(ctx)` callbacks see `ctx.locale = 'en'` as a safe default. */
|
|
210
328
|
readonly locale?: string;
|
|
329
|
+
/** Render the LAYOUT CHAIN ONLY, with an empty page slot at the leaf
|
|
330
|
+
* ({@link PageSlot}) instead of the page Component — the
|
|
331
|
+
* `renderMode:'spa'`-under-an-SSR-layout case. The caller runs only the
|
|
332
|
+
* layout loaders (not the page loader) and pairs this with a state script
|
|
333
|
+
* carrying `pageClientOnly: true`, so the client reproduces the empty slot
|
|
334
|
+
* on its first render and mounts the page after hydration. When set, the
|
|
335
|
+
* descriptor need not carry a `Component`. */
|
|
336
|
+
readonly pageSlot?: boolean;
|
|
211
337
|
}
|
|
212
338
|
|
|
213
339
|
export declare interface RenderPageResult {
|
|
@@ -249,26 +375,75 @@ export declare interface RenderPageStreamResult {
|
|
|
249
375
|
/**
|
|
250
376
|
* Render a page to an HTML string using its layout chain + loader data. The
|
|
251
377
|
* returned `html` is the body fragment — the caller composes the surrounding
|
|
252
|
-
* document. Renders synchronously via `renderToString
|
|
253
|
-
*
|
|
378
|
+
* document. Renders synchronously via `renderToString`.
|
|
379
|
+
*
|
|
380
|
+
* This is the ARTEFACT renderer: static prerender and ISR, both of which need
|
|
381
|
+
* the whole document. See the doctrine note above before switching a caller to
|
|
254
382
|
* {@link renderPageToStream}.
|
|
255
383
|
*/
|
|
256
384
|
export declare const renderPageToHtml: (options: RenderPageOptions) => RenderPageResult;
|
|
257
385
|
|
|
258
386
|
/**
|
|
259
|
-
* Streaming SSR — the
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* sync path; the difference is time-to-first-byte and native Suspense
|
|
264
|
-
* streaming.
|
|
387
|
+
* Streaming SSR — the RESPONSE renderer, for `renderMode: 'ssr'`. Renders via
|
|
388
|
+
* React's `renderToPipeableStream` so the server can flush the shell (and each
|
|
389
|
+
* Suspense boundary as it resolves) instead of buffering the whole document
|
|
390
|
+
* into one string. Same tree, same providers, same meta as the sync path.
|
|
265
391
|
*
|
|
266
392
|
* The caller drives the response: write the doctype + `<head>` (using the
|
|
267
393
|
* synchronously-returned `meta`) + opening `<body><div id="root">` in
|
|
268
|
-
* `onShellReady`,
|
|
394
|
+
* `onShellReady`, pipe the stream, then close the document once it ends.
|
|
395
|
+
* Both boot paths do that through ONE shared helper —
|
|
396
|
+
* `streamSsrResponse` in `@voltro/cli`'s `ssrShell.ts` — rather than
|
|
397
|
+
* hand-mirroring the sequence, which is the drift class that previously cost
|
|
398
|
+
* this pipeline its layout-loader data in dev.
|
|
399
|
+
*
|
|
400
|
+
* Two details the caller MUST get right, both learned the hard way:
|
|
401
|
+
*
|
|
402
|
+
* - **Pass `bootstrapModules`, don't leave the entry `<script type="module">`
|
|
403
|
+
* in the shell.** A plain module script is deferred until the document is
|
|
404
|
+
* fully parsed — on a streamed response that is AFTER the last deferred
|
|
405
|
+
* boundary, which deletes the entire benefit. React emits `bootstrapModules`
|
|
406
|
+
* as `async` at the end of the shell instead.
|
|
407
|
+
* - **Pipe through a `PassThrough`.** React's `pipe()` calls `end()` on the
|
|
408
|
+
* destination itself, so writing the closing `</div>` + shell tail straight
|
|
409
|
+
* to the response would land after it was already closed.
|
|
269
410
|
*/
|
|
270
411
|
export declare const renderPageToStream: (options: RenderPageStreamOptions) => RenderPageStreamResult;
|
|
271
412
|
|
|
413
|
+
/**
|
|
414
|
+
* The full `<script>` tag every SSR emitter injects into `<head>`. Emitters
|
|
415
|
+
* call THIS, never the pieces — that is what keeps the dev / serve / prerender
|
|
416
|
+
* paths on one payload shape.
|
|
417
|
+
*/
|
|
418
|
+
export declare const renderRouterStateScript: (input: RouterStateInput) => string;
|
|
419
|
+
|
|
420
|
+
/** id of the `<script type="application/json">` tag carrying the payload. */
|
|
421
|
+
export declare const ROUTER_STATE_SCRIPT_ID = "__voltro_state__";
|
|
422
|
+
|
|
423
|
+
export declare interface RouterStateInput {
|
|
424
|
+
/** Page loader result passed to `renderPageToHtml` / `renderPageToStream`. */
|
|
425
|
+
readonly loaderData: unknown;
|
|
426
|
+
/** Layout loader results, keyed by chain index — the same object passed to
|
|
427
|
+
* the renderer, so the inlined payload cannot disagree with the markup. */
|
|
428
|
+
readonly segmentLoaderData?: Readonly<Record<number, unknown>> | undefined;
|
|
429
|
+
/** Whether the page module actually exported a `loader`. Not derivable from
|
|
430
|
+
* `loaderData` (a loader may legitimately resolve to `undefined`/`null`,
|
|
431
|
+
* and the SSR paths default the field to `null` when there is no loader). */
|
|
432
|
+
readonly pageLoaderRan: boolean;
|
|
433
|
+
/** Set by the SSR-layout-shell path (a `renderMode:'spa'` page under an SSR
|
|
434
|
+
* layout chain): the server rendered the layouts + an empty page slot and
|
|
435
|
+
* ran only the layout loaders. Tells the client to reproduce the empty slot
|
|
436
|
+
* on its first render, then mount the page after hydration. Omit for every
|
|
437
|
+
* full-page render. */
|
|
438
|
+
readonly pageClientOnly?: boolean | undefined;
|
|
439
|
+
/** Page-loader deferred fields: name -> registry id. Produced by
|
|
440
|
+
* `prepareDeferredLoaderData` in the SSR pipeline; omit when nothing
|
|
441
|
+
* deferred (which is every non-streaming emitter). */
|
|
442
|
+
readonly deferredPageIds?: Readonly<Record<string, string>> | undefined;
|
|
443
|
+
/** Layout-loader deferred fields, keyed by chain index. */
|
|
444
|
+
readonly deferredSegmentIds?: Readonly<Record<number, Readonly<Record<string, string>>>> | undefined;
|
|
445
|
+
}
|
|
446
|
+
|
|
272
447
|
/**
|
|
273
448
|
* One layer in a page's nesting chain. Each represents a directory level
|
|
274
449
|
* in the file-convention layout (or a route group).
|
|
@@ -301,11 +476,11 @@ declare interface RouteSegment {
|
|
|
301
476
|
}
|
|
302
477
|
|
|
303
478
|
/**
|
|
304
|
-
* Serialise
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
479
|
+
* Serialise arbitrary data for inlining into an HTML `<script>` body.
|
|
480
|
+
*
|
|
481
|
+
* Escaping `<` is the well-known SSR XSS fix: it makes a `</script>` inside a
|
|
482
|
+
* string value un-parseable as a closing tag while staying valid JSON (`\u003c`
|
|
483
|
+
* decodes back to `<`), so no consumer has to un-escape anything.
|
|
309
484
|
*/
|
|
310
485
|
export declare const serialiseStateForInlining: (state: unknown) => string;
|
|
311
486
|
|
package/dist/ssr.js
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-DpUqogK8.js";
|
|
2
|
+
import { C as c, c as l, p as u, r as d, t as f, u as p } from "./serverContext-COzippNt.js";
|
|
3
|
+
import { createElement as m } from "react";
|
|
4
|
+
import { renderToPipeableStream as h, renderToString as g } from "react-dom/server";
|
|
4
5
|
//#region src/ssr.tsx
|
|
5
|
-
var
|
|
6
|
-
let
|
|
7
|
-
if (!
|
|
8
|
-
for (let
|
|
9
|
-
let
|
|
10
|
-
if (
|
|
11
|
-
let
|
|
12
|
-
|
|
6
|
+
var _ = (e, t, n, r) => {
|
|
7
|
+
let i = m(l.Provider, { value: n }, m(e));
|
|
8
|
+
if (!t) return i;
|
|
9
|
+
for (let e = t.length - 1; e >= 0; e--) {
|
|
10
|
+
let n = t[e];
|
|
11
|
+
if (n.Layout) {
|
|
12
|
+
let t = n.Layout;
|
|
13
|
+
i = m(l.Provider, { value: r(e) }, m(t, { children: i }));
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
|
-
return
|
|
16
|
-
},
|
|
17
|
-
let { descriptor:
|
|
18
|
-
pathname:
|
|
16
|
+
return i;
|
|
17
|
+
}, v = (e) => {
|
|
18
|
+
let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: l, pageSlot: d } = e, h = c(t.meta, n, i, l), g = {
|
|
19
|
+
pathname: r,
|
|
19
20
|
search: "",
|
|
20
|
-
params:
|
|
21
|
-
loaderData:
|
|
21
|
+
params: n,
|
|
22
|
+
loaderData: i,
|
|
22
23
|
navigate: () => {
|
|
23
24
|
throw Error("navigate() is not supported during server-side rendering. Trigger navigations on the client only.");
|
|
24
25
|
},
|
|
25
26
|
prefetch: () => {},
|
|
26
27
|
registerBlocker: () => () => {},
|
|
27
28
|
blocked: null
|
|
28
|
-
},
|
|
29
|
-
if (!
|
|
30
|
-
let
|
|
31
|
-
return
|
|
32
|
-
tree:
|
|
33
|
-
meta:
|
|
29
|
+
}, v = d ? p : t.Component;
|
|
30
|
+
if (!v) throw Error(`SSR received a descriptor with no Component for "${r}". Lazy page routes are a client-only optimization and must not be used on the server.`);
|
|
31
|
+
let y = m(u.Provider, { value: g }, _(v, t.chain, d ? void 0 : i, (e) => a?.[e]));
|
|
32
|
+
return o && (y = m(f.Provider, { value: o }, y)), s && (y = s(y)), {
|
|
33
|
+
tree: y,
|
|
34
|
+
meta: h
|
|
34
35
|
};
|
|
35
|
-
},
|
|
36
|
-
let { tree: t, meta: n } =
|
|
36
|
+
}, y = (e) => {
|
|
37
|
+
let { tree: t, meta: n } = v(e);
|
|
37
38
|
return {
|
|
38
|
-
html:
|
|
39
|
+
html: g(t),
|
|
39
40
|
meta: n
|
|
40
41
|
};
|
|
41
|
-
},
|
|
42
|
-
let { tree: t, meta: n } =
|
|
42
|
+
}, b = (e) => {
|
|
43
|
+
let { tree: t, meta: n } = v(e);
|
|
43
44
|
return {
|
|
44
|
-
stream:
|
|
45
|
+
stream: h(t, {
|
|
45
46
|
...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
|
|
46
47
|
...e.onShellReady ? { onShellReady: e.onShellReady } : {},
|
|
47
48
|
...e.onShellError ? { onShellError: e.onShellError } : {},
|
|
@@ -49,20 +50,20 @@ var c = (t, n, r, i) => {
|
|
|
49
50
|
}),
|
|
50
51
|
meta: n
|
|
51
52
|
};
|
|
52
|
-
},
|
|
53
|
+
}, x = (e) => {
|
|
53
54
|
if (!e) return "";
|
|
54
55
|
let t = [];
|
|
55
|
-
if (typeof e.title == "string" && t.push(`<title>${
|
|
56
|
-
let e = n.name ? `name="${
|
|
57
|
-
e && t.push(`<meta ${e} content="${
|
|
56
|
+
if (typeof e.title == "string" && t.push(`<title>${S(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${C(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${C(e.canonical)}" />`), e.tags) for (let n of e.tags) {
|
|
57
|
+
let e = n.name ? `name="${C(n.name)}"` : n.property ? `property="${C(n.property)}"` : "";
|
|
58
|
+
e && t.push(`<meta ${e} content="${C(n.content)}" />`);
|
|
58
59
|
}
|
|
59
60
|
if (e.links) for (let n of e.links) {
|
|
60
61
|
let e = [
|
|
61
|
-
`rel="${
|
|
62
|
-
`href="${
|
|
63
|
-
n.hreflang ? `hreflang="${
|
|
64
|
-
n.type ? `type="${
|
|
65
|
-
n.title ? `title="${
|
|
62
|
+
`rel="${C(n.rel)}"`,
|
|
63
|
+
`href="${C(n.href)}"`,
|
|
64
|
+
n.hreflang ? `hreflang="${C(n.hreflang)}"` : "",
|
|
65
|
+
n.type ? `type="${C(n.type)}"` : "",
|
|
66
|
+
n.title ? `title="${C(n.title)}"` : ""
|
|
66
67
|
].filter(Boolean).join(" ");
|
|
67
68
|
t.push(`<link ${e} />`);
|
|
68
69
|
}
|
|
@@ -71,6 +72,6 @@ var c = (t, n, r, i) => {
|
|
|
71
72
|
t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
|
|
72
73
|
}
|
|
73
74
|
return t.join("\n ");
|
|
74
|
-
},
|
|
75
|
+
}, S = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"), C = (e) => e.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
75
76
|
//#endregion
|
|
76
|
-
export {
|
|
77
|
+
export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, i as encodeRouterState, a as isDeferredLoaderResult, d as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, x as renderMetaToHtml, y as renderPageToHtml, b as renderPageToStream, t as renderRouterStateScript, e as serialiseStateForInlining };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"node": ">=24.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@voltro/client": "0.
|
|
56
|
-
"@voltro/ui": "0.
|
|
55
|
+
"@voltro/client": "0.9.0",
|
|
56
|
+
"@voltro/ui": "0.9.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@effect/platform": "^0.96.2",
|
package/dist/mount-DhGdfjSE.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { t as e } from "./frameworkBoot-C_d7E8Fj.js";
|
|
2
|
-
import { StrictMode as t, createElement as n } from "react";
|
|
3
|
-
import { createRoot as r, hydrateRoot as i } from "react-dom/client";
|
|
4
|
-
import { jsx as a } from "react/jsx-runtime";
|
|
5
|
-
//#region src/islands.tsx
|
|
6
|
-
var o = /* @__PURE__ */ new Map(), s = (e, t) => {
|
|
7
|
-
o.set(t.name, e);
|
|
8
|
-
let r = t.hydrate ?? "visible", i = (i) => n("div", {
|
|
9
|
-
"data-voltro-island": "",
|
|
10
|
-
"data-island-name": t.name,
|
|
11
|
-
"data-island-hydrate": r,
|
|
12
|
-
"data-island-props": l(i)
|
|
13
|
-
}, n(e, i));
|
|
14
|
-
return i.displayName = `Island(${t.name})`, i;
|
|
15
|
-
}, c = (e) => o.get(e), l = (e) => JSON.stringify(e ?? {}).replace(/</g, "\\u003c"), u = (e) => {
|
|
16
|
-
if (!e) return {};
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse(e);
|
|
19
|
-
} catch {
|
|
20
|
-
return {};
|
|
21
|
-
}
|
|
22
|
-
}, d = () => {
|
|
23
|
-
let e = document.querySelectorAll("[data-voltro-island]:not([data-voltro-hydrated])");
|
|
24
|
-
for (let t of e) {
|
|
25
|
-
let e = t.dataset.islandName ?? "", r = o.get(e);
|
|
26
|
-
if (!r) {
|
|
27
|
-
console.warn(`[voltro] island "${e}" referenced in DOM but not registered. Did the island file get imported in this bundle?`);
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
let a = u(t.dataset.islandProps ?? null), s = t.dataset.islandHydrate ?? "visible";
|
|
31
|
-
t.setAttribute("data-voltro-hydrated", "pending");
|
|
32
|
-
let c = () => {
|
|
33
|
-
t.getAttribute("data-voltro-hydrated") !== "done" && (t.setAttribute("data-voltro-hydrated", "done"), i(t, n(r, a)));
|
|
34
|
-
};
|
|
35
|
-
if (s === "load") c();
|
|
36
|
-
else if (s === "idle") {
|
|
37
|
-
let e = globalThis.requestIdleCallback;
|
|
38
|
-
e ? e(c) : setTimeout(c, 0);
|
|
39
|
-
} else if (s === "visible") {
|
|
40
|
-
let e = new IntersectionObserver((t) => {
|
|
41
|
-
t.some((e) => e.isIntersecting) && (e.disconnect(), c());
|
|
42
|
-
}, { rootMargin: "64px" });
|
|
43
|
-
e.observe(t);
|
|
44
|
-
} else if (s === "interaction") {
|
|
45
|
-
let e = () => {
|
|
46
|
-
t.removeEventListener("pointerdown", e), t.removeEventListener("keydown", e), c();
|
|
47
|
-
};
|
|
48
|
-
t.addEventListener("pointerdown", e, { passive: !0 }), t.addEventListener("keydown", e);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}, f = (e) => `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/ws/${e}`, p = (n, o) => {
|
|
52
|
-
let s = document.getElementById(o.rootId ?? "root");
|
|
53
|
-
if (!s) throw Error(`@voltro/web: no #${o.rootId ?? "root"} element in the document`);
|
|
54
|
-
let c = Object.entries(o.apis).map(([e, t]) => ({
|
|
55
|
-
name: e,
|
|
56
|
-
group: t.group,
|
|
57
|
-
descriptors: t.descriptors ?? {},
|
|
58
|
-
wsUrl: t.wsUrl ?? f(e),
|
|
59
|
-
headers: t.headers
|
|
60
|
-
})), l = document.getElementById("__voltro_state__") !== null && s.children.length > 0, u = document.querySelector("meta[name=\"voltro-interactive\"]")?.getAttribute("content") ?? "full";
|
|
61
|
-
if (u === "none") return;
|
|
62
|
-
if (u === "islands") {
|
|
63
|
-
d();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
let p = /* @__PURE__ */ a(t, { children: /* @__PURE__ */ a(e, {
|
|
67
|
-
App: n,
|
|
68
|
-
apis: c,
|
|
69
|
-
...o.Devtools ? { Devtools: o.Devtools } : {}
|
|
70
|
-
}) });
|
|
71
|
-
l ? i(s, p) : r(s).render(p);
|
|
72
|
-
};
|
|
73
|
-
//#endregion
|
|
74
|
-
export { s as i, c as n, d as r, p as t };
|