@voltro/web 0.8.0 → 0.10.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 +329 -7
- package/dist/index.d.ts +25 -5
- package/dist/index.js +32 -32
- package/dist/{mount-D3k8Ozdm.js → mount-CKsOnjiF.js} +3 -3
- package/dist/mount.js +1 -1
- package/dist/{routerState-ga64vk2B.js → routerState-cjTiUIum.js} +23 -11
- package/dist/{serverContext-BW0GF8fv.js → serverContext-BP0ywwLZ.js} +173 -163
- package/dist/ssr.d.ts +82 -15
- package/dist/ssr.js +32 -32
- package/package.json +3 -3
package/dist/ssr.d.ts
CHANGED
|
@@ -22,6 +22,11 @@ import { ReactNode } from 'react';
|
|
|
22
22
|
* and never hydrates the root, so nothing ever consumes the registry
|
|
23
23
|
* promises. Islands take their props from their own serialised payload, not
|
|
24
24
|
* from loader data, so there is no value in wiring them in.
|
|
25
|
+
*
|
|
26
|
+
* The one place `renderMode` is NOT the deciding input is the SSR layout shell
|
|
27
|
+
* (`layoutShell` above): a `renderMode:'spa'` page under an SSR layout chain is
|
|
28
|
+
* streamed per request by dev/serve — so a LAYOUT loader may defer there — and
|
|
29
|
+
* written to a file by the build, where it may not.
|
|
25
30
|
*/
|
|
26
31
|
export declare const assertDeferralSupported: (target: DeferralTarget) => void;
|
|
27
32
|
|
|
@@ -31,6 +36,23 @@ export declare interface DeferralTarget {
|
|
|
31
36
|
readonly page: string;
|
|
32
37
|
readonly renderMode: string;
|
|
33
38
|
readonly interactive: string;
|
|
39
|
+
/**
|
|
40
|
+
* Set ONLY on the SSR-layout-shell path — a `renderMode:'spa'` page under an
|
|
41
|
+
* SSR layout chain, where the server renders the LAYOUTS around an empty page
|
|
42
|
+
* slot and runs only the layout loaders. `renderMode` is `'spa'` there, so
|
|
43
|
+
* without this the shell would be rejected by the rule below; the thing that
|
|
44
|
+
* actually decides whether it can stream is HOW the shell is delivered:
|
|
45
|
+
*
|
|
46
|
+
* - `'stream'` — a per-request response (`voltro dev` / `voltro serve`). It
|
|
47
|
+
* goes out through `renderPageToStream`, so a LAYOUT loader may `defer()`:
|
|
48
|
+
* the chain + empty slot flush immediately and the deferred value streams
|
|
49
|
+
* in behind its `<Await>` boundary, exactly as on an `ssr` page.
|
|
50
|
+
* - `'artefact'` — the build-time prerender, which writes an HTML FILE. A
|
|
51
|
+
* file has no "after", so `defer()` is refused there by name.
|
|
52
|
+
*
|
|
53
|
+
* Absent for every full-page render, where `renderMode` alone decides.
|
|
54
|
+
*/
|
|
55
|
+
readonly layoutShell?: 'stream' | 'artefact' | undefined;
|
|
34
56
|
}
|
|
35
57
|
|
|
36
58
|
/** Brand key on the object `defer()` returns. A single explicit container
|
|
@@ -64,6 +86,13 @@ declare interface ErrorBoundaryProps {
|
|
|
64
86
|
* re-run it on hydration (the flash this whole payload exists to remove).
|
|
65
87
|
*/
|
|
66
88
|
export declare interface InlinedRouterState {
|
|
89
|
+
/** The pathname the server rendered THIS document for.
|
|
90
|
+
*
|
|
91
|
+
* Present so the client can tell "the server rendered my route" from "a
|
|
92
|
+
* static host handed me some other route's document as its SPA fallback".
|
|
93
|
+
* Without it the two are indistinguishable — the payload parses either way,
|
|
94
|
+
* and `mount()` would adopt markup for a route it is not on. */
|
|
95
|
+
readonly path?: string;
|
|
67
96
|
/** The page (leaf) loader's result — exactly the value the server render
|
|
68
97
|
* provided to the page's `LoaderDataContext`. */
|
|
69
98
|
readonly page?: unknown;
|
|
@@ -77,6 +106,13 @@ export declare interface InlinedRouterState {
|
|
|
77
106
|
readonly page?: boolean;
|
|
78
107
|
readonly segments?: ReadonlyArray<number>;
|
|
79
108
|
};
|
|
109
|
+
/** True when the server rendered the layout chain but left the page leaf as
|
|
110
|
+
* an EMPTY slot — the `renderMode:'spa'`-under-an-SSR-layout case. The
|
|
111
|
+
* server ran only the layout loaders; the page is client-only. The client's
|
|
112
|
+
* FIRST render must reproduce the identical empty slot so the layout chain
|
|
113
|
+
* hydrates without a mismatch, then mount the real page after commit. Absent
|
|
114
|
+
* (falsy) for every full-page SSR/ISR/static document. */
|
|
115
|
+
readonly pageClientOnly?: boolean;
|
|
80
116
|
/** Deferred (streamed) loader fields: field name -> client-registry id, for
|
|
81
117
|
* the page loader and per chain index. The VALUES are not here — they
|
|
82
118
|
* arrive later, published by the settle `<script>` each `<Await>` boundary
|
|
@@ -243,20 +279,6 @@ export declare interface PreparedLoaderData {
|
|
|
243
279
|
readonly deferredIds: Readonly<Record<string, string>> | undefined;
|
|
244
280
|
}
|
|
245
281
|
|
|
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
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Helper for the build pipeline: stringify the resolved meta into a
|
|
255
|
-
* `<head>`-ready HTML fragment. Returns an empty string when no meta
|
|
256
|
-
* is set so the caller can blindly inject the result.
|
|
257
|
-
*/
|
|
258
|
-
export declare const renderMetaToHtml: (meta: PageMeta | null) => string;
|
|
259
|
-
|
|
260
282
|
/**
|
|
261
283
|
* How a page's HTML is produced.
|
|
262
284
|
*
|
|
@@ -279,8 +301,33 @@ export declare const renderMetaToHtml: (meta: PageMeta | null) => string;
|
|
|
279
301
|
* tenant-scoped first paint.
|
|
280
302
|
* - 'isr' — Like 'ssr' on the first request, then cached and
|
|
281
303
|
* revalidated in the background on a TTL.
|
|
304
|
+
*
|
|
305
|
+
* The value set is single-sourced from {@link RENDER_MODES} so the type and
|
|
306
|
+
* every runtime check derive from ONE literal list. There are no aliases — an
|
|
307
|
+
* unrecognised value is rejected at codegen/discovery time by the CLI, not
|
|
308
|
+
* silently passed through.
|
|
309
|
+
*/
|
|
310
|
+
declare const RENDER_MODES: readonly ["static", "spa", "ssr", "isr"];
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The `<script>` every streaming SSR emitter puts in `<head>`, before the
|
|
314
|
+
* shell. Must precede any settle script — which is automatic, since settle
|
|
315
|
+
* scripts ride in the body.
|
|
316
|
+
*/
|
|
317
|
+
export declare const renderDeferredRegistryScript: () => string;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Helper for the build pipeline: stringify the resolved meta into a
|
|
321
|
+
* `<head>`-ready HTML fragment. Returns an empty string when no meta
|
|
322
|
+
* is set so the caller can blindly inject the result.
|
|
323
|
+
*/
|
|
324
|
+
export declare const renderMetaToHtml: (meta: PageMeta | null) => string;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* A page's render mode — one of {@link RENDER_MODES}, from which this type is
|
|
328
|
+
* derived. Closed set: an unrecognised value is rejected at codegen time.
|
|
282
329
|
*/
|
|
283
|
-
declare type RenderMode =
|
|
330
|
+
declare type RenderMode = (typeof RENDER_MODES)[number];
|
|
284
331
|
|
|
285
332
|
export declare interface RenderPageOptions {
|
|
286
333
|
/** Descriptor produced by codegen (`pageRoute(...)`). */
|
|
@@ -319,6 +366,14 @@ export declare interface RenderPageOptions {
|
|
|
319
366
|
* catalog at meta-resolve time. Optional: when absent, page
|
|
320
367
|
* `meta(ctx)` callbacks see `ctx.locale = 'en'` as a safe default. */
|
|
321
368
|
readonly locale?: string;
|
|
369
|
+
/** Render the LAYOUT CHAIN ONLY, with an empty page slot at the leaf
|
|
370
|
+
* ({@link PageSlot}) instead of the page Component — the
|
|
371
|
+
* `renderMode:'spa'`-under-an-SSR-layout case. The caller runs only the
|
|
372
|
+
* layout loaders (not the page loader) and pairs this with a state script
|
|
373
|
+
* carrying `pageClientOnly: true`, so the client reproduces the empty slot
|
|
374
|
+
* on its first render and mounts the page after hydration. When set, the
|
|
375
|
+
* descriptor need not carry a `Component`. */
|
|
376
|
+
readonly pageSlot?: boolean;
|
|
322
377
|
}
|
|
323
378
|
|
|
324
379
|
export declare interface RenderPageResult {
|
|
@@ -406,6 +461,12 @@ export declare const renderRouterStateScript: (input: RouterStateInput) => strin
|
|
|
406
461
|
export declare const ROUTER_STATE_SCRIPT_ID = "__voltro_state__";
|
|
407
462
|
|
|
408
463
|
export declare interface RouterStateInput {
|
|
464
|
+
/** The pathname this render is FOR. Every emitter has it — it is the same
|
|
465
|
+
* value handed to the renderer as `pathname` — and passing it is required
|
|
466
|
+
* rather than optional on purpose: a payload without it cannot be matched
|
|
467
|
+
* against the document it lands in, which is the whole point of carrying
|
|
468
|
+
* it (see {@link parseRouterState}). */
|
|
469
|
+
readonly path: string;
|
|
409
470
|
/** Page loader result passed to `renderPageToHtml` / `renderPageToStream`. */
|
|
410
471
|
readonly loaderData: unknown;
|
|
411
472
|
/** Layout loader results, keyed by chain index — the same object passed to
|
|
@@ -415,6 +476,12 @@ export declare interface RouterStateInput {
|
|
|
415
476
|
* `loaderData` (a loader may legitimately resolve to `undefined`/`null`,
|
|
416
477
|
* and the SSR paths default the field to `null` when there is no loader). */
|
|
417
478
|
readonly pageLoaderRan: boolean;
|
|
479
|
+
/** Set by the SSR-layout-shell path (a `renderMode:'spa'` page under an SSR
|
|
480
|
+
* layout chain): the server rendered the layouts + an empty page slot and
|
|
481
|
+
* ran only the layout loaders. Tells the client to reproduce the empty slot
|
|
482
|
+
* on its first render, then mount the page after hydration. Omit for every
|
|
483
|
+
* full-page render. */
|
|
484
|
+
readonly pageClientOnly?: boolean | undefined;
|
|
418
485
|
/** Page-loader deferred fields: name -> registry id. Produced by
|
|
419
486
|
* `prepareDeferredLoaderData` in the SSR pipeline; omit when nothing
|
|
420
487
|
* deferred (which is every non-streaming emitter). */
|
package/dist/ssr.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
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-
|
|
2
|
-
import { c,
|
|
3
|
-
import { createElement as
|
|
4
|
-
import { renderToPipeableStream as
|
|
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-cjTiUIum.js";
|
|
2
|
+
import { c, m as l, r as u, t as d, u as f, w as p } from "./serverContext-BP0ywwLZ.js";
|
|
3
|
+
import { createElement as m } from "react";
|
|
4
|
+
import { renderToPipeableStream as h, renderToString as g } from "react-dom/server";
|
|
5
5
|
//#region src/ssr.tsx
|
|
6
|
-
var
|
|
7
|
-
let i =
|
|
6
|
+
var _ = (e, t, n, r) => {
|
|
7
|
+
let i = m(c.Provider, { value: n }, m(e));
|
|
8
8
|
if (!t) return i;
|
|
9
9
|
for (let e = t.length - 1; e >= 0; e--) {
|
|
10
10
|
let n = t[e];
|
|
11
11
|
if (n.Layout) {
|
|
12
12
|
let t = n.Layout;
|
|
13
|
-
i =
|
|
13
|
+
i = m(c.Provider, { value: r(e) }, m(t, { children: i }));
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
return i;
|
|
17
|
-
},
|
|
18
|
-
let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: c } = e,
|
|
17
|
+
}, v = (e) => {
|
|
18
|
+
let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: c, pageSlot: u } = e, h = p(t.meta, n, i, c), g = {
|
|
19
19
|
pathname: r,
|
|
20
20
|
search: "",
|
|
21
21
|
params: n,
|
|
@@ -26,23 +26,23 @@ var g = (e, t, n, r) => {
|
|
|
26
26
|
prefetch: () => {},
|
|
27
27
|
registerBlocker: () => () => {},
|
|
28
28
|
blocked: null
|
|
29
|
-
},
|
|
30
|
-
if (!
|
|
31
|
-
let
|
|
32
|
-
return o && (
|
|
33
|
-
tree:
|
|
34
|
-
meta:
|
|
29
|
+
}, v = u ? f : 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(l.Provider, { value: g }, _(v, t.chain, u ? void 0 : i, (e) => a?.[e]));
|
|
32
|
+
return o && (y = m(d.Provider, { value: o }, y)), s && (y = s(y)), {
|
|
33
|
+
tree: y,
|
|
34
|
+
meta: h
|
|
35
35
|
};
|
|
36
|
-
},
|
|
37
|
-
let { tree: t, meta: n } =
|
|
36
|
+
}, y = (e) => {
|
|
37
|
+
let { tree: t, meta: n } = v(e);
|
|
38
38
|
return {
|
|
39
|
-
html:
|
|
39
|
+
html: g(t),
|
|
40
40
|
meta: n
|
|
41
41
|
};
|
|
42
|
-
},
|
|
43
|
-
let { tree: t, meta: n } =
|
|
42
|
+
}, b = (e) => {
|
|
43
|
+
let { tree: t, meta: n } = v(e);
|
|
44
44
|
return {
|
|
45
|
-
stream:
|
|
45
|
+
stream: h(t, {
|
|
46
46
|
...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
|
|
47
47
|
...e.onShellReady ? { onShellReady: e.onShellReady } : {},
|
|
48
48
|
...e.onShellError ? { onShellError: e.onShellError } : {},
|
|
@@ -50,20 +50,20 @@ var g = (e, t, n, r) => {
|
|
|
50
50
|
}),
|
|
51
51
|
meta: n
|
|
52
52
|
};
|
|
53
|
-
},
|
|
53
|
+
}, x = (e) => {
|
|
54
54
|
if (!e) return "";
|
|
55
55
|
let t = [];
|
|
56
|
-
if (typeof e.title == "string" && t.push(`<title>${
|
|
57
|
-
let e = n.name ? `name="${
|
|
58
|
-
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)}" />`);
|
|
59
59
|
}
|
|
60
60
|
if (e.links) for (let n of e.links) {
|
|
61
61
|
let e = [
|
|
62
|
-
`rel="${
|
|
63
|
-
`href="${
|
|
64
|
-
n.hreflang ? `hreflang="${
|
|
65
|
-
n.type ? `type="${
|
|
66
|
-
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)}"` : ""
|
|
67
67
|
].filter(Boolean).join(" ");
|
|
68
68
|
t.push(`<link ${e} />`);
|
|
69
69
|
}
|
|
@@ -72,6 +72,6 @@ var g = (e, t, n, r) => {
|
|
|
72
72
|
t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
|
|
73
73
|
}
|
|
74
74
|
return t.join("\n ");
|
|
75
|
-
},
|
|
75
|
+
}, S = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"), C = (e) => e.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
76
76
|
//#endregion
|
|
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,
|
|
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, 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.10.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.10.0",
|
|
56
|
+
"@voltro/ui": "0.10.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@effect/platform": "^0.96.2",
|