sibujs 4.13.0 → 4.14.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/README.md +1 -1
- package/dist/browser.cjs +15 -2
- package/dist/browser.js +9 -7
- package/dist/build.cjs +880 -281
- package/dist/build.d.cts +68 -3
- package/dist/build.d.ts +68 -3
- package/dist/build.js +88 -30
- package/dist/cdn.dev.global.js +12 -12
- package/dist/cdn.full.dev.global.js +13 -13
- package/dist/cdn.full.global.js +12 -12
- package/dist/cdn.global.js +12 -12
- package/dist/{chunk-7K7W4AA5.js → chunk-2TTRW2TE.js} +6 -6
- package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
- package/dist/chunk-3HZOSMPX.js +90 -0
- package/dist/{chunk-GNSRXK34.js → chunk-4FJSAUKB.js} +11 -3
- package/dist/{chunk-IV74C2YX.js → chunk-4TQDTWWN.js} +6 -5
- package/dist/{chunk-L3QHS4O4.js → chunk-7ILEHTVX.js} +1 -1
- package/dist/{chunk-CK5F2IR5.js → chunk-A454RAEK.js} +4 -4
- package/dist/{chunk-BRRRVLHQ.js → chunk-AE32RGWX.js} +10 -6
- package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
- package/dist/{chunk-F4W7WYXY.js → chunk-BKZ5N6NK.js} +8 -6
- package/dist/{chunk-MNEITZ27.js → chunk-BNXRWZZV.js} +49 -8
- package/dist/chunk-BOYKGGVD.js +2015 -0
- package/dist/{chunk-G6366I2A.js → chunk-CCKUNHVU.js} +2 -2
- package/dist/{chunk-346BYZL2.js → chunk-CJ7KS3DF.js} +16 -10
- package/dist/chunk-CNPT6NKV.js +1859 -0
- package/dist/{chunk-ENFRHCMK.js → chunk-F67F77IA.js} +2 -2
- package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
- package/dist/chunk-GVAMIU6K.js +195 -0
- package/dist/{chunk-HDKDBC6V.js → chunk-HQIAL7F2.js} +8 -6
- package/dist/{chunk-ZE5LF44R.js → chunk-JTFQ7IL4.js} +8 -8
- package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
- package/dist/{chunk-HVVRXRGO.js → chunk-MIRKTVH4.js} +7 -14
- package/dist/{chunk-YJ57VR6Y.js → chunk-N7A6FKWW.js} +5 -3
- package/dist/chunk-NT3ZYB5S.js +167 -0
- package/dist/{chunk-Z3EZX6DZ.js → chunk-QS6GMWDD.js} +151 -86
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/{chunk-3C56VNCK.js → chunk-RVR5NPFL.js} +3 -3
- package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
- package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
- package/dist/{chunk-QV5WEOFD.js → chunk-SVULV7O4.js} +119 -26
- package/dist/{chunk-C2FAO6VQ.js → chunk-TLS4NVIB.js} +5 -5
- package/dist/{chunk-M25RIH6Y.js → chunk-UUIKJJIK.js} +10 -8
- package/dist/chunk-UYTYNCMZ.js +199 -0
- package/dist/chunk-VM2U5LEL.js +2596 -0
- package/dist/{chunk-24L5RDPS.js → chunk-Y4LUM2XS.js} +8 -6
- package/dist/data.cjs +265 -35
- package/dist/data.d.cts +31 -1
- package/dist/data.d.ts +31 -1
- package/dist/data.js +16 -10
- package/dist/devtools.cjs +15 -2
- package/dist/devtools.js +9 -7
- package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
- package/dist/ecosystem.cjs +62 -23
- package/dist/ecosystem.js +14 -12
- package/dist/extras.cjs +3520 -270
- package/dist/extras.d.cts +3 -3
- package/dist/extras.d.ts +3 -3
- package/dist/extras.js +39 -29
- package/dist/index.cjs +442 -253
- package/dist/index.d.cts +93 -283
- package/dist/index.d.ts +93 -283
- package/dist/index.js +26 -16
- package/dist/motion.cjs +12 -1
- package/dist/motion.js +6 -4
- package/dist/patterns.cjs +15 -2
- package/dist/patterns.js +10 -8
- package/dist/performance.cjs +36 -2
- package/dist/performance.d.cts +2 -2
- package/dist/performance.d.ts +2 -2
- package/dist/performance.js +14 -10
- package/dist/plugins.cjs +5225 -1807
- package/dist/plugins.d.cts +8 -834
- package/dist/plugins.d.ts +8 -834
- package/dist/plugins.js +64 -2329
- package/dist/prerenderServer-MXJEVWA3.js +350 -0
- package/dist/renderRoute-DP20XwMS.d.cts +86 -0
- package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
- package/dist/routeHead-CE9SV-cH.d.cts +995 -0
- package/dist/routeHead-DzVxPviY.d.ts +995 -0
- package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
- package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
- package/dist/ssr-JpGpHnpC.d.cts +841 -0
- package/dist/ssr-JpGpHnpC.d.ts +841 -0
- package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
- package/dist/ssr-dom.cjs +1862 -0
- package/dist/ssr-dom.d.cts +341 -0
- package/dist/ssr-dom.d.ts +341 -0
- package/dist/ssr-dom.js +19 -0
- package/dist/ssr.cjs +7630 -2064
- package/dist/ssr.d.cts +63 -5
- package/dist/ssr.d.ts +63 -5
- package/dist/ssr.js +136 -14
- package/dist/testing.cjs +6543 -87
- package/dist/testing.d.cts +80 -1
- package/dist/testing.d.ts +80 -1
- package/dist/testing.js +117 -4
- package/dist/ui.cjs +36 -2
- package/dist/ui.js +17 -14
- package/dist/widgets.cjs +14 -1
- package/dist/widgets.js +11 -9
- package/package.json +9 -2
- package/dist/chunk-2F4PSJ5Z.js +0 -765
- package/dist/chunk-7XHATCIH.js +0 -121
- package/dist/chunk-B36ADEIR.js +0 -21
- package/dist/ssr-Db0Sgdu3.d.cts +0 -245
- package/dist/ssr-Db0Sgdu3.d.ts +0 -245
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
/** Event handlers for {@link EachBindings}, typed per event name. */
|
|
2
|
+
type EachEventBindings = {
|
|
3
|
+
[K in keyof HTMLElementEventMap]?: (event: HTMLElementEventMap[K], el: HTMLElement) => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* What {@link EnhanceContext.each} attaches to one element.
|
|
7
|
+
*
|
|
8
|
+
* Every field maps one-to-one onto an existing `ctx.*` helper and is committed
|
|
9
|
+
* through it — this is a shorthand for calls you could write by hand, not a
|
|
10
|
+
* template language and not a second binding engine. There is no expression
|
|
11
|
+
* parsing, no string interpolation and no `eval`: every value is a plain
|
|
12
|
+
* function you wrote, so it stays CSP-safe and fully type-checked.
|
|
13
|
+
*
|
|
14
|
+
* Anything not covered here (two-way `model()`, listener options, a nested
|
|
15
|
+
* `enhance`) is written imperatively in the same callback — it receives the
|
|
16
|
+
* element, so `ctx.model(el, …)` beside a returned descriptor is normal.
|
|
17
|
+
*/
|
|
18
|
+
interface EachBindings {
|
|
19
|
+
/** Reactive `textContent` — same as `ctx.text(el, value)`. */
|
|
20
|
+
text?: () => unknown;
|
|
21
|
+
/** Reactive attributes by name — same as `ctx.attr(el, name, value)`. */
|
|
22
|
+
attr?: Record<string, () => unknown>;
|
|
23
|
+
/** Reactive class toggles by class name — same as `ctx.classed(el, name, on)`. */
|
|
24
|
+
class?: Record<string, () => boolean>;
|
|
25
|
+
/** Reactive visibility — same as `ctx.show(el, when)`. */
|
|
26
|
+
show?: () => boolean;
|
|
27
|
+
/** Event listeners by event name — same as `ctx.on(el, event, handler)`. */
|
|
28
|
+
on?: EachEventBindings;
|
|
29
|
+
/** Per-element teardown, run with the rest of the enhancement's cleanups. */
|
|
30
|
+
cleanup?: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Helpers handed to an `enhance` setup. Every binding is fine-grained (its own
|
|
34
|
+
* effect) and auto-disposed when the root element (or the returned dispose) is
|
|
35
|
+
* torn down.
|
|
36
|
+
*
|
|
37
|
+
* Target resolution for every helper:
|
|
38
|
+
* - `"@name"` → a descendant marked `data-ref="name"` (the ergonomic form).
|
|
39
|
+
* - any other string → a raw CSS selector, queried within the root.
|
|
40
|
+
* - an `Element` → used as-is.
|
|
41
|
+
* - `null` / omitted (where allowed) → the root element itself.
|
|
42
|
+
*/
|
|
43
|
+
interface EnhanceContext {
|
|
44
|
+
/** The enhanced root element (the server-rendered node). */
|
|
45
|
+
root: HTMLElement;
|
|
46
|
+
/** First descendant matching a `@ref`/selector (or the root for `null`). */
|
|
47
|
+
ref<T extends Element = HTMLElement>(target: string | null): T | null;
|
|
48
|
+
/** All descendants matching a `@ref`/selector. */
|
|
49
|
+
refs<T extends Element = HTMLElement>(target: string): T[];
|
|
50
|
+
/** Attach an auto-removed event listener to a target (root if `null`). */
|
|
51
|
+
on<K extends keyof HTMLElementEventMap>(target: string | Element | null, event: K, handler: (event: HTMLElementEventMap[K], el: HTMLElement) => void, options?: AddEventListenerOptions): void;
|
|
52
|
+
/** Reactively drive `textContent` of an existing node. */
|
|
53
|
+
text(target: string | Element | null, value: () => unknown): void;
|
|
54
|
+
/** Reactively drive an attribute; a `null`/`false`/`undefined` value removes it. */
|
|
55
|
+
attr(target: string | Element | null, name: string, value: () => unknown): void;
|
|
56
|
+
/** Reactively toggle a class on a node. */
|
|
57
|
+
classed(target: string | Element | null, name: string, on: () => boolean): void;
|
|
58
|
+
/** Reactively toggle visibility (sets `display:none` when `false`). */
|
|
59
|
+
show(target: string | Element | null, when: () => boolean): void;
|
|
60
|
+
/** Two-way bind a form control to a `[get, set]` signal tuple. */
|
|
61
|
+
model<T>(target: string | Element, state: readonly [() => T, (value: T) => void], options?: {
|
|
62
|
+
event?: string;
|
|
63
|
+
}): void;
|
|
64
|
+
/**
|
|
65
|
+
* Bind a set of elements the server already rendered — a board, a table, a
|
|
66
|
+
* keyboard, a timeline, a legend — one descriptor at a time.
|
|
67
|
+
*
|
|
68
|
+
* The callback receives each element and its index and returns what to
|
|
69
|
+
* attach; every field is committed through the matching `ctx.*` helper, so
|
|
70
|
+
* ownership, disposal, write elision, attribute sanitization and error
|
|
71
|
+
* routing are byte-for-byte the same as writing the calls out by hand. No
|
|
72
|
+
* element is created, replaced, moved or re-parented — node identity is
|
|
73
|
+
* preserved, which is the entire point of enhancing existing markup.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* ctx.each<HTMLButtonElement>("@square", (el) => {
|
|
77
|
+
* const square = el.dataset.square as Square;
|
|
78
|
+
* return {
|
|
79
|
+
* text: () => pieceAt(square),
|
|
80
|
+
* class: { selected: () => selected() === square },
|
|
81
|
+
* attr: { "aria-label": () => describe(square) },
|
|
82
|
+
* on: { click: () => choose(square) },
|
|
83
|
+
* };
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* The callback may also return nothing and wire the element imperatively —
|
|
88
|
+
* `ctx.model(el, …)`, `ctx.on(el, "click", h, { passive: true })` — for the
|
|
89
|
+
* cases the descriptor deliberately does not cover.
|
|
90
|
+
*
|
|
91
|
+
* Zero matches is a silent no-op. Calling `each` twice over the same
|
|
92
|
+
* elements creates two independent sets of bindings, exactly as calling
|
|
93
|
+
* `ctx.text()` twice on one node does; the helper is sugar over those calls
|
|
94
|
+
* and does not track what a previous call attached.
|
|
95
|
+
*
|
|
96
|
+
* @param target A `@ref`/CSS selector resolved with {@link EnhanceContext.refs},
|
|
97
|
+
* or any iterable of elements (an array, a `NodeList`, an `HTMLCollection`).
|
|
98
|
+
* @param describe Called once per element, in document order.
|
|
99
|
+
*/
|
|
100
|
+
each<T extends Element = HTMLElement>(target: string | Iterable<Element>, describe: (element: T, index: number) => EachBindings | void): void;
|
|
101
|
+
/** Register arbitrary teardown to run on disposal. */
|
|
102
|
+
cleanup(fn: () => void): void;
|
|
103
|
+
}
|
|
104
|
+
/** A setup function for `enhance` — wire reactivity, optionally return cleanup.
|
|
105
|
+
* The `void | (() => void)` shape mirrors `useEffect`'s "return a teardown, or
|
|
106
|
+
* nothing". */
|
|
107
|
+
type EnhanceSetup = (ctx: EnhanceContext) => void | (() => void);
|
|
108
|
+
/**
|
|
109
|
+
* Attach reactivity to an existing element (typically server-rendered) without
|
|
110
|
+
* replacing it. Returns a dispose function; disposal is also wired to the
|
|
111
|
+
* element, so removing its subtree cleans everything up.
|
|
112
|
+
*
|
|
113
|
+
* **Setup is a transaction.** If `setup` throws, every binding, listener and
|
|
114
|
+
* cleanup it registered through the {@link EnhanceContext} is torn down and the
|
|
115
|
+
* original error is rethrown unchanged — the element is left exactly as
|
|
116
|
+
* unenhanced as it started, so a retry is legal. The rollback covers
|
|
117
|
+
* framework-owned resources only (`ctx.on`, `ctx.text`, `ctx.attr`,
|
|
118
|
+
* `ctx.classed`, `ctx.show`, `ctx.model`, `ctx.cleanup`, and a cleanup returned
|
|
119
|
+
* from setup); work the setup performed outside those helpers — `innerHTML`
|
|
120
|
+
* writes, network calls, global mutation — cannot be reversed generically, so
|
|
121
|
+
* register its undo with `ctx.cleanup()` as you go.
|
|
122
|
+
*
|
|
123
|
+
* **Ownership.** A successfully enhanced root is marked
|
|
124
|
+
* `data-sibu-enhanced="true"`; disposal removes it. The marker tracks *current*
|
|
125
|
+
* ownership rather than history, so a **disposed root can be enhanced again**,
|
|
126
|
+
* while enhancing a root that is still active is refused (dev-warns) to prevent
|
|
127
|
+
* two competing sets of bindings. See `docs/architecture/enhancement-lifecycle.md`.
|
|
128
|
+
*
|
|
129
|
+
* @param target An `Element` or a CSS selector resolved against `document`
|
|
130
|
+
* (the first match is used; see {@link enhanceAll} for many).
|
|
131
|
+
* @param setup Wires reactivity via the {@link EnhanceContext}.
|
|
132
|
+
* @throws Whatever `setup` throws, after rolling its resources back.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* // server HTML: <div data-counter><b data-ref="n">0</b><button data-ref="inc">+1</button></div>
|
|
137
|
+
* const [n, setN] = signal(0);
|
|
138
|
+
* enhance("[data-counter]", (ctx) => {
|
|
139
|
+
* ctx.text("@n", () => n());
|
|
140
|
+
* ctx.on("@inc", "click", () => setN((v) => v + 1));
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
declare function enhance(target: Element | string, setup: EnhanceSetup): () => void;
|
|
145
|
+
/**
|
|
146
|
+
* Enhance every element matching a selector. Returns a single dispose that
|
|
147
|
+
* tears down all of them.
|
|
148
|
+
*
|
|
149
|
+
* The whole collection is one transaction: if any element's setup throws, the
|
|
150
|
+
* elements already enhanced are rolled back (newest first, mirroring stack
|
|
151
|
+
* unwinding) and the original error is rethrown. A caller that never received
|
|
152
|
+
* the aggregate disposer is therefore never left holding live enhancements it
|
|
153
|
+
* has no way to release. A teardown that fails during that rollback is reported
|
|
154
|
+
* and skipped — it neither aborts the remaining rollback nor replaces the
|
|
155
|
+
* original setup error.
|
|
156
|
+
*
|
|
157
|
+
* The returned disposer is idempotent, and the collection may be enhanced again
|
|
158
|
+
* afterwards (see {@link enhance} on marker lifetime).
|
|
159
|
+
*
|
|
160
|
+
* @throws Whatever the failing element's setup threw, after rolling back.
|
|
161
|
+
*/
|
|
162
|
+
declare function enhanceAll(selector: string, setup: EnhanceSetup): () => void;
|
|
163
|
+
|
|
164
|
+
/** When an island activates. Declared per-element via `data-sibu-load`. */
|
|
165
|
+
type IslandStrategy = "load" | "idle" | "visible" | "interaction" | "media";
|
|
166
|
+
/** A lazy code loader: resolves to an island setup (or a module whose `default`
|
|
167
|
+
* is one). Only fetched when the island activates. Wrap with {@link lazyIsland}. */
|
|
168
|
+
type IslandLoader = () => Promise<EnhanceSetup | {
|
|
169
|
+
default: EnhanceSetup;
|
|
170
|
+
}>;
|
|
171
|
+
/**
|
|
172
|
+
* A loader that has been through {@link lazyIsland}.
|
|
173
|
+
*
|
|
174
|
+
* Exported for callers that want to be explicit about having wrapped one. It is
|
|
175
|
+
* NOT required by {@link IslandRegistration}, and that is deliberate.
|
|
176
|
+
*
|
|
177
|
+
* Requiring the brand would catch a forgotten `lazyIsland(...)` at compile time,
|
|
178
|
+
* which is where a mistake is cheapest to find — but it also rejects code that
|
|
179
|
+
* compiles today, and this package's contract is that the existing public API
|
|
180
|
+
* keeps working, widening rather than replacing, with a codemod for anything
|
|
181
|
+
* that cannot be widened. There is no codemod infrastructure here to ship one
|
|
182
|
+
* through, so the narrowing is not taken: the mistake is caught at mount time
|
|
183
|
+
* instead, by the thenable guard in `enhance()`, which throws immediately with
|
|
184
|
+
* an actionable message and leaves the element unenhanced. Loud and instant at
|
|
185
|
+
* runtime beats a type error that arrives with a breaking change attached.
|
|
186
|
+
*
|
|
187
|
+
* The brand is a phantom STRING-keyed property, never a `unique symbol`. A
|
|
188
|
+
* `unique symbol` has nominal identity per declaration, so two copies of this
|
|
189
|
+
* package's `.d.ts` in one dependency tree would produce two incompatible
|
|
190
|
+
* types. That is not hypothetical: the registry is deliberately shared through
|
|
191
|
+
* `Symbol.for` precisely because duplicate copies are expected. The property
|
|
192
|
+
* exists only in the type — the runtime marker is the global symbol below.
|
|
193
|
+
*/
|
|
194
|
+
type LazyIslandLoader = IslandLoader & {
|
|
195
|
+
readonly __sibujsLazyIsland: true;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Either an inline setup, or a loader.
|
|
199
|
+
*
|
|
200
|
+
* Unchanged from 4.2: an unbranded `IslandLoader` is still accepted, so no
|
|
201
|
+
* previously-compiling code breaks. Wrapping with {@link lazyIsland} is what
|
|
202
|
+
* makes it *work* — see {@link LazyIslandLoader} for why this is not narrowed.
|
|
203
|
+
*/
|
|
204
|
+
type IslandRegistration = EnhanceSetup | IslandLoader | LazyIslandLoader;
|
|
205
|
+
/**
|
|
206
|
+
* Mark a loader as lazy island code — its module is fetched only when the island
|
|
207
|
+
* activates, so a page ships ~0 JS for islands that never trigger.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* registerIsland("chart", lazyIsland(() => import("./islands/chart.js")));
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
declare function lazyIsland(loader: IslandLoader): LazyIslandLoader;
|
|
215
|
+
/**
|
|
216
|
+
* Register an island by name. The setup is applied via `enhance()` when a
|
|
217
|
+
* matching `[data-sibu-island="name"]` element activates.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* registerIsland("counter", (ctx) => {
|
|
222
|
+
* const [n, setN] = signal(0);
|
|
223
|
+
* ctx.text("@n", () => n());
|
|
224
|
+
* ctx.on("@inc", "click", () => setN((v) => v + 1));
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
declare function registerIsland(name: string, setup: IslandRegistration): void;
|
|
229
|
+
/** Remove a registration (mainly for tests / HMR). */
|
|
230
|
+
declare function unregisterIsland(name: string): void;
|
|
231
|
+
interface MountIslandsOptions {
|
|
232
|
+
/** IntersectionObserver options for the `visible` strategy. */
|
|
233
|
+
rootMargin?: string;
|
|
234
|
+
threshold?: number | number[];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Scan a root for `[data-sibu-island]` elements and activate each according to
|
|
238
|
+
* its `data-sibu-load` strategy (default `load`). Returns a cleanup function
|
|
239
|
+
* that cancels pending schedulers and disposes every mounted island.
|
|
240
|
+
*
|
|
241
|
+
* Strategies (`data-sibu-load`):
|
|
242
|
+
* - `load` — activate immediately (next microtask).
|
|
243
|
+
* - `idle` — `requestIdleCallback` (falls back to a timeout).
|
|
244
|
+
* - `visible` — when the element scrolls into view (IntersectionObserver).
|
|
245
|
+
* - `interaction` — on first pointer/focus/key/touch interaction.
|
|
246
|
+
* - `media` — when `data-sibu-media` (a media query) matches.
|
|
247
|
+
*
|
|
248
|
+
* **Error isolation is lifecycle isolation.** Each island is one `enhance()`
|
|
249
|
+
* transaction, so an island whose setup throws is reported and leaves zero live
|
|
250
|
+
* bindings and zero live listeners; siblings activate normally, and the failed
|
|
251
|
+
* island can be mounted again once its setup is fixed.
|
|
252
|
+
*
|
|
253
|
+
* **Remounting is supported.** Cleanup disposes each island and releases its
|
|
254
|
+
* `data-sibu-enhanced` marker, so the same server markup can be mounted again
|
|
255
|
+
* with a fresh generation. Mounting twice *without* cleanup is also safe:
|
|
256
|
+
* islands that currently own an enhancement are skipped, never double-wired.
|
|
257
|
+
*
|
|
258
|
+
* Activation can never outlive its owner. Cleanup landing before a lazy chunk
|
|
259
|
+
* resolves prevents activation entirely, and cleanup landing *during* a setup
|
|
260
|
+
* disposes the island rather than stranding it — DISPOSED never becomes ACTIVE.
|
|
261
|
+
* `data-sibu-hydrated` is hydration provenance rather than ownership, and is
|
|
262
|
+
* therefore not cleared on disposal; see
|
|
263
|
+
* `docs/architecture/enhancement-lifecycle.md`.
|
|
264
|
+
*/
|
|
265
|
+
declare function mountIslands(root?: ParentNode | null, options?: MountIslandsOptions): () => void;
|
|
266
|
+
|
|
267
|
+
/** The attribute carrying an island's serialized props. */
|
|
268
|
+
declare const ISLAND_PROPS_ATTR = "data-sibu-props";
|
|
269
|
+
/** Options of a server-rendered component island. */
|
|
270
|
+
interface ComponentIslandOptions {
|
|
271
|
+
/** When the browser activates it (`data-sibu-load`). Default `"load"`. */
|
|
272
|
+
load?: IslandStrategy;
|
|
273
|
+
/** Media query for `load: "media"`. */
|
|
274
|
+
media?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Render `Component(props)` as an island: on the server the markup carries the
|
|
278
|
+
* island marker, its props (JSON) and adoption ids; in the browser, outside an
|
|
279
|
+
* island runtime, the component simply renders.
|
|
280
|
+
*
|
|
281
|
+
* @param name Island name, matched by `registerIsland(name, islandComponent(Component))`.
|
|
282
|
+
* @param component The island's component.
|
|
283
|
+
* @param props JSON-serializable props, sent to the browser in the markup.
|
|
284
|
+
* @param options Load strategy.
|
|
285
|
+
* @returns The island's wrapper element (`display: contents`).
|
|
286
|
+
*/
|
|
287
|
+
declare function componentIsland<P>(name: string, component: (props: P) => Node, props: P, options?: ComponentIslandOptions): HTMLElement;
|
|
288
|
+
/**
|
|
289
|
+
* The browser side of a server-rendered component island: an island setup
|
|
290
|
+
* (for `registerIsland` / `lazyIsland`) that reads the props from the markup
|
|
291
|
+
* and adopts the island's server nodes with `Component(props)`.
|
|
292
|
+
*
|
|
293
|
+
* @param component The same component the server rendered.
|
|
294
|
+
* @param validate Optional guard for the props read from the markup (they come
|
|
295
|
+
* from the page, so treat them as input).
|
|
296
|
+
* @returns An island setup.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```ts
|
|
300
|
+
* registerIsland("cart", islandComponent(Cart));
|
|
301
|
+
* registerIsland("reviews", lazyIsland(() => import("./Reviews").then((m) => islandComponent(m.Reviews))));
|
|
302
|
+
* mountIslands();
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
declare function islandComponent<P>(component: (props: P) => Node, validate?: (props: unknown) => props is P): EnhanceSetup;
|
|
306
|
+
|
|
307
|
+
/** Attribute carrying an element's creation-scope id in adopt-marked server output. */
|
|
308
|
+
declare const ADOPT_ID_ATTR = "data-sibu-h";
|
|
309
|
+
/** One divergence between the server markup and the client tree. */
|
|
310
|
+
interface AdoptMismatch {
|
|
311
|
+
kind: "tag" | "missing" | "extra" | "text";
|
|
312
|
+
/** Where: the DOM path of the server node (or of the parent the client node went into). */
|
|
313
|
+
path: string;
|
|
314
|
+
/** The creation-scope id involved, when there is one. */
|
|
315
|
+
id: string | null;
|
|
316
|
+
serverValue: string;
|
|
317
|
+
clientValue: string;
|
|
318
|
+
/** What usually causes this divergence. */
|
|
319
|
+
cause: string;
|
|
320
|
+
message: string;
|
|
321
|
+
}
|
|
322
|
+
/** Options of an adopting `hydrate()`. */
|
|
323
|
+
interface AdoptOptions {
|
|
324
|
+
/** Called for every divergence instead of the development warning. */
|
|
325
|
+
onMismatch?: (report: AdoptMismatch) => void;
|
|
326
|
+
/**
|
|
327
|
+
* How long to wait for lazy chunks and route loads before releasing the
|
|
328
|
+
* server markup nothing claimed. Default 10 000 ms.
|
|
329
|
+
*/
|
|
330
|
+
timeout?: number;
|
|
331
|
+
}
|
|
332
|
+
/** What an adopting hydration did. */
|
|
333
|
+
interface AdoptResult {
|
|
334
|
+
/** Server elements reused as client elements. */
|
|
335
|
+
adopted: number;
|
|
336
|
+
/** Elements the client had to create (0 on matching markup). */
|
|
337
|
+
created: number;
|
|
338
|
+
/** Server nodes removed because nothing claimed them. */
|
|
339
|
+
removed: number;
|
|
340
|
+
/** Every divergence found. */
|
|
341
|
+
mismatches: AdoptMismatch[];
|
|
342
|
+
/** True when the root itself did not match and the whole tree was replaced. */
|
|
343
|
+
replacedRoot: boolean;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Default time a server render waits for pending work before serializing anyway. */
|
|
347
|
+
declare const DEFAULT_SETTLE_TIMEOUT = 5000;
|
|
348
|
+
/** Default cap on the transferred state, in UTF-8 bytes (the `serializeState` default). */
|
|
349
|
+
declare const DEFAULT_TRANSFER_MAX_BYTES: number;
|
|
350
|
+
/** Outcome of {@link settled}. */
|
|
351
|
+
interface SettleResult {
|
|
352
|
+
/** True when the timeout elapsed with work still pending. */
|
|
353
|
+
timedOut: boolean;
|
|
354
|
+
/** Labels of the work still pending at the timeout (`query:/api/catalog`). Empty when settled. */
|
|
355
|
+
pending: string[];
|
|
356
|
+
}
|
|
357
|
+
/** Options of {@link settled}. */
|
|
358
|
+
interface SettleOptions {
|
|
359
|
+
/** Milliseconds to wait for pending work. Default 5000. */
|
|
360
|
+
timeout?: number;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Wait until the current request's render has settled: every deferred
|
|
364
|
+
* directive commit has run and every registered async unit (lazy chunks,
|
|
365
|
+
* `query()` / `resource()` fetches, router navigation and route loads,
|
|
366
|
+
* `prerenderWait()` promises) has settled — or until `timeout`.
|
|
367
|
+
*
|
|
368
|
+
* Must be awaited inside the request's scope (`runInSSRContext`); outside one
|
|
369
|
+
* there is no registry, and it only drains the microtask queue.
|
|
370
|
+
*
|
|
371
|
+
* @param _root The tree being rendered. Accepted for symmetry with test
|
|
372
|
+
* helpers; settling is decided by the request's registry, not by the DOM.
|
|
373
|
+
* @param options `timeout` in milliseconds (default 5000).
|
|
374
|
+
* @returns Whether it timed out, and what was still pending.
|
|
375
|
+
*/
|
|
376
|
+
declare function settled(_root?: Node | null, options?: SettleOptions): Promise<SettleResult>;
|
|
377
|
+
/** Diagnostics of one async server render. */
|
|
378
|
+
interface RenderDiagnostics {
|
|
379
|
+
/** True when the settle timeout elapsed; the output shows fallbacks for what was pending. */
|
|
380
|
+
timedOut: boolean;
|
|
381
|
+
/** Work still pending at the timeout. */
|
|
382
|
+
pending: string[];
|
|
383
|
+
/** Browser-only APIs read during the render (see `installServerDom`), with the reading site. */
|
|
384
|
+
browserApis: string[];
|
|
385
|
+
/** Size of the transferred state in UTF-8 bytes (0 when nothing was transferred). */
|
|
386
|
+
stateBytes: number;
|
|
387
|
+
/** The largest transferred entries, biggest first: `q:/api/catalog` → bytes. */
|
|
388
|
+
largestState: Array<{
|
|
389
|
+
key: string;
|
|
390
|
+
bytes: number;
|
|
391
|
+
}>;
|
|
392
|
+
/** Entries left out of the transferred state because it exceeded `maxStateBytes`. */
|
|
393
|
+
droppedState: string[];
|
|
394
|
+
/** Wall-clock render time in milliseconds. */
|
|
395
|
+
durationMs: number;
|
|
396
|
+
}
|
|
397
|
+
/** Result of {@link renderToStringAsync} and {@link renderToDocumentAsync}. */
|
|
398
|
+
interface RenderResult {
|
|
399
|
+
/** The rendered markup: the body content, or the whole document for `renderToDocumentAsync`. */
|
|
400
|
+
html: string;
|
|
401
|
+
/** Head tags collected during the render (`<title>`, meta, link, JSON-LD), escaped. */
|
|
402
|
+
head: string;
|
|
403
|
+
/** The transfer `<script>` restoring server data in the browser, or `""`. */
|
|
404
|
+
state: string;
|
|
405
|
+
/** The transferred data itself (what `state` assigns), or `null`. */
|
|
406
|
+
stateData: Record<string, unknown> | null;
|
|
407
|
+
/** HTTP status: 200 unless a router render decided otherwise (see `renderRoute`). */
|
|
408
|
+
status: number;
|
|
409
|
+
/** Diagnostics: pending work at the timeout, browser-API reads, state size. */
|
|
410
|
+
diagnostics: RenderDiagnostics;
|
|
411
|
+
}
|
|
412
|
+
/** Options of {@link renderToStringAsync}. */
|
|
413
|
+
interface AsyncRenderOptions {
|
|
414
|
+
/** Milliseconds to wait for pending work before serializing anyway. Default 5000. */
|
|
415
|
+
timeout?: number;
|
|
416
|
+
/** Embed the data the render fetched so the browser does not fetch it again. Default `true`. */
|
|
417
|
+
transfer?: boolean;
|
|
418
|
+
/** CSP nonce for the transfer script. */
|
|
419
|
+
nonce?: string;
|
|
420
|
+
/** Cap on the transferred state in bytes. Larger entries are dropped (largest first). Default 1 MB. */
|
|
421
|
+
maxStateBytes?: number;
|
|
422
|
+
/**
|
|
423
|
+
* Where the `data-sibu-ssr="true"` provenance marker goes. Default `"root"`:
|
|
424
|
+
* top-level elements only (`renderToString` stamps every element, ~20 bytes
|
|
425
|
+
* each, and nothing reads it beneath the root).
|
|
426
|
+
*/
|
|
427
|
+
marker?: "all" | "root" | "none";
|
|
428
|
+
/**
|
|
429
|
+
* `"adopt"` marks the output for `hydrate(App, root, { strategy: "adopt" })`:
|
|
430
|
+
* every element built by a tag factory or `html` carries its creation id
|
|
431
|
+
* (`data-sibu-h`), so the browser binds to these nodes instead of replacing
|
|
432
|
+
* them. Default: no markers (the output `hydrate()`'s replace strategy uses).
|
|
433
|
+
*/
|
|
434
|
+
hydration?: "adopt" | "replace";
|
|
435
|
+
/**
|
|
436
|
+
* Log a one-block summary of the render in development (time, settle
|
|
437
|
+
* outcome, what was pending, browser-API reads, transferred state and its
|
|
438
|
+
* largest entries). A string labels it (`renderRoute` passes the URL).
|
|
439
|
+
*/
|
|
440
|
+
log?: boolean | string;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The diagnostics of one server render as readable text — what `log: true`
|
|
444
|
+
* prints, for a custom logger or a test failure message.
|
|
445
|
+
*
|
|
446
|
+
* @param d The render's `diagnostics`.
|
|
447
|
+
* @param label What was rendered (a URL).
|
|
448
|
+
* @returns A multi-line summary.
|
|
449
|
+
*/
|
|
450
|
+
declare function formatRenderDiagnostics(d: RenderDiagnostics, label?: string): string;
|
|
451
|
+
/**
|
|
452
|
+
* Describe where a node sits, for an error message: `main > section.grid > div.card`.
|
|
453
|
+
* The runtime has no component stack, so the DOM position is the component path.
|
|
454
|
+
*/
|
|
455
|
+
declare function describeNodePath(node: unknown): string;
|
|
456
|
+
/**
|
|
457
|
+
* The error an async server render rejects with for an error no
|
|
458
|
+
* `ErrorBoundary` claimed: the original as `cause`, the component name and DOM
|
|
459
|
+
* path of the failing node in the message.
|
|
460
|
+
* @internal
|
|
461
|
+
*/
|
|
462
|
+
declare function renderFailure(entry: {
|
|
463
|
+
error: unknown;
|
|
464
|
+
context: {
|
|
465
|
+
name?: string;
|
|
466
|
+
node?: unknown;
|
|
467
|
+
};
|
|
468
|
+
}): Error;
|
|
469
|
+
/**
|
|
470
|
+
* Render a tree to HTML once it has settled — one `await`, no manual
|
|
471
|
+
* microtask waits.
|
|
472
|
+
*
|
|
473
|
+
* Pass a FUNCTION so the tree is built inside the request scope: its queries
|
|
474
|
+
* then use the request's cache and its work is awaited. Called outside
|
|
475
|
+
* `runInSSRContext`, it opens a scope of its own.
|
|
476
|
+
*
|
|
477
|
+
* Rejects when an error escapes the tree (no `ErrorBoundary` claimed it), with
|
|
478
|
+
* the DOM path of the failing node in the message. A settle timeout does not
|
|
479
|
+
* reject: what is ready is serialized, `Suspense` and `lazy()` keep their
|
|
480
|
+
* fallbacks, and `diagnostics.pending` names what was still running.
|
|
481
|
+
*
|
|
482
|
+
* @param input The tree, or a function building it.
|
|
483
|
+
* @param options Timeout, state transfer, nonce, state cap.
|
|
484
|
+
* @returns `{ html, head, state, stateData, status, diagnostics }`.
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```ts
|
|
488
|
+
* const { html, head, state } = await renderToStringAsync(() => App());
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
declare function renderToStringAsync(input: Node | (() => Node), options?: AsyncRenderOptions): Promise<RenderResult>;
|
|
492
|
+
/**
|
|
493
|
+
* {@link renderToStringAsync}, wrapped in the document `renderToDocument`
|
|
494
|
+
* produces. The collected head tags go into `<head>` and the transfer script
|
|
495
|
+
* at the end of `<body>`, before `options.scripts`.
|
|
496
|
+
*
|
|
497
|
+
* @param input The tree, or a function building it.
|
|
498
|
+
* @param options Document options plus the async render options.
|
|
499
|
+
* @returns The render result; `html` is the whole document.
|
|
500
|
+
*/
|
|
501
|
+
declare function renderToDocumentAsync(input: Node | (() => Node), options?: DocumentOptions & AsyncRenderOptions): Promise<RenderResult>;
|
|
502
|
+
|
|
503
|
+
/** Options of the streaming `renderToReadableStream(() => App(), options)`. */
|
|
504
|
+
interface StreamRenderOptions extends AsyncRenderOptions {
|
|
505
|
+
/**
|
|
506
|
+
* Wrap the stream in `renderToDocument`'s document (head tags flushed with
|
|
507
|
+
* the shell, the transfer script and `scripts` at the end). Default `{}`;
|
|
508
|
+
* `false` streams the body markup only.
|
|
509
|
+
*/
|
|
510
|
+
document?: DocumentOptions | false;
|
|
511
|
+
/** Total time for every boundary (default 10 000 ms); a boundary still pending keeps its fallback. */
|
|
512
|
+
timeout?: number;
|
|
513
|
+
/** Time for the shell itself (default 5000 ms); past it the shell flushes with what is ready. */
|
|
514
|
+
shellTimeout?: number;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Serialize an element's open tag — `<tag attr="…" …` without the closing `>`
|
|
519
|
+
* — or return `null` when the element must not be emitted at all.
|
|
520
|
+
*
|
|
521
|
+
* THE single attribute serializer for server-emitted element trees, shared by
|
|
522
|
+
* `renderToString` and `renderToStream`:
|
|
523
|
+
*
|
|
524
|
+
* - names are validated, `on*` and `srcdoc` dropped, policy sinks sanitized
|
|
525
|
+
* through the same `sanitizeAttributeString` the client writers use;
|
|
526
|
+
* - a `<meta>` whose EMITTED attributes form a refresh directive the shared
|
|
527
|
+
* policy forbids is dropped whole, exactly as `renderToDocument`'s `meta`
|
|
528
|
+
* option and `Head()` drop such an entry. DOM built through the
|
|
529
|
+
* framework's writers can never reach this state; DOM built any other way
|
|
530
|
+
* (application code, a third-party library) could, and rendering target is
|
|
531
|
+
* not a security boundary.
|
|
532
|
+
* - on a `foreign` (SVG / MathML) element, names keep their case and their
|
|
533
|
+
* namespace prefix, and an SVG animation may not target a URL or handler
|
|
534
|
+
* attribute — its `attributeName` is omitted, as the client writers omit it.
|
|
535
|
+
* - an `<iframe>` built by `embed()` is re-judged against what `embed()`
|
|
536
|
+
* wrote (`embedSerializedAttribute`): only the attributes it writes (plus
|
|
537
|
+
* `id`, `class`, `style`, `width`, `height`, `data-*`, `aria-*`) are
|
|
538
|
+
* served, `src` must match the allowlist the frame was built under,
|
|
539
|
+
* `sandbox` / `allow` can only be narrower than what it wrote,
|
|
540
|
+
* `allowfullscreen` follows the served `allow`, and a frame without a
|
|
541
|
+
* non-blank `title` is not served — so a frame changed behind the
|
|
542
|
+
* framework's back is never served wider than `embed()` built it.
|
|
543
|
+
*/
|
|
544
|
+
/**
|
|
545
|
+
* How a render path marks its output.
|
|
546
|
+
* @internal
|
|
547
|
+
*/
|
|
548
|
+
interface SerializeOptions {
|
|
549
|
+
/**
|
|
550
|
+
* Where `data-sibu-ssr="true"` goes: on every element (`renderToString`'s
|
|
551
|
+
* historical output), on top-level elements only, or nowhere.
|
|
552
|
+
*/
|
|
553
|
+
marker: "all" | "root" | "none";
|
|
554
|
+
/** Adoption ids (`data-sibu-h`) of elements built under an adopt render. */
|
|
555
|
+
ids: WeakMap<Element, string> | null;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Converts an element tree to an HTML string for server-side rendering.
|
|
559
|
+
* HTML, SVG and MathML elements are serialized; SVG and MathML keep the case
|
|
560
|
+
* of their tag and attribute names and their `xlink:` / `xml:` prefixes.
|
|
561
|
+
*
|
|
562
|
+
* Security: attribute names are validated, `on*` handlers are dropped,
|
|
563
|
+
* URL-bearing attributes are routed through `sanitizeUrl`, attribute
|
|
564
|
+
* values are escaped against both `"` and `'`, and an SVG animation may not
|
|
565
|
+
* target a URL or handler attribute. `<script>` and `<style>` tags are
|
|
566
|
+
* stripped from the serialized output.
|
|
567
|
+
*/
|
|
568
|
+
declare function renderToString(element: HTMLElement | DocumentFragment | Node): string;
|
|
569
|
+
/**
|
|
570
|
+
* `renderToString` with explicit marker options — the async renderers' entry.
|
|
571
|
+
* @internal
|
|
572
|
+
*/
|
|
573
|
+
declare function renderToStringWith(element: Node, opts: SerializeOptions): string;
|
|
574
|
+
interface HydrateOptions {
|
|
575
|
+
/**
|
|
576
|
+
* Enable dev-mode hydration diagnostics. When true, the walker
|
|
577
|
+
* compares the server-rendered tree against the client tree and
|
|
578
|
+
* logs the first tag mismatch, attribute difference, or child count
|
|
579
|
+
* mismatch to the console. Disabled by default.
|
|
580
|
+
*/
|
|
581
|
+
diagnostics?: boolean;
|
|
582
|
+
/**
|
|
583
|
+
* Called for each detected mismatch instead of the default console
|
|
584
|
+
* warning. Useful for routing diagnostics to a telemetry system.
|
|
585
|
+
*/
|
|
586
|
+
onMismatch?: (report: HydrationMismatch) => void;
|
|
587
|
+
}
|
|
588
|
+
interface HydrationMismatch {
|
|
589
|
+
kind: "tag" | "attribute" | "child-count" | "text";
|
|
590
|
+
path: string;
|
|
591
|
+
serverValue: string;
|
|
592
|
+
clientValue: string;
|
|
593
|
+
message: string;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Activates a server-rendered SibuJS application by building the live client
|
|
597
|
+
* tree and **replacing** the inert server-rendered subtree with it.
|
|
598
|
+
*
|
|
599
|
+
* SibuJS uses replacement hydration rather than DOM adoption: the server markup
|
|
600
|
+
* is discarded, not attached to. It is not the case that listeners and bindings
|
|
601
|
+
* are wired onto the existing server nodes — reactive bindings belong to the
|
|
602
|
+
* nodes `component()` produced, and adopting server nodes would leave the
|
|
603
|
+
* visible DOM permanently frozen.
|
|
604
|
+
*
|
|
605
|
+
* Consequences worth knowing before deploying SSR:
|
|
606
|
+
*
|
|
607
|
+
* - DOM node identity is not preserved.
|
|
608
|
+
* - Pre-hydration user input, checkbox state, and focus are discarded.
|
|
609
|
+
* - Conversely, partial-adoption mismatch bugs are avoided by this design —
|
|
610
|
+
* the client tree is always authoritative.
|
|
611
|
+
*
|
|
612
|
+
* Re-hydrating the same container disposes the previous client tree first.
|
|
613
|
+
*
|
|
614
|
+
* When `options.diagnostics` is true, the walker reports the first
|
|
615
|
+
* server/client mismatch it finds. This is a dev-mode tool — pass
|
|
616
|
+
* `diagnostics: false` (or omit it) in production.
|
|
617
|
+
*
|
|
618
|
+
* @see docs/architecture/hydration.md
|
|
619
|
+
*/
|
|
620
|
+
declare function hydrate(component: () => Node, container: Element, options: AdoptOptions & {
|
|
621
|
+
strategy: "adopt";
|
|
622
|
+
}): Promise<AdoptResult>;
|
|
623
|
+
declare function hydrate(component: () => HTMLElement, container: HTMLElement, options?: HydrateOptions & {
|
|
624
|
+
strategy?: "replace";
|
|
625
|
+
}): void;
|
|
626
|
+
/**
|
|
627
|
+
* Branded type for raw HTML that has been explicitly marked as trusted.
|
|
628
|
+
* Use `trustHTML()` to create a value of this type. This prevents
|
|
629
|
+
* accidental injection of unsanitized user input into `headExtra`.
|
|
630
|
+
*/
|
|
631
|
+
type TrustedHTML = string & {
|
|
632
|
+
readonly __brand: "TrustedHTML";
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Mark an HTML string as trusted for use in contexts that accept raw HTML
|
|
636
|
+
* (e.g. `renderToDocument({ headExtra })`). Only call this on
|
|
637
|
+
* developer-controlled strings — never on user input.
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* ```ts
|
|
641
|
+
* const extra = trustHTML('<link rel="preconnect" href="https://fonts.googleapis.com">');
|
|
642
|
+
* renderToDocument(App, { headExtra: extra });
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
declare function trustHTML(html: string): TrustedHTML;
|
|
646
|
+
/**
|
|
647
|
+
* Should this meta entry be dropped entirely?
|
|
648
|
+
*
|
|
649
|
+
* Delegates to the SHARED meta-refresh policy. This used to be a private copy
|
|
650
|
+
* of four `includes()` checks that happened to match the client's private copy
|
|
651
|
+
* — two implementations of one rule, so a fix to either would silently diverge
|
|
652
|
+
* from the other. The exported name is kept for back-compat; the policy is no
|
|
653
|
+
* longer local.
|
|
654
|
+
*
|
|
655
|
+
* Returns true for a forbidden refresh directive AND for an entry carrying
|
|
656
|
+
* duplicate case-insensitive attribute names, since there the value validated
|
|
657
|
+
* would not be the value the browser ends up honouring.
|
|
658
|
+
*/
|
|
659
|
+
declare function isDangerousMetaRefresh(metaProps: Record<string, string>): boolean;
|
|
660
|
+
/**
|
|
661
|
+
* Renders a component to a full HTML document string.
|
|
662
|
+
*
|
|
663
|
+
* `headExtra` requires a `TrustedHTML` value created via `trustHTML()`.
|
|
664
|
+
* This prevents accidental injection of unsanitized user input.
|
|
665
|
+
*
|
|
666
|
+
* Security: meta/link/bodyAttrs keys are validated against
|
|
667
|
+
* `SAFE_ATTR_NAME` (rejecting crafted keys that would break out of the
|
|
668
|
+
* attribute context). URL attributes in meta/link/scripts/bodyAttrs pass
|
|
669
|
+
* through `sanitizeUrl`. The page `title` is HTML-escaped.
|
|
670
|
+
*/
|
|
671
|
+
/** Document options shared by `renderToDocument` and `renderToDocumentAsync`. */
|
|
672
|
+
interface DocumentOptions {
|
|
673
|
+
title?: string;
|
|
674
|
+
meta?: Record<string, string>[];
|
|
675
|
+
links?: Record<string, string>[];
|
|
676
|
+
/**
|
|
677
|
+
* External script URLs appended to `<body>`. Each passes the scheme
|
|
678
|
+
* allowlist; an allowed URL is still code that runs with the page's
|
|
679
|
+
* authority, so this option is an explicit trust decision — first-party
|
|
680
|
+
* or pinned URLs only, never request-derived values.
|
|
681
|
+
*/
|
|
682
|
+
scripts?: string[];
|
|
683
|
+
bodyAttrs?: Record<string, string>;
|
|
684
|
+
/**
|
|
685
|
+
* Raw HTML injected into `<head>`. Must be wrapped in `trustHTML()`
|
|
686
|
+
* to confirm the content is developer-controlled.
|
|
687
|
+
*/
|
|
688
|
+
headExtra?: TrustedHTML;
|
|
689
|
+
}
|
|
690
|
+
declare function renderToDocument(component: () => HTMLElement, options?: DocumentOptions): string;
|
|
691
|
+
/**
|
|
692
|
+
* The document `renderToDocument` emits, around already-rendered `content`.
|
|
693
|
+
* `head` and `bodyEnd` are framework-generated markup (collected head tags,
|
|
694
|
+
* the transfer script), escaped by their producers.
|
|
695
|
+
* @internal
|
|
696
|
+
*/
|
|
697
|
+
declare function assembleDocument(content: string, options: DocumentOptions, head: string, bodyEnd: string): string;
|
|
698
|
+
/**
|
|
699
|
+
* Renders a component tree to an async iterable of HTML chunks.
|
|
700
|
+
* Enables progressive server-side rendering — the consumer can write
|
|
701
|
+
* each chunk to a response stream as it becomes available.
|
|
702
|
+
*
|
|
703
|
+
* Same security posture as `renderToString`.
|
|
704
|
+
*/
|
|
705
|
+
declare function renderToStream(element: HTMLElement | DocumentFragment | Node): AsyncGenerator<string>;
|
|
706
|
+
/**
|
|
707
|
+
* Collects the full output of renderToStream into a string.
|
|
708
|
+
*/
|
|
709
|
+
declare function collectStream(stream: AsyncGenerator<string> | AsyncIterable<string>): Promise<string>;
|
|
710
|
+
/**
|
|
711
|
+
* Renders a component tree to a Web `ReadableStream<string>`.
|
|
712
|
+
*
|
|
713
|
+
* Uses pull-based backpressure — chunks are produced on demand.
|
|
714
|
+
*
|
|
715
|
+
* **Environment.** Supported on SibuJS's declared Node runtime (`>=22.3.0`) and
|
|
716
|
+
* on any other environment providing both the Web Streams API *and* a DOM
|
|
717
|
+
* implementation. Streaming SSR requires the same server DOM as every other
|
|
718
|
+
* SibuJS SSR API: this function takes real `Node` objects and walks them, so a
|
|
719
|
+
* DOM-less runtime cannot produce the input, let alone render it. Do not read
|
|
720
|
+
* "returns a Web stream" as "runs anywhere Web streams exist".
|
|
721
|
+
*
|
|
722
|
+
* **Semantics.** Suspense boundaries encountered during the walk are resolved
|
|
723
|
+
* as a batch before the affected chunk is emitted — the shell is not flushed
|
|
724
|
+
* ahead of individually-completing boundaries. See
|
|
725
|
+
* `docs/architecture/ssr.md` for the precise streaming and failure semantics.
|
|
726
|
+
*/
|
|
727
|
+
declare function renderToReadableStream(element: HTMLElement | DocumentFragment | Node): ReadableStream<string>;
|
|
728
|
+
/**
|
|
729
|
+
* Stream a page built by `input` with out-of-order `Suspense`: the head and the
|
|
730
|
+
* shell (with each boundary's fallback) flush as soon as everything outside the
|
|
731
|
+
* boundaries has settled, then each boundary streams when it is ready, with a
|
|
732
|
+
* CSP-nonce-aware swap script, then the transferred state and the scripts.
|
|
733
|
+
* Backpressure: the next chunk is produced once the previous one was read.
|
|
734
|
+
* See `ssrStream.ts`.
|
|
735
|
+
*/
|
|
736
|
+
declare function renderToReadableStream(input: () => Node, options?: StreamRenderOptions): ReadableStream<string>;
|
|
737
|
+
declare function island(id: string, component: () => HTMLElement): HTMLElement;
|
|
738
|
+
/**
|
|
739
|
+
* A component island rendered on the server: the markup carries the island
|
|
740
|
+
* marker, its serialized `props` and adoption ids, and the browser adopts just
|
|
741
|
+
* this subtree with `registerIsland(name, islandComponent(Component))` +
|
|
742
|
+
* `mountIslands()`. See `componentIsland`.
|
|
743
|
+
*/
|
|
744
|
+
declare function island<P>(name: string, component: (props: P) => Node, props: P, options?: ComponentIslandOptions): HTMLElement;
|
|
745
|
+
declare function hydrateIslands(container: HTMLElement, islands: Record<string, () => HTMLElement>): void;
|
|
746
|
+
/**
|
|
747
|
+
* Progressively hydrate islands only when they enter the viewport.
|
|
748
|
+
* Uses IntersectionObserver to defer hydration of off-screen islands,
|
|
749
|
+
* reducing initial JavaScript execution cost.
|
|
750
|
+
*
|
|
751
|
+
* Returns a cleanup function that disconnects all observers.
|
|
752
|
+
*/
|
|
753
|
+
declare function hydrateProgressively(container: HTMLElement, islands: Record<string, () => HTMLElement>, options?: IntersectionObserverInit): () => void;
|
|
754
|
+
/**
|
|
755
|
+
* Reset SSR state between requests. Call at the start of each SSR render
|
|
756
|
+
* to prevent ID drift in long-lived server processes.
|
|
757
|
+
*
|
|
758
|
+
* When running inside `runInSSRContext`, each request already owns its
|
|
759
|
+
* own counter via AsyncLocalStorage — this hook only touches the
|
|
760
|
+
* module-global fallback used outside that context.
|
|
761
|
+
*/
|
|
762
|
+
declare function resetSSRState(): void;
|
|
763
|
+
/**
|
|
764
|
+
* Create a suspense boundary for SSR streaming.
|
|
765
|
+
* Renders fallback HTML inline and returns a promise for the resolved content.
|
|
766
|
+
*
|
|
767
|
+
* The returned element contains the fallback UI with a `data-sibu-suspense-id`
|
|
768
|
+
* marker. The promise resolves to `{ id, html }` once async content is ready.
|
|
769
|
+
*
|
|
770
|
+
* The promise never rejects. Any failure to produce the content HTML — the
|
|
771
|
+
* content factory throwing synchronously, its promise rejecting, the timeout,
|
|
772
|
+
* or rendering the resolved element throwing — resolves it with the fallback
|
|
773
|
+
* HTML, so the stream still produces a deterministic swap payload.
|
|
774
|
+
*/
|
|
775
|
+
declare function ssrSuspense(props: {
|
|
776
|
+
fallback: () => HTMLElement;
|
|
777
|
+
content: () => Promise<HTMLElement>;
|
|
778
|
+
/** Milliseconds before the content promise is rejected. Defaults to 30000. */
|
|
779
|
+
timeoutMs?: number;
|
|
780
|
+
}): {
|
|
781
|
+
element: HTMLElement;
|
|
782
|
+
promise: Promise<{
|
|
783
|
+
id: string;
|
|
784
|
+
html: string;
|
|
785
|
+
}>;
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* Generate an inline script that swaps a suspense fallback with resolved content.
|
|
789
|
+
*
|
|
790
|
+
* Security: the `id` is validated against a strict allowlist. Values that
|
|
791
|
+
* do not match throw, so an attacker-controlled id cannot inject context
|
|
792
|
+
* breakers into the selector or the JS string. `ssrSuspense()`'s internal
|
|
793
|
+
* generator always produces allowlist-safe ids.
|
|
794
|
+
*/
|
|
795
|
+
declare function suspenseSwapScript(id: string, nonce?: string): string;
|
|
796
|
+
/**
|
|
797
|
+
* Renders a component tree with suspense boundaries as a stream.
|
|
798
|
+
* Yields the main tree HTML first (including fallback content for suspended
|
|
799
|
+
* boundaries), then flushes resolved content with inline swap scripts.
|
|
800
|
+
*
|
|
801
|
+
* Supports an optional `nonce` that is propagated to the resolved-content
|
|
802
|
+
* swap scripts so the stream works with strict CSP.
|
|
803
|
+
*/
|
|
804
|
+
declare function renderToSuspenseStream(element: HTMLElement | DocumentFragment | Node, pendingBoundaries?: Promise<{
|
|
805
|
+
id: string;
|
|
806
|
+
html: string;
|
|
807
|
+
}>[], options?: {
|
|
808
|
+
nonce?: string;
|
|
809
|
+
}): AsyncGenerator<string>;
|
|
810
|
+
/**
|
|
811
|
+
* Escape a JSON string for safe embedding inside a `<script>` tag. This
|
|
812
|
+
* goes beyond the usual `</script>` concern:
|
|
813
|
+
*
|
|
814
|
+
* - `<`, `>`, `&` are unicode-escaped so nothing inside a string literal
|
|
815
|
+
* can close the script tag or start a new one.
|
|
816
|
+
* - `U+2028` (LINE SEPARATOR) and `U+2029` (PARAGRAPH SEPARATOR) are
|
|
817
|
+
* unicode-escaped. Before ES2019 these were illegal inside a JS string
|
|
818
|
+
* literal, so including them verbatim would cause a SyntaxError on
|
|
819
|
+
* older engines and could break out of string context.
|
|
820
|
+
*/
|
|
821
|
+
declare function escapeScriptJson(json: string): string;
|
|
822
|
+
declare function serializeState(state: Record<string, unknown>, nonce?: string, options?: {
|
|
823
|
+
maxBytes?: number;
|
|
824
|
+
/**
|
|
825
|
+
* The window global to assign (default `__SIBU_SSR_DATA__`). Must be a
|
|
826
|
+
* plain identifier; anything else throws.
|
|
827
|
+
*/
|
|
828
|
+
global?: string;
|
|
829
|
+
}): string;
|
|
830
|
+
/**
|
|
831
|
+
* Retrieve state that was embedded by `serializeState()` during SSR.
|
|
832
|
+
*
|
|
833
|
+
* When a `validate` function is provided, it acts as a type guard —
|
|
834
|
+
* only data that passes validation is returned. This prevents
|
|
835
|
+
* tampered SSR payloads from being trusted by the client.
|
|
836
|
+
*
|
|
837
|
+
* @param validate Optional type guard to verify data integrity
|
|
838
|
+
*/
|
|
839
|
+
declare function deserializeState<T = Record<string, unknown>>(validate?: (data: unknown) => data is T): T | undefined;
|
|
840
|
+
|
|
841
|
+
export { type IslandStrategy as $, type AsyncRenderOptions as A, DEFAULT_TRANSFER_MAX_BYTES as B, type ComponentIslandOptions as C, type DocumentOptions as D, type RenderDiagnostics as E, type SettleOptions as F, type SettleResult as G, type HydrateOptions as H, ISLAND_PROPS_ATTR as I, type StreamRenderOptions as J, componentIsland as K, describeNodePath as L, formatRenderDiagnostics as M, islandComponent as N, renderFailure as O, renderToDocumentAsync as P, renderToStringAsync as Q, type RenderResult as R, type SerializeOptions as S, type TrustedHTML as T, settled as U, type EachBindings as V, type EachEventBindings as W, type EnhanceContext as X, type EnhanceSetup as Y, type IslandLoader as Z, type IslandRegistration as _, type HydrationMismatch as a, type LazyIslandLoader as a0, type MountIslandsOptions as a1, enhance as a2, enhanceAll as a3, lazyIsland as a4, mountIslands as a5, registerIsland as a6, unregisterIsland as a7, assembleDocument as b, collectStream as c, deserializeState as d, escapeScriptJson as e, hydrateIslands as f, hydrateProgressively as g, hydrate as h, isDangerousMetaRefresh as i, island as j, renderToReadableStream as k, renderToStream as l, renderToString as m, renderToStringWith as n, renderToSuspenseStream as o, resetSSRState as p, ssrSuspense as q, renderToDocument as r, serializeState as s, suspenseSwapScript as t, trustHTML as u, ADOPT_ID_ATTR as v, type AdoptMismatch as w, type AdoptOptions as x, type AdoptResult as y, DEFAULT_SETTLE_TIMEOUT as z };
|