@voltro/web 0.7.0 → 0.8.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 +21 -0
- package/dist/index.d.ts +81 -1
- package/dist/index.js +68 -45
- package/dist/mount-D3k8Ozdm.js +76 -0
- package/dist/mount.js +1 -1
- package/dist/routerState-ga64vk2B.js +99 -0
- package/dist/{serverContext-DSValgbm.js → serverContext-BW0GF8fv.js} +291 -249
- package/dist/ssr.d.ts +168 -14
- package/dist/ssr.js +40 -39
- package/package.json +3 -3
- package/dist/mount-DhGdfjSE.js +0 -74
package/dist/ssr.d.ts
CHANGED
|
@@ -2,11 +2,92 @@ 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
|
+
/** Deferred (streamed) loader fields: field name -> client-registry id, for
|
|
81
|
+
* the page loader and per chain index. The VALUES are not here — they
|
|
82
|
+
* arrive later, published by the settle `<script>` each `<Await>` boundary
|
|
83
|
+
* emits as its promise resolves. Absent entirely when nothing deferred, so
|
|
84
|
+
* a non-deferring page's payload is byte-identical to what it always was. */
|
|
85
|
+
readonly deferred?: {
|
|
86
|
+
readonly page?: Readonly<Record<string, string>>;
|
|
87
|
+
readonly segments?: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
10
91
|
/**
|
|
11
92
|
* How the client-side JS hydrates a server-rendered page.
|
|
12
93
|
*
|
|
@@ -25,6 +106,9 @@ declare interface ErrorBoundaryProps {
|
|
|
25
106
|
*/
|
|
26
107
|
declare type InteractiveMode = 'full' | 'islands' | 'none';
|
|
27
108
|
|
|
109
|
+
/** True for exactly the object {@link defer} returns. */
|
|
110
|
+
export declare const isDeferredLoaderResult: (value: unknown) => value is DeferredLoaderResult;
|
|
111
|
+
|
|
28
112
|
declare interface LoaderContext {
|
|
29
113
|
readonly params: Readonly<Record<string, string>>;
|
|
30
114
|
readonly pathname: string;
|
|
@@ -139,6 +223,33 @@ declare interface PageMeta {
|
|
|
139
223
|
|
|
140
224
|
export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
|
|
141
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Split one loader result for a streaming render. `scope` is `'page'` for the
|
|
228
|
+
* page loader or the layout's chain index, and only feeds the registry id.
|
|
229
|
+
*/
|
|
230
|
+
export declare const prepareDeferredLoaderData: (result: unknown, scope: "page" | number) => PreparedLoaderData;
|
|
231
|
+
|
|
232
|
+
/** One loader's result, prepared for a streaming SSR render. */
|
|
233
|
+
export declare interface PreparedLoaderData {
|
|
234
|
+
/** What the RENDERER gets: eager fields plus one tagged promise per
|
|
235
|
+
* deferred field. Identical to the plain result when nothing deferred. */
|
|
236
|
+
readonly renderData: unknown;
|
|
237
|
+
/** What the `__voltro_state__` payload gets: eager fields only. A promise
|
|
238
|
+
* serialises to `{}`, so the deferred half must never reach it. */
|
|
239
|
+
readonly stateData: unknown;
|
|
240
|
+
/** Deferred field name -> registry id, for the state payload. `undefined`
|
|
241
|
+
* when the loader did not defer — the wire shape is then byte-identical
|
|
242
|
+
* to what a non-deferring page has always emitted. */
|
|
243
|
+
readonly deferredIds: Readonly<Record<string, string>> | undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The `<script>` every streaming SSR emitter puts in `<head>`, before the
|
|
248
|
+
* shell. Must precede any settle script — which is automatic, since settle
|
|
249
|
+
* scripts ride in the body.
|
|
250
|
+
*/
|
|
251
|
+
export declare const renderDeferredRegistryScript: () => string;
|
|
252
|
+
|
|
142
253
|
/**
|
|
143
254
|
* Helper for the build pipeline: stringify the resolved meta into a
|
|
144
255
|
* `<head>`-ready HTML fragment. Returns an empty string when no meta
|
|
@@ -249,26 +360,69 @@ export declare interface RenderPageStreamResult {
|
|
|
249
360
|
/**
|
|
250
361
|
* Render a page to an HTML string using its layout chain + loader data. The
|
|
251
362
|
* returned `html` is the body fragment — the caller composes the surrounding
|
|
252
|
-
* document. Renders synchronously via `renderToString
|
|
253
|
-
*
|
|
363
|
+
* document. Renders synchronously via `renderToString`.
|
|
364
|
+
*
|
|
365
|
+
* This is the ARTEFACT renderer: static prerender and ISR, both of which need
|
|
366
|
+
* the whole document. See the doctrine note above before switching a caller to
|
|
254
367
|
* {@link renderPageToStream}.
|
|
255
368
|
*/
|
|
256
369
|
export declare const renderPageToHtml: (options: RenderPageOptions) => RenderPageResult;
|
|
257
370
|
|
|
258
371
|
/**
|
|
259
|
-
* Streaming SSR — the
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* sync path; the difference is time-to-first-byte and native Suspense
|
|
264
|
-
* streaming.
|
|
372
|
+
* Streaming SSR — the RESPONSE renderer, for `renderMode: 'ssr'`. Renders via
|
|
373
|
+
* React's `renderToPipeableStream` so the server can flush the shell (and each
|
|
374
|
+
* Suspense boundary as it resolves) instead of buffering the whole document
|
|
375
|
+
* into one string. Same tree, same providers, same meta as the sync path.
|
|
265
376
|
*
|
|
266
377
|
* The caller drives the response: write the doctype + `<head>` (using the
|
|
267
378
|
* synchronously-returned `meta`) + opening `<body><div id="root">` in
|
|
268
|
-
* `onShellReady`,
|
|
379
|
+
* `onShellReady`, pipe the stream, then close the document once it ends.
|
|
380
|
+
* Both boot paths do that through ONE shared helper —
|
|
381
|
+
* `streamSsrResponse` in `@voltro/cli`'s `ssrShell.ts` — rather than
|
|
382
|
+
* hand-mirroring the sequence, which is the drift class that previously cost
|
|
383
|
+
* this pipeline its layout-loader data in dev.
|
|
384
|
+
*
|
|
385
|
+
* Two details the caller MUST get right, both learned the hard way:
|
|
386
|
+
*
|
|
387
|
+
* - **Pass `bootstrapModules`, don't leave the entry `<script type="module">`
|
|
388
|
+
* in the shell.** A plain module script is deferred until the document is
|
|
389
|
+
* fully parsed — on a streamed response that is AFTER the last deferred
|
|
390
|
+
* boundary, which deletes the entire benefit. React emits `bootstrapModules`
|
|
391
|
+
* as `async` at the end of the shell instead.
|
|
392
|
+
* - **Pipe through a `PassThrough`.** React's `pipe()` calls `end()` on the
|
|
393
|
+
* destination itself, so writing the closing `</div>` + shell tail straight
|
|
394
|
+
* to the response would land after it was already closed.
|
|
269
395
|
*/
|
|
270
396
|
export declare const renderPageToStream: (options: RenderPageStreamOptions) => RenderPageStreamResult;
|
|
271
397
|
|
|
398
|
+
/**
|
|
399
|
+
* The full `<script>` tag every SSR emitter injects into `<head>`. Emitters
|
|
400
|
+
* call THIS, never the pieces — that is what keeps the dev / serve / prerender
|
|
401
|
+
* paths on one payload shape.
|
|
402
|
+
*/
|
|
403
|
+
export declare const renderRouterStateScript: (input: RouterStateInput) => string;
|
|
404
|
+
|
|
405
|
+
/** id of the `<script type="application/json">` tag carrying the payload. */
|
|
406
|
+
export declare const ROUTER_STATE_SCRIPT_ID = "__voltro_state__";
|
|
407
|
+
|
|
408
|
+
export declare interface RouterStateInput {
|
|
409
|
+
/** Page loader result passed to `renderPageToHtml` / `renderPageToStream`. */
|
|
410
|
+
readonly loaderData: unknown;
|
|
411
|
+
/** Layout loader results, keyed by chain index — the same object passed to
|
|
412
|
+
* the renderer, so the inlined payload cannot disagree with the markup. */
|
|
413
|
+
readonly segmentLoaderData?: Readonly<Record<number, unknown>> | undefined;
|
|
414
|
+
/** Whether the page module actually exported a `loader`. Not derivable from
|
|
415
|
+
* `loaderData` (a loader may legitimately resolve to `undefined`/`null`,
|
|
416
|
+
* and the SSR paths default the field to `null` when there is no loader). */
|
|
417
|
+
readonly pageLoaderRan: boolean;
|
|
418
|
+
/** Page-loader deferred fields: name -> registry id. Produced by
|
|
419
|
+
* `prepareDeferredLoaderData` in the SSR pipeline; omit when nothing
|
|
420
|
+
* deferred (which is every non-streaming emitter). */
|
|
421
|
+
readonly deferredPageIds?: Readonly<Record<string, string>> | undefined;
|
|
422
|
+
/** Layout-loader deferred fields, keyed by chain index. */
|
|
423
|
+
readonly deferredSegmentIds?: Readonly<Record<number, Readonly<Record<string, string>>>> | undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
272
426
|
/**
|
|
273
427
|
* One layer in a page's nesting chain. Each represents a directory level
|
|
274
428
|
* in the file-convention layout (or a route group).
|
|
@@ -301,11 +455,11 @@ declare interface RouteSegment {
|
|
|
301
455
|
}
|
|
302
456
|
|
|
303
457
|
/**
|
|
304
|
-
* Serialise
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
458
|
+
* Serialise arbitrary data for inlining into an HTML `<script>` body.
|
|
459
|
+
*
|
|
460
|
+
* Escaping `<` is the well-known SSR XSS fix: it makes a `</script>` inside a
|
|
461
|
+
* string value un-parseable as a closing tag while staying valid JSON (`\u003c`
|
|
462
|
+
* decodes back to `<`), so no consumer has to un-escape anything.
|
|
309
463
|
*/
|
|
310
464
|
export declare const serialiseStateForInlining: (state: unknown) => string;
|
|
311
465
|
|
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-ga64vk2B.js";
|
|
2
|
+
import { c, d as l, r as u, t as d, x as f } from "./serverContext-BW0GF8fv.js";
|
|
3
|
+
import { createElement as p } from "react";
|
|
4
|
+
import { renderToPipeableStream as m, renderToString as h } 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 g = (e, t, n, r) => {
|
|
7
|
+
let i = p(c.Provider, { value: n }, p(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 = p(c.Provider, { value: r(e) }, p(t, { children: i }));
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
|
-
return
|
|
16
|
-
},
|
|
17
|
-
let { descriptor:
|
|
18
|
-
pathname:
|
|
16
|
+
return i;
|
|
17
|
+
}, _ = (e) => {
|
|
18
|
+
let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: c } = e, u = f(t.meta, n, i, c), m = {
|
|
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
|
|
29
|
+
}, h = t.Component;
|
|
30
|
+
if (!h) 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 _ = p(l.Provider, { value: m }, g(h, t.chain, i, (e) => a?.[e]));
|
|
32
|
+
return o && (_ = p(d.Provider, { value: o }, _)), s && (_ = s(_)), {
|
|
32
33
|
tree: _,
|
|
33
|
-
meta:
|
|
34
|
+
meta: u
|
|
34
35
|
};
|
|
35
|
-
},
|
|
36
|
-
let { tree: t, meta: n } =
|
|
36
|
+
}, v = (e) => {
|
|
37
|
+
let { tree: t, meta: n } = _(e);
|
|
37
38
|
return {
|
|
38
|
-
html:
|
|
39
|
+
html: h(t),
|
|
39
40
|
meta: n
|
|
40
41
|
};
|
|
41
|
-
},
|
|
42
|
-
let { tree: t, meta: n } =
|
|
42
|
+
}, y = (e) => {
|
|
43
|
+
let { tree: t, meta: n } = _(e);
|
|
43
44
|
return {
|
|
44
|
-
stream:
|
|
45
|
+
stream: m(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
|
+
}, b = (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>${x(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${S(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${S(e.canonical)}" />`), e.tags) for (let n of e.tags) {
|
|
57
|
+
let e = n.name ? `name="${S(n.name)}"` : n.property ? `property="${S(n.property)}"` : "";
|
|
58
|
+
e && t.push(`<meta ${e} content="${S(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="${S(n.rel)}"`,
|
|
63
|
+
`href="${S(n.href)}"`,
|
|
64
|
+
n.hreflang ? `hreflang="${S(n.hreflang)}"` : "",
|
|
65
|
+
n.type ? `type="${S(n.type)}"` : "",
|
|
66
|
+
n.title ? `title="${S(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
|
+
}, x = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"), S = (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, u as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, b as renderMetaToHtml, v as renderPageToHtml, y 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.8.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.8.0",
|
|
56
|
+
"@voltro/ui": "0.8.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 };
|