@sigx/server-renderer 0.1.5 → 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,52 @@
1
+ /**
2
+ * SSR Client Plugin
3
+ *
4
+ * Provides app.hydrate() method for client-side hydration of server-rendered HTML.
5
+ * This plugin follows the same pattern as the router plugin.
6
+ */
7
+ import type { Plugin, AppContext } from '@sigx/runtime-core';
8
+ /**
9
+ * Hydrate function signature - matches MountFn pattern
10
+ */
11
+ export type HydrateFn<TContainer = any> = (element: any, container: TContainer, appContext: AppContext) => (() => void) | void;
12
+ declare module '@sigx/runtime-core' {
13
+ interface App<TContainer = any> {
14
+ /**
15
+ * Hydrate the app from server-rendered HTML.
16
+ *
17
+ * Unlike mount() which creates new DOM, hydrate() attaches to existing
18
+ * server-rendered DOM, adding event handlers and establishing reactivity.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * import { defineApp } from 'sigx';
23
+ * import { ssrClientPlugin } from '@sigx/server-renderer/client';
24
+ *
25
+ * const app = defineApp(<App />);
26
+ * app.use(router)
27
+ * .use(ssrClientPlugin)
28
+ * .hydrate(document.getElementById('app')!);
29
+ * ```
30
+ */
31
+ hydrate(container: TContainer): App<TContainer>;
32
+ }
33
+ }
34
+ /**
35
+ * SSR Client Plugin
36
+ *
37
+ * Adds the hydrate() method to the app instance for client-side hydration.
38
+ * Also registers the SSR context extension for all components.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * import { defineApp } from 'sigx';
43
+ * import { ssrClientPlugin } from '@sigx/server-renderer/client';
44
+ *
45
+ * const app = defineApp(<App />);
46
+ * app.use(ssrClientPlugin)
47
+ * .use(router)
48
+ * .hydrate('#app');
49
+ * ```
50
+ */
51
+ export declare const ssrClientPlugin: Plugin;
52
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/client/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAO,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAQlE;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,UAAU,GAAG,GAAG,IAAI,CACtC,OAAO,EAAE,GAAG,EACZ,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,KACrB,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzB,OAAO,QAAQ,oBAAoB,CAAC;IAChC,UAAU,GAAG,CAAC,UAAU,GAAG,GAAG;QAC1B;;;;;;;;;;;;;;;;WAgBG;QACH,OAAO,CAAC,SAAS,EAAE,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;KACnD;CACJ;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,EAAE,MAmD7B,CAAC"}
@@ -3,7 +3,15 @@
3
3
  *
4
4
  * Components must be registered before they can be hydrated as islands.
5
5
  */
6
- import type { ComponentFactory } from './types.js';
6
+ /**
7
+ * Minimal type for component factories used in hydration registry.
8
+ * Compatible with ComponentFactory from runtime-core.
9
+ */
10
+ export interface ComponentFactory {
11
+ __setup: Function;
12
+ __name?: string;
13
+ __async?: boolean;
14
+ }
7
15
  /**
8
16
  * Register a component for island hydration.
9
17
  * Components must be registered before hydrateIslands() is called.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/client/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAOnD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAEjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAMrF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEvE;AASD;;GAEG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,UAAU,CAAuC;IAEzD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAKzD,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI;IAS/D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI/C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAG7B"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/client/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,QAAQ,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAEjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAMrF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEvE;AAID;;GAEG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,UAAU,CAAuC;IAEzD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAKzD,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI;IAS/D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI/C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAG7B"}
@@ -1,14 +1,7 @@
1
1
  /**
2
2
  * Shared types for client-side hydration
3
3
  */
4
- /**
5
- * Component factory with setup function
6
- */
7
- export interface ComponentFactory {
8
- __setup: Function;
9
- __name?: string;
10
- __async?: boolean;
11
- }
4
+ export type { VNode } from 'sigx';
12
5
  /**
13
6
  * Hydration options
14
7
  */
@@ -27,17 +20,4 @@ export interface IslandInfo {
27
20
  /** Captured signal state from async setup for client hydration */
28
21
  state?: Record<string, any>;
29
22
  }
30
- /**
31
- * VNode representation
32
- */
33
- export interface VNode {
34
- type: any;
35
- props: Record<string, any>;
36
- key: string | number | null;
37
- children: VNode[];
38
- dom: any | null;
39
- text?: string | number;
40
- parent?: VNode | null;
41
- cleanup?: () => void;
42
- }
43
23
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,QAAQ,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,KAAK;IAClB,IAAI,EAAE,GAAG,CAAC;IACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B"}
@@ -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