@getforma/core 0.3.0 → 0.6.1
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 +201 -13
- package/dist/{chunk-KX5WRZH7.js → chunk-DPSAVBCP.js} +3 -4
- package/dist/chunk-DPSAVBCP.js.map +1 -0
- package/dist/{chunk-FPSLC62A.cjs → chunk-KH3F5NRU.cjs} +2 -4
- package/dist/chunk-KH3F5NRU.cjs.map +1 -0
- package/dist/{chunk-VKKPX4YU.js → chunk-TKGUHASG.js} +79 -19
- package/dist/chunk-TKGUHASG.js.map +1 -0
- package/dist/{chunk-VP5EOGM5.cjs → chunk-YSKF3VRA.cjs} +87 -26
- package/dist/chunk-YSKF3VRA.cjs.map +1 -0
- package/dist/forma-runtime-csp.js +2 -0
- package/dist/forma-runtime.js +2 -0
- package/dist/formajs-runtime-hardened.global.js +1 -1
- package/dist/formajs-runtime-hardened.global.js.map +1 -1
- package/dist/formajs-runtime.global.js +1 -1
- package/dist/formajs-runtime.global.js.map +1 -1
- package/dist/formajs.global.js +1 -1
- package/dist/formajs.global.js.map +1 -1
- package/dist/index.cjs +154 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -35
- package/dist/index.d.ts +83 -35
- package/dist/index.js +99 -40
- package/dist/index.js.map +1 -1
- package/dist/runtime-hardened.cjs +224 -60
- package/dist/runtime-hardened.cjs.map +1 -1
- package/dist/runtime-hardened.d.cts +86 -0
- package/dist/runtime-hardened.d.ts +86 -0
- package/dist/runtime-hardened.js +225 -61
- package/dist/runtime-hardened.js.map +1 -1
- package/dist/runtime.cjs +248 -83
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +226 -61
- package/dist/runtime.js.map +1 -1
- package/dist/ssr/index.cjs +69 -73
- package/dist/ssr/index.cjs.map +1 -1
- package/dist/ssr/index.d.cts +4 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.js +69 -73
- package/dist/ssr/index.js.map +1 -1
- package/dist/tc39-compat.cjs +3 -3
- package/dist/tc39-compat.js +1 -1
- package/package.json +24 -3
- package/dist/chunk-FPSLC62A.cjs.map +0 -1
- package/dist/chunk-KX5WRZH7.js.map +0 -1
- package/dist/chunk-VKKPX4YU.js.map +0 -1
- package/dist/chunk-VP5EOGM5.cjs.map +0 -1
package/dist/ssr/index.cjs
CHANGED
|
@@ -75,7 +75,17 @@ function renderToBuffer(node, parts) {
|
|
|
75
75
|
}
|
|
76
76
|
parts.push(">");
|
|
77
77
|
if (props?.["dangerouslySetInnerHTML"]) {
|
|
78
|
-
|
|
78
|
+
const raw = props["dangerouslySetInnerHTML"];
|
|
79
|
+
if (typeof raw === "object" && raw != null && "__html" in raw) {
|
|
80
|
+
const html = raw.__html;
|
|
81
|
+
if (typeof html === "string") {
|
|
82
|
+
parts.push(html);
|
|
83
|
+
} else {
|
|
84
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
88
|
+
}
|
|
79
89
|
} else {
|
|
80
90
|
for (const child of children) {
|
|
81
91
|
renderToBuffer(child, parts);
|
|
@@ -89,14 +99,13 @@ function renderToBuffer(node, parts) {
|
|
|
89
99
|
function isVNode(v) {
|
|
90
100
|
return v != null && typeof v === "object" && "tag" in v && "children" in v;
|
|
91
101
|
}
|
|
92
|
-
var hydrationId = 0;
|
|
93
102
|
function renderToStringWithHydration(node) {
|
|
94
|
-
|
|
103
|
+
const ctx = { id: 0 };
|
|
95
104
|
const parts = [];
|
|
96
|
-
renderToBufferHydrated(node, parts);
|
|
105
|
+
renderToBufferHydrated(node, parts, ctx);
|
|
97
106
|
return parts.join("");
|
|
98
107
|
}
|
|
99
|
-
function renderToBufferHydrated(node, parts) {
|
|
108
|
+
function renderToBufferHydrated(node, parts, ctx) {
|
|
100
109
|
if (node == null || node === true || node === false) return;
|
|
101
110
|
if (typeof node === "string") {
|
|
102
111
|
parts.push(escapeHtml(node));
|
|
@@ -107,21 +116,21 @@ function renderToBufferHydrated(node, parts) {
|
|
|
107
116
|
return;
|
|
108
117
|
}
|
|
109
118
|
if (typeof node === "function") {
|
|
110
|
-
const id =
|
|
119
|
+
const id = ctx.id++;
|
|
111
120
|
parts.push(`<!--forma-t:${id}-->`);
|
|
112
|
-
renderToBufferHydrated(node(), parts);
|
|
121
|
+
renderToBufferHydrated(node(), parts, ctx);
|
|
113
122
|
parts.push(`<!--/forma-t:${id}-->`);
|
|
114
123
|
return;
|
|
115
124
|
}
|
|
116
125
|
if (Array.isArray(node)) {
|
|
117
|
-
const id =
|
|
126
|
+
const id = ctx.id++;
|
|
118
127
|
parts.push(`<!--forma-l:${id}-->`);
|
|
119
|
-
for (const child of node) renderToBufferHydrated(child, parts);
|
|
128
|
+
for (const child of node) renderToBufferHydrated(child, parts, ctx);
|
|
120
129
|
parts.push(`<!--/forma-l:${id}-->`);
|
|
121
130
|
return;
|
|
122
131
|
}
|
|
123
132
|
if (isVNode(node)) {
|
|
124
|
-
const id =
|
|
133
|
+
const id = ctx.id++;
|
|
125
134
|
const { tag, props, children } = node;
|
|
126
135
|
parts.push("<", tag, ` data-forma-h="${id}"`);
|
|
127
136
|
if (props) {
|
|
@@ -142,10 +151,20 @@ function renderToBufferHydrated(node, parts) {
|
|
|
142
151
|
}
|
|
143
152
|
parts.push(">");
|
|
144
153
|
if (props?.["dangerouslySetInnerHTML"]) {
|
|
145
|
-
|
|
154
|
+
const raw = props["dangerouslySetInnerHTML"];
|
|
155
|
+
if (typeof raw === "object" && raw != null && "__html" in raw) {
|
|
156
|
+
const html = raw.__html;
|
|
157
|
+
if (typeof html === "string") {
|
|
158
|
+
parts.push(html);
|
|
159
|
+
} else {
|
|
160
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
164
|
+
}
|
|
146
165
|
} else {
|
|
147
166
|
for (const child of children) {
|
|
148
|
-
renderToBufferHydrated(child, parts);
|
|
167
|
+
renderToBufferHydrated(child, parts, ctx);
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
170
|
parts.push("</", tag, ">");
|
|
@@ -179,47 +198,14 @@ function $FORMA_SWAP(id,html){
|
|
|
179
198
|
</script>`;
|
|
180
199
|
}
|
|
181
200
|
function getSwapTag(id, html) {
|
|
182
|
-
|
|
183
|
-
return `<script>$FORMA_SWAP("${id}","${escaped.replace(/"/g, '\\"').replace(/\n/g, "\\n")}")</script>`;
|
|
201
|
+
return `<script>$FORMA_SWAP(${JSON.stringify(id)},${JSON.stringify(html)})</script>`;
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
// src/ssr/stream.ts
|
|
187
|
-
function isVNode2(v) {
|
|
188
|
-
return v != null && typeof v === "object" && "tag" in v && "children" in v;
|
|
189
|
-
}
|
|
190
|
-
function escapeHtml2(str) {
|
|
191
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
192
|
-
}
|
|
193
|
-
function escapeAttr2(str) {
|
|
194
|
-
return str.replace(/&/g, "&").replace(/"/g, """);
|
|
195
|
-
}
|
|
196
|
-
var VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
197
|
-
"area",
|
|
198
|
-
"base",
|
|
199
|
-
"br",
|
|
200
|
-
"col",
|
|
201
|
-
"embed",
|
|
202
|
-
"hr",
|
|
203
|
-
"img",
|
|
204
|
-
"input",
|
|
205
|
-
"link",
|
|
206
|
-
"meta",
|
|
207
|
-
"param",
|
|
208
|
-
"source",
|
|
209
|
-
"track",
|
|
210
|
-
"wbr"
|
|
211
|
-
]);
|
|
212
|
-
var PROP_TO_ATTR2 = {
|
|
213
|
-
className: "class",
|
|
214
|
-
htmlFor: "for",
|
|
215
|
-
tabIndex: "tabindex"
|
|
216
|
-
};
|
|
217
|
-
var suspenseCounter = 0;
|
|
218
|
-
var pendingBoundaries = [];
|
|
219
205
|
function renderSync(node, parts) {
|
|
220
206
|
if (node == null || node === true || node === false) return;
|
|
221
207
|
if (typeof node === "string") {
|
|
222
|
-
parts.push(
|
|
208
|
+
parts.push(escapeHtml(node));
|
|
223
209
|
return;
|
|
224
210
|
}
|
|
225
211
|
if (typeof node === "number") {
|
|
@@ -234,38 +220,42 @@ function renderSync(node, parts) {
|
|
|
234
220
|
for (const child of node) renderSync(child, parts);
|
|
235
221
|
return;
|
|
236
222
|
}
|
|
237
|
-
if (
|
|
223
|
+
if (isVNode(node)) {
|
|
238
224
|
const { tag, props, children } = node;
|
|
239
225
|
parts.push("<", tag);
|
|
240
226
|
if (props) {
|
|
241
227
|
for (const [key, value] of Object.entries(props)) {
|
|
242
228
|
if (key.startsWith("on") || key === "ref" || key === "dangerouslySetInnerHTML") continue;
|
|
243
|
-
const attrName =
|
|
229
|
+
const attrName = PROP_TO_ATTR[key] ?? key;
|
|
244
230
|
const resolved = typeof value === "function" ? value() : value;
|
|
245
231
|
if (resolved === true) {
|
|
246
232
|
parts.push(" ", attrName);
|
|
247
233
|
} else if (resolved !== false && resolved != null) {
|
|
248
|
-
parts.push(" ", attrName, '="',
|
|
234
|
+
parts.push(" ", attrName, '="', escapeAttr(String(resolved)), '"');
|
|
249
235
|
}
|
|
250
236
|
}
|
|
251
237
|
}
|
|
252
|
-
if (
|
|
238
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
253
239
|
parts.push(" />");
|
|
254
240
|
return;
|
|
255
241
|
}
|
|
256
242
|
parts.push(">");
|
|
257
243
|
if (props?.["dangerouslySetInnerHTML"]) {
|
|
258
|
-
|
|
244
|
+
const raw = props["dangerouslySetInnerHTML"];
|
|
245
|
+
if (typeof raw !== "object" || raw == null || !("__html" in raw) || typeof raw.__html !== "string") {
|
|
246
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
247
|
+
}
|
|
248
|
+
parts.push(raw.__html);
|
|
259
249
|
} else {
|
|
260
250
|
for (const child of children) renderSync(child, parts);
|
|
261
251
|
}
|
|
262
252
|
parts.push("</", tag, ">");
|
|
263
253
|
return;
|
|
264
254
|
}
|
|
265
|
-
parts.push(
|
|
255
|
+
parts.push(escapeHtml(String(node)));
|
|
266
256
|
}
|
|
267
257
|
function isSuspenseVNode(v) {
|
|
268
|
-
return
|
|
258
|
+
return isVNode(v) && v.tag === "forma-suspense";
|
|
269
259
|
}
|
|
270
260
|
function shSuspense(fallback, asyncChildren) {
|
|
271
261
|
return {
|
|
@@ -275,21 +265,23 @@ function shSuspense(fallback, asyncChildren) {
|
|
|
275
265
|
};
|
|
276
266
|
}
|
|
277
267
|
async function* renderToStreamNew(node, options) {
|
|
278
|
-
|
|
279
|
-
|
|
268
|
+
const state = {
|
|
269
|
+
suspenseCounter: 0,
|
|
270
|
+
pendingBoundaries: []
|
|
271
|
+
};
|
|
280
272
|
const injectSwap = options?.injectSwapScript !== false;
|
|
281
273
|
if (injectSwap) {
|
|
282
274
|
yield getSwapScript();
|
|
283
275
|
}
|
|
284
276
|
const mainParts = [];
|
|
285
|
-
renderStreamNode(node, mainParts);
|
|
277
|
+
renderStreamNode(node, mainParts, state);
|
|
286
278
|
yield mainParts.join("");
|
|
287
279
|
if (options?.bootstrapScript) {
|
|
288
280
|
yield options.bootstrapScript;
|
|
289
281
|
}
|
|
290
|
-
while (pendingBoundaries.length > 0) {
|
|
291
|
-
const pending = [...pendingBoundaries];
|
|
292
|
-
pendingBoundaries = [];
|
|
282
|
+
while (state.pendingBoundaries.length > 0) {
|
|
283
|
+
const pending = [...state.pendingBoundaries];
|
|
284
|
+
state.pendingBoundaries = [];
|
|
293
285
|
const results = await Promise.allSettled(
|
|
294
286
|
pending.map(async (boundary) => {
|
|
295
287
|
const html = await boundary.promise;
|
|
@@ -303,10 +295,10 @@ async function* renderToStreamNew(node, options) {
|
|
|
303
295
|
}
|
|
304
296
|
}
|
|
305
297
|
}
|
|
306
|
-
function renderStreamNode(node, parts) {
|
|
298
|
+
function renderStreamNode(node, parts, state) {
|
|
307
299
|
if (node == null || node === true || node === false) return;
|
|
308
300
|
if (typeof node === "string") {
|
|
309
|
-
parts.push(
|
|
301
|
+
parts.push(escapeHtml(node));
|
|
310
302
|
return;
|
|
311
303
|
}
|
|
312
304
|
if (typeof node === "number") {
|
|
@@ -314,15 +306,15 @@ function renderStreamNode(node, parts) {
|
|
|
314
306
|
return;
|
|
315
307
|
}
|
|
316
308
|
if (typeof node === "function") {
|
|
317
|
-
renderStreamNode(node(), parts);
|
|
309
|
+
renderStreamNode(node(), parts, state);
|
|
318
310
|
return;
|
|
319
311
|
}
|
|
320
312
|
if (Array.isArray(node)) {
|
|
321
|
-
for (const child of node) renderStreamNode(child, parts);
|
|
313
|
+
for (const child of node) renderStreamNode(child, parts, state);
|
|
322
314
|
return;
|
|
323
315
|
}
|
|
324
316
|
if (isSuspenseVNode(node)) {
|
|
325
|
-
const id = `forma-s:${suspenseCounter++}`;
|
|
317
|
+
const id = `forma-s:${state.suspenseCounter++}`;
|
|
326
318
|
const fallback = node.props.fallback;
|
|
327
319
|
const asyncFn = node.children[0];
|
|
328
320
|
parts.push(`<div id="${id}" style="display:contents">`);
|
|
@@ -333,39 +325,43 @@ function renderStreamNode(node, parts) {
|
|
|
333
325
|
renderSync(resolved, resolvedParts);
|
|
334
326
|
return resolvedParts.join("");
|
|
335
327
|
});
|
|
336
|
-
pendingBoundaries.push({ id, promise });
|
|
328
|
+
state.pendingBoundaries.push({ id, promise });
|
|
337
329
|
return;
|
|
338
330
|
}
|
|
339
|
-
if (
|
|
331
|
+
if (isVNode(node)) {
|
|
340
332
|
const { tag, props, children } = node;
|
|
341
333
|
parts.push("<", tag);
|
|
342
334
|
if (props) {
|
|
343
335
|
for (const [key, value] of Object.entries(props)) {
|
|
344
336
|
if (key === "fallback") continue;
|
|
345
337
|
if (key.startsWith("on") || key === "ref" || key === "dangerouslySetInnerHTML") continue;
|
|
346
|
-
const attrName =
|
|
338
|
+
const attrName = PROP_TO_ATTR[key] ?? key;
|
|
347
339
|
const resolved = typeof value === "function" ? value() : value;
|
|
348
340
|
if (resolved === true) {
|
|
349
341
|
parts.push(" ", attrName);
|
|
350
342
|
} else if (resolved !== false && resolved != null) {
|
|
351
|
-
parts.push(" ", attrName, '="',
|
|
343
|
+
parts.push(" ", attrName, '="', escapeAttr(String(resolved)), '"');
|
|
352
344
|
}
|
|
353
345
|
}
|
|
354
346
|
}
|
|
355
|
-
if (
|
|
347
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
356
348
|
parts.push(" />");
|
|
357
349
|
return;
|
|
358
350
|
}
|
|
359
351
|
parts.push(">");
|
|
360
352
|
if (props?.["dangerouslySetInnerHTML"]) {
|
|
361
|
-
|
|
353
|
+
const raw = props["dangerouslySetInnerHTML"];
|
|
354
|
+
if (typeof raw !== "object" || raw == null || !("__html" in raw) || typeof raw.__html !== "string") {
|
|
355
|
+
throw new TypeError("dangerouslySetInnerHTML must be { __html: string }");
|
|
356
|
+
}
|
|
357
|
+
parts.push(raw.__html);
|
|
362
358
|
} else {
|
|
363
|
-
for (const child of children) renderStreamNode(child, parts);
|
|
359
|
+
for (const child of children) renderStreamNode(child, parts, state);
|
|
364
360
|
}
|
|
365
361
|
parts.push("</", tag, ">");
|
|
366
362
|
return;
|
|
367
363
|
}
|
|
368
|
-
parts.push(
|
|
364
|
+
parts.push(escapeHtml(String(node)));
|
|
369
365
|
}
|
|
370
366
|
|
|
371
367
|
exports.getSwapScript = getSwapScript;
|
package/dist/ssr/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ssr/render.ts","../../src/ssr/ssr-reactive.ts","../../src/ssr/client-script.ts","../../src/ssr/stream.ts"],"names":["isVNode","escapeHtml","escapeAttr","VOID_ELEMENTS","PROP_TO_ATTR"],"mappings":"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","file":"index.cjs","sourcesContent":["import { ssrSignal } from './ssr-reactive.js';\n\n// Void elements that don't have closing tags\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// Map of prop names to HTML attribute names\nconst PROP_TO_ATTR: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n tabIndex: 'tabindex',\n};\n\n// Escape HTML entities\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Escape attribute values\nfunction escapeAttr(str: string): string {\n return str.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nexport interface VNode {\n tag: string;\n props: Record<string, unknown> | null;\n children: unknown[];\n}\n\n/**\n * Server-side hyperscript — creates a virtual node instead of a DOM element.\n */\nexport function sh(tag: string, props?: Record<string, unknown> | null, ...children: unknown[]): VNode {\n return { tag, props: props ?? null, children };\n}\n\n/**\n * Render a FormaJS virtual tree to an HTML string.\n *\n * Uses an array buffer internally to avoid O(n^2) string concatenation\n * for large trees, then joins once at the end.\n *\n * Usage:\n * ```ts\n * import { renderToString, sh } from '@getforma/core/ssr';\n *\n * const html = renderToString(\n * sh('div', { class: 'app' },\n * sh('h1', null, 'Hello SSR!'),\n * sh('p', null, () => count()), // signal getters resolved synchronously\n * )\n * );\n * ```\n */\nexport function renderToString(node: unknown): string {\n const parts: string[] = [];\n renderToBuffer(node, parts);\n return parts.join('');\n}\n\n/**\n * Internal: recursively render into a string array buffer.\n * Avoids O(n^2) string concatenation for large trees.\n */\nfunction renderToBuffer(node: unknown, parts: string[]): void {\n // null/undefined/boolean → empty\n if (node == null || node === true || node === false) return;\n\n // String → escaped text\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n\n // Number → stringified\n if (typeof node === 'number') { parts.push(String(node)); return; }\n\n // Function (signal getter) → resolve and render\n if (typeof node === 'function') { renderToBuffer(node(), parts); return; }\n\n // Array → render each\n if (Array.isArray(node)) {\n for (const child of node) renderToBuffer(child, parts);\n return;\n }\n\n // VNode\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n\n // Render props as attributes\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n // Skip event handlers, refs, and internal props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n\n const attrName = PROP_TO_ATTR[key] ?? key;\n\n // Resolve reactive values\n const resolved = typeof value === 'function' ? value() : value;\n\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n\n parts.push('>');\n\n // dangerouslySetInnerHTML\n if (props?.['dangerouslySetInnerHTML']) {\n parts.push((props['dangerouslySetInnerHTML'] as { __html: string }).__html);\n } else {\n // Render children\n for (const child of children) {\n renderToBuffer(child, parts);\n }\n }\n\n parts.push('</', tag, '>');\n return;\n }\n\n // Fallback: stringify\n parts.push(escapeHtml(String(node)));\n}\n\nfunction isVNode(v: unknown): v is VNode {\n return v != null && typeof v === 'object' && 'tag' in v && 'children' in v;\n}\n\n// ---------------------------------------------------------------------------\n// Hydration-aware rendering\n// ---------------------------------------------------------------------------\n\nexport interface RenderOptions {\n /** Enable hydration markers in output. */\n hydrate?: boolean;\n}\n\nlet hydrationId = 0;\n\n/**\n * Render a FormaJS virtual tree to an HTML string with hydration markers.\n *\n * Like `renderToString`, but injects comment markers and `data-forma-h`\n * attributes so the client-side `hydrate()` function can adopt existing\n * DOM nodes without re-creating them.\n *\n * Marker types:\n * - `data-forma-h=\"N\"` — element boundary (attribute on the element)\n * - `<!--forma-t:N-->` / `<!--/forma-t:N-->` — reactive text boundary\n * - `<!--forma-l:N-->` / `<!--/forma-l:N-->` — list boundary\n *\n * Usage:\n * ```ts\n * import { renderToStringWithHydration, sh } from '@getforma/core/ssr';\n *\n * const html = renderToStringWithHydration(\n * sh('div', { class: 'app' },\n * sh('h1', null, 'Hello SSR!'),\n * sh('p', null, () => count()),\n * )\n * );\n * ```\n */\nexport function renderToStringWithHydration(node: unknown): string {\n hydrationId = 0; // reset counter per render\n const parts: string[] = [];\n renderToBufferHydrated(node, parts);\n return parts.join('');\n}\n\n/**\n * Internal: recursively render into a string array buffer with hydration markers.\n */\nfunction renderToBufferHydrated(node: unknown, parts: string[]): void {\n // null/undefined/boolean → empty\n if (node == null || node === true || node === false) return;\n\n // String → escaped text\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n\n // Number → stringified\n if (typeof node === 'number') { parts.push(String(node)); return; }\n\n // Function (signal getter / reactive text) → wrap with text markers\n if (typeof node === 'function') {\n const id = hydrationId++;\n parts.push(`<!--forma-t:${id}-->`);\n renderToBufferHydrated(node(), parts);\n parts.push(`<!--/forma-t:${id}-->`);\n return;\n }\n\n // Array → wrap with list markers\n if (Array.isArray(node)) {\n const id = hydrationId++;\n parts.push(`<!--forma-l:${id}-->`);\n for (const child of node) renderToBufferHydrated(child, parts);\n parts.push(`<!--/forma-l:${id}-->`);\n return;\n }\n\n // VNode\n if (isVNode(node)) {\n const id = hydrationId++;\n const { tag, props, children } = node;\n\n // Add hydration data-attribute to element\n parts.push('<', tag, ` data-forma-h=\"${id}\"`);\n\n // Render props as attributes\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n // Skip event handlers, refs, and internal props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n\n const attrName = PROP_TO_ATTR[key] ?? key;\n\n // Resolve reactive values\n const resolved = typeof value === 'function' ? value() : value;\n\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n\n parts.push('>');\n\n // dangerouslySetInnerHTML\n if (props?.['dangerouslySetInnerHTML']) {\n parts.push((props['dangerouslySetInnerHTML'] as { __html: string }).__html);\n } else {\n // Render children\n for (const child of children) {\n renderToBufferHydrated(child, parts);\n }\n }\n\n parts.push('</', tag, '>');\n return;\n }\n\n // Fallback: stringify\n parts.push(escapeHtml(String(node)));\n}\n\n","/**\n * SSR-only signal: just a value holder, no reactivity needed on server.\n */\nexport function ssrSignal<T>(initial: T): [get: () => T, set: (v: T) => void] {\n let value = initial;\n return [() => value, (v) => { value = v; }];\n}\n\nexport function ssrComputed<T>(fn: () => T): () => T {\n return fn; // On server, just call the function directly\n}\n","/**\n * FormaJS SSR - Client Swap Script\n *\n * Tiny inline script injected into the HTML stream that handles\n * out-of-order Suspense boundary resolution.\n *\n * When the server resolves a Suspense boundary, it sends:\n * <script>$FORMA_SWAP(\"forma-s:0\",\"<resolved html>\")</script>\n *\n * This script replaces the fallback placeholder with the resolved content.\n */\n\n/**\n * Returns the client-side swap script as a string, ready to be injected\n * into the HTML stream (typically right after <body> or before first Suspense).\n */\nexport function getSwapScript(): string {\n return `<script>\nfunction $FORMA_SWAP(id,html){\n var el=document.getElementById(id);\n if(el){\n var tpl=document.createElement('template');\n tpl.innerHTML=html;\n el.replaceWith(tpl.content);\n }\n}\n</script>`;\n}\n\n/**\n * Returns a script tag that swaps a specific Suspense boundary's content.\n * Called when an async resource resolves during streaming.\n */\nexport function getSwapTag(id: string, html: string): string {\n // Escape </script> in the HTML to prevent breaking out of the script tag\n const escaped = html.replace(/<\\/script>/gi, '<\\\\/script>');\n return `<script>$FORMA_SWAP(\"${id}\",\"${escaped.replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n')}\")</script>`;\n}\n","/**\n * FormaJS SSR - Streaming Renderer\n *\n * Implements true chunked HTML streaming with out-of-order Suspense resolution.\n *\n * How it works:\n * 1. Yields shell HTML immediately (opening tags, head, static content)\n * 2. When a Suspense boundary is encountered:\n * - Yields the fallback HTML with a placeholder: <div id=\"forma-s:N\" style=\"display:contents\">\n * - Continues rendering siblings without blocking\n * - When the async resource resolves, yields a swap script\n * 3. Suspense boundaries resolve independently in any order (out-of-order streaming)\n */\n\nimport { getSwapScript, getSwapTag } from './client-script.js';\n\n// Re-use render helpers from render.ts\n// We need the VNode type and rendering logic\ninterface VNode {\n tag: string;\n props: Record<string, unknown> | null;\n children: unknown[];\n}\n\nfunction isVNode(v: unknown): v is VNode {\n return v != null && typeof v === 'object' && 'tag' in v && 'children' in v;\n}\n\n// Escape functions\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction escapeAttr(str: string): string {\n return str.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\nconst PROP_TO_ATTR: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n tabIndex: 'tabindex',\n};\n\n// ---------------------------------------------------------------------------\n// Suspense boundary tracking\n// ---------------------------------------------------------------------------\n\ninterface PendingBoundary {\n id: string;\n promise: Promise<string>;\n}\n\nlet suspenseCounter = 0;\nlet pendingBoundaries: PendingBoundary[] = [];\n\n// ---------------------------------------------------------------------------\n// Synchronous render to buffer (for non-async content)\n// ---------------------------------------------------------------------------\n\nfunction renderSync(node: unknown, parts: string[]): void {\n if (node == null || node === true || node === false) return;\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n if (typeof node === 'number') { parts.push(String(node)); return; }\n if (typeof node === 'function') { renderSync(node(), parts); return; }\n if (Array.isArray(node)) {\n for (const child of node) renderSync(child, parts);\n return;\n }\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n const attrName = PROP_TO_ATTR[key] ?? key;\n const resolved = typeof value === 'function' ? value() : value;\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n parts.push('>');\n if (props?.['dangerouslySetInnerHTML']) {\n parts.push((props['dangerouslySetInnerHTML'] as { __html: string }).__html);\n } else {\n for (const child of children) renderSync(child, parts);\n }\n parts.push('</', tag, '>');\n return;\n }\n parts.push(escapeHtml(String(node)));\n}\n\n// ---------------------------------------------------------------------------\n// Suspense VNode detection\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense boundary VNode has a special shape:\n * { tag: 'forma-suspense', props: { fallback: VNode }, children: [asyncChildren] }\n *\n * Or it can be created via:\n * shSuspense(fallbackVNode, () => asyncContent)\n */\nexport interface SuspenseVNode {\n tag: 'forma-suspense';\n props: { fallback: unknown };\n children: [() => Promise<unknown>];\n}\n\nfunction isSuspenseVNode(v: unknown): v is SuspenseVNode {\n return isVNode(v) && v.tag === 'forma-suspense';\n}\n\n/**\n * Create a server-side Suspense VNode for streaming SSR.\n */\nexport function shSuspense(fallback: unknown, asyncChildren: () => Promise<unknown>): SuspenseVNode {\n return {\n tag: 'forma-suspense',\n props: { fallback },\n children: [asyncChildren],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Streaming renderer\n// ---------------------------------------------------------------------------\n\nexport interface StreamOptions {\n /** Script to bootstrap the client-side app (e.g. <script src=\"/app.js\"></script>) */\n bootstrapScript?: string;\n /** Whether to inject the $FORMA_SWAP client script (default: true) */\n injectSwapScript?: boolean;\n}\n\n/**\n * Render a FormaJS virtual tree to a stream of HTML chunks.\n *\n * Yields HTML progressively:\n * - Static content is yielded immediately\n * - Suspense boundaries yield their fallback, then resolve later\n * - Resolved Suspense boundaries yield swap scripts\n *\n * Usage:\n * ```ts\n * import { renderToStream, shSuspense, sh } from '@getforma/core/ssr';\n *\n * const stream = renderToStream(\n * sh('html', null,\n * sh('body', null,\n * sh('h1', null, 'Hello'),\n * shSuspense(\n * sh('div', null, 'Loading...'),\n * async () => {\n * const data = await fetchData();\n * return sh('div', null, data.name);\n * },\n * ),\n * ),\n * ),\n * );\n *\n * for await (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport async function* renderToStreamNew(\n node: unknown,\n options?: StreamOptions,\n): AsyncGenerator<string> {\n // Reset state\n suspenseCounter = 0;\n pendingBoundaries = [];\n\n const injectSwap = options?.injectSwapScript !== false;\n\n // Inject the swap script first (if there could be Suspense boundaries)\n if (injectSwap) {\n yield getSwapScript();\n }\n\n // Render the main tree synchronously, collecting Suspense boundaries\n const mainParts: string[] = [];\n renderStreamNode(node, mainParts);\n yield mainParts.join('');\n\n // Yield bootstrap script if provided\n if (options?.bootstrapScript) {\n yield options.bootstrapScript;\n }\n\n // Wait for and yield all pending Suspense boundaries\n // Each resolves independently (out-of-order)\n while (pendingBoundaries.length > 0) {\n const pending = [...pendingBoundaries];\n pendingBoundaries = [];\n\n // Race all pending boundaries — yield each as it resolves\n const results = await Promise.allSettled(\n pending.map(async (boundary) => {\n const html = await boundary.promise;\n return { id: boundary.id, html };\n }),\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n yield getSwapTag(result.value.id, result.value.html);\n }\n // On rejection, the fallback stays visible (no swap)\n }\n }\n}\n\n/**\n * Render a node synchronously. If a Suspense boundary is encountered,\n * render the fallback inline and queue the async resolution.\n */\nfunction renderStreamNode(node: unknown, parts: string[]): void {\n if (node == null || node === true || node === false) return;\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n if (typeof node === 'number') { parts.push(String(node)); return; }\n if (typeof node === 'function') { renderStreamNode(node(), parts); return; }\n if (Array.isArray(node)) {\n for (const child of node) renderStreamNode(child, parts);\n return;\n }\n\n // Suspense boundary: render fallback, queue async resolution\n if (isSuspenseVNode(node)) {\n const id = `forma-s:${suspenseCounter++}`;\n const fallback = node.props.fallback;\n const asyncFn = node.children[0]!;\n\n // Render fallback inside a replaceable container\n parts.push(`<div id=\"${id}\" style=\"display:contents\">`);\n renderSync(fallback, parts);\n parts.push('</div>');\n\n // Queue the async resolution\n const promise = asyncFn().then((resolved: unknown) => {\n const resolvedParts: string[] = [];\n renderSync(resolved, resolvedParts);\n return resolvedParts.join('');\n });\n pendingBoundaries.push({ id, promise });\n return;\n }\n\n // Regular VNode\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (key === 'fallback') continue; // skip internal suspense props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n const attrName = PROP_TO_ATTR[key] ?? key;\n const resolved = typeof value === 'function' ? value() : value;\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n parts.push('>');\n if (props?.['dangerouslySetInnerHTML']) {\n parts.push((props['dangerouslySetInnerHTML'] as { __html: string }).__html);\n } else {\n for (const child of children) renderStreamNode(child, parts);\n }\n parts.push('</', tag, '>');\n return;\n }\n\n parts.push(escapeHtml(String(node)));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/ssr/render.ts","../../src/ssr/ssr-reactive.ts","../../src/ssr/client-script.ts","../../src/ssr/stream.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Void elements that don't have closing tags\nexport const VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// Map of prop names to HTML attribute names\nexport const PROP_TO_ATTR: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n tabIndex: 'tabindex',\n};\n\n// Escape HTML entities\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Escape attribute values\nexport function escapeAttr(str: string): string {\n return str.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nexport interface VNode {\n tag: string;\n props: Record<string, unknown> | null;\n children: unknown[];\n}\n\n/**\n * Server-side hyperscript — creates a virtual node instead of a DOM element.\n */\nexport function sh(tag: string, props?: Record<string, unknown> | null, ...children: unknown[]): VNode {\n return { tag, props: props ?? null, children };\n}\n\n/**\n * Render a FormaJS virtual tree to an HTML string.\n *\n * Uses an array buffer internally to avoid O(n^2) string concatenation\n * for large trees, then joins once at the end.\n *\n * Usage:\n * ```ts\n * import { renderToString, sh } from '@getforma/core/ssr';\n *\n * const html = renderToString(\n * sh('div', { class: 'app' },\n * sh('h1', null, 'Hello SSR!'),\n * sh('p', null, () => count()), // signal getters resolved synchronously\n * )\n * );\n * ```\n */\nexport function renderToString(node: unknown): string {\n const parts: string[] = [];\n renderToBuffer(node, parts);\n return parts.join('');\n}\n\n/**\n * Internal: recursively render into a string array buffer.\n * Avoids O(n^2) string concatenation for large trees.\n */\nfunction renderToBuffer(node: unknown, parts: string[]): void {\n // null/undefined/boolean → empty\n if (node == null || node === true || node === false) return;\n\n // String → escaped text\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n\n // Number → stringified\n if (typeof node === 'number') { parts.push(String(node)); return; }\n\n // Function (signal getter) → resolve and render\n if (typeof node === 'function') { renderToBuffer(node(), parts); return; }\n\n // Array → render each\n if (Array.isArray(node)) {\n for (const child of node) renderToBuffer(child, parts);\n return;\n }\n\n // VNode\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n\n // Render props as attributes\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n // Skip event handlers, refs, and internal props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n\n const attrName = PROP_TO_ATTR[key] ?? key;\n\n // Resolve reactive values\n const resolved = typeof value === 'function' ? value() : value;\n\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n\n parts.push('>');\n\n // dangerouslySetInnerHTML\n if (props?.['dangerouslySetInnerHTML']) {\n const raw = props['dangerouslySetInnerHTML'];\n if (typeof raw === 'object' && raw != null && '__html' in raw) {\n const html = (raw as { __html: unknown }).__html;\n if (typeof html === 'string') {\n parts.push(html);\n } else {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n } else {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n } else {\n // Render children\n for (const child of children) {\n renderToBuffer(child, parts);\n }\n }\n\n parts.push('</', tag, '>');\n return;\n }\n\n // Fallback: stringify\n parts.push(escapeHtml(String(node)));\n}\n\nexport function isVNode(v: unknown): v is VNode {\n return v != null && typeof v === 'object' && 'tag' in v && 'children' in v;\n}\n\n// ---------------------------------------------------------------------------\n// Hydration-aware rendering\n// ---------------------------------------------------------------------------\n\n/** Per-render hydration context — avoids shared module-level counter. */\ninterface HydrationContext {\n id: number;\n}\n\n/**\n * Render a FormaJS virtual tree to an HTML string with hydration markers.\n *\n * Like `renderToString`, but injects comment markers and `data-forma-h`\n * attributes so the client-side `hydrate()` function can adopt existing\n * DOM nodes without re-creating them.\n *\n * Each call creates its own hydration counter, so concurrent calls\n * (e.g. multiple SSR requests in the same process) produce independent,\n * non-overlapping hydration IDs.\n *\n * Marker types:\n * - `data-forma-h=\"N\"` — element boundary (attribute on the element)\n * - `<!--forma-t:N-->` / `<!--/forma-t:N-->` — reactive text boundary\n * - `<!--forma-l:N-->` / `<!--/forma-l:N-->` — list boundary\n *\n * Usage:\n * ```ts\n * import { renderToStringWithHydration, sh } from '@getforma/core/ssr';\n *\n * const html = renderToStringWithHydration(\n * sh('div', { class: 'app' },\n * sh('h1', null, 'Hello SSR!'),\n * sh('p', null, () => count()),\n * )\n * );\n * ```\n */\nexport function renderToStringWithHydration(node: unknown): string {\n const ctx: HydrationContext = { id: 0 };\n const parts: string[] = [];\n renderToBufferHydrated(node, parts, ctx);\n return parts.join('');\n}\n\n/**\n * Internal: recursively render into a string array buffer with hydration markers.\n * The `ctx` object carries the hydration counter so concurrent renders are isolated.\n */\nfunction renderToBufferHydrated(node: unknown, parts: string[], ctx: HydrationContext): void {\n // null/undefined/boolean → empty\n if (node == null || node === true || node === false) return;\n\n // String → escaped text\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n\n // Number → stringified\n if (typeof node === 'number') { parts.push(String(node)); return; }\n\n // Function (signal getter / reactive text) → wrap with text markers\n if (typeof node === 'function') {\n const id = ctx.id++;\n parts.push(`<!--forma-t:${id}-->`);\n renderToBufferHydrated(node(), parts, ctx);\n parts.push(`<!--/forma-t:${id}-->`);\n return;\n }\n\n // Array → wrap with list markers\n if (Array.isArray(node)) {\n const id = ctx.id++;\n parts.push(`<!--forma-l:${id}-->`);\n for (const child of node) renderToBufferHydrated(child, parts, ctx);\n parts.push(`<!--/forma-l:${id}-->`);\n return;\n }\n\n // VNode\n if (isVNode(node)) {\n const id = ctx.id++;\n const { tag, props, children } = node;\n\n // Add hydration data-attribute to element\n parts.push('<', tag, ` data-forma-h=\"${id}\"`);\n\n // Render props as attributes\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n // Skip event handlers, refs, and internal props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n\n const attrName = PROP_TO_ATTR[key] ?? key;\n\n // Resolve reactive values\n const resolved = typeof value === 'function' ? value() : value;\n\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n\n parts.push('>');\n\n // dangerouslySetInnerHTML\n if (props?.['dangerouslySetInnerHTML']) {\n const raw = props['dangerouslySetInnerHTML'];\n if (typeof raw === 'object' && raw != null && '__html' in raw) {\n const html = (raw as { __html: unknown }).__html;\n if (typeof html === 'string') {\n parts.push(html);\n } else {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n } else {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n } else {\n // Render children\n for (const child of children) {\n renderToBufferHydrated(child, parts, ctx);\n }\n }\n\n parts.push('</', tag, '>');\n return;\n }\n\n // Fallback: stringify\n parts.push(escapeHtml(String(node)));\n}\n\n","/**\n * SSR-only signal: just a value holder, no reactivity needed on server.\n */\nexport function ssrSignal<T>(initial: T): [get: () => T, set: (v: T) => void] {\n let value = initial;\n return [() => value, (v) => { value = v; }];\n}\n\nexport function ssrComputed<T>(fn: () => T): () => T {\n return fn; // On server, just call the function directly\n}\n","/**\n * FormaJS SSR - Client Swap Script\n *\n * Tiny inline script injected into the HTML stream that handles\n * out-of-order Suspense boundary resolution.\n *\n * When the server resolves a Suspense boundary, it sends:\n * <script>$FORMA_SWAP(\"forma-s:0\",\"<resolved html>\")</script>\n *\n * This script replaces the fallback placeholder with the resolved content.\n */\n\n/**\n * Returns the client-side swap script as a string, ready to be injected\n * into the HTML stream (typically right after <body> or before first Suspense).\n */\nexport function getSwapScript(): string {\n return `<script>\nfunction $FORMA_SWAP(id,html){\n var el=document.getElementById(id);\n if(el){\n var tpl=document.createElement('template');\n tpl.innerHTML=html;\n el.replaceWith(tpl.content);\n }\n}\n</script>`;\n}\n\n/**\n * Returns a script tag that swaps a specific Suspense boundary's content.\n * Called when an async resource resolves during streaming.\n */\nexport function getSwapTag(id: string, html: string): string {\n // Use JSON.stringify for both arguments to handle all edge cases:\n // backslashes, quotes, newlines, null bytes, </script>, unicode escapes\n return `<script>$FORMA_SWAP(${JSON.stringify(id)},${JSON.stringify(html)})</script>`;\n}\n","/**\n * FormaJS SSR - Streaming Renderer\n *\n * Implements true chunked HTML streaming with out-of-order Suspense resolution.\n *\n * How it works:\n * 1. Yields shell HTML immediately (opening tags, head, static content)\n * 2. When a Suspense boundary is encountered:\n * - Yields the fallback HTML with a placeholder: <div id=\"forma-s:N\" style=\"display:contents\">\n * - Continues rendering siblings without blocking\n * - When the async resource resolves, yields a swap script\n * 3. Suspense boundaries resolve independently in any order (out-of-order streaming)\n */\n\nimport { getSwapScript, getSwapTag } from './client-script.js';\nimport { escapeHtml, escapeAttr, isVNode, VOID_ELEMENTS, PROP_TO_ATTR, type VNode } from './render.js';\n\n// ---------------------------------------------------------------------------\n// Suspense boundary tracking\n// ---------------------------------------------------------------------------\n\ninterface PendingBoundary {\n id: string;\n promise: Promise<string>;\n}\n\n/** Per-render mutable state, scoped to a single renderToStreamNew call. */\ninterface StreamState {\n suspenseCounter: number;\n pendingBoundaries: PendingBoundary[];\n}\n\n// ---------------------------------------------------------------------------\n// Synchronous render to buffer (for non-async content)\n// ---------------------------------------------------------------------------\n\nfunction renderSync(node: unknown, parts: string[]): void {\n if (node == null || node === true || node === false) return;\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n if (typeof node === 'number') { parts.push(String(node)); return; }\n if (typeof node === 'function') { renderSync(node(), parts); return; }\n if (Array.isArray(node)) {\n for (const child of node) renderSync(child, parts);\n return;\n }\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n const attrName = PROP_TO_ATTR[key] ?? key;\n const resolved = typeof value === 'function' ? value() : value;\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n parts.push('>');\n if (props?.['dangerouslySetInnerHTML']) {\n const raw = props['dangerouslySetInnerHTML'];\n if (typeof raw !== 'object' || raw == null || !('__html' in raw) || typeof (raw as { __html: unknown }).__html !== 'string') {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n parts.push((raw as { __html: string }).__html);\n } else {\n for (const child of children) renderSync(child, parts);\n }\n parts.push('</', tag, '>');\n return;\n }\n parts.push(escapeHtml(String(node)));\n}\n\n// ---------------------------------------------------------------------------\n// Suspense VNode detection\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense boundary VNode has a special shape:\n * { tag: 'forma-suspense', props: { fallback: VNode }, children: [asyncChildren] }\n *\n * Or it can be created via:\n * shSuspense(fallbackVNode, () => asyncContent)\n */\nexport interface SuspenseVNode {\n tag: 'forma-suspense';\n props: { fallback: unknown };\n children: [() => Promise<unknown>];\n}\n\nfunction isSuspenseVNode(v: unknown): v is SuspenseVNode {\n return isVNode(v) && v.tag === 'forma-suspense';\n}\n\n/**\n * Create a server-side Suspense VNode for streaming SSR.\n */\nexport function shSuspense(fallback: unknown, asyncChildren: () => Promise<unknown>): SuspenseVNode {\n return {\n tag: 'forma-suspense',\n props: { fallback },\n children: [asyncChildren],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Streaming renderer\n// ---------------------------------------------------------------------------\n\nexport interface StreamOptions {\n /** Script to bootstrap the client-side app (e.g. <script src=\"/app.js\"></script>) */\n bootstrapScript?: string;\n /** Whether to inject the $FORMA_SWAP client script (default: true) */\n injectSwapScript?: boolean;\n}\n\n/**\n * Render a FormaJS virtual tree to a stream of HTML chunks.\n *\n * Yields HTML progressively:\n * - Static content is yielded immediately\n * - Suspense boundaries yield their fallback, then resolve later\n * - Resolved Suspense boundaries yield swap scripts\n *\n * Usage:\n * ```ts\n * import { renderToStream, shSuspense, sh } from '@getforma/core/ssr';\n *\n * const stream = renderToStream(\n * sh('html', null,\n * sh('body', null,\n * sh('h1', null, 'Hello'),\n * shSuspense(\n * sh('div', null, 'Loading...'),\n * async () => {\n * const data = await fetchData();\n * return sh('div', null, data.name);\n * },\n * ),\n * ),\n * ),\n * );\n *\n * for await (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport async function* renderToStreamNew(\n node: unknown,\n options?: StreamOptions,\n): AsyncGenerator<string> {\n // Per-render state — scoped to this generator invocation\n const state: StreamState = {\n suspenseCounter: 0,\n pendingBoundaries: [],\n };\n\n const injectSwap = options?.injectSwapScript !== false;\n\n // Inject the swap script first (if there could be Suspense boundaries)\n if (injectSwap) {\n yield getSwapScript();\n }\n\n // Render the main tree synchronously, collecting Suspense boundaries\n const mainParts: string[] = [];\n renderStreamNode(node, mainParts, state);\n yield mainParts.join('');\n\n // Yield bootstrap script if provided\n if (options?.bootstrapScript) {\n yield options.bootstrapScript;\n }\n\n // Wait for and yield all pending Suspense boundaries\n // Each resolves independently (out-of-order)\n while (state.pendingBoundaries.length > 0) {\n const pending = [...state.pendingBoundaries];\n state.pendingBoundaries = [];\n\n // Race all pending boundaries — yield each as it resolves\n const results = await Promise.allSettled(\n pending.map(async (boundary) => {\n const html = await boundary.promise;\n return { id: boundary.id, html };\n }),\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n yield getSwapTag(result.value.id, result.value.html);\n }\n // On rejection, the fallback stays visible (no swap)\n }\n }\n}\n\n/**\n * Render a node synchronously. If a Suspense boundary is encountered,\n * render the fallback inline and queue the async resolution.\n */\nfunction renderStreamNode(node: unknown, parts: string[], state: StreamState): void {\n if (node == null || node === true || node === false) return;\n if (typeof node === 'string') { parts.push(escapeHtml(node)); return; }\n if (typeof node === 'number') { parts.push(String(node)); return; }\n if (typeof node === 'function') { renderStreamNode(node(), parts, state); return; }\n if (Array.isArray(node)) {\n for (const child of node) renderStreamNode(child, parts, state);\n return;\n }\n\n // Suspense boundary: render fallback, queue async resolution\n if (isSuspenseVNode(node)) {\n const id = `forma-s:${state.suspenseCounter++}`;\n const fallback = node.props.fallback;\n const asyncFn = node.children[0]!;\n\n // Render fallback inside a replaceable container\n parts.push(`<div id=\"${id}\" style=\"display:contents\">`);\n renderSync(fallback, parts);\n parts.push('</div>');\n\n // Queue the async resolution\n const promise = asyncFn().then((resolved: unknown) => {\n const resolvedParts: string[] = [];\n renderSync(resolved, resolvedParts);\n return resolvedParts.join('');\n });\n state.pendingBoundaries.push({ id, promise });\n return;\n }\n\n // Regular VNode\n if (isVNode(node)) {\n const { tag, props, children } = node;\n parts.push('<', tag);\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (key === 'fallback') continue; // skip internal suspense props\n if (key.startsWith('on') || key === 'ref' || key === 'dangerouslySetInnerHTML') continue;\n const attrName = PROP_TO_ATTR[key] ?? key;\n const resolved = typeof value === 'function' ? value() : value;\n if (resolved === true) {\n parts.push(' ', attrName);\n } else if (resolved !== false && resolved != null) {\n parts.push(' ', attrName, '=\"', escapeAttr(String(resolved)), '\"');\n }\n }\n }\n if (VOID_ELEMENTS.has(tag)) { parts.push(' />'); return; }\n parts.push('>');\n if (props?.['dangerouslySetInnerHTML']) {\n const raw = props['dangerouslySetInnerHTML'];\n if (typeof raw !== 'object' || raw == null || !('__html' in raw) || typeof (raw as { __html: unknown }).__html !== 'string') {\n throw new TypeError('dangerouslySetInnerHTML must be { __html: string }');\n }\n parts.push((raw as { __html: string }).__html);\n } else {\n for (const child of children) renderStreamNode(child, parts, state);\n }\n parts.push('</', tag, '>');\n return;\n }\n\n parts.push(escapeHtml(String(node)));\n}\n"]}
|
package/dist/ssr/index.d.cts
CHANGED
|
@@ -33,6 +33,10 @@ declare function renderToString(node: unknown): string;
|
|
|
33
33
|
* attributes so the client-side `hydrate()` function can adopt existing
|
|
34
34
|
* DOM nodes without re-creating them.
|
|
35
35
|
*
|
|
36
|
+
* Each call creates its own hydration counter, so concurrent calls
|
|
37
|
+
* (e.g. multiple SSR requests in the same process) produce independent,
|
|
38
|
+
* non-overlapping hydration IDs.
|
|
39
|
+
*
|
|
36
40
|
* Marker types:
|
|
37
41
|
* - `data-forma-h="N"` — element boundary (attribute on the element)
|
|
38
42
|
* - `<!--forma-t:N-->` / `<!--/forma-t:N-->` — reactive text boundary
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ declare function renderToString(node: unknown): string;
|
|
|
33
33
|
* attributes so the client-side `hydrate()` function can adopt existing
|
|
34
34
|
* DOM nodes without re-creating them.
|
|
35
35
|
*
|
|
36
|
+
* Each call creates its own hydration counter, so concurrent calls
|
|
37
|
+
* (e.g. multiple SSR requests in the same process) produce independent,
|
|
38
|
+
* non-overlapping hydration IDs.
|
|
39
|
+
*
|
|
36
40
|
* Marker types:
|
|
37
41
|
* - `data-forma-h="N"` — element boundary (attribute on the element)
|
|
38
42
|
* - `<!--forma-t:N-->` / `<!--/forma-t:N-->` — reactive text boundary
|