@sigx/server-renderer 0.1.4 → 0.1.6

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.
@@ -0,0 +1,459 @@
1
+ import { Fragment, Text, createPropsAccessor, filterClientDirectives, getHydrationDirective, isComponent, provideAppContext, serializeProps, setCurrentInstance, signal } from "sigx";
2
+ function createSSRContext(options = {}) {
3
+ let componentId = 0;
4
+ const componentStack = [];
5
+ const islands = /* @__PURE__ */ new Map();
6
+ const head = [];
7
+ const pendingAsync = [];
8
+ return {
9
+ _componentId: componentId,
10
+ _componentStack: componentStack,
11
+ _islands: islands,
12
+ _head: head,
13
+ _pendingAsync: pendingAsync,
14
+ nextId() {
15
+ return ++componentId;
16
+ },
17
+ pushComponent(id) {
18
+ componentStack.push(id);
19
+ },
20
+ popComponent() {
21
+ return componentStack.pop();
22
+ },
23
+ registerIsland(id, info) {
24
+ islands.set(id, info);
25
+ },
26
+ getIslands() {
27
+ return islands;
28
+ },
29
+ addHead(html) {
30
+ head.push(html);
31
+ },
32
+ getHead() {
33
+ return head.join("\n");
34
+ },
35
+ addPendingAsync(pending) {
36
+ pendingAsync.push(pending);
37
+ },
38
+ getPendingAsync() {
39
+ return pendingAsync;
40
+ }
41
+ };
42
+ }
43
+ function isApp(input) {
44
+ return input && typeof input === "object" && "_rootComponent" in input && "_context" in input;
45
+ }
46
+ function createTrackingSignal(signalMap) {
47
+ let signalIndex = 0;
48
+ return function trackingSignal(initial, name) {
49
+ const key = name ?? `$${signalIndex++}`;
50
+ const sig = signal(initial);
51
+ signalMap.set(key, initial);
52
+ return new Proxy(sig, {
53
+ get(target, prop) {
54
+ if (prop === "value") return target.value;
55
+ return target[prop];
56
+ },
57
+ set(target, prop, newValue) {
58
+ if (prop === "value") {
59
+ target.value = newValue;
60
+ signalMap.set(key, newValue);
61
+ return true;
62
+ }
63
+ target[prop] = newValue;
64
+ return true;
65
+ }
66
+ });
67
+ };
68
+ }
69
+ function serializeSignalState(signalMap) {
70
+ if (signalMap.size === 0) return void 0;
71
+ const state = {};
72
+ for (const [key, value] of signalMap) try {
73
+ JSON.stringify(value);
74
+ state[key] = value;
75
+ } catch {
76
+ console.warn(`SSR: Signal "${key}" has non-serializable value, skipping`);
77
+ }
78
+ return Object.keys(state).length > 0 ? state : void 0;
79
+ }
80
+ async function* renderToChunks(element, ctx, parentCtx = null, appContext = null) {
81
+ if (element == null || element === false || element === true) return;
82
+ if (typeof element === "string" || typeof element === "number") {
83
+ yield escapeHtml(String(element));
84
+ return;
85
+ }
86
+ const vnode = element;
87
+ if (vnode.type === Text) {
88
+ yield escapeHtml(String(vnode.text));
89
+ return;
90
+ }
91
+ if (vnode.type === Fragment) {
92
+ for (const child of vnode.children) yield* renderToChunks(child, ctx, parentCtx, appContext);
93
+ return;
94
+ }
95
+ if (isComponent(vnode.type)) {
96
+ const setup = vnode.type.__setup;
97
+ const componentName = vnode.type.__name || "Anonymous";
98
+ const allProps = vnode.props || {};
99
+ const hydration = getHydrationDirective(allProps);
100
+ const { children, slots: slotsFromProps, $models: modelsData, ...propsData } = filterClientDirectives(allProps);
101
+ const id = ctx.nextId();
102
+ ctx.pushComponent(id);
103
+ const signalMap = /* @__PURE__ */ new Map();
104
+ const shouldTrackState = !!hydration;
105
+ if (hydration) {
106
+ const islandInfo = {
107
+ strategy: hydration.strategy,
108
+ media: hydration.media,
109
+ props: serializeProps(propsData),
110
+ componentId: componentName
111
+ };
112
+ ctx.registerIsland(id, islandInfo);
113
+ }
114
+ if (hydration?.strategy === "only") {
115
+ yield `<div data-island="${id}"></div>`;
116
+ yield `<!--$c:${id}-->`;
117
+ ctx.popComponent();
118
+ return;
119
+ }
120
+ const slots = {
121
+ default: () => children ? Array.isArray(children) ? children : [children] : [],
122
+ ...slotsFromProps
123
+ };
124
+ const signalFn = shouldTrackState ? createTrackingSignal(signalMap) : signal;
125
+ const ssrLoads = [];
126
+ const componentCtx = {
127
+ el: null,
128
+ signal: signalFn,
129
+ props: createPropsAccessor(propsData),
130
+ slots,
131
+ emit: () => {},
132
+ parent: parentCtx,
133
+ onMounted: () => {},
134
+ onUnmounted: () => {},
135
+ onCreated: () => {},
136
+ onUpdated: () => {},
137
+ expose: () => {},
138
+ renderFn: null,
139
+ update: () => {},
140
+ ssr: {
141
+ load(fn) {
142
+ ssrLoads.push(fn());
143
+ },
144
+ isServer: true,
145
+ isHydrating: false
146
+ },
147
+ _signals: shouldTrackState ? signalMap : void 0,
148
+ _ssrLoads: ssrLoads
149
+ };
150
+ if (!parentCtx && appContext) provideAppContext(componentCtx, appContext);
151
+ const prev = setCurrentInstance(componentCtx);
152
+ try {
153
+ let renderFn = setup(componentCtx);
154
+ if (renderFn && typeof renderFn.then === "function") renderFn = await renderFn;
155
+ if (ssrLoads.length > 0 && hydration) {
156
+ const deferredRender = (async () => {
157
+ await Promise.all(ssrLoads);
158
+ let html = "";
159
+ if (renderFn) {
160
+ const result = renderFn();
161
+ if (result) html = await renderVNodeToString(result, ctx);
162
+ }
163
+ if (signalMap.size > 0) {
164
+ const state = serializeSignalState(signalMap);
165
+ if (state) {
166
+ const islandInfo = ctx.getIslands().get(id);
167
+ if (islandInfo) islandInfo.state = state;
168
+ }
169
+ }
170
+ return html;
171
+ })();
172
+ const islandInfo = ctx.getIslands().get(id);
173
+ ctx.addPendingAsync({
174
+ id,
175
+ promise: deferredRender,
176
+ signalMap,
177
+ islandInfo
178
+ });
179
+ yield `<div data-async-placeholder="${id}" style="display:contents;">`;
180
+ if (renderFn) {
181
+ const result = renderFn();
182
+ if (result) if (Array.isArray(result)) for (const item of result) yield* renderToChunks(item, ctx, componentCtx, appContext);
183
+ else yield* renderToChunks(result, ctx, componentCtx, appContext);
184
+ }
185
+ yield `</div>`;
186
+ } else {
187
+ if (ssrLoads.length > 0) await Promise.all(ssrLoads);
188
+ if (shouldTrackState && signalMap.size > 0) {
189
+ const state = serializeSignalState(signalMap);
190
+ if (state) {
191
+ const islandInfo = ctx.getIslands().get(id);
192
+ if (islandInfo) islandInfo.state = state;
193
+ }
194
+ }
195
+ if (renderFn) {
196
+ const result = renderFn();
197
+ if (result) if (Array.isArray(result)) for (const item of result) yield* renderToChunks(item, ctx, componentCtx, appContext);
198
+ else yield* renderToChunks(result, ctx, componentCtx, appContext);
199
+ }
200
+ }
201
+ } catch (e) {
202
+ console.error(`Error rendering component ${componentName}:`, e);
203
+ } finally {
204
+ setCurrentInstance(prev || null);
205
+ }
206
+ yield `<!--$c:${id}-->`;
207
+ ctx.popComponent();
208
+ return;
209
+ }
210
+ if (typeof vnode.type === "string") {
211
+ const tagName = vnode.type;
212
+ let props = "";
213
+ for (const key in vnode.props) {
214
+ const value = vnode.props[key];
215
+ if (key === "children" || key === "key" || key === "ref") continue;
216
+ if (key.startsWith("client:")) continue;
217
+ if (key === "style") {
218
+ const styleString = typeof value === "object" ? Object.entries(value).map(([k, v]) => `${camelToKebab(k)}:${v}`).join(";") : String(value);
219
+ props += ` style="${escapeHtml(styleString)}"`;
220
+ } else if (key === "className") props += ` class="${escapeHtml(String(value))}"`;
221
+ else if (key.startsWith("on")) {} else if (value === true) props += ` ${key}`;
222
+ else if (value !== false && value != null) props += ` ${key}="${escapeHtml(String(value))}"`;
223
+ }
224
+ if ([
225
+ "area",
226
+ "base",
227
+ "br",
228
+ "col",
229
+ "embed",
230
+ "hr",
231
+ "img",
232
+ "input",
233
+ "link",
234
+ "meta",
235
+ "param",
236
+ "source",
237
+ "track",
238
+ "wbr"
239
+ ].includes(tagName)) {
240
+ yield `<${tagName}${props}>`;
241
+ return;
242
+ }
243
+ yield `<${tagName}${props}>`;
244
+ let prevWasText = false;
245
+ for (const child of vnode.children) {
246
+ const isText = isTextContent(child);
247
+ if (isText && prevWasText) yield "<!--t-->";
248
+ yield* renderToChunks(child, ctx, parentCtx, appContext);
249
+ prevWasText = isText;
250
+ }
251
+ yield `</${tagName}>`;
252
+ }
253
+ }
254
+ function isTextContent(element) {
255
+ if (element == null || element === false || element === true) return false;
256
+ if (typeof element === "string" || typeof element === "number") return true;
257
+ return element.type === Text;
258
+ }
259
+ async function renderVNodeToString(element, ctx, appContext = null) {
260
+ let result = "";
261
+ for await (const chunk of renderToChunks(element, ctx, null, appContext)) result += chunk;
262
+ return result;
263
+ }
264
+ function renderToStream(input, context) {
265
+ const ctx = context || createSSRContext();
266
+ let element;
267
+ let appContext = null;
268
+ if (isApp(input)) {
269
+ element = input._rootComponent;
270
+ appContext = input._context;
271
+ } else element = input;
272
+ return new ReadableStream({ async start(controller) {
273
+ try {
274
+ for await (const chunk of renderToChunks(element, ctx, null, appContext)) controller.enqueue(chunk);
275
+ const pendingAsync = ctx.getPendingAsync();
276
+ if (pendingAsync.length > 0) {
277
+ controller.enqueue(generateStreamingScript());
278
+ await Promise.all(pendingAsync.map(async (pending) => {
279
+ try {
280
+ const html = await pending.promise;
281
+ const state = serializeSignalState(pending.signalMap);
282
+ if (state) pending.islandInfo.state = state;
283
+ controller.enqueue(generateReplacementScript(pending.id, html, state));
284
+ } catch (error) {
285
+ console.error(`Error streaming async component ${pending.id}:`, error);
286
+ controller.enqueue(generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0));
287
+ }
288
+ }));
289
+ }
290
+ if (ctx.getIslands().size > 0) controller.enqueue(generateHydrationScript(ctx));
291
+ controller.enqueue(`<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));<\/script>`);
292
+ controller.close();
293
+ } catch (error) {
294
+ controller.error(error);
295
+ }
296
+ } });
297
+ }
298
+ async function renderToStreamWithCallbacks(input, callbacks, context) {
299
+ const ctx = context || createSSRContext();
300
+ let element;
301
+ let appContext = null;
302
+ if (isApp(input)) {
303
+ element = input._rootComponent;
304
+ appContext = input._context;
305
+ } else element = input;
306
+ try {
307
+ let shellHtml = "";
308
+ for await (const chunk of renderToChunks(element, ctx, null, appContext)) shellHtml += chunk;
309
+ const pendingAsync = ctx.getPendingAsync();
310
+ const syncIslandIds = /* @__PURE__ */ new Set();
311
+ const pendingAsyncIds = new Set(pendingAsync.map((p) => p.id));
312
+ ctx.getIslands().forEach((_, id) => {
313
+ if (!pendingAsyncIds.has(id)) syncIslandIds.add(id);
314
+ });
315
+ if (syncIslandIds.size > 0) shellHtml += generateSyncHydrationScript(ctx, syncIslandIds);
316
+ if (pendingAsync.length > 0) shellHtml += `<script>window.__SIGX_PENDING_ISLANDS__ = {};<\/script>`;
317
+ shellHtml += `<script>window.__SIGX_STREAMING_COMPLETE__ = true; window.dispatchEvent(new Event('sigx:ready'));<\/script>`;
318
+ callbacks.onShellReady(shellHtml);
319
+ if (pendingAsync.length > 0) {
320
+ callbacks.onAsyncChunk(generateStreamingScript());
321
+ await Promise.all(pendingAsync.map(async (pending) => {
322
+ try {
323
+ const html = await pending.promise;
324
+ const state = serializeSignalState(pending.signalMap);
325
+ if (state) pending.islandInfo.state = state;
326
+ callbacks.onAsyncChunk(generateReplacementScriptWithIsland(pending.id, html, pending.islandInfo));
327
+ } catch (error) {
328
+ console.error(`Error streaming async component ${pending.id}:`, error);
329
+ callbacks.onAsyncChunk(generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0));
330
+ }
331
+ }));
332
+ callbacks.onAsyncChunk(`<script>window.dispatchEvent(new Event('sigx:async-complete'));<\/script>`);
333
+ }
334
+ callbacks.onComplete();
335
+ } catch (error) {
336
+ callbacks.onError(error);
337
+ }
338
+ }
339
+ async function renderToString(input, context) {
340
+ const ctx = context || createSSRContext();
341
+ let element;
342
+ let appContext = null;
343
+ if (isApp(input)) {
344
+ element = input._rootComponent;
345
+ appContext = input._context;
346
+ } else element = input;
347
+ let result = "";
348
+ for await (const chunk of renderToChunks(element, ctx, null, appContext)) result += chunk;
349
+ const pendingAsync = ctx.getPendingAsync();
350
+ if (pendingAsync.length > 0) {
351
+ result += generateStreamingScript();
352
+ await Promise.all(pendingAsync.map(async (pending) => {
353
+ try {
354
+ const html = await pending.promise;
355
+ const state = serializeSignalState(pending.signalMap);
356
+ if (state) pending.islandInfo.state = state;
357
+ result += generateReplacementScript(pending.id, html, state);
358
+ } catch (error) {
359
+ console.error(`Error rendering async component ${pending.id}:`, error);
360
+ result += generateReplacementScript(pending.id, `<div style="color:red;">Error loading component</div>`, void 0);
361
+ }
362
+ }));
363
+ }
364
+ if (ctx.getIslands().size > 0) result += generateHydrationScript(ctx);
365
+ return result;
366
+ }
367
+ function generateStreamingScript() {
368
+ return `
369
+ <script>
370
+ window.$SIGX_REPLACE = function(id, html, state) {
371
+ var placeholder = document.querySelector('[data-async-placeholder="' + id + '"]');
372
+ if (placeholder) {
373
+ // Create a template to parse the HTML
374
+ var template = document.createElement('template');
375
+ template.innerHTML = html;
376
+
377
+ // Replace placeholder content
378
+ placeholder.innerHTML = '';
379
+ while (template.content.firstChild) {
380
+ placeholder.appendChild(template.content.firstChild);
381
+ }
382
+
383
+ // Update island state in the hydration data
384
+ if (state) {
385
+ var dataScript = document.getElementById('__SIGX_ISLANDS__');
386
+ if (dataScript) {
387
+ try {
388
+ var data = JSON.parse(dataScript.textContent || '{}');
389
+ if (data[id]) {
390
+ data[id].state = state;
391
+ dataScript.textContent = JSON.stringify(data);
392
+ }
393
+ } catch(e) {}
394
+ }
395
+ }
396
+
397
+ // Dispatch event for hydration to pick up
398
+ placeholder.dispatchEvent(new CustomEvent('sigx:async-ready', { bubbles: true, detail: { id: id, state: state } }));
399
+ }
400
+ };
401
+ <\/script>`;
402
+ }
403
+ function generateReplacementScript(id, html, state) {
404
+ return `<script>$SIGX_REPLACE(${id}, ${JSON.stringify(html)}, ${state ? JSON.stringify(state) : "null"});<\/script>`;
405
+ }
406
+ function generateReplacementScriptWithIsland(id, html, islandInfo) {
407
+ const escapedHtml = JSON.stringify(html);
408
+ return `<script>
409
+ (function() {
410
+ // Add island data to the existing hydration data
411
+ var dataScript = document.getElementById('__SIGX_ISLANDS__');
412
+ if (dataScript) {
413
+ try {
414
+ var data = JSON.parse(dataScript.textContent || '{}');
415
+ data[${id}] = ${JSON.stringify(islandInfo)};
416
+ dataScript.textContent = JSON.stringify(data);
417
+ } catch(e) { console.error('Failed to update island data:', e); }
418
+ }
419
+ // Replace the placeholder content
420
+ $SIGX_REPLACE(${id}, ${escapedHtml}, ${islandInfo.state ? JSON.stringify(islandInfo.state) : "null"});
421
+ })();
422
+ <\/script>`;
423
+ }
424
+ function generateSyncHydrationScript(ctx, syncIslandIds) {
425
+ const islands = ctx.getIslands();
426
+ const islandData = {};
427
+ islands.forEach((info, id) => {
428
+ if (syncIslandIds.has(id)) islandData[id] = info;
429
+ });
430
+ if (Object.keys(islandData).length === 0) return "";
431
+ return `
432
+ <script type="application/json" id="__SIGX_ISLANDS__">${JSON.stringify(islandData)}<\/script>`;
433
+ }
434
+ function generateHydrationScript(ctx) {
435
+ const islands = ctx.getIslands();
436
+ if (islands.size === 0) return "";
437
+ const islandData = {};
438
+ islands.forEach((info, id) => {
439
+ islandData[id] = info;
440
+ });
441
+ return `
442
+ <script type="application/json" id="__SIGX_ISLANDS__">${JSON.stringify(islandData)}<\/script>`;
443
+ }
444
+ var ESCAPE = {
445
+ "&": "&amp;",
446
+ "<": "&lt;",
447
+ ">": "&gt;",
448
+ "\"": "&quot;",
449
+ "'": "&#39;"
450
+ };
451
+ function escapeHtml(s) {
452
+ return s.replace(/[&<>"']/g, (c) => ESCAPE[c]);
453
+ }
454
+ function camelToKebab(str) {
455
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
456
+ }
457
+ export { createSSRContext as i, renderToStreamWithCallbacks as n, renderToString as r, renderToStream as t };
458
+
459
+ //# sourceMappingURL=server-BCOJt2Bi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-BCOJt2Bi.js","names":[],"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 getCurrentInstance,\r\n signal,\r\n Signal,\r\n Text,\r\n SlotsObject,\r\n PropsAccessor,\r\n isComponent,\r\n createPropsAccessor,\r\n getHydrationDirective,\r\n filterClientDirectives,\r\n serializeProps,\r\n provideAppContext\r\n} from 'sigx';\r\nimport type { App, AppContext } from 'sigx';\r\nimport { SSRContext, createSSRContext, IslandInfo, HydrationStrategy, PendingAsyncComponent } from './context.js';\r\n\r\n/**\r\n * Check if the input is an App instance (created via defineApp)\r\n */\r\nfunction isApp(input: any): input is App<any> {\r\n return input && typeof input === 'object' && '_rootComponent' in input && '_context' in input;\r\n}\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): 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 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// getHydrationDirective, filterClientDirectives, and serializeProps are imported from sigx\r\n\r\n/**\r\n * Render element to string chunks (generator for streaming)\r\n * @param element - The JSX element to render\r\n * @param ctx - The SSR context for tracking state\r\n * @param parentCtx - The parent component context for provide/inject\r\n * @param appContext - The app context for app-level provides (from defineApp)\r\n */\r\nasync function* renderToChunks(\r\n element: JSXElement,\r\n ctx: SSRContext,\r\n parentCtx: ComponentSetupContext | null = null,\r\n appContext: AppContext | null = null\r\n): 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, parentCtx, appContext);\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, $models: modelsData, ...propsData } = filterClientDirectives(allProps);\r\n\r\n const id = ctx.nextId();\r\n ctx.pushComponent(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 (island data goes to __SIGX_ISLANDS__ JSON, no marker needed)\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\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: createPropsAccessor(propsData),\r\n slots: slots,\r\n emit: () => { },\r\n parent: parentCtx,\r\n onMounted: () => { },\r\n onUnmounted: () => { },\r\n onCreated: () => { },\r\n onUpdated: () => { },\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 // For ROOT component only (no parent), provide the AppContext\r\n // This enables the DI system to find app-level provides by traversing up the tree\r\n if (!parentCtx && appContext) {\r\n provideAppContext(componentCtx, appContext);\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, componentCtx, appContext);\r\n }\r\n } else {\r\n yield* renderToChunks(result, ctx, componentCtx, appContext);\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, componentCtx, appContext);\r\n }\r\n } else {\r\n yield* renderToChunks(result, ctx, componentCtx, appContext);\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 trailing component marker (unified with runtime-core pattern)\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, parentCtx, appContext);\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, appContext: AppContext | null = null): Promise<string> {\r\n let result = '';\r\n for await (const chunk of renderToChunks(element, ctx, null, appContext)) {\r\n result += chunk;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Render JSX element or App to a ReadableStream with streaming async support.\r\n * \r\n * @example\r\n * ```tsx\r\n * // Simple usage with JSX\r\n * renderToStream(<App />)\r\n * \r\n * // With App instance for DI/plugins\r\n * const app = defineApp(<App />).use(router);\r\n * renderToStream(app)\r\n * ```\r\n */\r\nexport function renderToStream(input: JSXElement | App, context?: SSRContext): ReadableStream<string> {\r\n const ctx = context || createSSRContext();\r\n \r\n // Extract element and app context\r\n let element: JSXElement;\r\n let appContext: AppContext | null = null;\r\n \r\n if (isApp(input)) {\r\n element = input._rootComponent;\r\n appContext = input._context;\r\n } else {\r\n element = input;\r\n }\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, null, appContext)) {\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\n * @example\r\n * ```tsx\r\n * // With App instance for DI/plugins\r\n * const app = defineApp(<App />).use(router);\r\n * await renderToStreamWithCallbacks(app, callbacks)\r\n * ```\r\n */\r\nexport async function renderToStreamWithCallbacks(\r\n input: JSXElement | App,\r\n callbacks: StreamCallbacks,\r\n context?: SSRContext\r\n): Promise<void> {\r\n const ctx = context || createSSRContext();\r\n \r\n // Extract element and app context\r\n let element: JSXElement;\r\n let appContext: AppContext | null = null;\r\n \r\n if (isApp(input)) {\r\n element = input._rootComponent;\r\n appContext = input._context;\r\n } else {\r\n element = input;\r\n }\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, null, appContext)) {\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 or App 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\n * @example\r\n * ```tsx\r\n * // Simple usage with JSX\r\n * const html = await renderToString(<App />);\r\n * \r\n * // With App instance for DI/plugins\r\n * const app = defineApp(<App />).use(router);\r\n * const html = await renderToString(app);\r\n * ```\r\n */\r\nexport async function renderToString(input: JSXElement | App, context?: SSRContext): Promise<string> {\r\n const ctx = context || createSSRContext();\r\n \r\n // Extract element and app context\r\n let element: JSXElement;\r\n let appContext: AppContext | null = null;\r\n \r\n if (isApp(input)) {\r\n element = input._rootComponent;\r\n appContext = input._context;\r\n } else {\r\n element = input;\r\n }\r\n \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, null, appContext)) {\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 '&': '&amp;',\r\n '<': '&lt;',\r\n '>': '&gt;',\r\n '\"': '&quot;',\r\n \"'\": '&#39;'\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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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared utilities for server-renderer
3
+ *
4
+ * @deprecated These utilities are now exported from `sigx` directly.
5
+ * Import from `sigx` or `sigx/hydration` instead.
6
+ * This module re-exports for backwards compatibility.
7
+ */
8
+ export { CLIENT_DIRECTIVE_PREFIX, CLIENT_DIRECTIVES, type ClientDirective, type HydrationStrategy, type HydrationDirective, filterClientDirectives, getHydrationDirective, hasClientDirective, serializeProps, createEmit } from 'sigx';
9
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACb,MAAM,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sigx/server-renderer",
3
- "version": "0.1.4",
4
- "description": "Server-side renderer for SignalX",
3
+ "version": "0.1.6",
4
+ "description": "Server-side rendering and client hydration for SigX",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -9,17 +9,31 @@
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
+ },
13
+ "./server": {
14
+ "import": "./dist/server/index.js",
15
+ "types": "./dist/server/index.d.ts"
16
+ },
17
+ "./client": {
18
+ "import": "./dist/client/index.js",
19
+ "types": "./dist/client/index.d.ts"
20
+ },
21
+ "./jsx": {
22
+ "types": "./src/jsx.d.ts"
12
23
  }
13
24
  },
14
25
  "files": [
15
- "dist"
26
+ "dist",
27
+ "src/jsx.d.ts"
16
28
  ],
17
29
  "keywords": [
18
30
  "sigx",
19
31
  "ssr",
20
32
  "server-side-rendering",
21
33
  "server",
22
- "renderer"
34
+ "renderer",
35
+ "hydration",
36
+ "islands"
23
37
  ],
24
38
  "author": "Andreas Ekdahl",
25
39
  "license": "MIT",
@@ -33,15 +47,15 @@
33
47
  "url": "https://github.com/signalxjs/core/issues"
34
48
  },
35
49
  "dependencies": {
36
- "@sigx/runtime-core": "^0.1.4",
37
- "@sigx/reactivity": "^0.1.4"
50
+ "sigx": "^0.1.6"
38
51
  },
39
52
  "devDependencies": {
40
- "rolldown": "^1.0.0-beta.52",
41
- "typescript": "^5.9.3"
53
+ "typescript": "^5.9.3",
54
+ "vite": "^8.0.0-beta.9",
55
+ "@sigx/vite": "^0.1.6"
42
56
  },
43
57
  "scripts": {
44
- "build": "rolldown -c && tsc --emitDeclarationOnly",
45
- "dev": "rolldown -c -w"
58
+ "build": "vite build && tsc --emitDeclarationOnly",
59
+ "dev": "vite build --watch"
46
60
  }
47
61
  }