@fluixi/dom 1.0.0-alpha.53
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/LICENSE +21 -0
- package/README.md +657 -0
- package/dist/cdn/dom.cjs +1 -0
- package/dist/cdn/dom.d.ts +16 -0
- package/dist/cdn/dom.d.ts.map +1 -0
- package/dist/cdn/dom.global.js +1 -0
- package/dist/cdn/dom.js +15 -0
- package/dist/cdn/dom.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/component/index.cjs +1 -0
- package/dist/lib/component/index.d.ts +2 -0
- package/dist/lib/component/index.d.ts.map +1 -0
- package/dist/lib/component/index.js +1 -0
- package/dist/lib/component/index.mjs +1 -0
- package/dist/lib/component/modifiers.cjs +1 -0
- package/dist/lib/component/modifiers.d.ts +21 -0
- package/dist/lib/component/modifiers.d.ts.map +1 -0
- package/dist/lib/component/modifiers.js +171 -0
- package/dist/lib/component/modifiers.mjs +1 -0
- package/dist/lib/dom/delegate.cjs +1 -0
- package/dist/lib/dom/delegate.d.ts +17 -0
- package/dist/lib/dom/delegate.d.ts.map +1 -0
- package/dist/lib/dom/delegate.js +50 -0
- package/dist/lib/dom/delegate.mjs +1 -0
- package/dist/lib/dom/hydration-state.cjs +1 -0
- package/dist/lib/dom/hydration-state.d.ts +45 -0
- package/dist/lib/dom/hydration-state.d.ts.map +1 -0
- package/dist/lib/dom/hydration-state.js +94 -0
- package/dist/lib/dom/hydration-state.mjs +1 -0
- package/dist/lib/dom/hydration.cjs +1 -0
- package/dist/lib/dom/hydration.d.ts +10 -0
- package/dist/lib/dom/hydration.d.ts.map +1 -0
- package/dist/lib/dom/hydration.js +143 -0
- package/dist/lib/dom/hydration.mjs +1 -0
- package/dist/lib/dom/index.cjs +1 -0
- package/dist/lib/dom/index.d.ts +9 -0
- package/dist/lib/dom/index.d.ts.map +1 -0
- package/dist/lib/dom/index.js +12 -0
- package/dist/lib/dom/index.mjs +1 -0
- package/dist/lib/dom/integration.cjs +1 -0
- package/dist/lib/dom/integration.d.ts +16 -0
- package/dist/lib/dom/integration.d.ts.map +1 -0
- package/dist/lib/dom/integration.js +25 -0
- package/dist/lib/dom/integration.mjs +1 -0
- package/dist/lib/dom/island.cjs +1 -0
- package/dist/lib/dom/island.d.ts +23 -0
- package/dist/lib/dom/island.d.ts.map +1 -0
- package/dist/lib/dom/island.js +100 -0
- package/dist/lib/dom/island.mjs +1 -0
- package/dist/lib/dom/runtime.cjs +1 -0
- package/dist/lib/dom/runtime.d.ts +131 -0
- package/dist/lib/dom/runtime.d.ts.map +1 -0
- package/dist/lib/dom/runtime.js +1425 -0
- package/dist/lib/dom/runtime.mjs +1 -0
- package/dist/lib/dom/server/fx-data.cjs +1 -0
- package/dist/lib/dom/server/fx-data.d.ts +36 -0
- package/dist/lib/dom/server/fx-data.d.ts.map +1 -0
- package/dist/lib/dom/server/fx-data.js +60 -0
- package/dist/lib/dom/server/fx-data.mjs +1 -0
- package/dist/lib/dom/server/host.cjs +1 -0
- package/dist/lib/dom/server/host.d.ts +14 -0
- package/dist/lib/dom/server/host.d.ts.map +1 -0
- package/dist/lib/dom/server/host.js +38 -0
- package/dist/lib/dom/server/host.mjs +1 -0
- package/dist/lib/dom/server/index.cjs +1 -0
- package/dist/lib/dom/server/index.d.ts +13 -0
- package/dist/lib/dom/server/index.d.ts.map +1 -0
- package/dist/lib/dom/server/index.js +12 -0
- package/dist/lib/dom/server/index.mjs +1 -0
- package/dist/lib/dom/server/nodes.cjs +1 -0
- package/dist/lib/dom/server/nodes.d.ts +105 -0
- package/dist/lib/dom/server/nodes.d.ts.map +1 -0
- package/dist/lib/dom/server/nodes.js +304 -0
- package/dist/lib/dom/server/nodes.mjs +1 -0
- package/dist/lib/dom/server/render.cjs +1 -0
- package/dist/lib/dom/server/render.d.ts +45 -0
- package/dist/lib/dom/server/render.d.ts.map +1 -0
- package/dist/lib/dom/server/render.js +174 -0
- package/dist/lib/dom/server/render.mjs +1 -0
- package/dist/lib/dom/server/request-context.cjs +1 -0
- package/dist/lib/dom/server/request-context.d.ts +85 -0
- package/dist/lib/dom/server/request-context.d.ts.map +1 -0
- package/dist/lib/dom/server/request-context.js +122 -0
- package/dist/lib/dom/server/request-context.mjs +1 -0
- package/dist/lib/dom/server/serialize.cjs +1 -0
- package/dist/lib/dom/server/serialize.d.ts +2 -0
- package/dist/lib/dom/server/serialize.d.ts.map +1 -0
- package/dist/lib/dom/server/serialize.js +98 -0
- package/dist/lib/dom/server/serialize.mjs +1 -0
- package/dist/lib/dom/server-renderer.cjs +1 -0
- package/dist/lib/dom/server-renderer.d.ts +2 -0
- package/dist/lib/dom/server-renderer.d.ts.map +1 -0
- package/dist/lib/dom/server-renderer.js +5 -0
- package/dist/lib/dom/server-renderer.mjs +1 -0
- package/dist/lib/dom/types.cjs +1 -0
- package/dist/lib/dom/types.d.ts +119 -0
- package/dist/lib/dom/types.d.ts.map +1 -0
- package/dist/lib/dom/types.js +1 -0
- package/dist/lib/dom/types.mjs +0 -0
- package/dist/lib/dom/utils.cjs +1 -0
- package/dist/lib/dom/utils.d.ts +411 -0
- package/dist/lib/dom/utils.d.ts.map +1 -0
- package/dist/lib/dom/utils.js +746 -0
- package/dist/lib/dom/utils.mjs +1 -0
- package/dist/lib/element/index.cjs +1 -0
- package/dist/lib/element/index.d.ts +2 -0
- package/dist/lib/element/index.d.ts.map +1 -0
- package/dist/lib/element/index.js +1 -0
- package/dist/lib/element/index.mjs +0 -0
- package/dist/lib/element/types.cjs +1 -0
- package/dist/lib/element/types.d.ts +129 -0
- package/dist/lib/element/types.d.ts.map +1 -0
- package/dist/lib/element/types.js +5 -0
- package/dist/lib/element/types.mjs +0 -0
- package/dist/lib/flow/class-map.cjs +1 -0
- package/dist/lib/flow/class-map.d.ts +40 -0
- package/dist/lib/flow/class-map.d.ts.map +1 -0
- package/dist/lib/flow/class-map.js +44 -0
- package/dist/lib/flow/class-map.mjs +1 -0
- package/dist/lib/flow/dynamic.cjs +1 -0
- package/dist/lib/flow/dynamic.d.ts +27 -0
- package/dist/lib/flow/dynamic.d.ts.map +1 -0
- package/dist/lib/flow/dynamic.js +30 -0
- package/dist/lib/flow/dynamic.mjs +1 -0
- package/dist/lib/flow/error-boundary.cjs +1 -0
- package/dist/lib/flow/error-boundary.d.ts +34 -0
- package/dist/lib/flow/error-boundary.d.ts.map +1 -0
- package/dist/lib/flow/error-boundary.js +49 -0
- package/dist/lib/flow/error-boundary.mjs +1 -0
- package/dist/lib/flow/for.cjs +1 -0
- package/dist/lib/flow/for.d.ts +64 -0
- package/dist/lib/flow/for.d.ts.map +1 -0
- package/dist/lib/flow/for.js +28 -0
- package/dist/lib/flow/for.mjs +1 -0
- package/dist/lib/flow/index-flow.cjs +1 -0
- package/dist/lib/flow/index-flow.d.ts +34 -0
- package/dist/lib/flow/index-flow.d.ts.map +1 -0
- package/dist/lib/flow/index-flow.js +27 -0
- package/dist/lib/flow/index-flow.mjs +1 -0
- package/dist/lib/flow/index.cjs +1 -0
- package/dist/lib/flow/index.d.ts +20 -0
- package/dist/lib/flow/index.d.ts.map +1 -0
- package/dist/lib/flow/index.js +11 -0
- package/dist/lib/flow/index.mjs +1 -0
- package/dist/lib/flow/portal.cjs +1 -0
- package/dist/lib/flow/portal.d.ts +30 -0
- package/dist/lib/flow/portal.d.ts.map +1 -0
- package/dist/lib/flow/portal.js +44 -0
- package/dist/lib/flow/portal.mjs +1 -0
- package/dist/lib/flow/show.cjs +1 -0
- package/dist/lib/flow/show.d.ts +35 -0
- package/dist/lib/flow/show.d.ts.map +1 -0
- package/dist/lib/flow/show.js +162 -0
- package/dist/lib/flow/show.mjs +1 -0
- package/dist/lib/flow/style-map.cjs +1 -0
- package/dist/lib/flow/style-map.d.ts +49 -0
- package/dist/lib/flow/style-map.d.ts.map +1 -0
- package/dist/lib/flow/style-map.js +58 -0
- package/dist/lib/flow/style-map.mjs +1 -0
- package/dist/lib/flow/switch.cjs +1 -0
- package/dist/lib/flow/switch.d.ts +63 -0
- package/dist/lib/flow/switch.d.ts.map +1 -0
- package/dist/lib/flow/switch.js +69 -0
- package/dist/lib/flow/switch.mjs +1 -0
- package/dist/lib/flow/utils.cjs +1 -0
- package/dist/lib/flow/utils.d.ts +19 -0
- package/dist/lib/flow/utils.d.ts.map +1 -0
- package/dist/lib/flow/utils.js +34 -0
- package/dist/lib/flow/utils.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +21 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +30 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/jsx/control-flow/flow.cjs +1 -0
- package/dist/lib/jsx/control-flow/flow.d.ts +2 -0
- package/dist/lib/jsx/control-flow/flow.d.ts.map +1 -0
- package/dist/lib/jsx/control-flow/flow.js +627 -0
- package/dist/lib/jsx/control-flow/flow.mjs +0 -0
- package/dist/lib/jsx/control-flow/for-flow.cjs +1 -0
- package/dist/lib/jsx/control-flow/for-flow.d.ts +2 -0
- package/dist/lib/jsx/control-flow/for-flow.d.ts.map +1 -0
- package/dist/lib/jsx/control-flow/for-flow.js +1 -0
- package/dist/lib/jsx/control-flow/for-flow.mjs +0 -0
- package/dist/lib/shared/types.cjs +1 -0
- package/dist/lib/shared/types.d.ts +47 -0
- package/dist/lib/shared/types.d.ts.map +1 -0
- package/dist/lib/shared/types.js +1 -0
- package/dist/lib/shared/types.mjs +0 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server node model — a minimal, serialization-oriented stand-in for the DOM
|
|
3
|
+
* Node/Element API that @fluixi/dom's runtime calls. Under server mode the leaf
|
|
4
|
+
* factories (createNativeElement/createTextNode/createMarker) return these
|
|
5
|
+
* instead of real DOM nodes, so insert/spread/Show/For run unchanged and the
|
|
6
|
+
* result serializes to an HTML string. This is the "universal renderer" seam:
|
|
7
|
+
* our runtime talks to nodes via methods, so we mock exactly those methods
|
|
8
|
+
* (no jsdom). See ./serialize.ts for the string emitter.
|
|
9
|
+
*
|
|
10
|
+
* Only the surface the runtime actually uses is implemented (measured against
|
|
11
|
+
* runtime.ts): the linked tree (parentNode/childNodes/*Sibling + append/insert/
|
|
12
|
+
* remove/replace), attributes, the JSX_PROPERTIES properties (className/value/
|
|
13
|
+
* checked/selected/innerHTML/textContent/innerText/htmlFor/indeterminate),
|
|
14
|
+
* style, classList, and no-op event listeners.
|
|
15
|
+
*/
|
|
16
|
+
import { SERVER_NODE } from './host.js';
|
|
17
|
+
export const NODE_ELEMENT = 1;
|
|
18
|
+
export const NODE_TEXT = 3;
|
|
19
|
+
export const NODE_COMMENT = 8;
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Base node — the linked-tree mechanics insert() relies on.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export class ServerNode {
|
|
24
|
+
// Brand the host.ts guards check for (lets them skip an instanceof on these classes).
|
|
25
|
+
[SERVER_NODE] = true;
|
|
26
|
+
parentNode = null;
|
|
27
|
+
childNodes = [];
|
|
28
|
+
get firstChild() {
|
|
29
|
+
return this.childNodes[0] ?? null;
|
|
30
|
+
}
|
|
31
|
+
get lastChild() {
|
|
32
|
+
return this.childNodes[this.childNodes.length - 1] ?? null;
|
|
33
|
+
}
|
|
34
|
+
get nextSibling() {
|
|
35
|
+
const p = this.parentNode;
|
|
36
|
+
if (!p)
|
|
37
|
+
return null;
|
|
38
|
+
const i = p.childNodes.indexOf(this);
|
|
39
|
+
return i >= 0 ? p.childNodes[i + 1] ?? null : null;
|
|
40
|
+
}
|
|
41
|
+
get previousSibling() {
|
|
42
|
+
const p = this.parentNode;
|
|
43
|
+
if (!p)
|
|
44
|
+
return null;
|
|
45
|
+
const i = p.childNodes.indexOf(this);
|
|
46
|
+
return i > 0 ? p.childNodes[i - 1] ?? null : null;
|
|
47
|
+
}
|
|
48
|
+
get parentElement() {
|
|
49
|
+
return this.parentNode;
|
|
50
|
+
}
|
|
51
|
+
appendChild(child) {
|
|
52
|
+
if (child.parentNode)
|
|
53
|
+
child.parentNode.removeChild(child);
|
|
54
|
+
child.parentNode = this;
|
|
55
|
+
this.childNodes.push(child);
|
|
56
|
+
return child;
|
|
57
|
+
}
|
|
58
|
+
insertBefore(child, ref) {
|
|
59
|
+
if (ref == null)
|
|
60
|
+
return this.appendChild(child);
|
|
61
|
+
if (child.parentNode)
|
|
62
|
+
child.parentNode.removeChild(child);
|
|
63
|
+
const i = this.childNodes.indexOf(ref);
|
|
64
|
+
child.parentNode = this;
|
|
65
|
+
if (i < 0)
|
|
66
|
+
this.childNodes.push(child);
|
|
67
|
+
else
|
|
68
|
+
this.childNodes.splice(i, 0, child);
|
|
69
|
+
return child;
|
|
70
|
+
}
|
|
71
|
+
removeChild(child) {
|
|
72
|
+
const i = this.childNodes.indexOf(child);
|
|
73
|
+
if (i >= 0)
|
|
74
|
+
this.childNodes.splice(i, 1);
|
|
75
|
+
child.parentNode = null;
|
|
76
|
+
return child;
|
|
77
|
+
}
|
|
78
|
+
replaceChild(next, old) {
|
|
79
|
+
const i = this.childNodes.indexOf(old);
|
|
80
|
+
if (i >= 0) {
|
|
81
|
+
if (next.parentNode)
|
|
82
|
+
next.parentNode.removeChild(next);
|
|
83
|
+
next.parentNode = this;
|
|
84
|
+
this.childNodes[i] = next;
|
|
85
|
+
old.parentNode = null;
|
|
86
|
+
}
|
|
87
|
+
return old;
|
|
88
|
+
}
|
|
89
|
+
// Event listeners are inert on the server.
|
|
90
|
+
addEventListener() { }
|
|
91
|
+
removeEventListener() { }
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Text + Comment
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
export class ServerText extends ServerNode {
|
|
97
|
+
nodeType = NODE_TEXT;
|
|
98
|
+
data;
|
|
99
|
+
constructor(value) {
|
|
100
|
+
super();
|
|
101
|
+
this.data = value;
|
|
102
|
+
}
|
|
103
|
+
get nodeValue() {
|
|
104
|
+
return this.data;
|
|
105
|
+
}
|
|
106
|
+
set nodeValue(v) {
|
|
107
|
+
this.data = v == null ? '' : String(v);
|
|
108
|
+
}
|
|
109
|
+
get textContent() {
|
|
110
|
+
return this.data;
|
|
111
|
+
}
|
|
112
|
+
set textContent(v) {
|
|
113
|
+
this.data = v == null ? '' : String(v);
|
|
114
|
+
}
|
|
115
|
+
cloneNode() {
|
|
116
|
+
return new ServerText(this.data);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export class ServerComment extends ServerNode {
|
|
120
|
+
nodeType = NODE_COMMENT;
|
|
121
|
+
data;
|
|
122
|
+
constructor(value) {
|
|
123
|
+
super();
|
|
124
|
+
this.data = value;
|
|
125
|
+
}
|
|
126
|
+
get nodeValue() {
|
|
127
|
+
return this.data;
|
|
128
|
+
}
|
|
129
|
+
set nodeValue(v) {
|
|
130
|
+
this.data = v == null ? '' : String(v);
|
|
131
|
+
}
|
|
132
|
+
cloneNode() {
|
|
133
|
+
return new ServerComment(this.data);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// style + classList helpers (runtime sets element.style.cssText, element.style
|
|
138
|
+
// .camelKey, and element.classList.toggle/add/remove/contains).
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
export class ServerStyle {
|
|
141
|
+
// declared keys are stored as own props; cssText holds a raw string blob.
|
|
142
|
+
cssText = '';
|
|
143
|
+
setProperty(name, value) {
|
|
144
|
+
this[name] = value;
|
|
145
|
+
}
|
|
146
|
+
removeProperty(name) {
|
|
147
|
+
delete this[name];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
class ServerClassList {
|
|
151
|
+
el;
|
|
152
|
+
constructor(el) {
|
|
153
|
+
this.el = el;
|
|
154
|
+
}
|
|
155
|
+
list() {
|
|
156
|
+
const c = this.el.getAttribute('class');
|
|
157
|
+
return c ? c.split(/\s+/).filter(Boolean) : [];
|
|
158
|
+
}
|
|
159
|
+
write(arr) {
|
|
160
|
+
if (arr.length)
|
|
161
|
+
this.el.setAttribute('class', arr.join(' '));
|
|
162
|
+
else
|
|
163
|
+
this.el.removeAttribute('class');
|
|
164
|
+
}
|
|
165
|
+
add(...names) {
|
|
166
|
+
const arr = this.list();
|
|
167
|
+
for (const n of names)
|
|
168
|
+
if (!arr.includes(n))
|
|
169
|
+
arr.push(n);
|
|
170
|
+
this.write(arr);
|
|
171
|
+
}
|
|
172
|
+
remove(...names) {
|
|
173
|
+
this.write(this.list().filter((c) => !names.includes(c)));
|
|
174
|
+
}
|
|
175
|
+
contains(name) {
|
|
176
|
+
return this.list().includes(name);
|
|
177
|
+
}
|
|
178
|
+
toggle(name, force) {
|
|
179
|
+
const has = this.contains(name);
|
|
180
|
+
const on = force === undefined ? !has : force;
|
|
181
|
+
if (on)
|
|
182
|
+
this.add(name);
|
|
183
|
+
else
|
|
184
|
+
this.remove(name);
|
|
185
|
+
return on;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Element — attributes + the JSX_PROPERTIES properties + style/classList.
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
export class ServerElement extends ServerNode {
|
|
192
|
+
nodeType = NODE_ELEMENT;
|
|
193
|
+
localName;
|
|
194
|
+
tagName;
|
|
195
|
+
isSVG;
|
|
196
|
+
namespaceURI;
|
|
197
|
+
attributes = new Map();
|
|
198
|
+
style = new ServerStyle();
|
|
199
|
+
classList = new ServerClassList(this);
|
|
200
|
+
/** raw HTML set via innerHTML — emitted verbatim, bypassing childNodes. */
|
|
201
|
+
rawHTML = null;
|
|
202
|
+
constructor(tag, isSVG = false) {
|
|
203
|
+
super();
|
|
204
|
+
this.localName = tag.toLowerCase();
|
|
205
|
+
this.tagName = isSVG ? tag : tag.toUpperCase();
|
|
206
|
+
this.isSVG = isSVG;
|
|
207
|
+
this.namespaceURI = isSVG ? 'http://www.w3.org/2000/svg' : null;
|
|
208
|
+
}
|
|
209
|
+
setAttribute(name, value) {
|
|
210
|
+
this.attributes.set(name, String(value));
|
|
211
|
+
}
|
|
212
|
+
removeAttribute(name) {
|
|
213
|
+
this.attributes.delete(name);
|
|
214
|
+
}
|
|
215
|
+
getAttribute(name) {
|
|
216
|
+
return this.attributes.has(name) ? this.attributes.get(name) : null;
|
|
217
|
+
}
|
|
218
|
+
hasAttribute(name) {
|
|
219
|
+
return this.attributes.has(name);
|
|
220
|
+
}
|
|
221
|
+
get id() {
|
|
222
|
+
return this.getAttribute('id') ?? '';
|
|
223
|
+
}
|
|
224
|
+
set id(v) {
|
|
225
|
+
if (v == null)
|
|
226
|
+
this.removeAttribute('id');
|
|
227
|
+
else
|
|
228
|
+
this.setAttribute('id', v);
|
|
229
|
+
}
|
|
230
|
+
// JSX_PROPERTIES: className/value/checked/selected/innerHTML/innerText/
|
|
231
|
+
// textContent/indeterminate/htmlFor flow through setProperty (element[k]=v).
|
|
232
|
+
get className() {
|
|
233
|
+
return this.getAttribute('class') ?? '';
|
|
234
|
+
}
|
|
235
|
+
set className(v) {
|
|
236
|
+
if (v == null)
|
|
237
|
+
this.removeAttribute('class');
|
|
238
|
+
else
|
|
239
|
+
this.setAttribute('class', v);
|
|
240
|
+
}
|
|
241
|
+
get htmlFor() {
|
|
242
|
+
return this.getAttribute('for') ?? '';
|
|
243
|
+
}
|
|
244
|
+
set htmlFor(v) {
|
|
245
|
+
if (v == null)
|
|
246
|
+
this.removeAttribute('for');
|
|
247
|
+
else
|
|
248
|
+
this.setAttribute('for', v);
|
|
249
|
+
}
|
|
250
|
+
set value(v) {
|
|
251
|
+
if (v == null)
|
|
252
|
+
this.removeAttribute('value');
|
|
253
|
+
else
|
|
254
|
+
this.setAttribute('value', String(v));
|
|
255
|
+
}
|
|
256
|
+
set checked(v) {
|
|
257
|
+
if (v)
|
|
258
|
+
this.setAttribute('checked', '');
|
|
259
|
+
else
|
|
260
|
+
this.removeAttribute('checked');
|
|
261
|
+
}
|
|
262
|
+
set selected(v) {
|
|
263
|
+
if (v)
|
|
264
|
+
this.setAttribute('selected', '');
|
|
265
|
+
else
|
|
266
|
+
this.removeAttribute('selected');
|
|
267
|
+
}
|
|
268
|
+
// indeterminate is a live-only DOM property with no HTML serialization.
|
|
269
|
+
set indeterminate(_v) { }
|
|
270
|
+
get textContent() {
|
|
271
|
+
return this.childNodes.map((c) => c.textContent ?? '').join('');
|
|
272
|
+
}
|
|
273
|
+
set textContent(v) {
|
|
274
|
+
for (const c of this.childNodes)
|
|
275
|
+
c.parentNode = null;
|
|
276
|
+
this.childNodes = [];
|
|
277
|
+
this.rawHTML = null;
|
|
278
|
+
if (v != null && v !== '')
|
|
279
|
+
this.appendChild(new ServerText(String(v)));
|
|
280
|
+
}
|
|
281
|
+
set innerText(v) {
|
|
282
|
+
this.textContent = v;
|
|
283
|
+
}
|
|
284
|
+
set innerHTML(v) {
|
|
285
|
+
for (const c of this.childNodes)
|
|
286
|
+
c.parentNode = null;
|
|
287
|
+
this.childNodes = [];
|
|
288
|
+
this.rawHTML = v == null ? '' : String(v);
|
|
289
|
+
}
|
|
290
|
+
cloneNode(deep = false) {
|
|
291
|
+
const el = new ServerElement(this.localName, this.isSVG);
|
|
292
|
+
el.attributes = new Map(this.attributes);
|
|
293
|
+
el.style.cssText = this.style.cssText;
|
|
294
|
+
el.rawHTML = this.rawHTML;
|
|
295
|
+
if (deep)
|
|
296
|
+
for (const c of this.childNodes)
|
|
297
|
+
el.appendChild(c.cloneNode(true));
|
|
298
|
+
return el;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Server-only; serialize.ts uses it. The client-facing guards are in host.ts.
|
|
302
|
+
export function isServerNodeValue(x) {
|
|
303
|
+
return x instanceof ServerNode;
|
|
304
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var a=Symbol.for("fluixi.server-node");var g=1,p=3,N=8,c;c=a;var n=class{constructor(){this[c]=!0;this.parentNode=null;this.childNodes=[]}get firstChild(){return this.childNodes[0]??null}get lastChild(){return this.childNodes[this.childNodes.length-1]??null}get nextSibling(){let t=this.parentNode;if(!t)return null;let e=t.childNodes.indexOf(this);return e>=0?t.childNodes[e+1]??null:null}get previousSibling(){let t=this.parentNode;if(!t)return null;let e=t.childNodes.indexOf(this);return e>0?t.childNodes[e-1]??null:null}get parentElement(){return this.parentNode}appendChild(t){return t.parentNode&&t.parentNode.removeChild(t),t.parentNode=this,this.childNodes.push(t),t}insertBefore(t,e){if(e==null)return this.appendChild(t);t.parentNode&&t.parentNode.removeChild(t);let r=this.childNodes.indexOf(e);return t.parentNode=this,r<0?this.childNodes.push(t):this.childNodes.splice(r,0,t),t}removeChild(t){let e=this.childNodes.indexOf(t);return e>=0&&this.childNodes.splice(e,1),t.parentNode=null,t}replaceChild(t,e){let r=this.childNodes.indexOf(e);return r>=0&&(t.parentNode&&t.parentNode.removeChild(t),t.parentNode=this,this.childNodes[r]=t,e.parentNode=null),e}addEventListener(){}removeEventListener(){}},o=class s extends n{constructor(e){super();this.nodeType=p;this.data=e}get nodeValue(){return this.data}set nodeValue(e){this.data=e==null?"":String(e)}get textContent(){return this.data}set textContent(e){this.data=e==null?"":String(e)}cloneNode(){return new s(this.data)}},u=class s extends n{constructor(e){super();this.nodeType=N;this.data=e}get nodeValue(){return this.data}set nodeValue(e){this.data=e==null?"":String(e)}cloneNode(){return new s(this.data)}},l=class{constructor(){this.cssText=""}setProperty(t,e){this[t]=e}removeProperty(t){delete this[t]}},d=class{constructor(t){this.el=t}list(){let t=this.el.getAttribute("class");return t?t.split(/\s+/).filter(Boolean):[]}write(t){t.length?this.el.setAttribute("class",t.join(" ")):this.el.removeAttribute("class")}add(...t){let e=this.list();for(let r of t)e.includes(r)||e.push(r);this.write(e)}remove(...t){this.write(this.list().filter(e=>!t.includes(e)))}contains(t){return this.list().includes(t)}toggle(t,e){let r=this.contains(t),i=e===void 0?!r:e;return i?this.add(t):this.remove(t),i}},h=class s extends n{constructor(e,r=!1){super();this.nodeType=g;this.attributes=new Map;this.style=new l;this.classList=new d(this);this.rawHTML=null;this.localName=e.toLowerCase(),this.tagName=r?e:e.toUpperCase(),this.isSVG=r,this.namespaceURI=r?"http://www.w3.org/2000/svg":null}setAttribute(e,r){this.attributes.set(e,String(r))}removeAttribute(e){this.attributes.delete(e)}getAttribute(e){return this.attributes.has(e)?this.attributes.get(e):null}hasAttribute(e){return this.attributes.has(e)}get id(){return this.getAttribute("id")??""}set id(e){e==null?this.removeAttribute("id"):this.setAttribute("id",e)}get className(){return this.getAttribute("class")??""}set className(e){e==null?this.removeAttribute("class"):this.setAttribute("class",e)}get htmlFor(){return this.getAttribute("for")??""}set htmlFor(e){e==null?this.removeAttribute("for"):this.setAttribute("for",e)}set value(e){e==null?this.removeAttribute("value"):this.setAttribute("value",String(e))}set checked(e){e?this.setAttribute("checked",""):this.removeAttribute("checked")}set selected(e){e?this.setAttribute("selected",""):this.removeAttribute("selected")}set indeterminate(e){}get textContent(){return this.childNodes.map(e=>e.textContent??"").join("")}set textContent(e){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=null,e!=null&&e!==""&&this.appendChild(new o(String(e)))}set innerText(e){this.textContent=e}set innerHTML(e){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=e==null?"":String(e)}cloneNode(e=!1){let r=new s(this.localName,this.isSVG);if(r.attributes=new Map(this.attributes),r.style.cssText=this.style.cssText,r.rawHTML=this.rawHTML,e)for(let i of this.childNodes)r.appendChild(i.cloneNode(!0));return r}};function m(s){return s instanceof n}export{N as NODE_COMMENT,g as NODE_ELEMENT,p as NODE_TEXT,u as ServerComment,h as ServerElement,n as ServerNode,l as ServerStyle,o as ServerText,m as isServerNodeValue};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var y=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var B=(n,e)=>{for(var t in e)y(n,t,{get:e[t],enumerable:!0})},G=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of W(e))!J.call(n,s)&&s!==t&&y(n,s,{get:()=>e[s],enumerable:!(r=U(e,s))||r.enumerable});return n};var K=n=>G(y({},"__esModule",{value:!0}),n);var pe={};B(pe,{renderToString:()=>ue,renderToStringAsync:()=>de,serializeResourceData:()=>z,setDataRedactor:()=>le});module.exports=K(pe);var k=require("@fluixi/reactive/signal"),i=require("@fluixi/reactive/signal");var f=typeof document>"u";function c(n){f=n}var _=Symbol.for("fluixi.server-node"),Q=null;function D(n){Q=n}var w=1,R=3,b=8,M;M=_;var d=class{constructor(){this[M]=!0;this.parentNode=null;this.childNodes=[]}get firstChild(){return this.childNodes[0]??null}get lastChild(){return this.childNodes[this.childNodes.length-1]??null}get nextSibling(){let e=this.parentNode;if(!e)return null;let t=e.childNodes.indexOf(this);return t>=0?e.childNodes[t+1]??null:null}get previousSibling(){let e=this.parentNode;if(!e)return null;let t=e.childNodes.indexOf(this);return t>0?e.childNodes[t-1]??null:null}get parentElement(){return this.parentNode}appendChild(e){return e.parentNode&&e.parentNode.removeChild(e),e.parentNode=this,this.childNodes.push(e),e}insertBefore(e,t){if(t==null)return this.appendChild(e);e.parentNode&&e.parentNode.removeChild(e);let r=this.childNodes.indexOf(t);return e.parentNode=this,r<0?this.childNodes.push(e):this.childNodes.splice(r,0,e),e}removeChild(e){let t=this.childNodes.indexOf(e);return t>=0&&this.childNodes.splice(t,1),e.parentNode=null,e}replaceChild(e,t){let r=this.childNodes.indexOf(t);return r>=0&&(e.parentNode&&e.parentNode.removeChild(e),e.parentNode=this,this.childNodes[r]=e,t.parentNode=null),t}addEventListener(){}removeEventListener(){}},p=class n extends d{constructor(t){super();this.nodeType=R;this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t==null?"":String(t)}get textContent(){return this.data}set textContent(t){this.data=t==null?"":String(t)}cloneNode(){return new n(this.data)}},g=class n extends d{constructor(t){super();this.nodeType=b;this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t==null?"":String(t)}cloneNode(){return new n(this.data)}},S=class{constructor(){this.cssText=""}setProperty(e,t){this[e]=t}removeProperty(e){delete this[e]}},T=class{constructor(e){this.el=e}list(){let e=this.el.getAttribute("class");return e?e.split(/\s+/).filter(Boolean):[]}write(e){e.length?this.el.setAttribute("class",e.join(" ")):this.el.removeAttribute("class")}add(...e){let t=this.list();for(let r of e)t.includes(r)||t.push(r);this.write(t)}remove(...e){this.write(this.list().filter(t=>!e.includes(t)))}contains(e){return this.list().includes(e)}toggle(e,t){let r=this.contains(e),s=t===void 0?!r:t;return s?this.add(e):this.remove(e),s}},h=class n extends d{constructor(t,r=!1){super();this.nodeType=w;this.attributes=new Map;this.style=new S;this.classList=new T(this);this.rawHTML=null;this.localName=t.toLowerCase(),this.tagName=r?t:t.toUpperCase(),this.isSVG=r,this.namespaceURI=r?"http://www.w3.org/2000/svg":null}setAttribute(t,r){this.attributes.set(t,String(r))}removeAttribute(t){this.attributes.delete(t)}getAttribute(t){return this.attributes.has(t)?this.attributes.get(t):null}hasAttribute(t){return this.attributes.has(t)}get id(){return this.getAttribute("id")??""}set id(t){t==null?this.removeAttribute("id"):this.setAttribute("id",t)}get className(){return this.getAttribute("class")??""}set className(t){t==null?this.removeAttribute("class"):this.setAttribute("class",t)}get htmlFor(){return this.getAttribute("for")??""}set htmlFor(t){t==null?this.removeAttribute("for"):this.setAttribute("for",t)}set value(t){t==null?this.removeAttribute("value"):this.setAttribute("value",String(t))}set checked(t){t?this.setAttribute("checked",""):this.removeAttribute("checked")}set selected(t){t?this.setAttribute("selected",""):this.removeAttribute("selected")}set indeterminate(t){}get textContent(){return this.childNodes.map(t=>t.textContent??"").join("")}set textContent(t){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=null,t!=null&&t!==""&&this.appendChild(new p(String(t)))}set innerText(t){this.textContent=t}set innerHTML(t){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=t==null?"":String(t)}cloneNode(t=!1){let r=new n(this.localName,this.isSVG);if(r.attributes=new Map(this.attributes),r.style.cssText=this.style.cssText,r.rawHTML=this.rawHTML,t)for(let s of this.childNodes)r.appendChild(s.cloneNode(!0));return r}};var Y=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),L=/&/g,Z=/</g,ee=/>/g,te=/"/g;function E(n){return n.replace(L,"&").replace(Z,"<").replace(ee,">")}function O(n){return n.replace(L,"&").replace(te,""")}function ne(n){return n.startsWith("--")?n:n.replace(/[A-Z]/g,e=>"-"+e.toLowerCase())}function re(n){if(!n)return"";let e=[];n.cssText&&e.push(n.cssText.trim().replace(/;\s*$/,""));for(let t of Object.keys(n)){if(t==="cssText")continue;let r=n[t];r==null||r===""||e.push(`${ne(t)}: ${r}`)}return e.join("; ")}function se(n){let e="",t=re(n.style);for(let[r,s]of n.attributes)r==="style"&&t||(e+=` ${r}="${O(s)}"`);if(t){let r=n.attributes.get("style"),s=r?`${r.replace(/;\s*$/,"")}; ${t}`:t;e+=` style="${O(s)}"`}return e}function a(n){if(n==null||n===!1||n===!0)return"";if(typeof n=="string")return E(n);if(typeof n=="number")return E(String(n));if(typeof n=="function")return a(n());if(Array.isArray(n))return n.map(a).join("");switch(n.nodeType){case R:return E(n.data??"");case b:return`<!--${n.data??""}-->`;case w:{let e=n.localName,t=`<${e}${se(n)}>`;if(Y.has(e))return t;let r=n.rawHTML!=null?n.rawHTML:(n.childNodes??[]).map(a).join("");return`${t}${r}</${e}>`}}return Array.isArray(n.childNodes)?n.childNodes.map(a).join(""):""}var $="__FX_DATA__";function I(n){return n.replace(/[<>&\u2028\u2029]/g,e=>"\\u"+e.charCodeAt(0).toString(16).padStart(4,"0"))}var m,ie={getStore:()=>m,run(n,e){let t=m;m=n;try{return e()}finally{m=t}}},F=ie;function C(n={}){n.locals||(n.locals=n.request?oe(n.request):{});let e=0,t=0,r=null,s=new Map,o=new Map;return{event:n,routeData:new Map,matchedRoute:new Map,nextId:()=>`s${e++}`,nextResourceId:()=>{if(r===null)return`r${t++}`;let u=s.get(r)??0;return s.set(r,u+1),`${r}:r${u}`},nextIslandNamespace:u=>{let l=o.get(u)??0;return o.set(u,l+1),`${u}#${l}`},withResourceScope(u,l){let v=r;r=u;try{return l()}finally{r=v}},pending:new Set,data:new Map}}function A(n,e){return F.run(n,e)}function N(){return F.getStore()}var j=new WeakMap;function oe(n){let e=j.get(n);return e||(e={},j.set(n,e)),e}var H=!1;function X(){H||(H=!0,D({createElement:(n,e)=>new h(n,e),createText:n=>new p(n),createComment:n=>new g(n)}))}function ue(n,e={}){let t=f;X(),c(!0);let r=C(e.event);try{return A(r,()=>(0,k.createRoot)(s=>{try{let o=typeof n=="function"?n():n;return a(o)}finally{s()}}))}finally{c(t)}}var V=!1;function ae(){V||(V=!0,(0,i.setResourceTracker)(n=>{N()?.pending.add(n)}),(0,i.setResourceIdSource)(()=>N()?.nextResourceId()??""),(0,i.setResourceDataSink)((n,e)=>{n&&N()?.data.set(n,e)}))}var q=null;function le(n){q=n}function z(n){if(n.data.size===0)return"";let e={};for(let[r,s]of n.data){let o=s;if(q)try{o=q(s,r)}catch{continue}o!==void 0&&(e[r]=o)}if(Object.keys(e).length===0)return"";let t=I(JSON.stringify(e));return`<script type="application/json" id="${$}">${t}<\/script>`}var ce=50;async function de(n,e={}){let t=f;X(),c(!0),ae();let r=C(e.event);try{return await A(r,async()=>{e.preload&&await e.preload(e.event);let s,o=null,u=()=>{};(0,k.createRoot)(x=>{u=x,o=(0,i.getOwner)(),s=typeof n=="function"?n():n});let l=()=>{try{(0,i.runWithOwner)(o,()=>a(s))}catch{}};l();let v=0;for(;r.pending.size>0&&v++<ce;){let x=Array.from(r.pending);r.pending.clear(),await Promise.allSettled(x),await(0,i.flush)(),l()}let P=(0,i.runWithOwner)(o,()=>a(s))+z(r);return u(),P})}finally{c(t)}}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type RequestContext, type RequestEvent } from './request-context.js';
|
|
2
|
+
export interface RenderOptions {
|
|
3
|
+
/** request being rendered — exposed to the app via getRequestEvent() */
|
|
4
|
+
event?: RequestEvent;
|
|
5
|
+
/**
|
|
6
|
+
* Runs ONCE inside the request context, before the app renders. Use it to resolve
|
|
7
|
+
* request-scoped state the render depends on (region, locale, session) and seed it
|
|
8
|
+
* into the request data so the client hydrates from the same value. @fluixi/server
|
|
9
|
+
* passes `preloadServerState` here to drive `createIsomorphicState`.
|
|
10
|
+
*/
|
|
11
|
+
preload?: (event?: RequestEvent) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function renderToString(app: () => any, options?: RenderOptions): string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional redactor: transform (or drop) each resource value before it's serialized
|
|
16
|
+
* into `__FX_DATA__`. Resource data is embedded in the page HTML and visible to anyone,
|
|
17
|
+
* so use this to strip server-only fields (tokens, password hashes, internal ids).
|
|
18
|
+
* Return the value to serialize, or `undefined` to omit the entry entirely (the client
|
|
19
|
+
* then refetches that resource instead of seeding it). Server-only, app-wide.
|
|
20
|
+
*
|
|
21
|
+
* setDataRedactor((value) => {
|
|
22
|
+
* if (value && typeof value === 'object' && 'passwordHash' in value) {
|
|
23
|
+
* const { passwordHash, ...safe } = value as any;
|
|
24
|
+
* return safe;
|
|
25
|
+
* }
|
|
26
|
+
* return value;
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
type DataRedactor = (value: unknown, id: string) => unknown;
|
|
30
|
+
export declare function setDataRedactor(fn: DataRedactor | null): void;
|
|
31
|
+
/**
|
|
32
|
+
* Serialize the collected resource data into an inline script the client reads
|
|
33
|
+
* before hydrating (seeds resources so they don't refetch). Empty when no data.
|
|
34
|
+
* Each value passes through the redactor (if set) first.
|
|
35
|
+
*/
|
|
36
|
+
export declare function serializeResourceData(ctx: RequestContext): string;
|
|
37
|
+
/** Ref-count of in-flight async renders, to scope eager stale-effect scheduling. */
|
|
38
|
+
/**
|
|
39
|
+
* Async SSR: render, then await every resource fetch the render kicked off,
|
|
40
|
+
* letting their settle() re-run the render effects in place, until the tree is
|
|
41
|
+
* stable; then serialize. Resolves Suspense boundaries to their content.
|
|
42
|
+
*/
|
|
43
|
+
export declare function renderToStringAsync(app: () => any, options?: RenderOptions): Promise<string>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/lib/dom/server/render.ts"],"names":[],"mappings":"AAiCA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAmBlF;AAqBD;;;;;;;;;;;;;;GAcG;AACH,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;AAE5D,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAsBjE;AAKD,oFAAoF;AAEpF;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,GAAG,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAyDjB"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server rendering — run an imperative app() under server mode and serialize the
|
|
3
|
+
* resulting node tree to HTML. `renderToString` is synchronous render-once (for
|
|
4
|
+
* loader-free apps); `renderToStringAsync` awaits in-flight resources/Suspense
|
|
5
|
+
* before serializing. Both wrap the render in createRoot so effects/owner are
|
|
6
|
+
* disposed afterwards.
|
|
7
|
+
*/
|
|
8
|
+
import { createRoot } from '@fluixi/reactive/signal';
|
|
9
|
+
import { setResourceTracker, setResourceIdSource, setResourceDataSink, getOwner, runWithOwner, flush, } from '@fluixi/reactive/signal';
|
|
10
|
+
import { isServer, setServerMode, registerServerNodes } from './host.js';
|
|
11
|
+
import { ServerElement, ServerText, ServerComment } from './nodes.js';
|
|
12
|
+
import { serializeNode } from './serialize.js';
|
|
13
|
+
// Register the node classes with the runtime seam on first render. Done from here
|
|
14
|
+
// (not the client barrel) so the classes only load on the server.
|
|
15
|
+
let _nodesRegistered = false;
|
|
16
|
+
function ensureServerNodes() {
|
|
17
|
+
if (_nodesRegistered)
|
|
18
|
+
return;
|
|
19
|
+
_nodesRegistered = true;
|
|
20
|
+
registerServerNodes({
|
|
21
|
+
createElement: (tag, isSVG) => new ServerElement(tag, isSVG),
|
|
22
|
+
createText: (value) => new ServerText(value),
|
|
23
|
+
createComment: (value) => new ServerComment(value),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
import { escapeFxJson, FX_DATA_ID } from './fx-data.js';
|
|
27
|
+
import { createRequestContext, runWithRequestContext, getRequestContext, } from './request-context.js';
|
|
28
|
+
export function renderToString(app, options = {}) {
|
|
29
|
+
const prev = isServer;
|
|
30
|
+
ensureServerNodes();
|
|
31
|
+
setServerMode(true);
|
|
32
|
+
const ctx = createRequestContext(options.event);
|
|
33
|
+
try {
|
|
34
|
+
return runWithRequestContext(ctx, () => createRoot((dispose) => {
|
|
35
|
+
try {
|
|
36
|
+
const tree = typeof app === 'function' ? app() : app;
|
|
37
|
+
return serializeNode(tree);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
dispose();
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
setServerMode(prev);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Install the SSR resource hooks once (never unset). They route to the *current*
|
|
49
|
+
// request context — so concurrent async renders stay isolated (with an
|
|
50
|
+
// AsyncLocalStorage store injected) instead of a per-call setter clobbering a
|
|
51
|
+
// sibling render's sink.
|
|
52
|
+
let _hooksInstalled = false;
|
|
53
|
+
function ensureResourceHooks() {
|
|
54
|
+
if (_hooksInstalled)
|
|
55
|
+
return;
|
|
56
|
+
_hooksInstalled = true;
|
|
57
|
+
setResourceTracker((p) => {
|
|
58
|
+
getRequestContext()?.pending.add(p);
|
|
59
|
+
});
|
|
60
|
+
// Stable id (creation order) the client matches during hydration.
|
|
61
|
+
setResourceIdSource(() => getRequestContext()?.nextResourceId() ?? '');
|
|
62
|
+
// Resolved values are collected for serialization into the page.
|
|
63
|
+
setResourceDataSink((id, value) => {
|
|
64
|
+
if (id)
|
|
65
|
+
getRequestContext()?.data.set(id, value);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let _redactor = null;
|
|
69
|
+
export function setDataRedactor(fn) {
|
|
70
|
+
_redactor = fn;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Serialize the collected resource data into an inline script the client reads
|
|
74
|
+
* before hydrating (seeds resources so they don't refetch). Empty when no data.
|
|
75
|
+
* Each value passes through the redactor (if set) first.
|
|
76
|
+
*/
|
|
77
|
+
export function serializeResourceData(ctx) {
|
|
78
|
+
if (ctx.data.size === 0)
|
|
79
|
+
return '';
|
|
80
|
+
const obj = {};
|
|
81
|
+
for (const [k, v] of ctx.data) {
|
|
82
|
+
let out = v;
|
|
83
|
+
if (_redactor) {
|
|
84
|
+
// A throwing redactor must not crash the render or leak — omit on error.
|
|
85
|
+
try {
|
|
86
|
+
out = _redactor(v, k);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (out === undefined)
|
|
93
|
+
continue; // redacted → omit (client refetches)
|
|
94
|
+
obj[k] = out;
|
|
95
|
+
}
|
|
96
|
+
if (Object.keys(obj).length === 0)
|
|
97
|
+
return '';
|
|
98
|
+
// Non-executable JSON block (CSP-friendly; never runs as JS), escaped with the standard
|
|
99
|
+
// SSR-JSON set so no value can break out of the <script>. The client reads it via
|
|
100
|
+
// getServerData() → JSON.parse(textContent).
|
|
101
|
+
const json = escapeFxJson(JSON.stringify(obj));
|
|
102
|
+
return `<script type="application/json" id="${FX_DATA_ID}">${json}</script>`;
|
|
103
|
+
}
|
|
104
|
+
/** Bound on resolve→refetch waterfalls so a buggy loader can't spin forever. */
|
|
105
|
+
const MAX_ASYNC_ROUNDS = 50;
|
|
106
|
+
/** Ref-count of in-flight async renders, to scope eager stale-effect scheduling. */
|
|
107
|
+
/**
|
|
108
|
+
* Async SSR: render, then await every resource fetch the render kicked off,
|
|
109
|
+
* letting their settle() re-run the render effects in place, until the tree is
|
|
110
|
+
* stable; then serialize. Resolves Suspense boundaries to their content.
|
|
111
|
+
*/
|
|
112
|
+
export async function renderToStringAsync(app, options = {}) {
|
|
113
|
+
const prev = isServer;
|
|
114
|
+
ensureServerNodes();
|
|
115
|
+
setServerMode(true);
|
|
116
|
+
ensureResourceHooks();
|
|
117
|
+
const ctx = createRequestContext(options.event);
|
|
118
|
+
try {
|
|
119
|
+
return await runWithRequestContext(ctx, async () => {
|
|
120
|
+
// Resolve request-scoped state (region/locale/session) BEFORE the app renders,
|
|
121
|
+
// inside the request context so it can read the event and write into ctx.data
|
|
122
|
+
// for client transfer. Runs once per request. Guarded so the no-preload path
|
|
123
|
+
// keeps its original synchronous flow into createRoot (an extra `await undefined`
|
|
124
|
+
// tick here reorders the lazy/Suspense drain and breaks hydration).
|
|
125
|
+
if (options.preload)
|
|
126
|
+
await options.preload(options.event);
|
|
127
|
+
let tree;
|
|
128
|
+
let rootOwner = null;
|
|
129
|
+
let dispose = () => { };
|
|
130
|
+
createRoot((d) => {
|
|
131
|
+
dispose = d;
|
|
132
|
+
rootOwner = getOwner();
|
|
133
|
+
tree = typeof app === 'function' ? app() : app;
|
|
134
|
+
});
|
|
135
|
+
// Serialize INSIDE the render root's owner. The probe/serialize below force
|
|
136
|
+
// lazy subtrees to render, which can CREATE resources; running with the root as
|
|
137
|
+
// owner makes those resources owned by the root, so dispose() cancels them. If
|
|
138
|
+
// we serialized with Owner=null (the default after createRoot returns), a
|
|
139
|
+
// resource created during serialize would be unowned → never cancelled → it
|
|
140
|
+
// settles AFTER the response is serialized and the root disposed, re-running an
|
|
141
|
+
// effect on a torn-down/detached tree (the storefront's "page shows, then errors
|
|
142
|
+
// ~2-3s later" crash). Wrapped because a suspended read may throw its promise.
|
|
143
|
+
const probe = () => {
|
|
144
|
+
try {
|
|
145
|
+
runWithOwner(rootOwner, () => serializeNode(tree));
|
|
146
|
+
}
|
|
147
|
+
catch { /* suspended read — resource self-registered */ }
|
|
148
|
+
};
|
|
149
|
+
// Force the (lazily-built) component tree to evaluate so every resource/lazy
|
|
150
|
+
// load is created and self-registers in ctx.pending BEFORE we drain. A tree
|
|
151
|
+
// behind deferred memos (e.g. a router <Outlet/>) renders NOTHING until serialize.
|
|
152
|
+
probe();
|
|
153
|
+
let rounds = 0;
|
|
154
|
+
while (ctx.pending.size > 0 && rounds++ < MAX_ASYNC_ROUNDS) {
|
|
155
|
+
const batch = Array.from(ctx.pending);
|
|
156
|
+
ctx.pending.clear();
|
|
157
|
+
await Promise.allSettled(batch);
|
|
158
|
+
// resource settle() queues the dependent render effects — run them so
|
|
159
|
+
// the tree reflects resolved data (and any waterfall fetch registers).
|
|
160
|
+
await flush();
|
|
161
|
+
// Re-probe: a freshly-resolved lazy/route reveals its OWN resources only
|
|
162
|
+
// when its subtree is evaluated — force that so they register for the next round.
|
|
163
|
+
probe();
|
|
164
|
+
}
|
|
165
|
+
// Serialize the resolved resource data so the client seeds it (no refetch).
|
|
166
|
+
const html = runWithOwner(rootOwner, () => serializeNode(tree)) + serializeResourceData(ctx);
|
|
167
|
+
dispose();
|
|
168
|
+
return html;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
setServerMode(prev);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRoot as H}from"@fluixi/reactive/signal";import{setResourceTracker as Z,setResourceIdSource as ee,setResourceDataSink as te,getOwner as ne,runWithOwner as I,flush as re}from"@fluixi/reactive/signal";var p=typeof document>"u";function l(n){p=n}var A=Symbol.for("fluixi.server-node"),z=null;function q(n){z=n}var S=1,T=3,w=8,k;k=A;var c=class{constructor(){this[k]=!0;this.parentNode=null;this.childNodes=[]}get firstChild(){return this.childNodes[0]??null}get lastChild(){return this.childNodes[this.childNodes.length-1]??null}get nextSibling(){let t=this.parentNode;if(!t)return null;let e=t.childNodes.indexOf(this);return e>=0?t.childNodes[e+1]??null:null}get previousSibling(){let t=this.parentNode;if(!t)return null;let e=t.childNodes.indexOf(this);return e>0?t.childNodes[e-1]??null:null}get parentElement(){return this.parentNode}appendChild(t){return t.parentNode&&t.parentNode.removeChild(t),t.parentNode=this,this.childNodes.push(t),t}insertBefore(t,e){if(e==null)return this.appendChild(t);t.parentNode&&t.parentNode.removeChild(t);let r=this.childNodes.indexOf(e);return t.parentNode=this,r<0?this.childNodes.push(t):this.childNodes.splice(r,0,t),t}removeChild(t){let e=this.childNodes.indexOf(t);return e>=0&&this.childNodes.splice(e,1),t.parentNode=null,t}replaceChild(t,e){let r=this.childNodes.indexOf(e);return r>=0&&(t.parentNode&&t.parentNode.removeChild(t),t.parentNode=this,this.childNodes[r]=t,e.parentNode=null),e}addEventListener(){}removeEventListener(){}},d=class n extends c{constructor(e){super();this.nodeType=T;this.data=e}get nodeValue(){return this.data}set nodeValue(e){this.data=e==null?"":String(e)}get textContent(){return this.data}set textContent(e){this.data=e==null?"":String(e)}cloneNode(){return new n(this.data)}},f=class n extends c{constructor(e){super();this.nodeType=w;this.data=e}get nodeValue(){return this.data}set nodeValue(e){this.data=e==null?"":String(e)}cloneNode(){return new n(this.data)}},x=class{constructor(){this.cssText=""}setProperty(t,e){this[t]=e}removeProperty(t){delete this[t]}},y=class{constructor(t){this.el=t}list(){let t=this.el.getAttribute("class");return t?t.split(/\s+/).filter(Boolean):[]}write(t){t.length?this.el.setAttribute("class",t.join(" ")):this.el.removeAttribute("class")}add(...t){let e=this.list();for(let r of t)e.includes(r)||e.push(r);this.write(e)}remove(...t){this.write(this.list().filter(e=>!t.includes(e)))}contains(t){return this.list().includes(t)}toggle(t,e){let r=this.contains(t),s=e===void 0?!r:e;return s?this.add(t):this.remove(t),s}},g=class n extends c{constructor(e,r=!1){super();this.nodeType=S;this.attributes=new Map;this.style=new x;this.classList=new y(this);this.rawHTML=null;this.localName=e.toLowerCase(),this.tagName=r?e:e.toUpperCase(),this.isSVG=r,this.namespaceURI=r?"http://www.w3.org/2000/svg":null}setAttribute(e,r){this.attributes.set(e,String(r))}removeAttribute(e){this.attributes.delete(e)}getAttribute(e){return this.attributes.has(e)?this.attributes.get(e):null}hasAttribute(e){return this.attributes.has(e)}get id(){return this.getAttribute("id")??""}set id(e){e==null?this.removeAttribute("id"):this.setAttribute("id",e)}get className(){return this.getAttribute("class")??""}set className(e){e==null?this.removeAttribute("class"):this.setAttribute("class",e)}get htmlFor(){return this.getAttribute("for")??""}set htmlFor(e){e==null?this.removeAttribute("for"):this.setAttribute("for",e)}set value(e){e==null?this.removeAttribute("value"):this.setAttribute("value",String(e))}set checked(e){e?this.setAttribute("checked",""):this.removeAttribute("checked")}set selected(e){e?this.setAttribute("selected",""):this.removeAttribute("selected")}set indeterminate(e){}get textContent(){return this.childNodes.map(e=>e.textContent??"").join("")}set textContent(e){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=null,e!=null&&e!==""&&this.appendChild(new d(String(e)))}set innerText(e){this.textContent=e}set innerHTML(e){for(let r of this.childNodes)r.parentNode=null;this.childNodes=[],this.rawHTML=e==null?"":String(e)}cloneNode(e=!1){let r=new n(this.localName,this.isSVG);if(r.attributes=new Map(this.attributes),r.style.cssText=this.style.cssText,r.rawHTML=this.rawHTML,e)for(let s of this.childNodes)r.appendChild(s.cloneNode(!0));return r}};var P=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),D=/&/g,U=/</g,W=/>/g,J=/"/g;function R(n){return n.replace(D,"&").replace(U,"<").replace(W,">")}function _(n){return n.replace(D,"&").replace(J,""")}function B(n){return n.startsWith("--")?n:n.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}function G(n){if(!n)return"";let t=[];n.cssText&&t.push(n.cssText.trim().replace(/;\s*$/,""));for(let e of Object.keys(n)){if(e==="cssText")continue;let r=n[e];r==null||r===""||t.push(`${B(e)}: ${r}`)}return t.join("; ")}function K(n){let t="",e=G(n.style);for(let[r,s]of n.attributes)r==="style"&&e||(t+=` ${r}="${_(s)}"`);if(e){let r=n.attributes.get("style"),s=r?`${r.replace(/;\s*$/,"")}; ${e}`:e;t+=` style="${_(s)}"`}return t}function u(n){if(n==null||n===!1||n===!0)return"";if(typeof n=="string")return R(n);if(typeof n=="number")return R(String(n));if(typeof n=="function")return u(n());if(Array.isArray(n))return n.map(u).join("");switch(n.nodeType){case T:return R(n.data??"");case w:return`<!--${n.data??""}-->`;case S:{let t=n.localName,e=`<${t}${K(n)}>`;if(P.has(t))return e;let r=n.rawHTML!=null?n.rawHTML:(n.childNodes??[]).map(u).join("");return`${e}${r}</${t}>`}}return Array.isArray(n.childNodes)?n.childNodes.map(u).join(""):""}var M="__FX_DATA__";function O(n){return n.replace(/[<>&\u2028\u2029]/g,t=>"\\u"+t.charCodeAt(0).toString(16).padStart(4,"0"))}var h,Q={getStore:()=>h,run(n,t){let e=h;h=n;try{return t()}finally{h=e}}},$=Q;function b(n={}){n.locals||(n.locals=n.request?Y(n.request):{});let t=0,e=0,r=null,s=new Map,i=new Map;return{event:n,routeData:new Map,matchedRoute:new Map,nextId:()=>`s${t++}`,nextResourceId:()=>{if(r===null)return`r${e++}`;let o=s.get(r)??0;return s.set(r,o+1),`${r}:r${o}`},nextIslandNamespace:o=>{let a=i.get(o)??0;return i.set(o,a+1),`${o}#${a}`},withResourceScope(o,a){let N=r;r=o;try{return a()}finally{r=N}},pending:new Set,data:new Map}}function E(n,t){return $.run(n,t)}function m(){return $.getStore()}var L=new WeakMap;function Y(n){let t=L.get(n);return t||(t={},L.set(n,t)),t}var j=!1;function V(){j||(j=!0,q({createElement:(n,t)=>new g(n,t),createText:n=>new d(n),createComment:n=>new f(n)}))}function Te(n,t={}){let e=p;V(),l(!0);let r=b(t.event);try{return E(r,()=>H(s=>{try{let i=typeof n=="function"?n():n;return u(i)}finally{s()}}))}finally{l(e)}}var F=!1;function se(){F||(F=!0,Z(n=>{m()?.pending.add(n)}),ee(()=>m()?.nextResourceId()??""),te((n,t)=>{n&&m()?.data.set(n,t)}))}var C=null;function we(n){C=n}function ie(n){if(n.data.size===0)return"";let t={};for(let[r,s]of n.data){let i=s;if(C)try{i=C(s,r)}catch{continue}i!==void 0&&(t[r]=i)}if(Object.keys(t).length===0)return"";let e=O(JSON.stringify(t));return`<script type="application/json" id="${M}">${e}<\/script>`}var oe=50;async function Re(n,t={}){let e=p;V(),l(!0),se();let r=b(t.event);try{return await E(r,async()=>{t.preload&&await t.preload(t.event);let s,i=null,o=()=>{};H(v=>{o=v,i=ne(),s=typeof n=="function"?n():n});let a=()=>{try{I(i,()=>u(s))}catch{}};a();let N=0;for(;r.pending.size>0&&N++<oe;){let v=Array.from(r.pending);r.pending.clear(),await Promise.allSettled(v),await re(),a()}let X=I(i,()=>u(s))+ie(r);return o(),X})}finally{l(e)}}export{Te as renderToString,Re as renderToStringAsync,ie as serializeResourceData,we as setDataRedactor};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var a=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var k=(e,t)=>{for(var r in t)a(e,r,{get:t[r],enumerable:!0})},C=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of w(t))!S.call(e,s)&&s!==r&&a(e,s,{get:()=>t[s],enumerable:!(n=f(t,s))||n.enumerable});return e};var M=e=>C(a({},"__esModule",{value:!0}),e);var b={};k(b,{createRequestContext:()=>L,getLocals:()=>y,getRequestContext:()=>q,getRequestEvent:()=>x,getRequestLocals:()=>g,runWithRequestContext:()=>v,setRequestStore:()=>T});module.exports=M(b);var c,p={getStore:()=>c,run(e,t){let r=c;c=e;try{return t()}finally{c=r}}},i=p;function T(e){i=e??p}function L(e={}){e.locals||(e.locals=e.request?g(e.request):{});let t=0,r=0,n=null,s=new Map,l=new Map;return{event:e,routeData:new Map,matchedRoute:new Map,nextId:()=>`s${t++}`,nextResourceId:()=>{if(n===null)return`r${r++}`;let o=s.get(n)??0;return s.set(n,o+1),`${n}:r${o}`},nextIslandNamespace:o=>{let u=l.get(o)??0;return l.set(o,u+1),`${o}#${u}`},withResourceScope(o,u){let d=n;n=o;try{return u()}finally{n=d}},pending:new Set,data:new Map}}function v(e,t){return i.run(e,t)}function q(){return i.getStore()}function x(){return q()?.event}function y(){let e=x();return e?(e.locals||(e.locals={}),e.locals):{}}var R=new WeakMap;function g(e){let t=R.get(e);return t||(t={},R.set(e,t)),t}
|