@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.
- package/dist/client/hydrate.d.ts +24 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +2 -0
- package/dist/client/plugin.d.ts +52 -0
- package/dist/client/plugin.d.ts.map +1 -0
- package/dist/client/registry.d.ts +54 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/types.d.ts +23 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client-DiLwBAD-.js +541 -0
- package/dist/client-DiLwBAD-.js.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 +47 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -133
- 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 +2 -0
- package/dist/server/stream.d.ts +62 -0
- package/dist/server/stream.d.ts.map +1 -0
- package/dist/server-BCOJt2Bi.js +459 -0
- package/dist/server-BCOJt2Bi.js.map +1 -0
- package/dist/shared/utils.d.ts +9 -0
- package/dist/shared/utils.d.ts.map +1 -0
- package/package.json +24 -10
- package/src/jsx.d.ts +62 -0
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { Fragment, Text, createEmit, createPropsAccessor, createSlots, effect, filterClientDirectives, getCurrentInstance, getHydrationDirective, isComponent, mount, normalizeSubTree, patch, patchProp, provideAppContext, registerContextExtension, render, setCurrentInstance, signal } from "sigx";
|
|
2
|
+
var componentRegistry = /* @__PURE__ */ new Map();
|
|
3
|
+
function registerComponent(name, component) {
|
|
4
|
+
componentRegistry.set(name, component);
|
|
5
|
+
}
|
|
6
|
+
function registerComponents(components) {
|
|
7
|
+
for (const [name, component] of Object.entries(components)) if (isComponent(component)) registerComponent(name, component);
|
|
8
|
+
}
|
|
9
|
+
function getComponent(name) {
|
|
10
|
+
return componentRegistry.get(name);
|
|
11
|
+
}
|
|
12
|
+
var HydrationRegistry = class {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.components = /* @__PURE__ */ new Map();
|
|
15
|
+
}
|
|
16
|
+
register(name, component) {
|
|
17
|
+
this.components.set(name, component);
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
registerAll(components) {
|
|
21
|
+
for (const [name, component] of Object.entries(components)) if (isComponent(component)) this.register(name, component);
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
get(name) {
|
|
25
|
+
return this.components.get(name);
|
|
26
|
+
}
|
|
27
|
+
has(name) {
|
|
28
|
+
return this.components.has(name);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var _pendingServerState = null;
|
|
32
|
+
var _currentAppContext = null;
|
|
33
|
+
function setPendingServerState(state) {
|
|
34
|
+
_pendingServerState = state;
|
|
35
|
+
}
|
|
36
|
+
registerContextExtension((ctx) => {
|
|
37
|
+
const serverState = _pendingServerState;
|
|
38
|
+
if (serverState) {
|
|
39
|
+
ctx._serverState = serverState;
|
|
40
|
+
_pendingServerState = null;
|
|
41
|
+
ctx.signal = createRestoringSignal(serverState);
|
|
42
|
+
ctx.ssr = {
|
|
43
|
+
load: (_fn) => {},
|
|
44
|
+
isServer: false,
|
|
45
|
+
isHydrating: true
|
|
46
|
+
};
|
|
47
|
+
} else if (ctx._serverState) ctx.ssr = {
|
|
48
|
+
load: (_fn) => {},
|
|
49
|
+
isServer: false,
|
|
50
|
+
isHydrating: true
|
|
51
|
+
};
|
|
52
|
+
else ctx.ssr = {
|
|
53
|
+
load: (fn) => {
|
|
54
|
+
fn().catch((err) => console.error("[SSR] load error:", err));
|
|
55
|
+
},
|
|
56
|
+
isServer: false,
|
|
57
|
+
isHydrating: false
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
function createRestoringSignal(serverState) {
|
|
61
|
+
let signalIndex = 0;
|
|
62
|
+
return function restoringSignal(initial, name) {
|
|
63
|
+
const key = name ?? `$${signalIndex++}`;
|
|
64
|
+
if (key in serverState) return signal(serverState[key]);
|
|
65
|
+
return signal(initial);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function hydrate(element, container, appContext) {
|
|
69
|
+
const vnode = normalizeElement(element);
|
|
70
|
+
if (!vnode) return;
|
|
71
|
+
_currentAppContext = appContext ?? null;
|
|
72
|
+
hydrateNode(vnode, container.firstChild, container);
|
|
73
|
+
container._vnode = vnode;
|
|
74
|
+
}
|
|
75
|
+
var _cachedIslandData = null;
|
|
76
|
+
function invalidateIslandCache() {
|
|
77
|
+
_cachedIslandData = null;
|
|
78
|
+
}
|
|
79
|
+
function getIslandData() {
|
|
80
|
+
if (_cachedIslandData !== null) return _cachedIslandData;
|
|
81
|
+
const dataScript = document.getElementById("__SIGX_ISLANDS__");
|
|
82
|
+
if (!dataScript) {
|
|
83
|
+
_cachedIslandData = {};
|
|
84
|
+
return _cachedIslandData;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
_cachedIslandData = JSON.parse(dataScript.textContent || "{}");
|
|
88
|
+
} catch {
|
|
89
|
+
console.error("Failed to parse island data");
|
|
90
|
+
_cachedIslandData = {};
|
|
91
|
+
}
|
|
92
|
+
return _cachedIslandData;
|
|
93
|
+
}
|
|
94
|
+
function getIslandServerState(componentId) {
|
|
95
|
+
return getIslandData()[String(componentId)]?.state;
|
|
96
|
+
}
|
|
97
|
+
function normalizeElement(element) {
|
|
98
|
+
if (element == null || element === true || element === false) return null;
|
|
99
|
+
if (typeof element === "string" || typeof element === "number") return {
|
|
100
|
+
type: Text,
|
|
101
|
+
props: {},
|
|
102
|
+
key: null,
|
|
103
|
+
children: [],
|
|
104
|
+
dom: null,
|
|
105
|
+
text: element
|
|
106
|
+
};
|
|
107
|
+
return element;
|
|
108
|
+
}
|
|
109
|
+
function hydrateNode(vnode, dom, parent) {
|
|
110
|
+
if (!vnode) return dom;
|
|
111
|
+
while (dom && dom.nodeType === Node.COMMENT_NODE) {
|
|
112
|
+
if (dom.data.startsWith("$c:")) break;
|
|
113
|
+
dom = dom.nextSibling;
|
|
114
|
+
}
|
|
115
|
+
if (vnode.type === Text) {
|
|
116
|
+
if (dom && dom.nodeType === Node.TEXT_NODE) {
|
|
117
|
+
vnode.dom = dom;
|
|
118
|
+
return dom.nextSibling;
|
|
119
|
+
}
|
|
120
|
+
return dom;
|
|
121
|
+
}
|
|
122
|
+
if (vnode.type === Fragment) {
|
|
123
|
+
let current = dom;
|
|
124
|
+
for (const child of vnode.children) current = hydrateNode(child, current, parent);
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
if (isComponent(vnode.type)) {
|
|
128
|
+
const strategy = vnode.props ? getHydrationDirective(vnode.props) : null;
|
|
129
|
+
if (strategy) return scheduleComponentHydration(vnode, dom, parent, strategy);
|
|
130
|
+
return hydrateComponent(vnode, dom, parent);
|
|
131
|
+
}
|
|
132
|
+
if (typeof vnode.type === "string") {
|
|
133
|
+
if (!dom || dom.nodeType !== Node.ELEMENT_NODE) {
|
|
134
|
+
console.warn("[Hydrate] Expected element but got:", dom);
|
|
135
|
+
return dom;
|
|
136
|
+
}
|
|
137
|
+
const el = dom;
|
|
138
|
+
vnode.dom = el;
|
|
139
|
+
if (vnode.props) for (const key in vnode.props) {
|
|
140
|
+
if (key === "children" || key === "key") continue;
|
|
141
|
+
if (key.startsWith("client:")) continue;
|
|
142
|
+
patchProp(el, key, null, vnode.props[key]);
|
|
143
|
+
}
|
|
144
|
+
let childDom = el.firstChild;
|
|
145
|
+
for (const child of vnode.children) childDom = hydrateNode(child, childDom, el);
|
|
146
|
+
if (vnode.type === "select" && vnode.props) fixSelectValue(el, vnode.props);
|
|
147
|
+
return el.nextSibling;
|
|
148
|
+
}
|
|
149
|
+
return dom;
|
|
150
|
+
}
|
|
151
|
+
function scheduleComponentHydration(vnode, dom, parent, strategy) {
|
|
152
|
+
const { contentStart, trailingMarker } = findComponentBoundaries(dom);
|
|
153
|
+
const capturedAppContext = _currentAppContext;
|
|
154
|
+
const doHydrate = () => {
|
|
155
|
+
const prevAppContext = _currentAppContext;
|
|
156
|
+
_currentAppContext = capturedAppContext;
|
|
157
|
+
try {
|
|
158
|
+
hydrateComponent(vnode, contentStart, parent, void 0, trailingMarker);
|
|
159
|
+
} finally {
|
|
160
|
+
_currentAppContext = prevAppContext;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
switch (strategy.strategy) {
|
|
164
|
+
case "load":
|
|
165
|
+
doHydrate();
|
|
166
|
+
break;
|
|
167
|
+
case "idle":
|
|
168
|
+
if ("requestIdleCallback" in window) requestIdleCallback(() => doHydrate());
|
|
169
|
+
else setTimeout(() => doHydrate(), 200);
|
|
170
|
+
break;
|
|
171
|
+
case "visible":
|
|
172
|
+
observeComponentVisibility(contentStart, trailingMarker, doHydrate);
|
|
173
|
+
break;
|
|
174
|
+
case "media":
|
|
175
|
+
if (strategy.media) {
|
|
176
|
+
const mql = window.matchMedia(strategy.media);
|
|
177
|
+
if (mql.matches) doHydrate();
|
|
178
|
+
else {
|
|
179
|
+
const handler = (e) => {
|
|
180
|
+
if (e.matches) {
|
|
181
|
+
mql.removeEventListener("change", handler);
|
|
182
|
+
doHydrate();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
mql.addEventListener("change", handler);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
case "only":
|
|
190
|
+
doHydrate();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
return trailingMarker ? trailingMarker.nextSibling : dom;
|
|
194
|
+
}
|
|
195
|
+
function findComponentBoundaries(dom) {
|
|
196
|
+
let contentStart = dom;
|
|
197
|
+
let trailingMarker = null;
|
|
198
|
+
let current = dom;
|
|
199
|
+
while (current) {
|
|
200
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
201
|
+
if (current.data.startsWith("$c:")) {
|
|
202
|
+
trailingMarker = current;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
current = current.nextSibling;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
contentStart,
|
|
210
|
+
trailingMarker
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function observeComponentVisibility(contentStart, trailingMarker, callback) {
|
|
214
|
+
let targetElement = null;
|
|
215
|
+
let current = contentStart;
|
|
216
|
+
while (current && current !== trailingMarker) {
|
|
217
|
+
if (current.nodeType === Node.ELEMENT_NODE) {
|
|
218
|
+
targetElement = current;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
current = current.nextSibling;
|
|
222
|
+
}
|
|
223
|
+
if (!targetElement) {
|
|
224
|
+
callback();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const observer = new IntersectionObserver((entries) => {
|
|
228
|
+
for (const entry of entries) if (entry.isIntersecting) {
|
|
229
|
+
observer.disconnect();
|
|
230
|
+
callback();
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}, { rootMargin: "50px" });
|
|
234
|
+
observer.observe(targetElement);
|
|
235
|
+
}
|
|
236
|
+
function hydrateComponent(vnode, dom, parent, serverState, trailingMarker) {
|
|
237
|
+
const componentFactory = vnode.type;
|
|
238
|
+
const setup = componentFactory.__setup;
|
|
239
|
+
const componentName = componentFactory.__name || "Anonymous";
|
|
240
|
+
if (componentName && componentName !== "Anonymous") registerComponent(componentName, componentFactory);
|
|
241
|
+
let anchor = trailingMarker || null;
|
|
242
|
+
let componentId = null;
|
|
243
|
+
if (!anchor) {
|
|
244
|
+
let current = dom;
|
|
245
|
+
while (current) {
|
|
246
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
247
|
+
const text = current.data;
|
|
248
|
+
if (text.startsWith("$c:")) {
|
|
249
|
+
anchor = current;
|
|
250
|
+
componentId = parseInt(text.slice(3), 10);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
current = current.nextSibling;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
const text = anchor.data;
|
|
258
|
+
if (text.startsWith("$c:")) componentId = parseInt(text.slice(3), 10);
|
|
259
|
+
}
|
|
260
|
+
if (!serverState && componentId !== null) serverState = getIslandServerState(componentId);
|
|
261
|
+
const internalVNode = vnode;
|
|
262
|
+
const { children, slots: slotsFromProps, $models: modelsData, ...propsData } = filterClientDirectives(vnode.props || {});
|
|
263
|
+
const reactiveProps = signal(propsData);
|
|
264
|
+
internalVNode._componentProps = reactiveProps;
|
|
265
|
+
const slots = createSlots(children, slotsFromProps);
|
|
266
|
+
internalVNode._slots = slots;
|
|
267
|
+
const mountHooks = [];
|
|
268
|
+
const unmountHooks = [];
|
|
269
|
+
const createdHooks = [];
|
|
270
|
+
const updatedHooks = [];
|
|
271
|
+
const parentInstance = getCurrentInstance();
|
|
272
|
+
const signalFn = serverState ? createRestoringSignal(serverState) : signal;
|
|
273
|
+
const ssrHelper = {
|
|
274
|
+
load(_fn) {},
|
|
275
|
+
isServer: false,
|
|
276
|
+
isHydrating: !!serverState
|
|
277
|
+
};
|
|
278
|
+
const componentCtx = {
|
|
279
|
+
el: parent,
|
|
280
|
+
signal: signalFn,
|
|
281
|
+
props: createPropsAccessor(reactiveProps),
|
|
282
|
+
slots,
|
|
283
|
+
emit: createEmit(reactiveProps),
|
|
284
|
+
parent: parentInstance,
|
|
285
|
+
onMounted: (fn) => {
|
|
286
|
+
mountHooks.push(fn);
|
|
287
|
+
},
|
|
288
|
+
onUnmounted: (fn) => {
|
|
289
|
+
unmountHooks.push(fn);
|
|
290
|
+
},
|
|
291
|
+
onCreated: (fn) => {
|
|
292
|
+
createdHooks.push(fn);
|
|
293
|
+
},
|
|
294
|
+
onUpdated: (fn) => {
|
|
295
|
+
updatedHooks.push(fn);
|
|
296
|
+
},
|
|
297
|
+
expose: () => {},
|
|
298
|
+
renderFn: null,
|
|
299
|
+
update: () => {},
|
|
300
|
+
ssr: ssrHelper,
|
|
301
|
+
_serverState: serverState
|
|
302
|
+
};
|
|
303
|
+
if (!parentInstance && _currentAppContext) provideAppContext(componentCtx, _currentAppContext);
|
|
304
|
+
const prev = setCurrentInstance(componentCtx);
|
|
305
|
+
let renderFn;
|
|
306
|
+
try {
|
|
307
|
+
renderFn = setup(componentCtx);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error(`Error hydrating component ${componentName}:`, err);
|
|
310
|
+
} finally {
|
|
311
|
+
setCurrentInstance(prev);
|
|
312
|
+
}
|
|
313
|
+
let endDom = dom;
|
|
314
|
+
if (renderFn) {
|
|
315
|
+
componentCtx.renderFn = renderFn;
|
|
316
|
+
let isFirstRender = true;
|
|
317
|
+
const componentEffect = effect(() => {
|
|
318
|
+
const prevInstance = setCurrentInstance(componentCtx);
|
|
319
|
+
try {
|
|
320
|
+
const subTreeResult = componentCtx.renderFn();
|
|
321
|
+
const prevSubTree = internalVNode._subTree;
|
|
322
|
+
if (subTreeResult == null) {
|
|
323
|
+
if (isFirstRender) isFirstRender = false;
|
|
324
|
+
else if (prevSubTree && prevSubTree.dom) {
|
|
325
|
+
const patchContainer = prevSubTree.dom.parentNode || parent;
|
|
326
|
+
const emptyNode = normalizeSubTree(null);
|
|
327
|
+
patch(prevSubTree, emptyNode, patchContainer);
|
|
328
|
+
internalVNode._subTree = emptyNode;
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const subTree = normalizeSubTree(subTreeResult);
|
|
333
|
+
if (isFirstRender) {
|
|
334
|
+
isFirstRender = false;
|
|
335
|
+
endDom = hydrateNode(subTree, dom, parent);
|
|
336
|
+
internalVNode._subTree = subTree;
|
|
337
|
+
} else {
|
|
338
|
+
if (prevSubTree) patch(prevSubTree, subTree, prevSubTree.dom?.parentNode || parent);
|
|
339
|
+
else mount(subTree, parent, anchor || null);
|
|
340
|
+
internalVNode._subTree = subTree;
|
|
341
|
+
}
|
|
342
|
+
} finally {
|
|
343
|
+
setCurrentInstance(prevInstance);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
internalVNode._effect = componentEffect;
|
|
347
|
+
componentCtx.update = () => componentEffect();
|
|
348
|
+
}
|
|
349
|
+
vnode.dom = anchor || endDom;
|
|
350
|
+
const mountCtx = { el: parent };
|
|
351
|
+
createdHooks.forEach((hook) => hook());
|
|
352
|
+
mountHooks.forEach((hook) => hook(mountCtx));
|
|
353
|
+
vnode.cleanup = () => {
|
|
354
|
+
unmountHooks.forEach((hook) => hook(mountCtx));
|
|
355
|
+
};
|
|
356
|
+
return anchor ? anchor.nextSibling : endDom;
|
|
357
|
+
}
|
|
358
|
+
function fixSelectValue(dom, props) {
|
|
359
|
+
if (dom.tagName === "SELECT" && "value" in props) {
|
|
360
|
+
const val = props.value;
|
|
361
|
+
if (dom.multiple) {
|
|
362
|
+
const options = dom.options;
|
|
363
|
+
const valArray = Array.isArray(val) ? val : [val];
|
|
364
|
+
for (let i = 0; i < options.length; i++) options[i].selected = valArray.includes(options[i].value);
|
|
365
|
+
} else dom.value = String(val);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function hydrateIslands() {
|
|
369
|
+
const dataScript = document.getElementById("__SIGX_ISLANDS__");
|
|
370
|
+
if (!dataScript) return;
|
|
371
|
+
let islandData;
|
|
372
|
+
try {
|
|
373
|
+
islandData = JSON.parse(dataScript.textContent || "{}");
|
|
374
|
+
} catch {
|
|
375
|
+
console.error("Failed to parse island data");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
for (const [idStr, info] of Object.entries(islandData)) scheduleHydration(parseInt(idStr, 10), info);
|
|
379
|
+
}
|
|
380
|
+
function scheduleHydration(id, info) {
|
|
381
|
+
const marker = findIslandMarker(id);
|
|
382
|
+
if (!marker) {
|
|
383
|
+
console.warn(`Island marker not found for id ${id}`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const component = info.componentId ? getComponent(info.componentId) : null;
|
|
387
|
+
if (!component && info.strategy !== "only") {
|
|
388
|
+
console.warn(`Component "${info.componentId}" not registered for hydration`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
switch (info.strategy) {
|
|
392
|
+
case "load":
|
|
393
|
+
hydrateIsland(marker, component, info);
|
|
394
|
+
break;
|
|
395
|
+
case "idle":
|
|
396
|
+
if ("requestIdleCallback" in window) requestIdleCallback(() => hydrateIsland(marker, component, info));
|
|
397
|
+
else setTimeout(() => hydrateIsland(marker, component, info), 200);
|
|
398
|
+
break;
|
|
399
|
+
case "visible":
|
|
400
|
+
observeVisibility(marker, () => hydrateIsland(marker, component, info));
|
|
401
|
+
break;
|
|
402
|
+
case "media":
|
|
403
|
+
if (info.media) {
|
|
404
|
+
const mql = window.matchMedia(info.media);
|
|
405
|
+
if (mql.matches) hydrateIsland(marker, component, info);
|
|
406
|
+
else mql.addEventListener("change", function handler(e) {
|
|
407
|
+
if (e.matches) {
|
|
408
|
+
mql.removeEventListener("change", handler);
|
|
409
|
+
hydrateIsland(marker, component, info);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
case "only":
|
|
415
|
+
if (component) mountClientOnly(marker, component, info);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function findIslandMarker(id) {
|
|
420
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
|
|
421
|
+
let node;
|
|
422
|
+
while (node = walker.nextNode()) if (node.data === `$c:${id}`) return node;
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
function observeVisibility(marker, callback) {
|
|
426
|
+
let node = marker.previousSibling;
|
|
427
|
+
while (node && node.nodeType !== Node.ELEMENT_NODE) node = node.previousSibling;
|
|
428
|
+
if (!node) {
|
|
429
|
+
callback();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const observer = new IntersectionObserver((entries) => {
|
|
433
|
+
for (const entry of entries) if (entry.isIntersecting) {
|
|
434
|
+
observer.disconnect();
|
|
435
|
+
callback();
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
observer.observe(node);
|
|
440
|
+
}
|
|
441
|
+
function hydrateIsland(marker, component, info) {
|
|
442
|
+
let container = marker.previousSibling;
|
|
443
|
+
while (container && container.nodeType !== Node.ELEMENT_NODE) container = container.previousSibling;
|
|
444
|
+
if (!container || container.nodeType !== Node.ELEMENT_NODE) {
|
|
445
|
+
console.warn("No element found for island hydration");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const props = info.props || {};
|
|
449
|
+
if (info.state) setPendingServerState(info.state);
|
|
450
|
+
const vnode = {
|
|
451
|
+
type: component,
|
|
452
|
+
props,
|
|
453
|
+
key: null,
|
|
454
|
+
children: [],
|
|
455
|
+
dom: null
|
|
456
|
+
};
|
|
457
|
+
const wrapper = document.createElement("div");
|
|
458
|
+
wrapper.style.display = "contents";
|
|
459
|
+
const parent = container.parentNode;
|
|
460
|
+
parent.insertBefore(wrapper, container);
|
|
461
|
+
parent.removeChild(container);
|
|
462
|
+
render(vnode, wrapper);
|
|
463
|
+
}
|
|
464
|
+
function mountClientOnly(marker, component, info) {
|
|
465
|
+
let placeholder = marker.previousSibling;
|
|
466
|
+
while (placeholder && placeholder.nodeType !== Node.ELEMENT_NODE) placeholder = placeholder.previousSibling;
|
|
467
|
+
if (!placeholder || !placeholder.hasAttribute?.("data-island")) return;
|
|
468
|
+
const props = info.props || {};
|
|
469
|
+
const container = placeholder;
|
|
470
|
+
container.innerHTML = "";
|
|
471
|
+
render({
|
|
472
|
+
type: component,
|
|
473
|
+
props,
|
|
474
|
+
key: null,
|
|
475
|
+
children: [],
|
|
476
|
+
dom: null
|
|
477
|
+
}, container);
|
|
478
|
+
}
|
|
479
|
+
var _asyncListenerSetup = false;
|
|
480
|
+
function ensureAsyncHydrationListener() {
|
|
481
|
+
if (_asyncListenerSetup) return;
|
|
482
|
+
_asyncListenerSetup = true;
|
|
483
|
+
document.addEventListener("sigx:async-ready", (event) => {
|
|
484
|
+
const { id, state } = event.detail || {};
|
|
485
|
+
invalidateIslandCache();
|
|
486
|
+
const placeholder = document.querySelector(`[data-async-placeholder="${id}"]`);
|
|
487
|
+
if (!placeholder) {
|
|
488
|
+
console.warn(`[Hydrate] Could not find placeholder for async component ${id}`);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const info = getIslandData()[String(id)];
|
|
492
|
+
if (!info) {
|
|
493
|
+
console.warn(`[Hydrate] No island data for async component ${id}`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
hydrateAsyncComponent(placeholder, info);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (typeof document !== "undefined") ensureAsyncHydrationListener();
|
|
500
|
+
function hydrateAsyncComponent(container, info) {
|
|
501
|
+
if (!info.componentId) {
|
|
502
|
+
console.error(`[Hydrate] No componentId in island info`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const component = getComponent(info.componentId);
|
|
506
|
+
if (!component) {
|
|
507
|
+
console.error(`[Hydrate] Component "${info.componentId}" not registered`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const props = info.props || {};
|
|
511
|
+
const serverState = info.state;
|
|
512
|
+
container.innerHTML = "";
|
|
513
|
+
if (serverState) setPendingServerState(serverState);
|
|
514
|
+
render({
|
|
515
|
+
type: component,
|
|
516
|
+
props,
|
|
517
|
+
key: null,
|
|
518
|
+
children: [],
|
|
519
|
+
dom: null
|
|
520
|
+
}, container);
|
|
521
|
+
}
|
|
522
|
+
const ssrClientPlugin = {
|
|
523
|
+
name: "@sigx/server-renderer/client",
|
|
524
|
+
install(app) {
|
|
525
|
+
app.hydrate = function(container) {
|
|
526
|
+
const resolvedContainer = typeof container === "string" ? document.querySelector(container) : container;
|
|
527
|
+
if (!resolvedContainer) throw new Error(`[ssrClientPlugin] Cannot find container: ${container}. Make sure the element exists in the DOM before calling hydrate().`);
|
|
528
|
+
const rootComponent = app._rootComponent;
|
|
529
|
+
if (!rootComponent) throw new Error("[ssrClientPlugin] No root component found on app. Make sure you created the app with defineApp(<Component />).");
|
|
530
|
+
const hasSSRContent = resolvedContainer.firstElementChild !== null || resolvedContainer.firstChild !== null && resolvedContainer.firstChild.nodeType !== Node.COMMENT_NODE;
|
|
531
|
+
const appContext = app._context;
|
|
532
|
+
if (hasSSRContent) hydrate(rootComponent, resolvedContainer, appContext);
|
|
533
|
+
else render(rootComponent, resolvedContainer, appContext);
|
|
534
|
+
resolvedContainer._app = app;
|
|
535
|
+
return app;
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
export { registerComponents as a, registerComponent as i, hydrateIslands as n, HydrationRegistry as r, ssrClientPlugin as t };
|
|
540
|
+
|
|
541
|
+
//# sourceMappingURL=client-DiLwBAD-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-DiLwBAD-.js","names":[],"sources":["../src/client/registry.ts","../src/client/hydrate.ts","../src/client/plugin.ts"],"sourcesContent":["/**\r\n * Component Registry for Island Hydration\r\n * \r\n * Components must be registered before they can be hydrated as islands.\r\n */\r\n\r\nimport { isComponent as isSignalXComponent } from 'sigx';\r\n\r\n/**\r\n * Minimal type for component factories used in hydration registry.\r\n * Compatible with ComponentFactory from runtime-core.\r\n */\r\nexport interface ComponentFactory {\r\n __setup: Function;\r\n __name?: string;\r\n __async?: boolean;\r\n}\r\n\r\n/**\r\n * Registry mapping component names to component factories\r\n */\r\nconst componentRegistry = new Map<string, ComponentFactory>();\r\n\r\n/**\r\n * Register a component for island hydration.\r\n * Components must be registered before hydrateIslands() is called.\r\n * \r\n * @example\r\n * ```ts\r\n * import { registerComponent } from '@sigx/server-renderer/client';\r\n * import { Counter } from './components/Counter';\r\n * \r\n * registerComponent('Counter', Counter);\r\n * ```\r\n */\r\nexport function registerComponent(name: string, component: ComponentFactory): void {\r\n componentRegistry.set(name, component);\r\n}\r\n\r\n/**\r\n * Register multiple components at once\r\n * \r\n * @example\r\n * ```ts\r\n * import { registerComponents } from '@sigx/server-renderer/client';\r\n * import * as Components from './components';\r\n * \r\n * registerComponents(Components);\r\n * ```\r\n */\r\nexport function registerComponents(components: Record<string, ComponentFactory>): void {\r\n for (const [name, component] of Object.entries(components)) {\r\n if (isSignalXComponent(component)) {\r\n registerComponent(name, component);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Get a registered component by name\r\n */\r\nexport function getComponent(name: string): ComponentFactory | undefined {\r\n return componentRegistry.get(name);\r\n}\r\n\r\n// isComponent is imported from sigx as isSignalXComponent\r\n\r\n/**\r\n * Hydration Registry class for more advanced use cases\r\n */\r\nexport class HydrationRegistry {\r\n private components = new Map<string, ComponentFactory>();\r\n\r\n register(name: string, component: ComponentFactory): this {\r\n this.components.set(name, component);\r\n return this;\r\n }\r\n\r\n registerAll(components: Record<string, ComponentFactory>): this {\r\n for (const [name, component] of Object.entries(components)) {\r\n if (isSignalXComponent(component)) {\r\n this.register(name, component);\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n get(name: string): ComponentFactory | undefined {\r\n return this.components.get(name);\r\n }\r\n\r\n has(name: string): boolean {\r\n return this.components.has(name);\r\n }\r\n}\r\n","/**\r\n * Client-side hydration for SSR'd content\r\n * \r\n * Hydration attaches the app to existing server-rendered DOM,\r\n * then delegates all updates to the runtime-dom renderer.\r\n */\r\n\r\nimport {\r\n VNode,\r\n Fragment,\r\n Text,\r\n ComponentSetupContext,\r\n setCurrentInstance,\r\n getCurrentInstance,\r\n signal,\r\n Signal,\r\n SlotsObject,\r\n createPropsAccessor,\r\n createSlots,\r\n normalizeSubTree,\r\n registerContextExtension,\r\n effect,\r\n patch,\r\n render,\r\n mount,\r\n patchProp,\r\n isComponent,\r\n getHydrationDirective,\r\n filterClientDirectives,\r\n createEmit,\r\n AppContext,\r\n provideAppContext\r\n} from 'sigx';\r\nimport { getComponent, registerComponent, type ComponentFactory } from './registry.js';\r\nimport type { IslandInfo, HydrationOptions } from './types.js';\r\n\r\n// Re-export types\r\nexport type { HydrationOptions } from './types.js';\r\n\r\n// Track server state for async components being mounted after streaming\r\nlet _pendingServerState: Record<string, any> | null = null;\r\n\r\n// Track current app context during hydration for DI\r\n// Used for deferred hydration callbacks\r\nlet _currentAppContext: AppContext | null = null;\r\n\r\n/**\r\n * Set server state that should be used for the next component mount.\r\n * Used internally when mounting async components after streaming.\r\n */\r\nfunction setPendingServerState(state: Record<string, any> | null): void {\r\n _pendingServerState = state;\r\n}\r\n\r\n/**\r\n * Register the SSR context extension for all components.\r\n * This provides the `ssr` object with a no-op `load()` for client-side rendering.\r\n * Also handles server state restoration for async streamed components.\r\n */\r\nregisterContextExtension((ctx: any) => {\r\n // Check if we have pending server state (from async streaming)\r\n const serverState = _pendingServerState;\r\n if (serverState) {\r\n ctx._serverState = serverState;\r\n _pendingServerState = null; // Clear after use\r\n\r\n // Override signal function to use restoring signal\r\n ctx.signal = createRestoringSignal(serverState);\r\n\r\n // ssr.load() should be a no-op since we have restored state\r\n ctx.ssr = {\r\n load: (_fn: () => Promise<void>) => {\r\n // Skip - using restored server state\r\n },\r\n isServer: false,\r\n isHydrating: true\r\n };\r\n } else if (ctx._serverState) {\r\n // Already has server state (from hydration)\r\n ctx.ssr = {\r\n load: (_fn: () => Promise<void>) => {\r\n // Skip - using restored server state\r\n },\r\n isServer: false,\r\n isHydrating: true\r\n };\r\n } else {\r\n // Default client-side ssr helper - runs async functions for client-side navigation\r\n ctx.ssr = {\r\n load: (fn: () => Promise<void>) => {\r\n // On client-side navigation (not hydration), execute the async function\r\n fn().catch(err => console.error('[SSR] load error:', err));\r\n },\r\n isServer: false,\r\n isHydrating: false\r\n };\r\n }\r\n});\r\n\r\ninterface InternalVNode extends VNode {\r\n _subTree?: VNode;\r\n _effect?: any;\r\n _componentProps?: any;\r\n _slots?: any;\r\n}\r\n\r\n/**\r\n * Creates a signal function that restores state from server-captured values.\r\n * Used during hydration of async components to avoid re-fetching data.\r\n */\r\nfunction createRestoringSignal(serverState: Record<string, any>) {\r\n let signalIndex = 0;\r\n\r\n return function restoringSignal<T extends object>(initial: T, name?: string): Signal<T> {\r\n // Generate a stable key for this signal (must match server-side)\r\n const key = name ?? `$${signalIndex++}`;\r\n\r\n // Check if we have server state for this signal\r\n if (key in serverState) {\r\n return signal(serverState[key] as T);\r\n }\r\n\r\n // No server state, use initial value\r\n return signal(initial);\r\n };\r\n}\r\n\r\n/**\r\n * Hydrate a server-rendered app.\r\n * \r\n * This walks the existing DOM to attach event handlers, runs component\r\n * setup functions to establish reactivity, then uses runtime-dom for updates.\r\n * \r\n * @param element - The root element/VNode to hydrate\r\n * @param container - The DOM container with SSR content\r\n * @param appContext - The app context for DI (provides, etc.)\r\n */\r\nexport function hydrate(element: any, container: Element, appContext?: AppContext): void {\r\n const vnode = normalizeElement(element);\r\n if (!vnode) return;\r\n\r\n // Store app context for component hydration (DI needs this)\r\n _currentAppContext = appContext ?? null;\r\n\r\n // Walk existing DOM, attach handlers, and mount components\r\n hydrateNode(vnode, container.firstChild, container);\r\n\r\n // Store vnode on container for potential future use\r\n (container as any)._vnode = vnode;\r\n}\r\n\r\n// Cache for parsed island data - can be invalidated when async components stream in\r\nlet _cachedIslandData: Record<string, IslandInfo> | null = null;\r\n\r\n/**\r\n * Invalidate the island data cache (called when async components stream in)\r\n */\r\nfunction invalidateIslandCache(): void {\r\n _cachedIslandData = null;\r\n}\r\n\r\n/**\r\n * Get island data from the __SIGX_ISLANDS__ script tag\r\n */\r\nfunction getIslandData(): Record<string, IslandInfo> {\r\n if (_cachedIslandData !== null) {\r\n return _cachedIslandData;\r\n }\r\n\r\n const dataScript = document.getElementById('__SIGX_ISLANDS__');\r\n if (!dataScript) {\r\n _cachedIslandData = {};\r\n return _cachedIslandData!;\r\n }\r\n\r\n try {\r\n _cachedIslandData = JSON.parse(dataScript.textContent || '{}');\r\n } catch {\r\n console.error('Failed to parse island data');\r\n _cachedIslandData = {};\r\n }\r\n\r\n return _cachedIslandData!;\r\n}\r\n\r\n/**\r\n * Get server state for a specific island/component by ID\r\n */\r\nfunction getIslandServerState(componentId: number): Record<string, any> | undefined {\r\n const islandData = getIslandData();\r\n const info = islandData[String(componentId)];\r\n return info?.state;\r\n}\r\n\r\n/**\r\n * Normalize any element to VNode\r\n */\r\nfunction normalizeElement(element: any): VNode | null {\r\n if (element == null || element === true || element === false) {\r\n return null;\r\n }\r\n\r\n if (typeof element === 'string' || typeof element === 'number') {\r\n return {\r\n type: Text,\r\n props: {},\r\n key: null,\r\n children: [],\r\n dom: null,\r\n text: element\r\n };\r\n }\r\n\r\n return element as VNode;\r\n}\r\n\r\n// getHydrationDirective is imported from sigx\r\n\r\n/**\r\n * Hydrate a VNode against existing DOM\r\n * This only attaches event handlers and refs - no DOM creation\r\n */\r\nfunction hydrateNode(vnode: VNode, dom: Node | null, parent: Node): Node | null {\r\n if (!vnode) return dom;\r\n\r\n // Skip text separator comments (<!--t-->) but NOT component markers\r\n while (dom && dom.nodeType === Node.COMMENT_NODE) {\r\n const commentText = (dom as Comment).data;\r\n // Component markers start with $c: - don't skip those (trailing markers)\r\n if (commentText.startsWith('$c:')) {\r\n break;\r\n }\r\n dom = dom.nextSibling;\r\n }\r\n\r\n if (vnode.type === Text) {\r\n if (dom && dom.nodeType === Node.TEXT_NODE) {\r\n vnode.dom = dom;\r\n return dom.nextSibling;\r\n }\r\n return dom;\r\n }\r\n\r\n if (vnode.type === Fragment) {\r\n let current = dom;\r\n for (const child of vnode.children) {\r\n current = hydrateNode(child, current, parent);\r\n }\r\n return current;\r\n }\r\n\r\n if (isComponent(vnode.type)) {\r\n // Check for client:* hydration strategy\r\n const strategy = vnode.props ? getHydrationDirective(vnode.props) : null;\r\n\r\n if (strategy) {\r\n // Schedule hydration based on strategy\r\n return scheduleComponentHydration(vnode, dom, parent, strategy);\r\n }\r\n\r\n // No directive - hydrate immediately\r\n return hydrateComponent(vnode, dom, parent);\r\n }\r\n\r\n if (typeof vnode.type === 'string') {\r\n if (!dom || dom.nodeType !== Node.ELEMENT_NODE) {\r\n console.warn('[Hydrate] Expected element but got:', dom);\r\n return dom;\r\n }\r\n\r\n const el = dom as Element;\r\n vnode.dom = el;\r\n\r\n // Attach event handlers and props using patchProp from runtime-dom\r\n if (vnode.props) {\r\n for (const key in vnode.props) {\r\n if (key === 'children' || key === 'key') continue;\r\n if (key.startsWith('client:')) continue;\r\n\r\n // Use patchProp for consistent prop handling (events, refs, etc.)\r\n patchProp(el, key, null, vnode.props[key]);\r\n }\r\n }\r\n\r\n // Hydrate children\r\n let childDom: Node | null = el.firstChild;\r\n for (const child of vnode.children) {\r\n childDom = hydrateNode(child, childDom, el);\r\n }\r\n\r\n // Fix select value after children are hydrated\r\n if (vnode.type === 'select' && vnode.props) {\r\n fixSelectValue(el as HTMLElement, vnode.props);\r\n }\r\n\r\n return el.nextSibling;\r\n }\r\n\r\n return dom;\r\n}\r\n\r\n/**\r\n * Schedule component hydration based on strategy.\r\n * Returns the next DOM node after this component's content.\r\n * \r\n * With trailing markers, the structure is: <content><!--$c:id-->\r\n * - dom points to start of content\r\n * - trailing marker comes after all content\r\n */\r\nfunction scheduleComponentHydration(\r\n vnode: VNode,\r\n dom: Node | null,\r\n parent: Node,\r\n strategy: { strategy: 'load' | 'idle' | 'visible' | 'media' | 'only'; media?: string }\r\n): Node | null {\r\n // Find the trailing marker (content is before it)\r\n const { contentStart, trailingMarker } = findComponentBoundaries(dom);\r\n\r\n // Capture app context now for deferred hydration (callback runs later with no parent instance)\r\n const capturedAppContext = _currentAppContext;\r\n \r\n const doHydrate = () => {\r\n // Restore app context for deferred hydration\r\n const prevAppContext = _currentAppContext;\r\n _currentAppContext = capturedAppContext;\r\n try {\r\n hydrateComponent(vnode, contentStart, parent, undefined, trailingMarker);\r\n } finally {\r\n _currentAppContext = prevAppContext;\r\n }\r\n };\r\n\r\n switch (strategy.strategy) {\r\n case 'load':\r\n // Hydrate immediately\r\n doHydrate();\r\n break;\r\n\r\n case 'idle':\r\n // Hydrate during browser idle time\r\n if ('requestIdleCallback' in window) {\r\n requestIdleCallback(() => doHydrate());\r\n } else {\r\n setTimeout(() => doHydrate(), 200);\r\n }\r\n break;\r\n\r\n case 'visible':\r\n // Hydrate when component scrolls into view\r\n observeComponentVisibility(contentStart, trailingMarker, doHydrate);\r\n break;\r\n\r\n case 'media':\r\n // Hydrate when media query matches\r\n if (strategy.media) {\r\n const mql = window.matchMedia(strategy.media);\r\n if (mql.matches) {\r\n doHydrate();\r\n } else {\r\n const handler = (e: MediaQueryListEvent) => {\r\n if (e.matches) {\r\n mql.removeEventListener('change', handler);\r\n doHydrate();\r\n }\r\n };\r\n mql.addEventListener('change', handler);\r\n }\r\n }\r\n break;\r\n\r\n case 'only':\r\n // client:only - component was not server rendered, mount fresh\r\n // For now, just hydrate (server renders a placeholder)\r\n doHydrate();\r\n break;\r\n }\r\n\r\n // Return the node after the trailing marker\r\n return trailingMarker ? trailingMarker.nextSibling : dom;\r\n}\r\n\r\n/**\r\n * Find component boundaries in DOM with trailing marker pattern.\r\n * Structure: <content><!--$c:id-->\r\n * \r\n * @param dom - The starting DOM node (content starts here)\r\n * @returns contentStart and the trailing marker comment\r\n */\r\nfunction findComponentBoundaries(dom: Node | null): { contentStart: Node | null; trailingMarker: Comment | null } {\r\n let contentStart = dom;\r\n let trailingMarker: Comment | null = null;\r\n\r\n // Traverse forward to find the trailing marker <!--$c:N-->\r\n let current: Node | null = dom;\r\n while (current) {\r\n if (current.nodeType === Node.COMMENT_NODE) {\r\n const text = (current as Comment).data;\r\n if (text.startsWith('$c:')) {\r\n trailingMarker = current as Comment;\r\n break;\r\n }\r\n }\r\n current = current.nextSibling;\r\n }\r\n\r\n return { contentStart, trailingMarker };\r\n}\r\n\r\n/**\r\n * Observe when a component's DOM becomes visible.\r\n * With trailing markers, content is BEFORE the marker.\r\n */\r\nfunction observeComponentVisibility(contentStart: Node | null, trailingMarker: Comment | null, callback: () => void): void {\r\n // Find an element to observe (content is before the trailing marker)\r\n let targetElement: Element | null = null;\r\n let current = contentStart;\r\n\r\n while (current && current !== trailingMarker) {\r\n if (current.nodeType === Node.ELEMENT_NODE) {\r\n targetElement = current as Element;\r\n break;\r\n }\r\n current = current.nextSibling;\r\n }\r\n\r\n if (!targetElement) {\r\n // No element found, hydrate immediately\r\n callback();\r\n return;\r\n }\r\n\r\n const observer = new IntersectionObserver((entries) => {\r\n for (const entry of entries) {\r\n if (entry.isIntersecting) {\r\n observer.disconnect();\r\n callback();\r\n break;\r\n }\r\n }\r\n }, { rootMargin: '50px' });\r\n\r\n observer.observe(targetElement);\r\n}\r\n\r\n/**\r\n * Hydrate a component - run setup and create reactive effect\r\n * \r\n * With trailing markers, the structure is: <content><!--$c:id-->\r\n * - dom points to start of content\r\n * - trailingMarker (if provided) is the anchor at the end\r\n * \r\n * @param vnode - The VNode to hydrate\r\n * @param dom - The DOM node to start from (content starts here)\r\n * @param parent - The parent node\r\n * @param serverState - Optional state captured from server for async components\r\n * @param trailingMarker - Optional trailing marker comment (the component anchor)\r\n */\r\nfunction hydrateComponent(vnode: VNode, dom: Node | null, parent: Node, serverState?: Record<string, any>, trailingMarker?: Comment | null): Node | null {\r\n const componentFactory = vnode.type as unknown as ComponentFactory;\r\n const setup = componentFactory.__setup;\r\n const componentName = componentFactory.__name || 'Anonymous';\r\n\r\n // Auto-register component for async streaming hydration\r\n if (componentName && componentName !== 'Anonymous') {\r\n registerComponent(componentName, componentFactory);\r\n }\r\n\r\n // With trailing markers, find the marker if not provided\r\n let anchor: Comment | null = trailingMarker || null;\r\n let componentId: number | null = null;\r\n \r\n if (!anchor) {\r\n // Find trailing marker by traversing forward\r\n let current: Node | null = dom;\r\n while (current) {\r\n if (current.nodeType === Node.COMMENT_NODE) {\r\n const text = (current as Comment).data;\r\n if (text.startsWith('$c:')) {\r\n anchor = current as Comment;\r\n componentId = parseInt(text.slice(3), 10);\r\n break;\r\n }\r\n }\r\n current = current.nextSibling;\r\n }\r\n } else {\r\n // Extract component ID from provided marker\r\n const text = anchor.data;\r\n if (text.startsWith('$c:')) {\r\n componentId = parseInt(text.slice(3), 10);\r\n }\r\n }\r\n\r\n // Try to get server state from island data if not provided\r\n if (!serverState && componentId !== null) {\r\n serverState = getIslandServerState(componentId);\r\n }\r\n\r\n const internalVNode = vnode as InternalVNode;\r\n const initialProps = vnode.props || {};\r\n const { children, slots: slotsFromProps, $models: modelsData, ...propsData } = filterClientDirectives(initialProps);\r\n\r\n // Create reactive props\r\n const reactiveProps = signal(propsData);\r\n internalVNode._componentProps = reactiveProps;\r\n\r\n // Create slots\r\n const slots = createSlots(children, slotsFromProps);\r\n internalVNode._slots = slots;\r\n\r\n const mountHooks: ((ctx: any) => void)[] = [];\r\n const unmountHooks: ((ctx: any) => void)[] = [];\r\n const createdHooks: (() => void)[] = [];\r\n const updatedHooks: (() => void)[] = [];\r\n\r\n const parentInstance = getCurrentInstance();\r\n\r\n // Use restoring signal when we have server state to restore\r\n const signalFn = serverState\r\n ? createRestoringSignal(serverState)\r\n : signal;\r\n\r\n // Create SSR helper for client-side\r\n // When hydrating with server state, ssr.load() is a no-op (data already restored)\r\n const hasServerState = !!serverState;\r\n const ssrHelper = {\r\n load(_fn: () => Promise<void>): void {\r\n // No-op on client when hydrating - signal state was restored from server\r\n // The data is already in the signals, no need to re-fetch\r\n },\r\n isServer: false,\r\n isHydrating: hasServerState\r\n };\r\n\r\n const componentCtx: ComponentSetupContext = {\r\n el: parent as HTMLElement,\r\n signal: signalFn as typeof signal,\r\n props: createPropsAccessor(reactiveProps),\r\n slots: slots,\r\n emit: createEmit(reactiveProps),\r\n parent: parentInstance,\r\n onMounted: (fn) => { mountHooks.push(fn); },\r\n onUnmounted: (fn) => { unmountHooks.push(fn); },\r\n onCreated: (fn) => { createdHooks.push(fn); },\r\n onUpdated: (fn) => { updatedHooks.push(fn); },\r\n expose: () => { },\r\n renderFn: null,\r\n update: () => { },\r\n ssr: ssrHelper,\r\n _serverState: serverState\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 (!parentInstance && _currentAppContext) {\r\n provideAppContext(componentCtx, _currentAppContext);\r\n }\r\n\r\n const prev = setCurrentInstance(componentCtx);\r\n let renderFn: (() => any) | undefined;\r\n\r\n try {\r\n renderFn = setup(componentCtx);\r\n } catch (err) {\r\n console.error(`Error hydrating component ${componentName}:`, err);\r\n } finally {\r\n setCurrentInstance(prev);\r\n }\r\n\r\n // Track where the component's DOM starts\r\n let endDom: Node | null = dom;\r\n\r\n if (renderFn) {\r\n componentCtx.renderFn = renderFn;\r\n let isFirstRender = true;\r\n\r\n // Create reactive effect - on first run, hydrate; on subsequent, use render()\r\n const componentEffect = effect(() => {\r\n const prevInstance = setCurrentInstance(componentCtx);\r\n try {\r\n const subTreeResult = componentCtx.renderFn!();\r\n const prevSubTree = internalVNode._subTree;\r\n\r\n // Handle null/undefined renders (e.g., conditional components like Modal)\r\n if (subTreeResult == null) {\r\n if (isFirstRender) {\r\n // First render returned null - SSR didn't render content, nothing to hydrate\r\n // Mark as hydrated so future renders mount fresh instead of trying to hydrate\r\n isFirstRender = false;\r\n } else if (prevSubTree && prevSubTree.dom) {\r\n // Had content before, now returning null - unmount the previous subtree\r\n const patchContainer = prevSubTree.dom.parentNode as Element || parent;\r\n // Create an empty text node to represent \"nothing\"\r\n const emptyNode = normalizeSubTree(null);\r\n patch(prevSubTree, emptyNode, patchContainer);\r\n internalVNode._subTree = emptyNode;\r\n }\r\n return;\r\n }\r\n\r\n const subTree = normalizeSubTree(subTreeResult);\r\n\r\n if (isFirstRender) {\r\n // First render - hydrate against existing DOM\r\n isFirstRender = false;\r\n endDom = hydrateNode(subTree, dom, parent);\r\n internalVNode._subTree = subTree;\r\n } else {\r\n // Subsequent renders - use patch directly like runtime-core does\r\n if (prevSubTree) {\r\n // Use the actual DOM parent of the previous subtree, not the hydration parent\r\n // This is crucial when the component's subtree is a component that renders\r\n // into a different container (e.g., RouterView rendering different pages)\r\n const patchContainer = prevSubTree.dom?.parentNode as Element || parent;\r\n patch(prevSubTree, subTree, patchContainer);\r\n } else {\r\n // No previous subtree - mount fresh using the component's anchor\r\n // Use mount() instead of render() to support anchor positioning\r\n mount(subTree, parent as Element, anchor || null);\r\n }\r\n internalVNode._subTree = subTree;\r\n }\r\n } finally {\r\n setCurrentInstance(prevInstance);\r\n }\r\n });\r\n\r\n internalVNode._effect = componentEffect;\r\n componentCtx.update = () => componentEffect();\r\n }\r\n\r\n // Use trailing anchor comment as the component's dom reference (like runtime-dom does)\r\n // This ensures the reconciler can use it as a stable anchor\r\n vnode.dom = anchor || endDom;\r\n\r\n // Run mount hooks\r\n const mountCtx = { el: parent as Element };\r\n createdHooks.forEach(hook => hook());\r\n mountHooks.forEach(hook => hook(mountCtx));\r\n\r\n // Store cleanup\r\n vnode.cleanup = () => {\r\n unmountHooks.forEach(hook => hook(mountCtx));\r\n };\r\n\r\n // With trailing markers, the anchor IS the end - return next sibling\r\n return anchor ? anchor.nextSibling : endDom;\r\n}\r\n\r\n// ============= Helper Functions =============\r\n\r\n// filterClientDirectives is imported from shared/utils.ts\r\n\r\n/**\r\n * Fix select element value after hydrating children.\r\n * This is needed because <select>.value only works after <option> children exist in DOM.\r\n */\r\nfunction fixSelectValue(dom: HTMLElement, props: any) {\r\n if (dom.tagName === 'SELECT' && 'value' in props) {\r\n const val = props.value;\r\n if ((dom as HTMLSelectElement).multiple) {\r\n const options = (dom as HTMLSelectElement).options;\r\n const valArray = Array.isArray(val) ? val : [val];\r\n for (let i = 0; i < options.length; i++) {\r\n options[i].selected = valArray.includes(options[i].value);\r\n }\r\n } else {\r\n (dom as HTMLSelectElement).value = String(val);\r\n }\r\n }\r\n}\r\n\r\n// createPropsAccessor, createSlots, and normalizeSubTree are now imported from @sigx/runtime-core\r\n\r\n// ============= Island Hydration =============\r\n\r\n/**\r\n * Hydrate islands based on their strategies (selective hydration)\r\n */\r\nexport function hydrateIslands(): void {\r\n const dataScript = document.getElementById('__SIGX_ISLANDS__');\r\n if (!dataScript) {\r\n return;\r\n }\r\n\r\n let islandData: Record<string, IslandInfo>;\r\n try {\r\n islandData = JSON.parse(dataScript.textContent || '{}');\r\n } catch {\r\n console.error('Failed to parse island data');\r\n return;\r\n }\r\n\r\n for (const [idStr, info] of Object.entries(islandData)) {\r\n const id = parseInt(idStr, 10);\r\n scheduleHydration(id, info);\r\n }\r\n}\r\n\r\n/**\r\n * Schedule hydration based on strategy\r\n */\r\nfunction scheduleHydration(id: number, info: IslandInfo): void {\r\n const marker = findIslandMarker(id);\r\n if (!marker) {\r\n console.warn(`Island marker not found for id ${id}`);\r\n return;\r\n }\r\n\r\n const component = info.componentId ? getComponent(info.componentId) : null;\r\n if (!component && info.strategy !== 'only') {\r\n console.warn(`Component \"${info.componentId}\" not registered for hydration`);\r\n return;\r\n }\r\n\r\n switch (info.strategy) {\r\n case 'load':\r\n hydrateIsland(marker, component!, info);\r\n break;\r\n\r\n case 'idle':\r\n if ('requestIdleCallback' in window) {\r\n requestIdleCallback(() => hydrateIsland(marker, component!, info));\r\n } else {\r\n setTimeout(() => hydrateIsland(marker, component!, info), 200);\r\n }\r\n break;\r\n\r\n case 'visible':\r\n observeVisibility(marker, () => hydrateIsland(marker, component!, info));\r\n break;\r\n\r\n case 'media':\r\n if (info.media) {\r\n const mql = window.matchMedia(info.media);\r\n if (mql.matches) {\r\n hydrateIsland(marker, component!, info);\r\n } else {\r\n mql.addEventListener('change', function handler(e) {\r\n if (e.matches) {\r\n mql.removeEventListener('change', handler);\r\n hydrateIsland(marker, component!, info);\r\n }\r\n });\r\n }\r\n }\r\n break;\r\n\r\n case 'only':\r\n if (component) {\r\n mountClientOnly(marker, component, info);\r\n }\r\n break;\r\n }\r\n}\r\n\r\nfunction findIslandMarker(id: number): Comment | null {\r\n const walker = document.createTreeWalker(\r\n document.body,\r\n NodeFilter.SHOW_COMMENT,\r\n null\r\n );\r\n\r\n let node: Comment | null;\r\n while ((node = walker.nextNode() as Comment | null)) {\r\n if (node.data === `$c:${id}`) {\r\n return node;\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nfunction observeVisibility(marker: Comment, callback: () => void): void {\r\n // With trailing markers, content is BEFORE the marker\r\n // Find an element by looking at previous siblings\r\n let node: Node | null = marker.previousSibling;\r\n while (node && node.nodeType !== Node.ELEMENT_NODE) {\r\n node = node.previousSibling;\r\n }\r\n\r\n if (!node) {\r\n // No element found, hydrate immediately\r\n callback();\r\n return;\r\n }\r\n\r\n const observer = new IntersectionObserver((entries) => {\r\n for (const entry of entries) {\r\n if (entry.isIntersecting) {\r\n observer.disconnect();\r\n callback();\r\n break;\r\n }\r\n }\r\n });\r\n\r\n observer.observe(node as Element);\r\n}\r\n\r\nfunction hydrateIsland(marker: Comment, component: ComponentFactory, info: IslandInfo): void {\r\n // With trailing markers, the content is BEFORE the marker\r\n // Find the container element by looking at previous siblings\r\n let container: Node | null = marker.previousSibling;\r\n while (container && container.nodeType !== Node.ELEMENT_NODE) {\r\n container = container.previousSibling;\r\n }\r\n\r\n if (!container || container.nodeType !== Node.ELEMENT_NODE) {\r\n console.warn('No element found for island hydration');\r\n return;\r\n }\r\n\r\n const props = info.props || {};\r\n\r\n // Set pending server state so context extension picks it up\r\n if (info.state) {\r\n setPendingServerState(info.state);\r\n }\r\n\r\n // Create VNode and render with runtime-dom\r\n const vnode: VNode = {\r\n type: component as any,\r\n props: props,\r\n key: null,\r\n children: [],\r\n dom: null\r\n };\r\n\r\n // Use a wrapper to contain the component\r\n const wrapper = document.createElement('div');\r\n wrapper.style.display = 'contents';\r\n\r\n // Replace the SSR content with the hydrated component\r\n const parent = container.parentNode!;\r\n parent.insertBefore(wrapper, container);\r\n parent.removeChild(container);\r\n\r\n render(vnode, wrapper);\r\n}\r\n\r\nfunction mountClientOnly(marker: Comment, component: ComponentFactory, info: IslandInfo): void {\r\n // With trailing markers, the placeholder is BEFORE the marker\r\n let placeholder = marker.previousSibling;\r\n while (placeholder && placeholder.nodeType !== Node.ELEMENT_NODE) {\r\n placeholder = placeholder.previousSibling;\r\n }\r\n\r\n if (!placeholder || !(placeholder as Element).hasAttribute?.('data-island')) {\r\n return;\r\n }\r\n\r\n const props = info.props || {};\r\n const container = placeholder as Element;\r\n container.innerHTML = '';\r\n\r\n const vnode: VNode = {\r\n type: component as any,\r\n props,\r\n key: null,\r\n children: [],\r\n dom: null\r\n };\r\n\r\n render(vnode, container);\r\n}\r\n\r\n/**\r\n * Set up listener for async components streaming in.\r\n * This is set up at module load time to catch events early.\r\n */\r\nlet _asyncListenerSetup = false;\r\n\r\nfunction ensureAsyncHydrationListener(): void {\r\n if (_asyncListenerSetup) return;\r\n _asyncListenerSetup = true;\r\n\r\n // Listen for async component ready events\r\n document.addEventListener('sigx:async-ready', (event: Event) => {\r\n const customEvent = event as CustomEvent;\r\n const { id, state } = customEvent.detail || {};\r\n\r\n // Invalidate cache so we get fresh island data\r\n invalidateIslandCache();\r\n\r\n // Find the placeholder element\r\n const placeholder = document.querySelector(`[data-async-placeholder=\"${id}\"]`);\r\n if (!placeholder) {\r\n console.warn(`[Hydrate] Could not find placeholder for async component ${id}`);\r\n return;\r\n }\r\n\r\n // Get the island info (should have been updated by the streaming script)\r\n const islandData = getIslandData();\r\n const info = islandData[String(id)];\r\n\r\n if (!info) {\r\n console.warn(`[Hydrate] No island data for async component ${id}`);\r\n return;\r\n }\r\n\r\n // Hydrate the async component\r\n hydrateAsyncComponent(placeholder as Element, info);\r\n });\r\n}\r\n\r\n// Set up the listener immediately when this module loads (browser only)\r\nif (typeof document !== 'undefined') {\r\n ensureAsyncHydrationListener();\r\n}\r\n\r\n/**\r\n * Hydrate an async component that just streamed in.\r\n * The DOM has already been replaced with server-rendered content by $SIGX_REPLACE.\r\n * Since the streamed content doesn't include component markers, we mount fresh\r\n * with the server state restored via the context extension.\r\n */\r\nfunction hydrateAsyncComponent(container: Element, info: IslandInfo): void {\r\n if (!info.componentId) {\r\n console.error(`[Hydrate] No componentId in island info`);\r\n return;\r\n }\r\n\r\n const component = getComponent(info.componentId);\r\n if (!component) {\r\n console.error(`[Hydrate] Component \"${info.componentId}\" not registered`);\r\n return;\r\n }\r\n\r\n const props = info.props || {};\r\n const serverState = info.state;\r\n\r\n // Clear the streamed HTML - we'll mount fresh with reactivity\r\n container.innerHTML = '';\r\n\r\n // Set pending server state so context extension picks it up\r\n if (serverState) {\r\n setPendingServerState(serverState);\r\n }\r\n\r\n // Create vnode (no need for __serverState prop, context extension handles it)\r\n const vnode: VNode = {\r\n type: component as any,\r\n props: props,\r\n key: null,\r\n children: [],\r\n dom: null\r\n };\r\n\r\n // Mount the component fresh\r\n render(vnode, container);\r\n}\r\n","/**\r\n * SSR Client Plugin\r\n * \r\n * Provides app.hydrate() method for client-side hydration of server-rendered HTML.\r\n * This plugin follows the same pattern as the router plugin.\r\n */\r\n\r\nimport type { Plugin, App, AppContext } from '@sigx/runtime-core';\r\nimport { render } from 'sigx';\r\nimport { hydrate as hydrateImpl } from './hydrate.js';\r\n\r\n// ============================================================================\r\n// Type Augmentation\r\n// ============================================================================\r\n\r\n/**\r\n * Hydrate function signature - matches MountFn pattern\r\n */\r\nexport type HydrateFn<TContainer = any> = (\r\n element: any,\r\n container: TContainer,\r\n appContext: AppContext\r\n) => (() => void) | void;\r\n\r\ndeclare module '@sigx/runtime-core' {\r\n interface App<TContainer = any> {\r\n /**\r\n * Hydrate the app from server-rendered HTML.\r\n * \r\n * Unlike mount() which creates new DOM, hydrate() attaches to existing\r\n * server-rendered DOM, adding event handlers and establishing reactivity.\r\n * \r\n * @example\r\n * ```tsx\r\n * import { defineApp } from 'sigx';\r\n * import { ssrClientPlugin } from '@sigx/server-renderer/client';\r\n * \r\n * const app = defineApp(<App />);\r\n * app.use(router)\r\n * .use(ssrClientPlugin)\r\n * .hydrate(document.getElementById('app')!);\r\n * ```\r\n */\r\n hydrate(container: TContainer): App<TContainer>;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Plugin Implementation\r\n// ============================================================================\r\n\r\n/**\r\n * SSR Client Plugin\r\n * \r\n * Adds the hydrate() method to the app instance for client-side hydration.\r\n * Also registers the SSR context extension for all components.\r\n * \r\n * @example\r\n * ```tsx\r\n * import { defineApp } from 'sigx';\r\n * import { ssrClientPlugin } from '@sigx/server-renderer/client';\r\n * \r\n * const app = defineApp(<App />);\r\n * app.use(ssrClientPlugin)\r\n * .use(router)\r\n * .hydrate('#app');\r\n * ```\r\n */\r\nexport const ssrClientPlugin: Plugin = {\r\n name: '@sigx/server-renderer/client',\r\n\r\n install(app: App) {\r\n // Add hydrate method to the app instance\r\n (app as any).hydrate = function(container: Element | string): App {\r\n // Resolve container if string selector\r\n const resolvedContainer = typeof container === 'string'\r\n ? document.querySelector(container)\r\n : container;\r\n\r\n if (!resolvedContainer) {\r\n throw new Error(\r\n `[ssrClientPlugin] Cannot find container: ${container}. ` +\r\n 'Make sure the element exists in the DOM before calling hydrate().'\r\n );\r\n }\r\n\r\n // Get the root component from the app\r\n const rootComponent = (app as any)._rootComponent;\r\n \r\n if (!rootComponent) {\r\n throw new Error(\r\n '[ssrClientPlugin] No root component found on app. ' +\r\n 'Make sure you created the app with defineApp(<Component />).'\r\n );\r\n }\r\n\r\n // Check if there's actual SSR content to hydrate\r\n // If container is empty or only has comments, fall back to client-side render\r\n const hasSSRContent = resolvedContainer.firstElementChild !== null ||\r\n (resolvedContainer.firstChild !== null && \r\n resolvedContainer.firstChild.nodeType !== Node.COMMENT_NODE);\r\n\r\n // Get app context for passing to render (needed for inject() to work)\r\n const appContext = (app as any)._context;\r\n\r\n if (hasSSRContent) {\r\n // Perform hydration with app context for DI\r\n hydrateImpl(rootComponent, resolvedContainer, appContext);\r\n } else {\r\n // No SSR content - fall back to client-side render (dev mode)\r\n render(rootComponent, resolvedContainer, appContext);\r\n }\r\n\r\n // Store container on the vnode for potential unmount\r\n (resolvedContainer as any)._app = app;\r\n\r\n return app;\r\n };\r\n }\r\n};\r\n"],"mappings":"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"}
|