@sigx/server-renderer 0.1.3 → 0.1.5
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/dist/client/hydrate.d.ts +19 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +661 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/registry.d.ts +46 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/types.d.ts +43 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client-directives.d.ts +96 -0
- package/dist/client-directives.d.ts.map +1 -0
- package/dist/index.d.ts +42 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1097 -50
- package/dist/index.js.map +1 -1
- package/dist/server/context.d.ts +118 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +574 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/stream.d.ts +34 -0
- package/dist/server/stream.d.ts.map +1 -0
- package/package.json +21 -6
- package/src/jsx.d.ts +62 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { Fragment, Text, setCurrentInstance, signal } from "@sigx/runtime-core";
|
|
2
|
+
|
|
3
|
+
//#region src/server/context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a new SSR context for rendering
|
|
6
|
+
*/
|
|
7
|
+
function createSSRContext(options = {}) {
|
|
8
|
+
let componentId = 0;
|
|
9
|
+
const componentStack = [];
|
|
10
|
+
const islands = /* @__PURE__ */ new Map();
|
|
11
|
+
const head = [];
|
|
12
|
+
const pendingAsync = [];
|
|
13
|
+
return {
|
|
14
|
+
_componentId: componentId,
|
|
15
|
+
_componentStack: componentStack,
|
|
16
|
+
_islands: islands,
|
|
17
|
+
_head: head,
|
|
18
|
+
_pendingAsync: pendingAsync,
|
|
19
|
+
nextId() {
|
|
20
|
+
return ++componentId;
|
|
21
|
+
},
|
|
22
|
+
pushComponent(id) {
|
|
23
|
+
componentStack.push(id);
|
|
24
|
+
},
|
|
25
|
+
popComponent() {
|
|
26
|
+
return componentStack.pop();
|
|
27
|
+
},
|
|
28
|
+
registerIsland(id, info) {
|
|
29
|
+
islands.set(id, info);
|
|
30
|
+
},
|
|
31
|
+
getIslands() {
|
|
32
|
+
return islands;
|
|
33
|
+
},
|
|
34
|
+
addHead(html) {
|
|
35
|
+
head.push(html);
|
|
36
|
+
},
|
|
37
|
+
getHead() {
|
|
38
|
+
return head.join("\n");
|
|
39
|
+
},
|
|
40
|
+
addPendingAsync(pending) {
|
|
41
|
+
pendingAsync.push(pending);
|
|
42
|
+
},
|
|
43
|
+
getPendingAsync() {
|
|
44
|
+
return pendingAsync;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/server/stream.ts
|
|
51
|
+
/**
|
|
52
|
+
* Streaming SSR renderer with hydration markers
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Creates a tracking signal function that records signal names and values.
|
|
56
|
+
* Used during async setup to capture state for client hydration.
|
|
57
|
+
*/
|
|
58
|
+
function createTrackingSignal(signalMap) {
|
|
59
|
+
let signalIndex = 0;
|
|
60
|
+
return function trackingSignal(initial, name) {
|
|
61
|
+
const key = name ?? `$${signalIndex++}`;
|
|
62
|
+
const sig = signal(initial);
|
|
63
|
+
signalMap.set(key, initial);
|
|
64
|
+
return new Proxy(sig, {
|
|
65
|
+
get(target, prop) {
|
|
66
|
+
if (prop === "value") return target.value;
|
|
67
|
+
return target[prop];
|
|
68
|
+
},
|
|
69
|
+
set(target, prop, newValue) {
|
|
70
|
+
if (prop === "value") {
|
|
71
|
+
target.value = newValue;
|
|
72
|
+
signalMap.set(key, newValue);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
target[prop] = newValue;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Serialize captured signal state for client hydration
|
|
83
|
+
*/
|
|
84
|
+
function serializeSignalState(signalMap) {
|
|
85
|
+
if (signalMap.size === 0) return void 0;
|
|
86
|
+
const state = {};
|
|
87
|
+
for (const [key, value] of signalMap) try {
|
|
88
|
+
JSON.stringify(value);
|
|
89
|
+
state[key] = value;
|
|
90
|
+
} catch {
|
|
91
|
+
console.warn(`SSR: Signal "${key}" has non-serializable value, skipping`);
|
|
92
|
+
}
|
|
93
|
+
return Object.keys(state).length > 0 ? state : void 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if a vnode type is a component (has __setup)
|
|
97
|
+
*/
|
|
98
|
+
function isComponent(type) {
|
|
99
|
+
return typeof type === "function" && "__setup" in type;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates a simple props accessor for SSR (no reactivity needed)
|
|
103
|
+
*/
|
|
104
|
+
function createSSRPropsAccessor(rawProps) {
|
|
105
|
+
let defaults = {};
|
|
106
|
+
const proxy = new Proxy(function propsAccessor() {}, {
|
|
107
|
+
get(_, key) {
|
|
108
|
+
if (typeof key === "symbol") return void 0;
|
|
109
|
+
const value = rawProps[key];
|
|
110
|
+
return value != null ? value : defaults[key];
|
|
111
|
+
},
|
|
112
|
+
apply(_, __, args) {
|
|
113
|
+
if (args[0] && typeof args[0] === "object") defaults = {
|
|
114
|
+
...defaults,
|
|
115
|
+
...args[0]
|
|
116
|
+
};
|
|
117
|
+
return proxy;
|
|
118
|
+
},
|
|
119
|
+
has(_, key) {
|
|
120
|
+
if (typeof key === "symbol") return false;
|
|
121
|
+
return key in rawProps || key in defaults;
|
|
122
|
+
},
|
|
123
|
+
ownKeys() {
|
|
124
|
+
return [...new Set([...Object.keys(rawProps), ...Object.keys(defaults)])];
|
|
125
|
+
},
|
|
126
|
+
getOwnPropertyDescriptor(_, key) {
|
|
127
|
+
if (typeof key === "symbol") return void 0;
|
|
128
|
+
if (key in rawProps || key in defaults) return {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
configurable: true,
|
|
131
|
+
writable: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return proxy;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Detect hydration directive from props
|
|
139
|
+
*/
|
|
140
|
+
function getHydrationDirective(props) {
|
|
141
|
+
if (props["client:load"] !== void 0) return { strategy: "load" };
|
|
142
|
+
if (props["client:idle"] !== void 0) return { strategy: "idle" };
|
|
143
|
+
if (props["client:visible"] !== void 0) return { strategy: "visible" };
|
|
144
|
+
if (props["client:only"] !== void 0) return { strategy: "only" };
|
|
145
|
+
if (props["client:media"] !== void 0) return {
|
|
146
|
+
strategy: "media",
|
|
147
|
+
media: props["client:media"]
|
|
148
|
+
};
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Filter out client directives from props
|
|
153
|
+
*/
|
|
154
|
+
function filterClientDirectives(props) {
|
|
155
|
+
const filtered = {};
|
|
156
|
+
for (const key in props) if (!key.startsWith("client:")) filtered[key] = props[key];
|
|
157
|
+
return filtered;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Serialize props for client hydration (must be JSON-safe)
|
|
161
|
+
*/
|
|
162
|
+
function serializeProps(props) {
|
|
163
|
+
const serialized = {};
|
|
164
|
+
for (const key in props) {
|
|
165
|
+
const value = props[key];
|
|
166
|
+
if (typeof value === "function" || typeof value === "symbol") continue;
|
|
167
|
+
if (key === "children" || key === "key" || key === "ref" || key === "slots") continue;
|
|
168
|
+
if (key.startsWith("on")) continue;
|
|
169
|
+
try {
|
|
170
|
+
JSON.stringify(value);
|
|
171
|
+
serialized[key] = value;
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
174
|
+
return serialized;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Render element to string chunks (generator for streaming)
|
|
178
|
+
*/
|
|
179
|
+
async function* renderToChunks(element, ctx) {
|
|
180
|
+
if (element == null || element === false || element === true) return;
|
|
181
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
182
|
+
yield escapeHtml(String(element));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const vnode = element;
|
|
186
|
+
if (vnode.type === Text) {
|
|
187
|
+
yield escapeHtml(String(vnode.text));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (vnode.type === Fragment) {
|
|
191
|
+
for (const child of vnode.children) yield* renderToChunks(child, ctx);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (isComponent(vnode.type)) {
|
|
195
|
+
const setup = vnode.type.__setup;
|
|
196
|
+
const componentName = vnode.type.__name || "Anonymous";
|
|
197
|
+
const allProps = vnode.props || {};
|
|
198
|
+
const hydration = getHydrationDirective(allProps);
|
|
199
|
+
const { children, slots: slotsFromProps, ...propsData } = filterClientDirectives(allProps);
|
|
200
|
+
const id = ctx.nextId();
|
|
201
|
+
ctx.pushComponent(id);
|
|
202
|
+
yield `<!--$c:${id}-->`;
|
|
203
|
+
const signalMap = /* @__PURE__ */ new Map();
|
|
204
|
+
const shouldTrackState = !!hydration;
|
|
205
|
+
if (hydration) {
|
|
206
|
+
const islandInfo = {
|
|
207
|
+
strategy: hydration.strategy,
|
|
208
|
+
media: hydration.media,
|
|
209
|
+
props: serializeProps(propsData),
|
|
210
|
+
componentId: componentName
|
|
211
|
+
};
|
|
212
|
+
ctx.registerIsland(id, islandInfo);
|
|
213
|
+
yield `<!--$island:${hydration.strategy}:${id}${hydration.media ? `:${hydration.media}` : ""}-->`;
|
|
214
|
+
}
|
|
215
|
+
if (hydration?.strategy === "only") {
|
|
216
|
+
yield `<div data-island="${id}"></div>`;
|
|
217
|
+
yield `<!--/$c:${id}-->`;
|
|
218
|
+
ctx.popComponent();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const slots = {
|
|
222
|
+
default: () => children ? Array.isArray(children) ? children : [children] : [],
|
|
223
|
+
...slotsFromProps
|
|
224
|
+
};
|
|
225
|
+
const signalFn = shouldTrackState ? createTrackingSignal(signalMap) : signal;
|
|
226
|
+
const ssrLoads = [];
|
|
227
|
+
const componentCtx = {
|
|
228
|
+
el: null,
|
|
229
|
+
signal: signalFn,
|
|
230
|
+
props: createSSRPropsAccessor(propsData),
|
|
231
|
+
slots,
|
|
232
|
+
emit: () => {},
|
|
233
|
+
parent: null,
|
|
234
|
+
onMount: () => {},
|
|
235
|
+
onCleanup: () => {},
|
|
236
|
+
expose: () => {},
|
|
237
|
+
renderFn: null,
|
|
238
|
+
update: () => {},
|
|
239
|
+
ssr: {
|
|
240
|
+
load(fn) {
|
|
241
|
+
ssrLoads.push(fn());
|
|
242
|
+
},
|
|
243
|
+
isServer: true,
|
|
244
|
+
isHydrating: false
|
|
245
|
+
},
|
|
246
|
+
_signals: shouldTrackState ? signalMap : void 0,
|
|
247
|
+
_ssrLoads: ssrLoads
|
|
248
|
+
};
|
|
249
|
+
const prev = setCurrentInstance(componentCtx);
|
|
250
|
+
try {
|
|
251
|
+
let renderFn = setup(componentCtx);
|
|
252
|
+
if (renderFn && typeof renderFn.then === "function") renderFn = await renderFn;
|
|
253
|
+
if (ssrLoads.length > 0 && hydration) {
|
|
254
|
+
const deferredRender = (async () => {
|
|
255
|
+
await Promise.all(ssrLoads);
|
|
256
|
+
let html = "";
|
|
257
|
+
if (renderFn) {
|
|
258
|
+
const result = renderFn();
|
|
259
|
+
if (result) html = await renderVNodeToString(result, ctx);
|
|
260
|
+
}
|
|
261
|
+
if (signalMap.size > 0) {
|
|
262
|
+
const state = serializeSignalState(signalMap);
|
|
263
|
+
if (state) {
|
|
264
|
+
const islandInfo$1 = ctx.getIslands().get(id);
|
|
265
|
+
if (islandInfo$1) islandInfo$1.state = state;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return html;
|
|
269
|
+
})();
|
|
270
|
+
const islandInfo = ctx.getIslands().get(id);
|
|
271
|
+
ctx.addPendingAsync({
|
|
272
|
+
id,
|
|
273
|
+
promise: deferredRender,
|
|
274
|
+
signalMap,
|
|
275
|
+
islandInfo
|
|
276
|
+
});
|
|
277
|
+
yield `<div data-async-placeholder="${id}" style="display:contents;">`;
|
|
278
|
+
if (renderFn) {
|
|
279
|
+
const result = renderFn();
|
|
280
|
+
if (result) if (Array.isArray(result)) for (const item of result) yield* renderToChunks(item, ctx);
|
|
281
|
+
else yield* renderToChunks(result, ctx);
|
|
282
|
+
}
|
|
283
|
+
yield `</div>`;
|
|
284
|
+
} else {
|
|
285
|
+
if (ssrLoads.length > 0) await Promise.all(ssrLoads);
|
|
286
|
+
if (shouldTrackState && signalMap.size > 0) {
|
|
287
|
+
const state = serializeSignalState(signalMap);
|
|
288
|
+
if (state) {
|
|
289
|
+
const islandInfo = ctx.getIslands().get(id);
|
|
290
|
+
if (islandInfo) islandInfo.state = state;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (renderFn) {
|
|
294
|
+
const result = renderFn();
|
|
295
|
+
if (result) if (Array.isArray(result)) for (const item of result) yield* renderToChunks(item, ctx);
|
|
296
|
+
else yield* renderToChunks(result, ctx);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
console.error(`Error rendering component ${componentName}:`, e);
|
|
301
|
+
} finally {
|
|
302
|
+
setCurrentInstance(prev || null);
|
|
303
|
+
}
|
|
304
|
+
yield `<!--/$c:${id}-->`;
|
|
305
|
+
ctx.popComponent();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (typeof vnode.type === "string") {
|
|
309
|
+
const tagName = vnode.type;
|
|
310
|
+
let props = "";
|
|
311
|
+
for (const key in vnode.props) {
|
|
312
|
+
const value = vnode.props[key];
|
|
313
|
+
if (key === "children" || key === "key" || key === "ref") continue;
|
|
314
|
+
if (key.startsWith("client:")) continue;
|
|
315
|
+
if (key === "style") {
|
|
316
|
+
const styleString = typeof value === "object" ? Object.entries(value).map(([k, v]) => `${camelToKebab(k)}:${v}`).join(";") : String(value);
|
|
317
|
+
props += ` style="${escapeHtml(styleString)}"`;
|
|
318
|
+
} else if (key === "className") props += ` class="${escapeHtml(String(value))}"`;
|
|
319
|
+
else if (key.startsWith("on")) {} else if (value === true) props += ` ${key}`;
|
|
320
|
+
else if (value !== false && value != null) props += ` ${key}="${escapeHtml(String(value))}"`;
|
|
321
|
+
}
|
|
322
|
+
if ([
|
|
323
|
+
"area",
|
|
324
|
+
"base",
|
|
325
|
+
"br",
|
|
326
|
+
"col",
|
|
327
|
+
"embed",
|
|
328
|
+
"hr",
|
|
329
|
+
"img",
|
|
330
|
+
"input",
|
|
331
|
+
"link",
|
|
332
|
+
"meta",
|
|
333
|
+
"param",
|
|
334
|
+
"source",
|
|
335
|
+
"track",
|
|
336
|
+
"wbr"
|
|
337
|
+
].includes(tagName)) {
|
|
338
|
+
yield `<${tagName}${props}>`;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
yield `<${tagName}${props}>`;
|
|
342
|
+
let prevWasText = false;
|
|
343
|
+
for (const child of vnode.children) {
|
|
344
|
+
const isText = isTextContent(child);
|
|
345
|
+
if (isText && prevWasText) yield "<!--t-->";
|
|
346
|
+
yield* renderToChunks(child, ctx);
|
|
347
|
+
prevWasText = isText;
|
|
348
|
+
}
|
|
349
|
+
yield `</${tagName}>`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Check if element will render as text content
|
|
354
|
+
*/
|
|
355
|
+
function isTextContent(element) {
|
|
356
|
+
if (element == null || element === false || element === true) return false;
|
|
357
|
+
if (typeof element === "string" || typeof element === "number") return true;
|
|
358
|
+
return element.type === Text;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Helper to render a VNode to string (for deferred async content)
|
|
362
|
+
*/
|
|
363
|
+
async function renderVNodeToString(element, ctx) {
|
|
364
|
+
let result = "";
|
|
365
|
+
for await (const chunk of renderToChunks(element, ctx)) result += chunk;
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Render JSX element to a ReadableStream with streaming async support
|
|
370
|
+
*/
|
|
371
|
+
function renderToStream(element, context) {
|
|
372
|
+
const ctx = context || createSSRContext();
|
|
373
|
+
return new ReadableStream({ async start(controller) {
|
|
374
|
+
try {
|
|
375
|
+
for await (const chunk of renderToChunks(element, ctx)) controller.enqueue(chunk);
|
|
376
|
+
const pendingAsync = ctx.getPendingAsync();
|
|
377
|
+
if (pendingAsync.length > 0) {
|
|
378
|
+
controller.enqueue(generateStreamingScript());
|
|
379
|
+
await Promise.all(pendingAsync.map(async (pending) => {
|
|
380
|
+
try {
|
|
381
|
+
const html = await pending.promise;
|
|
382
|
+
const state = serializeSignalState(pending.signalMap);
|
|
383
|
+
if (state) pending.islandInfo.state = state;
|
|
384
|
+
controller.enqueue(generateReplacementScript(pending.id, html, state));
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(`Error streaming async component ${pending.id}:`, error);
|
|
387
|
+
controller.enqueue(generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0));
|
|
388
|
+
}
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
if (ctx.getIslands().size > 0) controller.enqueue(generateHydrationScript(ctx));
|
|
392
|
+
controller.enqueue(`<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));<\/script>`);
|
|
393
|
+
controller.close();
|
|
394
|
+
} catch (error) {
|
|
395
|
+
controller.error(error);
|
|
396
|
+
}
|
|
397
|
+
} });
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Render with callbacks for fine-grained streaming control.
|
|
401
|
+
* This allows the server to inject scripts between shell and async content.
|
|
402
|
+
*/
|
|
403
|
+
async function renderToStreamWithCallbacks(element, callbacks, context) {
|
|
404
|
+
const ctx = context || createSSRContext();
|
|
405
|
+
try {
|
|
406
|
+
let shellHtml = "";
|
|
407
|
+
for await (const chunk of renderToChunks(element, ctx)) shellHtml += chunk;
|
|
408
|
+
const pendingAsync = ctx.getPendingAsync();
|
|
409
|
+
const syncIslandIds = /* @__PURE__ */ new Set();
|
|
410
|
+
const pendingAsyncIds = new Set(pendingAsync.map((p) => p.id));
|
|
411
|
+
ctx.getIslands().forEach((_, id) => {
|
|
412
|
+
if (!pendingAsyncIds.has(id)) syncIslandIds.add(id);
|
|
413
|
+
});
|
|
414
|
+
if (syncIslandIds.size > 0) shellHtml += generateSyncHydrationScript(ctx, syncIslandIds);
|
|
415
|
+
if (pendingAsync.length > 0) shellHtml += `<script>window.__SIGX_PENDING_ISLANDS__ = {};<\/script>`;
|
|
416
|
+
shellHtml += `<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));<\/script>`;
|
|
417
|
+
callbacks.onShellReady(shellHtml);
|
|
418
|
+
if (pendingAsync.length > 0) {
|
|
419
|
+
callbacks.onAsyncChunk(generateStreamingScript());
|
|
420
|
+
await Promise.all(pendingAsync.map(async (pending) => {
|
|
421
|
+
try {
|
|
422
|
+
const html = await pending.promise;
|
|
423
|
+
const state = serializeSignalState(pending.signalMap);
|
|
424
|
+
if (state) pending.islandInfo.state = state;
|
|
425
|
+
callbacks.onAsyncChunk(generateReplacementScriptWithIsland(pending.id, html, pending.islandInfo));
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error(`Error streaming async component ${pending.id}:`, error);
|
|
428
|
+
callbacks.onAsyncChunk(generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0));
|
|
429
|
+
}
|
|
430
|
+
}));
|
|
431
|
+
callbacks.onAsyncChunk(`<script>window.dispatchEvent(new Event('sigx:async-complete'));<\/script>`);
|
|
432
|
+
}
|
|
433
|
+
callbacks.onComplete();
|
|
434
|
+
} catch (error) {
|
|
435
|
+
callbacks.onError(error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Render JSX element to string (convenience wrapper around stream)
|
|
440
|
+
* For renderToString, we wait for all async components to complete,
|
|
441
|
+
* then include the replacement scripts inline so the final HTML is complete.
|
|
442
|
+
*/
|
|
443
|
+
async function renderToString(element, context) {
|
|
444
|
+
const ctx = context || createSSRContext();
|
|
445
|
+
let result = "";
|
|
446
|
+
for await (const chunk of renderToChunks(element, ctx)) result += chunk;
|
|
447
|
+
const pendingAsync = ctx.getPendingAsync();
|
|
448
|
+
if (pendingAsync.length > 0) {
|
|
449
|
+
result += generateStreamingScript();
|
|
450
|
+
await Promise.all(pendingAsync.map(async (pending) => {
|
|
451
|
+
try {
|
|
452
|
+
const html = await pending.promise;
|
|
453
|
+
const state = serializeSignalState(pending.signalMap);
|
|
454
|
+
if (state) pending.islandInfo.state = state;
|
|
455
|
+
result += generateReplacementScript(pending.id, html, state);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(`Error rendering async component ${pending.id}:`, error);
|
|
458
|
+
result += generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0);
|
|
459
|
+
}
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
if (ctx.getIslands().size > 0) result += generateHydrationScript(ctx);
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Generate the streaming replacement script (injected once before any replacements)
|
|
467
|
+
* This script provides the $SIGX_REPLACE function used by replacement chunks
|
|
468
|
+
*/
|
|
469
|
+
function generateStreamingScript() {
|
|
470
|
+
return `
|
|
471
|
+
<script>
|
|
472
|
+
window.$SIGX_REPLACE = function(id, html, state) {
|
|
473
|
+
var placeholder = document.querySelector('[data-async-placeholder="' + id + '"]');
|
|
474
|
+
if (placeholder) {
|
|
475
|
+
// Create a template to parse the HTML
|
|
476
|
+
var template = document.createElement('template');
|
|
477
|
+
template.innerHTML = html;
|
|
478
|
+
|
|
479
|
+
// Replace placeholder content
|
|
480
|
+
placeholder.innerHTML = '';
|
|
481
|
+
while (template.content.firstChild) {
|
|
482
|
+
placeholder.appendChild(template.content.firstChild);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Update island state in the hydration data
|
|
486
|
+
if (state) {
|
|
487
|
+
var dataScript = document.getElementById('__SIGX_ISLANDS__');
|
|
488
|
+
if (dataScript) {
|
|
489
|
+
try {
|
|
490
|
+
var data = JSON.parse(dataScript.textContent || '{}');
|
|
491
|
+
if (data[id]) {
|
|
492
|
+
data[id].state = state;
|
|
493
|
+
dataScript.textContent = JSON.stringify(data);
|
|
494
|
+
}
|
|
495
|
+
} catch(e) {}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Dispatch event for hydration to pick up
|
|
500
|
+
placeholder.dispatchEvent(new CustomEvent('sigx:async-ready', { bubbles: true, detail: { id: id, state: state } }));
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
<\/script>`;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Generate a replacement script for a specific async component
|
|
507
|
+
*/
|
|
508
|
+
function generateReplacementScript(id, html, state) {
|
|
509
|
+
return `<script>$SIGX_REPLACE(${id}, ${JSON.stringify(html)}, ${state ? JSON.stringify(state) : "null"});<\/script>`;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Generate a replacement script that also includes island data for async component
|
|
513
|
+
*/
|
|
514
|
+
function generateReplacementScriptWithIsland(id, html, islandInfo) {
|
|
515
|
+
const escapedHtml = JSON.stringify(html);
|
|
516
|
+
return `<script>
|
|
517
|
+
(function() {
|
|
518
|
+
// Add island data to the existing hydration data
|
|
519
|
+
var dataScript = document.getElementById('__SIGX_ISLANDS__');
|
|
520
|
+
if (dataScript) {
|
|
521
|
+
try {
|
|
522
|
+
var data = JSON.parse(dataScript.textContent || '{}');
|
|
523
|
+
data[${id}] = ${JSON.stringify(islandInfo)};
|
|
524
|
+
dataScript.textContent = JSON.stringify(data);
|
|
525
|
+
} catch(e) { console.error('Failed to update island data:', e); }
|
|
526
|
+
}
|
|
527
|
+
// Replace the placeholder content
|
|
528
|
+
$SIGX_REPLACE(${id}, ${escapedHtml}, ${islandInfo.state ? JSON.stringify(islandInfo.state) : "null"});
|
|
529
|
+
})();
|
|
530
|
+
<\/script>`;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Generate hydration script for sync islands only
|
|
534
|
+
*/
|
|
535
|
+
function generateSyncHydrationScript(ctx, syncIslandIds) {
|
|
536
|
+
const islands = ctx.getIslands();
|
|
537
|
+
const islandData = {};
|
|
538
|
+
islands.forEach((info, id) => {
|
|
539
|
+
if (syncIslandIds.has(id)) islandData[id] = info;
|
|
540
|
+
});
|
|
541
|
+
if (Object.keys(islandData).length === 0) return "";
|
|
542
|
+
return `
|
|
543
|
+
<script type="application/json" id="__SIGX_ISLANDS__">${JSON.stringify(islandData)}<\/script>`;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Generate the hydration bootstrap script
|
|
547
|
+
*/
|
|
548
|
+
function generateHydrationScript(ctx) {
|
|
549
|
+
const islands = ctx.getIslands();
|
|
550
|
+
if (islands.size === 0) return "";
|
|
551
|
+
const islandData = {};
|
|
552
|
+
islands.forEach((info, id) => {
|
|
553
|
+
islandData[id] = info;
|
|
554
|
+
});
|
|
555
|
+
return `
|
|
556
|
+
<script type="application/json" id="__SIGX_ISLANDS__">${JSON.stringify(islandData)}<\/script>`;
|
|
557
|
+
}
|
|
558
|
+
const ESCAPE = {
|
|
559
|
+
"&": "&",
|
|
560
|
+
"<": "<",
|
|
561
|
+
">": ">",
|
|
562
|
+
"\"": """,
|
|
563
|
+
"'": "'"
|
|
564
|
+
};
|
|
565
|
+
function escapeHtml(s) {
|
|
566
|
+
return s.replace(/[&<>"']/g, (c) => ESCAPE[c]);
|
|
567
|
+
}
|
|
568
|
+
function camelToKebab(str) {
|
|
569
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
//#endregion
|
|
573
|
+
export { createSSRContext, renderToStream, renderToStreamWithCallbacks, renderToString };
|
|
574
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["componentStack: number[]","head: string[]","pendingAsync: PendingAsyncComponent[]","state: Record<string, any>","defaults: Partial<TProps>","filtered: Record<string, any>","serialized: Record<string, any>","islandInfo: IslandInfo","slots: SlotsObject<any>","ssrLoads: Promise<void>[]","componentCtx: ComponentSetupContext","islandInfo","islandData: Record<number, IslandInfo>","ESCAPE: Record<string, string>"],"sources":["../../src/server/context.ts","../../src/server/stream.ts"],"sourcesContent":["/**\r\n * SSR Context - tracks component boundaries and hydration markers during rendering\r\n */\r\n\r\nexport interface SSRContextOptions {\r\n /**\r\n * Enable streaming mode (default: true)\r\n */\r\n streaming?: boolean;\r\n}\r\n\r\nexport interface RenderOptions {\r\n /**\r\n * Custom SSR context (created automatically if not provided)\r\n */\r\n context?: SSRContext;\r\n}\r\n\r\nexport interface SSRContext {\r\n /**\r\n * Unique ID counter for component markers\r\n */\r\n _componentId: number;\r\n\r\n /**\r\n * Stack of component IDs for nested tracking\r\n */\r\n _componentStack: number[];\r\n\r\n /**\r\n * Registered islands and their hydration strategies\r\n */\r\n _islands: Map<number, IslandInfo>;\r\n\r\n /**\r\n * Collected head elements (scripts, styles, etc.)\r\n */\r\n _head: string[];\r\n\r\n /**\r\n * Pending async components for streaming\r\n */\r\n _pendingAsync: PendingAsyncComponent[];\r\n\r\n /**\r\n * Generate next component ID\r\n */\r\n nextId(): number;\r\n\r\n /**\r\n * Push a component onto the stack\r\n */\r\n pushComponent(id: number): void;\r\n\r\n /**\r\n * Pop the current component from stack\r\n */\r\n popComponent(): number | undefined;\r\n\r\n /**\r\n * Register an island for selective hydration\r\n */\r\n registerIsland(id: number, info: IslandInfo): void;\r\n\r\n /**\r\n * Get all registered islands\r\n */\r\n getIslands(): Map<number, IslandInfo>;\r\n\r\n /**\r\n * Add a head element\r\n */\r\n addHead(html: string): void;\r\n\r\n /**\r\n * Get collected head HTML\r\n */\r\n getHead(): string;\r\n\r\n /**\r\n * Register a pending async component for streaming\r\n */\r\n addPendingAsync(pending: PendingAsyncComponent): void;\r\n\r\n /**\r\n * Get all pending async components\r\n */\r\n getPendingAsync(): PendingAsyncComponent[];\r\n}\r\n\r\nexport interface IslandInfo {\r\n /**\r\n * Hydration strategy: 'load' | 'idle' | 'visible' | 'media' | 'only'\r\n */\r\n strategy: HydrationStrategy;\r\n\r\n /**\r\n * Media query for 'media' strategy\r\n */\r\n media?: string;\r\n\r\n /**\r\n * Component props to serialize for client hydration\r\n */\r\n props?: Record<string, any>;\r\n\r\n /**\r\n * Component name/identifier for client\r\n */\r\n componentId?: string;\r\n\r\n /**\r\n * Captured signal state from async setup for client hydration\r\n */\r\n state?: Record<string, any>;\r\n\r\n /**\r\n * Placeholder HTML for streaming async components\r\n */\r\n placeholder?: string;\r\n}\r\n\r\n/**\r\n * Pending async component that will be streamed later\r\n */\r\nexport interface PendingAsyncComponent {\r\n /** Component ID */\r\n id: number;\r\n /** Promise that resolves to rendered HTML */\r\n promise: Promise<string>;\r\n /** Signal state captured during render */\r\n signalMap: Map<string, any>;\r\n /** Island info reference */\r\n islandInfo: IslandInfo;\r\n}\r\n\r\nexport type HydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'only';\r\n\r\n/**\r\n * Create a new SSR context for rendering\r\n */\r\nexport function createSSRContext(options: SSRContextOptions = {}): SSRContext {\r\n let componentId = 0;\r\n const componentStack: number[] = [];\r\n const islands = new Map<number, IslandInfo>();\r\n const head: string[] = [];\r\n const pendingAsync: PendingAsyncComponent[] = [];\r\n\r\n return {\r\n _componentId: componentId,\r\n _componentStack: componentStack,\r\n _islands: islands,\r\n _head: head,\r\n _pendingAsync: pendingAsync,\r\n\r\n nextId() {\r\n return ++componentId;\r\n },\r\n\r\n pushComponent(id: number) {\r\n componentStack.push(id);\r\n },\r\n\r\n popComponent() {\r\n return componentStack.pop();\r\n },\r\n\r\n registerIsland(id: number, info: IslandInfo) {\r\n islands.set(id, info);\r\n },\r\n\r\n getIslands() {\r\n return islands;\r\n },\r\n\r\n addHead(html: string) {\r\n head.push(html);\r\n },\r\n\r\n getHead() {\r\n return head.join('\\n');\r\n },\r\n\r\n addPendingAsync(pending: PendingAsyncComponent) {\r\n pendingAsync.push(pending);\r\n },\r\n\r\n getPendingAsync() {\r\n return pendingAsync;\r\n }\r\n };\r\n}\r\n","/**\r\n * Streaming SSR renderer with hydration markers\r\n */\r\n\r\nimport {\r\n VNode,\r\n Fragment,\r\n JSXElement,\r\n ComponentSetupContext,\r\n setCurrentInstance,\r\n signal,\r\n Text,\r\n SlotsObject,\r\n PropsAccessor\r\n} from '@sigx/runtime-core';\r\nimport { SSRContext, createSSRContext, IslandInfo, HydrationStrategy, PendingAsyncComponent } from './context.js';\r\n\r\n// Client directive props\r\nconst CLIENT_DIRECTIVES = ['client:load', 'client:idle', 'client:visible', 'client:media', 'client:only'] as const;\r\ntype ClientDirective = typeof CLIENT_DIRECTIVES[number];\r\n\r\n/**\r\n * Creates a tracking signal function that records signal names and values.\r\n * Used during async setup to capture state for client hydration.\r\n */\r\nfunction createTrackingSignal(signalMap: Map<string, any>) {\r\n let signalIndex = 0;\r\n\r\n return function trackingSignal<T extends object>(initial: T, name?: string): ReturnType<typeof signal<T>> {\r\n // Generate a stable key for this signal\r\n const key = name ?? `$${signalIndex++}`;\r\n\r\n // Create the real signal\r\n const sig = signal(initial);\r\n\r\n // Capture initial value\r\n signalMap.set(key, initial);\r\n\r\n // Create a proxy that tracks writes\r\n const proxy = new Proxy(sig, {\r\n get(target, prop) {\r\n if (prop === 'value') {\r\n // Return current value from the real signal\r\n return (target as any).value;\r\n }\r\n return (target as any)[prop];\r\n },\r\n set(target, prop, newValue) {\r\n if (prop === 'value') {\r\n // Update the signal and track the new value\r\n (target as any).value = newValue;\r\n signalMap.set(key, newValue);\r\n return true;\r\n }\r\n (target as any)[prop] = newValue;\r\n return true;\r\n }\r\n });\r\n\r\n return proxy as ReturnType<typeof signal<T>>;\r\n };\r\n}\r\n\r\n/**\r\n * Serialize captured signal state for client hydration\r\n */\r\nfunction serializeSignalState(signalMap: Map<string, any>): Record<string, any> | undefined {\r\n if (signalMap.size === 0) return undefined;\r\n\r\n const state: Record<string, any> = {};\r\n for (const [key, value] of signalMap) {\r\n try {\r\n // Test if serializable\r\n JSON.stringify(value);\r\n state[key] = value;\r\n } catch {\r\n // Skip non-serializable values\r\n console.warn(`SSR: Signal \"${key}\" has non-serializable value, skipping`);\r\n }\r\n }\r\n return Object.keys(state).length > 0 ? state : undefined;\r\n}\r\n\r\n/**\r\n * Check if a vnode type is a component (has __setup)\r\n */\r\nfunction isComponent(type: any): type is { __setup: Function; __name?: string; __async?: boolean } {\r\n return typeof type === 'function' && '__setup' in type;\r\n}\r\n\r\n/**\r\n * Creates a simple props accessor for SSR (no reactivity needed)\r\n */\r\nfunction createSSRPropsAccessor<TProps extends Record<string, any>>(rawProps: TProps): PropsAccessor<TProps> {\r\n let defaults: Partial<TProps> = {};\r\n\r\n const handler: ProxyHandler<any> = {\r\n get(_, key: string | symbol) {\r\n if (typeof key === 'symbol') return undefined;\r\n const value = (rawProps as any)[key];\r\n return value != null ? value : (defaults as any)[key];\r\n },\r\n apply(_, __, args: [Partial<TProps>]) {\r\n if (args[0] && typeof args[0] === 'object') {\r\n defaults = { ...defaults, ...args[0] };\r\n }\r\n return proxy;\r\n },\r\n has(_, key: string | symbol) {\r\n if (typeof key === 'symbol') return false;\r\n return key in rawProps || key in defaults;\r\n },\r\n ownKeys() {\r\n return [...new Set([...Object.keys(rawProps), ...Object.keys(defaults)])];\r\n },\r\n getOwnPropertyDescriptor(_, key: string | symbol) {\r\n if (typeof key === 'symbol') return undefined;\r\n if (key in rawProps || key in defaults) {\r\n return { enumerable: true, configurable: true, writable: false };\r\n }\r\n return undefined;\r\n }\r\n };\r\n\r\n const proxy = new Proxy(\r\n function propsAccessor() { } as unknown as PropsAccessor<TProps>,\r\n handler\r\n );\r\n return proxy;\r\n}\r\n\r\n/**\r\n * Detect hydration directive from props\r\n */\r\nfunction getHydrationDirective(props: Record<string, any>): { strategy: HydrationStrategy; media?: string } | null {\r\n if (props['client:load'] !== undefined) return { strategy: 'load' };\r\n if (props['client:idle'] !== undefined) return { strategy: 'idle' };\r\n if (props['client:visible'] !== undefined) return { strategy: 'visible' };\r\n if (props['client:only'] !== undefined) return { strategy: 'only' };\r\n if (props['client:media'] !== undefined) {\r\n return { strategy: 'media', media: props['client:media'] };\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Filter out client directives from props\r\n */\r\nfunction filterClientDirectives(props: Record<string, any>): Record<string, any> {\r\n const filtered: Record<string, any> = {};\r\n for (const key in props) {\r\n if (!key.startsWith('client:')) {\r\n filtered[key] = props[key];\r\n }\r\n }\r\n return filtered;\r\n}\r\n\r\n/**\r\n * Serialize props for client hydration (must be JSON-safe)\r\n */\r\nfunction serializeProps(props: Record<string, any>): Record<string, any> {\r\n const serialized: Record<string, any> = {};\r\n for (const key in props) {\r\n const value = props[key];\r\n // Skip functions, symbols, and internal props\r\n if (typeof value === 'function' || typeof value === 'symbol') continue;\r\n if (key === 'children' || key === 'key' || key === 'ref' || key === 'slots') continue;\r\n if (key.startsWith('on')) continue; // Skip event handlers\r\n\r\n try {\r\n // Test if serializable\r\n JSON.stringify(value);\r\n serialized[key] = value;\r\n } catch {\r\n // Skip non-serializable values\r\n }\r\n }\r\n return serialized;\r\n}\r\n\r\n/**\r\n * Render element to string chunks (generator for streaming)\r\n */\r\nasync function* renderToChunks(element: JSXElement, ctx: SSRContext): AsyncGenerator<string> {\r\n if (element == null || element === false || element === true) {\r\n return;\r\n }\r\n\r\n if (typeof element === 'string' || typeof element === 'number') {\r\n yield escapeHtml(String(element));\r\n return;\r\n }\r\n\r\n const vnode = element as VNode;\r\n\r\n if (vnode.type === Text) {\r\n yield escapeHtml(String(vnode.text));\r\n return;\r\n }\r\n\r\n if (vnode.type === Fragment) {\r\n for (const child of vnode.children) {\r\n yield* renderToChunks(child, ctx);\r\n }\r\n return;\r\n }\r\n\r\n // Handle Components\r\n if (isComponent(vnode.type)) {\r\n const setup = vnode.type.__setup;\r\n const componentName = vnode.type.__name || 'Anonymous';\r\n const allProps = vnode.props || {};\r\n\r\n // Check for hydration directive\r\n const hydration = getHydrationDirective(allProps);\r\n const { children, slots: slotsFromProps, ...propsData } = filterClientDirectives(allProps);\r\n\r\n const id = ctx.nextId();\r\n ctx.pushComponent(id);\r\n\r\n // Emit component start marker\r\n yield `<!--$c:${id}-->`;\r\n\r\n // Track signals for islands that may need state serialization\r\n const signalMap = new Map<string, any>();\r\n const shouldTrackState = !!hydration;\r\n\r\n // If this is an island, register it and add island marker\r\n if (hydration) {\r\n const islandInfo: IslandInfo = {\r\n strategy: hydration.strategy,\r\n media: hydration.media,\r\n props: serializeProps(propsData),\r\n componentId: componentName\r\n };\r\n ctx.registerIsland(id, islandInfo);\r\n\r\n // Add island marker with strategy\r\n yield `<!--$island:${hydration.strategy}:${id}${hydration.media ? `:${hydration.media}` : ''}-->`;\r\n }\r\n\r\n // For client:only, don't render component content (placeholder only)\r\n if (hydration?.strategy === 'only') {\r\n yield `<div data-island=\"${id}\"></div>`;\r\n yield `<!--/$c:${id}-->`;\r\n ctx.popComponent();\r\n return;\r\n }\r\n\r\n // Create slots from children\r\n const slots: SlotsObject<any> = {\r\n default: () => children ? (Array.isArray(children) ? children : [children]) : [],\r\n ...slotsFromProps\r\n };\r\n\r\n // Use tracking signal for async islands, regular signal otherwise\r\n const signalFn = shouldTrackState ? createTrackingSignal(signalMap) : signal;\r\n\r\n // Track SSR loads for this component\r\n const ssrLoads: Promise<void>[] = [];\r\n let ssrLoadResolved = false;\r\n\r\n // Create SSR helper for async data loading\r\n const ssrHelper = {\r\n load(fn: () => Promise<void>): void {\r\n // Queue the async work - will be processed in parallel\r\n ssrLoads.push(fn());\r\n },\r\n isServer: true,\r\n isHydrating: false\r\n };\r\n\r\n const componentCtx: ComponentSetupContext = {\r\n el: null as any,\r\n signal: signalFn as typeof signal,\r\n props: createSSRPropsAccessor(propsData),\r\n slots: slots,\r\n emit: () => { },\r\n parent: null,\r\n onMount: () => { },\r\n onCleanup: () => { },\r\n expose: () => { },\r\n renderFn: null,\r\n update: () => { },\r\n ssr: ssrHelper,\r\n _signals: shouldTrackState ? signalMap : undefined,\r\n _ssrLoads: ssrLoads\r\n };\r\n\r\n const prev = setCurrentInstance(componentCtx);\r\n try {\r\n // Run setup synchronously - it registers ssr.load() callbacks\r\n let renderFn = setup(componentCtx);\r\n\r\n // Support legacy async setup - await if it returns a promise\r\n if (renderFn && typeof (renderFn as any).then === 'function') {\r\n renderFn = await (renderFn as Promise<any>);\r\n }\r\n\r\n // Check if we have pending ssr.load() calls\r\n if (ssrLoads.length > 0 && hydration) {\r\n // NON-BLOCKING: Don't await! Render placeholder, stream content later\r\n\r\n // Create a promise that will render the component after data loads\r\n const deferredRender = (async () => {\r\n await Promise.all(ssrLoads);\r\n ssrLoadResolved = true;\r\n\r\n // Now render the component with loaded data\r\n let html = '';\r\n if (renderFn) {\r\n const result = (renderFn as () => any)();\r\n if (result) {\r\n // Render to string (no streaming for deferred content)\r\n html = await renderVNodeToString(result, ctx);\r\n }\r\n }\r\n\r\n // Capture signal state for hydration\r\n if (signalMap.size > 0) {\r\n const state = serializeSignalState(signalMap);\r\n if (state) {\r\n const islandInfo = ctx.getIslands().get(id);\r\n if (islandInfo) {\r\n islandInfo.state = state;\r\n }\r\n }\r\n }\r\n\r\n return html;\r\n })();\r\n\r\n // Register for streaming later\r\n const islandInfo = ctx.getIslands().get(id)!;\r\n ctx.addPendingAsync({\r\n id,\r\n promise: deferredRender,\r\n signalMap,\r\n islandInfo\r\n });\r\n\r\n // Render placeholder immediately\r\n yield `<div data-async-placeholder=\"${id}\" style=\"display:contents;\">`;\r\n\r\n // Render with initial state (before data loads)\r\n if (renderFn) {\r\n const result = (renderFn as () => any)();\r\n if (result) {\r\n if (Array.isArray(result)) {\r\n for (const item of result) {\r\n yield* renderToChunks(item, ctx);\r\n }\r\n } else {\r\n yield* renderToChunks(result, ctx);\r\n }\r\n }\r\n }\r\n\r\n yield `</div>`;\r\n } else {\r\n // Synchronous component or no ssr.load() calls - await if needed\r\n if (ssrLoads.length > 0) {\r\n await Promise.all(ssrLoads);\r\n }\r\n\r\n // After async loads complete, capture signal state for hydration\r\n if (shouldTrackState && signalMap.size > 0) {\r\n const state = serializeSignalState(signalMap);\r\n if (state) {\r\n // Update the island info with captured state\r\n const islandInfo = ctx.getIslands().get(id);\r\n if (islandInfo) {\r\n islandInfo.state = state;\r\n }\r\n }\r\n }\r\n\r\n if (renderFn) {\r\n const result = (renderFn as () => any)();\r\n if (result) {\r\n if (Array.isArray(result)) {\r\n for (const item of result) {\r\n yield* renderToChunks(item, ctx);\r\n }\r\n } else {\r\n yield* renderToChunks(result, ctx);\r\n }\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n console.error(`Error rendering component ${componentName}:`, e);\r\n } finally {\r\n setCurrentInstance(prev || null);\r\n }\r\n\r\n // Emit component end marker\r\n yield `<!--/$c:${id}-->`;\r\n ctx.popComponent();\r\n return;\r\n }\r\n\r\n // Handle host elements\r\n if (typeof vnode.type === 'string') {\r\n const tagName = vnode.type;\r\n let props = '';\r\n\r\n // Serialize props\r\n for (const key in vnode.props) {\r\n const value = vnode.props[key];\r\n if (key === 'children' || key === 'key' || key === 'ref') continue;\r\n if (key.startsWith('client:')) continue; // Skip client directives\r\n\r\n if (key === 'style') {\r\n const styleString = typeof value === 'object'\r\n ? Object.entries(value).map(([k, v]) => `${camelToKebab(k)}:${v}`).join(';')\r\n : String(value);\r\n props += ` style=\"${escapeHtml(styleString)}\"`;\r\n } else if (key === 'className') {\r\n props += ` class=\"${escapeHtml(String(value))}\"`;\r\n } else if (key.startsWith('on')) {\r\n // Skip event listeners on server\r\n } else if (value === true) {\r\n props += ` ${key}`;\r\n } else if (value !== false && value != null) {\r\n props += ` ${key}=\"${escapeHtml(String(value))}\"`;\r\n }\r\n }\r\n\r\n // Void elements\r\n const voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];\r\n if (voidElements.includes(tagName)) {\r\n yield `<${tagName}${props}>`;\r\n return;\r\n }\r\n\r\n yield `<${tagName}${props}>`;\r\n\r\n // Render children with text boundary markers\r\n // Adjacent text nodes get merged by the browser, so we insert <!--t--> markers\r\n let prevWasText = false;\r\n for (const child of vnode.children) {\r\n const isText = isTextContent(child);\r\n if (isText && prevWasText) {\r\n // Insert marker between adjacent text nodes\r\n yield '<!--t-->';\r\n }\r\n yield* renderToChunks(child, ctx);\r\n prevWasText = isText;\r\n }\r\n\r\n yield `</${tagName}>`;\r\n }\r\n}\r\n\r\n/**\r\n * Check if element will render as text content\r\n */\r\nfunction isTextContent(element: JSXElement): boolean {\r\n if (element == null || element === false || element === true) return false;\r\n if (typeof element === 'string' || typeof element === 'number') return true;\r\n const vnode = element as VNode;\r\n return vnode.type === Text;\r\n}\r\n\r\n/**\r\n * Helper to render a VNode to string (for deferred async content)\r\n */\r\nasync function renderVNodeToString(element: JSXElement, ctx: SSRContext): Promise<string> {\r\n let result = '';\r\n for await (const chunk of renderToChunks(element, ctx)) {\r\n result += chunk;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Render JSX element to a ReadableStream with streaming async support\r\n */\r\nexport function renderToStream(element: JSXElement, context?: SSRContext): ReadableStream<string> {\r\n const ctx = context || createSSRContext();\r\n\r\n return new ReadableStream<string>({\r\n async start(controller) {\r\n try {\r\n // Phase 1: Render the main page (async components get placeholders)\r\n for await (const chunk of renderToChunks(element, ctx)) {\r\n controller.enqueue(chunk);\r\n }\r\n\r\n // Phase 2: Stream async component replacements as they resolve\r\n const pendingAsync = ctx.getPendingAsync();\r\n if (pendingAsync.length > 0) {\r\n // Inject the streaming replacement script\r\n controller.enqueue(generateStreamingScript());\r\n\r\n // Wait for all pending async components and stream their content\r\n await Promise.all(\r\n pendingAsync.map(async (pending) => {\r\n try {\r\n const html = await pending.promise;\r\n\r\n // Get the updated state after data loaded\r\n const state = serializeSignalState(pending.signalMap);\r\n if (state) {\r\n pending.islandInfo.state = state;\r\n }\r\n\r\n // Stream the replacement\r\n controller.enqueue(generateReplacementScript(\r\n pending.id,\r\n html,\r\n state\r\n ));\r\n } catch (error) {\r\n console.error(`Error streaming async component ${pending.id}:`, error);\r\n // Stream error fallback\r\n controller.enqueue(generateReplacementScript(\r\n pending.id,\r\n `<div style=\"color:red;\">Error loading component</div>`,\r\n undefined\r\n ));\r\n }\r\n })\r\n );\r\n }\r\n\r\n // Phase 3: Append the hydration data script (with final state)\r\n if (ctx.getIslands().size > 0) {\r\n controller.enqueue(generateHydrationScript(ctx));\r\n }\r\n\r\n // Phase 4: Signal that streaming is complete - client can now hydrate\r\n controller.enqueue(`<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));</script>`);\r\n\r\n controller.close();\r\n } catch (error) {\r\n controller.error(error);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Streaming callbacks interface\r\n */\r\nexport interface StreamCallbacks {\r\n /** Called when the initial shell (synchronous content) is ready */\r\n onShellReady: (html: string) => void;\r\n /** Called for each async chunk (replacement scripts, hydration data) */\r\n onAsyncChunk: (chunk: string) => void;\r\n /** Called when all streaming is complete */\r\n onComplete: () => void;\r\n /** Called on error */\r\n onError: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Render with callbacks for fine-grained streaming control.\r\n * This allows the server to inject scripts between shell and async content.\r\n */\r\nexport async function renderToStreamWithCallbacks(\r\n element: JSXElement,\r\n callbacks: StreamCallbacks,\r\n context?: SSRContext\r\n): Promise<void> {\r\n const ctx = context || createSSRContext();\r\n\r\n try {\r\n // Phase 1: Render the shell (sync content + async placeholders)\r\n let shellHtml = '';\r\n for await (const chunk of renderToChunks(element, ctx)) {\r\n shellHtml += chunk;\r\n }\r\n\r\n // Phase 2: Include sync island data immediately so sync components can hydrate\r\n // At this point, only sync component islands are registered (async have placeholders)\r\n const pendingAsync = ctx.getPendingAsync();\r\n const syncIslandIds = new Set<number>();\r\n\r\n // Get islands that are NOT pending async (they're sync)\r\n const pendingAsyncIds = new Set(pendingAsync.map(p => p.id));\r\n ctx.getIslands().forEach((_, id) => {\r\n if (!pendingAsyncIds.has(id)) {\r\n syncIslandIds.add(id);\r\n }\r\n });\r\n\r\n // Generate sync islands data script\r\n if (syncIslandIds.size > 0) {\r\n shellHtml += generateSyncHydrationScript(ctx, syncIslandIds);\r\n }\r\n\r\n // If there are pending async, set up the async islands container\r\n if (pendingAsync.length > 0) {\r\n shellHtml += `<script>window.__SIGX_PENDING_ISLANDS__ = {};</script>`;\r\n }\r\n\r\n // Signal that sync hydration can start\r\n shellHtml += `<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));</script>`;\r\n\r\n // Send the shell immediately - this lets scripts start loading and sync components hydrate!\r\n callbacks.onShellReady(shellHtml);\r\n\r\n // Phase 3: Wait for async components and stream replacements\r\n if (pendingAsync.length > 0) {\r\n // Send the streaming replacement script\r\n callbacks.onAsyncChunk(generateStreamingScript());\r\n\r\n // Wait for all pending async components\r\n await Promise.all(\r\n pendingAsync.map(async (pending) => {\r\n try {\r\n const html = await pending.promise;\r\n\r\n const state = serializeSignalState(pending.signalMap);\r\n if (state) {\r\n pending.islandInfo.state = state;\r\n }\r\n\r\n // Include island data with the replacement\r\n callbacks.onAsyncChunk(generateReplacementScriptWithIsland(pending.id, html, pending.islandInfo));\r\n } catch (error) {\r\n console.error(`Error streaming async component ${pending.id}:`, error);\r\n callbacks.onAsyncChunk(generateReplacementScript(\r\n pending.id,\r\n `<div style=\"color:red;\">Error loading component</div>`,\r\n undefined\r\n ));\r\n }\r\n })\r\n );\r\n\r\n // Signal async streaming complete\r\n callbacks.onAsyncChunk(`<script>window.dispatchEvent(new Event('sigx:async-complete'));</script>`);\r\n }\r\n\r\n callbacks.onComplete();\r\n } catch (error) {\r\n callbacks.onError(error as Error);\r\n }\r\n}\r\n\r\n/**\r\n * Render JSX element to string (convenience wrapper around stream)\r\n * For renderToString, we wait for all async components to complete,\r\n * then include the replacement scripts inline so the final HTML is complete.\r\n */\r\nexport async function renderToString(element: JSXElement, context?: SSRContext): Promise<string> {\r\n const ctx = context || createSSRContext();\r\n let result = '';\r\n\r\n // Phase 1: Render main content (async components get placeholders)\r\n for await (const chunk of renderToChunks(element, ctx)) {\r\n result += chunk;\r\n }\r\n\r\n // Phase 2: Wait for pending async components and add replacement scripts\r\n const pendingAsync = ctx.getPendingAsync();\r\n if (pendingAsync.length > 0) {\r\n // Add the streaming replacement script\r\n result += generateStreamingScript();\r\n\r\n // Wait for all pending async components\r\n await Promise.all(\r\n pendingAsync.map(async (pending) => {\r\n try {\r\n const html = await pending.promise;\r\n\r\n // Get the updated state after data loaded\r\n const state = serializeSignalState(pending.signalMap);\r\n if (state) {\r\n pending.islandInfo.state = state;\r\n }\r\n\r\n // Add the replacement script\r\n result += generateReplacementScript(pending.id, html, state);\r\n } catch (error) {\r\n console.error(`Error rendering async component ${pending.id}:`, error);\r\n result += generateReplacementScript(\r\n pending.id,\r\n `<div style=\"color:red;\">Error loading component</div>`,\r\n undefined\r\n );\r\n }\r\n })\r\n );\r\n }\r\n\r\n // Phase 3: Append hydration script with final state\r\n if (ctx.getIslands().size > 0) {\r\n result += generateHydrationScript(ctx);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Generate the streaming replacement script (injected once before any replacements)\r\n * This script provides the $SIGX_REPLACE function used by replacement chunks\r\n */\r\nfunction generateStreamingScript(): string {\r\n return `\r\n<script>\r\nwindow.$SIGX_REPLACE = function(id, html, state) {\r\n var placeholder = document.querySelector('[data-async-placeholder=\"' + id + '\"]');\r\n if (placeholder) {\r\n // Create a template to parse the HTML\r\n var template = document.createElement('template');\r\n template.innerHTML = html;\r\n \r\n // Replace placeholder content\r\n placeholder.innerHTML = '';\r\n while (template.content.firstChild) {\r\n placeholder.appendChild(template.content.firstChild);\r\n }\r\n \r\n // Update island state in the hydration data\r\n if (state) {\r\n var dataScript = document.getElementById('__SIGX_ISLANDS__');\r\n if (dataScript) {\r\n try {\r\n var data = JSON.parse(dataScript.textContent || '{}');\r\n if (data[id]) {\r\n data[id].state = state;\r\n dataScript.textContent = JSON.stringify(data);\r\n }\r\n } catch(e) {}\r\n }\r\n }\r\n \r\n // Dispatch event for hydration to pick up\r\n placeholder.dispatchEvent(new CustomEvent('sigx:async-ready', { bubbles: true, detail: { id: id, state: state } }));\r\n }\r\n};\r\n</script>`;\r\n}\r\n\r\n/**\r\n * Generate a replacement script for a specific async component\r\n */\r\nfunction generateReplacementScript(id: number, html: string, state?: Record<string, any>): string {\r\n // Escape the HTML for embedding in a script\r\n const escapedHtml = JSON.stringify(html);\r\n const stateJson = state ? JSON.stringify(state) : 'null';\r\n\r\n return `<script>$SIGX_REPLACE(${id}, ${escapedHtml}, ${stateJson});</script>`;\r\n}\r\n\r\n/**\r\n * Generate a replacement script that also includes island data for async component\r\n */\r\nfunction generateReplacementScriptWithIsland(id: number, html: string, islandInfo: IslandInfo): string {\r\n const escapedHtml = JSON.stringify(html);\r\n const islandJson = JSON.stringify(islandInfo);\r\n\r\n return `<script>\r\n(function() {\r\n // Add island data to the existing hydration data\r\n var dataScript = document.getElementById('__SIGX_ISLANDS__');\r\n if (dataScript) {\r\n try {\r\n var data = JSON.parse(dataScript.textContent || '{}');\r\n data[${id}] = ${islandJson};\r\n dataScript.textContent = JSON.stringify(data);\r\n } catch(e) { console.error('Failed to update island data:', e); }\r\n }\r\n // Replace the placeholder content\r\n $SIGX_REPLACE(${id}, ${escapedHtml}, ${islandInfo.state ? JSON.stringify(islandInfo.state) : 'null'});\r\n})();\r\n</script>`;\r\n}\r\n\r\n/**\r\n * Generate hydration script for sync islands only\r\n */\r\nfunction generateSyncHydrationScript(ctx: SSRContext, syncIslandIds: Set<number>): string {\r\n const islands = ctx.getIslands();\r\n const islandData: Record<number, IslandInfo> = {};\r\n\r\n islands.forEach((info, id) => {\r\n if (syncIslandIds.has(id)) {\r\n islandData[id] = info;\r\n }\r\n });\r\n\r\n if (Object.keys(islandData).length === 0) return '';\r\n\r\n return `\r\n<script type=\"application/json\" id=\"__SIGX_ISLANDS__\">${JSON.stringify(islandData)}</script>`;\r\n}\r\n\r\n/**\r\n * Generate the hydration bootstrap script\r\n */\r\nfunction generateHydrationScript(ctx: SSRContext): string {\r\n const islands = ctx.getIslands();\r\n if (islands.size === 0) return '';\r\n\r\n const islandData: Record<number, IslandInfo> = {};\r\n islands.forEach((info, id) => {\r\n islandData[id] = info;\r\n });\r\n\r\n // Only output the JSON data - the client entry is responsible for calling hydrateIslands()\r\n // We don't inject a script tag because the browser can't resolve npm package specifiers\r\n // The client bundle (via Vite/Rollup) handles the import resolution\r\n return `\r\n<script type=\"application/json\" id=\"__SIGX_ISLANDS__\">${JSON.stringify(islandData)}</script>`;\r\n}\r\n\r\n// HTML escaping\r\nconst ESCAPE: Record<string, string> = {\r\n '&': '&',\r\n '<': '<',\r\n '>': '>',\r\n '\"': '"',\r\n \"'\": '''\r\n};\r\n\r\nfunction escapeHtml(s: string): string {\r\n return s.replace(/[&<>\"']/g, c => ESCAPE[c]);\r\n}\r\n\r\nfunction camelToKebab(str: string): string {\r\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\r\n}\r\n"],"mappings":"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"}
|