@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.
- package/dist/client/hydrate.d.ts +6 -1
- package/dist/client/hydrate.d.ts.map +1 -1
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -661
- package/dist/client/plugin.d.ts +52 -0
- package/dist/client/plugin.d.ts.map +1 -0
- package/dist/client/registry.d.ts +9 -1
- package/dist/client/registry.d.ts.map +1 -1
- package/dist/client/types.d.ts +1 -21
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client-DiLwBAD-.js +541 -0
- package/dist/client-DiLwBAD-.js.map +1 -0
- package/dist/client-directives.d.ts +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1172
- package/dist/server/index.js +1 -573
- package/dist/server/stream.d.ts +34 -6
- package/dist/server/stream.d.ts.map +1 -1
- 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 +7 -8
- package/dist/client/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/server/index.js.map +0 -1
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/dist/client/types.d.ts
CHANGED
|
@@ -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;
|
|
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
|