@estjs/template 0.0.12 → 0.0.13-beta.2

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.
@@ -4,27 +4,49 @@ var shared = require('@estjs/shared');
4
4
  var signal = require('@estjs/signal');
5
5
 
6
6
  /**
7
- * @estjs/template v0.0.12
7
+ * @estjs/template v0.0.13-beta.2
8
8
  * (c) 2023-Present jiangxd <jiangxd2016@gmail.com>
9
9
  * @license MIT
10
10
  **/
11
- var __defProp = Object.defineProperty;
12
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
13
- var __hasOwnProp = Object.prototype.hasOwnProperty;
14
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
15
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
16
- var __spreadValues = (a, b) => {
17
- for (var prop in b || (b = {}))
18
- if (__hasOwnProp.call(b, prop))
19
- __defNormalProp(a, prop, b[prop]);
20
- if (__getOwnPropSymbols)
21
- for (var prop of __getOwnPropSymbols(b)) {
22
- if (__propIsEnum.call(b, prop))
23
- __defNormalProp(a, prop, b[prop]);
24
- }
25
- return a;
11
+
12
+
13
+ // src/shared-config.ts
14
+ var componentMap = /* @__PURE__ */ new Map();
15
+ var RenderContext = class {
16
+ constructor() {
17
+ this.renderMode = 0 /* CLIENT */;
18
+ }
19
+ get isSSG() {
20
+ return this.renderMode === 1 /* SSG */;
21
+ }
22
+ get isSSR() {
23
+ return this.renderMode === 2 /* SSR */;
24
+ }
25
+ get isClient() {
26
+ return this.renderMode === 0 /* CLIENT */;
27
+ }
28
+ setSSR() {
29
+ this.renderMode = 2 /* SSR */;
30
+ }
31
+ setSSG() {
32
+ this.renderMode = 1 /* SSG */;
33
+ }
34
+ setClient() {
35
+ this.renderMode = 0 /* CLIENT */;
36
+ }
26
37
  };
27
- var _Hooks = class _Hooks {
38
+ var renderContext = new RenderContext();
39
+ function enterComponent(temp, index) {
40
+ componentMap.set(temp, {
41
+ index
42
+ });
43
+ }
44
+ function getComponentIndex(temp) {
45
+ return componentMap.get(temp).index;
46
+ }
47
+
48
+ // src/lifecycle-context.ts
49
+ var _LifecycleContext = class _LifecycleContext {
28
50
  constructor() {
29
51
  this.hooks = {
30
52
  mounted: /* @__PURE__ */ new Set(),
@@ -40,158 +62,125 @@ var _Hooks = class _Hooks {
40
62
  (_a = this.hooks[hook]) == null ? void 0 : _a.add(cb);
41
63
  }
42
64
  getContext(context) {
43
- return _Hooks.context[context];
65
+ return _LifecycleContext.context[context];
44
66
  }
45
67
  setContext(context, value) {
46
- _Hooks.context[context] = value;
68
+ _LifecycleContext.context[context] = value;
47
69
  }
48
70
  initRef() {
49
- _Hooks.ref = this;
71
+ _LifecycleContext.ref = this;
50
72
  }
51
73
  removeRef() {
52
- _Hooks.ref = null;
74
+ _LifecycleContext.ref = null;
75
+ }
76
+ clearHooks() {
77
+ Object.values(this.hooks).forEach((set) => set.clear());
53
78
  }
54
79
  };
55
- _Hooks.ref = null;
56
- _Hooks.context = {};
57
- var Hooks = _Hooks;
58
- var ComponentNode = class extends Hooks {
59
- constructor(template2, props, key) {
80
+ // current context ref
81
+ _LifecycleContext.ref = null;
82
+ _LifecycleContext.context = {};
83
+ var LifecycleContext = _LifecycleContext;
84
+
85
+ // src/ssg-node.ts
86
+ function isSSGNode(node) {
87
+ return node instanceof SSGNode;
88
+ }
89
+ var componentIndex = 1;
90
+ var SSGNode = class extends LifecycleContext {
91
+ constructor(template, props = {}, key) {
60
92
  super();
61
- this.template = template2;
93
+ this.template = template;
62
94
  this.props = props;
63
95
  this.key = key;
64
- this.proxyProps = {};
65
- this.emitter = /* @__PURE__ */ new Set();
66
- this.mounted = false;
67
- this.rootNode = null;
68
- this.context = {};
69
- this.trackMap = /* @__PURE__ */ new Map();
70
- this.proxyProps = signal.signalObject(
71
- props,
72
- (key2) => shared.startsWith(key2, "on") || shared.startsWith(key2, "update")
73
- );
74
- this.key = this.key || props.key;
75
- }
76
- get firstChild() {
77
- var _a, _b;
78
- return (_b = (_a = this.rootNode) == null ? void 0 : _a.firstChild) != null ? _b : null;
79
- }
80
- get isConnected() {
81
- var _a, _b;
82
- return (_b = (_a = this.rootNode) == null ? void 0 : _a.isConnected) != null ? _b : false;
83
- }
84
- inheritNode(node) {
85
- this.context = node.context;
86
- this.hooks = node.hooks;
87
- Object.assign(this.proxyProps, node.proxyProps);
88
- this.rootNode = node.rootNode;
89
- this.trackMap = node.trackMap;
90
- const props = this.props;
91
- this.props = node.props;
92
- this.patchProps(props);
93
- }
94
- mount(parent, before) {
95
- var _a, _b, _c, _d;
96
- if (!shared.isFunction(this.template)) {
97
- throw new Error("Template must be a function");
98
- }
99
- if (this.isConnected) {
100
- return (_b = (_a = this.rootNode) == null ? void 0 : _a.mount(parent, before)) != null ? _b : [];
96
+ enterComponent(template, componentIndex);
97
+ if (shared.isArray(this.template)) {
98
+ const PLACEHOLDER = " __PLACEHOLDER__ ";
99
+ const htmlString = this.template.join(PLACEHOLDER);
100
+ const processedString = htmlString.replaceAll(/(<[^>]+>)|([^<]+)/g, (match, p1, p2) => {
101
+ if (p1) {
102
+ if (p1.includes("data-ci")) return match;
103
+ return p1.replace(/<\s*([\da-z]+)(\s[^>]*)?>/i, (_, tagName, attrs) => {
104
+ return `<${tagName} data-ci="${componentIndex}"${attrs || ""}>`;
105
+ });
106
+ } else if (p2 && p2.replace(PLACEHOLDER, "").trim()) {
107
+ return `<!--${0 /* TEXT */}-${componentIndex}-->${p2}<!$>`;
108
+ }
109
+ return match;
110
+ });
111
+ this.template = processedString.split(PLACEHOLDER);
101
112
  }
113
+ }
114
+ mount() {
102
115
  this.initRef();
103
- this.rootNode = this.template(signal.useReactive(this.proxyProps, ["children"]));
116
+ const output = this.render();
104
117
  this.removeRef();
105
- this.mounted = true;
106
- const mountedNode = (_d = (_c = this.rootNode) == null ? void 0 : _c.mount(parent, before)) != null ? _d : [];
107
- this.hooks.mounted.forEach((handler) => handler());
108
- this.patchProps(this.props);
109
- return mountedNode;
110
- }
111
- unmount() {
112
- var _a;
113
- this.hooks.destroy.forEach((handler) => handler());
114
- Object.values(this.hooks).forEach((set) => set.clear());
115
- (_a = this.rootNode) == null ? void 0 : _a.unmount();
116
- this.rootNode = null;
117
- this.proxyProps = {};
118
- this.mounted = false;
119
- this.emitter.forEach((emitter) => emitter());
118
+ return output;
120
119
  }
121
- getNodeTrack(trackKey, suppressCleanupCall) {
122
- let track = this.trackMap.get(trackKey);
123
- if (!track) {
124
- track = { cleanup: () => {
125
- } };
126
- this.trackMap.set(trackKey, track);
127
- }
128
- if (!suppressCleanupCall) {
129
- track.cleanup();
120
+ render() {
121
+ if (shared.isFunction(this.template)) {
122
+ const root = this.template(this.props);
123
+ if (isSSGNode(root)) {
124
+ return root.mount();
125
+ } else {
126
+ return String(root);
127
+ }
130
128
  }
131
- return track;
132
- }
133
- patchProps(props) {
134
- var _a, _b, _c, _d;
135
- for (const [key, prop] of Object.entries(props)) {
136
- if (shared.startsWith(key, "on") && ((_a = this.rootNode) == null ? void 0 : _a.nodes)) {
137
- const event = key.slice(2).toLowerCase();
138
- const listener = prop;
139
- const cleanup = addEventListener(this.rootNode.nodes[0], event, listener);
140
- this.emitter.add(cleanup);
141
- } else if (key === "ref") {
142
- props[key].value = (_b = this.rootNode) == null ? void 0 : _b.nodes[0];
143
- } else if (shared.startsWith(key, "update")) {
144
- props[key] = signal.isSignal(prop) ? prop.value : prop;
145
- } else if (key !== "children") {
146
- const newValue = (_d = (_c = this.proxyProps)[key]) != null ? _d : _c[key] = signal.useSignal(prop);
147
- const track = this.getNodeTrack(key);
148
- track.cleanup = signal.useEffect(() => {
149
- newValue.value = shared.isFunction(prop) ? prop() : prop;
129
+ const template = this.template;
130
+ Object.keys(this.props).forEach((key) => {
131
+ const cur = this.props[key];
132
+ const childrens = cur.children;
133
+ normalizeProp(cur);
134
+ const findIndex = template.findIndex((t) => t.includes(`data-hk="${key}"`));
135
+ if (childrens) {
136
+ childrens.forEach(([child]) => {
137
+ componentIndex++;
138
+ const children = renderChildren(child, cur);
139
+ this.template[findIndex] += children;
150
140
  });
151
141
  }
152
- }
153
- this.props = props;
142
+ this.template[findIndex].replaceAll(
143
+ `data-hk="${key}"`,
144
+ `data-hk="${key}" ${generateAttributes(cur)}`
145
+ );
146
+ });
147
+ return template.join("");
154
148
  }
155
149
  };
156
-
157
- // src/template.ts
158
- function h(_template, props, key) {
159
- if (shared.isString(_template)) {
160
- if (isHtmlTagName(_template)) {
161
- _template = convertToHtmlTag(_template);
162
- props = {
163
- 1: props
164
- };
165
- }
166
- if (_template === "") {
167
- props = {
168
- 0: props
169
- };
150
+ function normalizeProp(props) {
151
+ Object.entries(props).forEach(([key, value]) => {
152
+ if (key === "children") {
153
+ delete props[key];
154
+ } else if (shared.isFunction(value)) {
155
+ delete props[key];
156
+ } else if (signal.isSignal(value)) {
157
+ props[key] = value.value;
170
158
  }
171
- _template = template(_template);
172
- }
173
- return shared.isFunction(_template) ? new ComponentNode(_template, props, key) : new TemplateNode(_template, props, key);
174
- }
175
- function isComponent(node) {
176
- return node instanceof ComponentNode;
177
- }
178
- function isJsxElement(node) {
179
- return node instanceof ComponentNode || node instanceof TemplateNode;
180
- }
181
- function template(html) {
182
- const template2 = document.createElement("template");
183
- template2.innerHTML = closeHtmlTags(html);
184
- return template2;
159
+ });
185
160
  }
186
- function Fragment(props) {
187
- return props.children;
161
+ function generateAttributes(props) {
162
+ return Object.entries(props).filter(([key, value]) => key !== "children" && !shared.isFunction(value)).map(([key, value]) => `${key}="${shared.escape(String(value))}"`).join(" ");
163
+ }
164
+ function renderChildren(children, prop) {
165
+ if (shared.isFunction(children)) {
166
+ return renderChildren(children(prop), prop);
167
+ } else if (signal.isSignal(children)) {
168
+ return `<!--${1 /* TEXT_COMPONENT */}-${componentIndex}-->${children.value}<!$>`;
169
+ } else if (isSSGNode(children)) {
170
+ const childResult = children.mount();
171
+ return shared.isFunction(childResult) ? childResult(prop) : extractSignal(childResult);
172
+ } else {
173
+ return `<!--${1 /* TEXT_COMPONENT */}-${componentIndex}-->${children}<!$>`;
174
+ }
188
175
  }
189
176
 
190
177
  // src/utils.ts
191
- var selfClosingTags = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr";
192
- var htmlTags = "a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,nav,nobr,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rb,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp";
178
+ var selfClosingTags = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr".split(",");
179
+ var htmlTags = "a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,nav,nobr,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rb,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp".split(
180
+ ","
181
+ );
193
182
  function coerceNode(data) {
194
- if (isJsxElement(data) || data instanceof Node) {
183
+ if (isJsxElement(data) || data instanceof Node || isSSGNode(data)) {
195
184
  return data;
196
185
  }
197
186
  const text = shared.isFalsy(data) ? "" : String(data);
@@ -199,11 +188,12 @@ function coerceNode(data) {
199
188
  }
200
189
  function insertChild(parent, child, before = null) {
201
190
  const beforeNode = isJsxElement(before) ? before.firstChild : before;
191
+ const ssr = renderContext.isSSR;
202
192
  if (isJsxElement(child)) {
203
193
  child.mount(parent, beforeNode);
204
- } else if (beforeNode) {
194
+ } else if (beforeNode && !ssr) {
205
195
  beforeNode.before(child);
206
- } else {
196
+ } else if (!ssr) {
207
197
  parent.append(child);
208
198
  }
209
199
  }
@@ -225,7 +215,7 @@ function setAttribute(element, attr, value) {
225
215
  if (attr === "class") {
226
216
  if (typeof value === "string") {
227
217
  element.className = value;
228
- } else if (Array.isArray(value)) {
218
+ } else if (shared.isArray(value)) {
229
219
  element.className = value.join(" ");
230
220
  } else if (value && typeof value === "object") {
231
221
  element.className = Object.entries(value).reduce((acc, [key, value2]) => acc + (value2 ? ` ${key}` : ""), "").trim();
@@ -248,14 +238,14 @@ function setAttribute(element, attr, value) {
248
238
  } else if (value === true) {
249
239
  element.setAttribute(attr, "");
250
240
  } else {
251
- if (element instanceof HTMLInputElement) {
241
+ if (element instanceof HTMLInputElement && attr === "value") {
252
242
  element.value = String(value);
253
243
  } else {
254
244
  element.setAttribute(attr, String(value));
255
245
  }
256
246
  }
257
247
  }
258
- function binNode(node, setter) {
248
+ function bindNode(node, setter) {
259
249
  if (node instanceof HTMLInputElement) {
260
250
  switch (node.type) {
261
251
  case "checkbox":
@@ -298,16 +288,12 @@ function binNode(node, setter) {
298
288
  });
299
289
  }
300
290
  }
301
- var p = Promise.resolve();
302
- function nextTick(fn) {
303
- return fn ? p.then(fn) : p;
304
- }
291
+ Promise.resolve();
305
292
  function addEventListener(node, eventName, handler) {
306
293
  node.addEventListener(eventName, handler);
307
294
  return () => node.removeEventListener(eventName, handler);
308
295
  }
309
296
  function closeHtmlTags(input) {
310
- const selfClosingTagList = selfClosingTags.split(",");
311
297
  const tagStack = [];
312
298
  const output = [];
313
299
  const tagPattern = /<\/?([\da-z-]+)([^>]*)>/gi;
@@ -329,7 +315,7 @@ function closeHtmlTags(input) {
329
315
  if (tagStack.length > 0) {
330
316
  tagStack.pop();
331
317
  }
332
- } else if (!selfClosingTagList.includes(tagName)) {
318
+ } else if (!selfClosingTags.includes(tagName)) {
333
319
  tagStack.push(tagName);
334
320
  }
335
321
  output.push(fullMatch);
@@ -344,46 +330,147 @@ function closeHtmlTags(input) {
344
330
  return output.join("");
345
331
  }
346
332
  function isHtmlTagName(tagName) {
347
- const htmlTagsList = htmlTags.split(",");
348
- return htmlTagsList.includes(tagName);
333
+ return htmlTags.includes(tagName);
349
334
  }
350
335
  function convertToHtmlTag(tagName) {
351
- const selfClosingTagList = selfClosingTags.split(",");
352
- if (selfClosingTagList.includes(tagName)) {
336
+ if (selfClosingTags.includes(tagName)) {
353
337
  return `<${tagName}/>`;
354
338
  } else {
355
339
  return `<${tagName}></${tagName}>`;
356
340
  }
357
341
  }
342
+ function extractSignal(signal$1) {
343
+ if (signal.isSignal(signal$1)) {
344
+ return signal$1.value;
345
+ } else {
346
+ return signal$1;
347
+ }
348
+ }
349
+ var ComponentNode = class extends LifecycleContext {
350
+ constructor(template, props, key) {
351
+ super();
352
+ this.template = template;
353
+ this.props = props;
354
+ this.key = key;
355
+ this.emitter = /* @__PURE__ */ new Set();
356
+ this.rootNode = null;
357
+ this.trackMap = /* @__PURE__ */ new Map();
358
+ this.proxyProps = props ? signal.useReactive(
359
+ props,
360
+ (key2) => shared.startsWith(key2, "on") || shared.startsWith(key2, "update") || key2 === "children"
361
+ ) : {};
362
+ }
363
+ get firstChild() {
364
+ var _a, _b;
365
+ return (_b = (_a = this.rootNode) == null ? void 0 : _a.firstChild) != null ? _b : null;
366
+ }
367
+ get isConnected() {
368
+ var _a, _b;
369
+ return (_b = (_a = this.rootNode) == null ? void 0 : _a.isConnected) != null ? _b : false;
370
+ }
371
+ mount(parent, before) {
372
+ var _a, _b, _c, _d;
373
+ if (!shared.isFunction(this.template)) {
374
+ throw new Error("Template must be a function");
375
+ }
376
+ if (this.isConnected) {
377
+ return (_b = (_a = this.rootNode) == null ? void 0 : _a.mount(parent, before)) != null ? _b : [];
378
+ }
379
+ this.initRef();
380
+ this.rootNode = this.template(this.proxyProps);
381
+ const mountedNode = (_d = (_c = this.rootNode) == null ? void 0 : _c.mount(parent, before)) != null ? _d : [];
382
+ this.hooks.mounted.forEach((handler) => handler());
383
+ this.patchProps(this.props);
384
+ this.removeRef();
385
+ return mountedNode;
386
+ }
387
+ unmount() {
388
+ var _a;
389
+ this.hooks.destroy.forEach((handler) => handler());
390
+ this.clearHooks();
391
+ (_a = this.rootNode) == null ? void 0 : _a.unmount();
392
+ this.rootNode = null;
393
+ this.proxyProps = {};
394
+ for (const cleanup of this.emitter) {
395
+ cleanup();
396
+ }
397
+ this.emitter.clear();
398
+ }
399
+ /**
400
+ * Inherit props and state from another ComponentNode.
401
+ * It will:
402
+ * 1. Copy props from the node to this proxyProps.
403
+ * 2. Copy the rootNode, trackMap and hooks from the node.
404
+ * 3. Copy the props from the node to this.
405
+ * 4. Patch props from the props passed in the constructor.
406
+ * @param node The node to inherit from.
407
+ */
408
+ inheritNode(node) {
409
+ Object.assign(this.proxyProps, node.proxyProps);
410
+ this.rootNode = node.rootNode;
411
+ this.trackMap = node.trackMap;
412
+ this.hooks = node.hooks;
413
+ const props = this.props;
414
+ this.props = node.props;
415
+ this.patchProps(props);
416
+ }
417
+ /**
418
+ * Get a NodeTrack from the trackMap. If the track is not in the trackMap, create a new one.
419
+ * Then, call the cleanup function to remove any previously registered hooks.
420
+ * @param trackKey the key of the node track to get.
421
+ * @returns the NodeTrack, cleaned up and ready to use.
422
+ */
423
+ getNodeTrack(trackKey) {
424
+ let track = this.trackMap.get(trackKey);
425
+ if (!track) {
426
+ track = { cleanup: () => {
427
+ } };
428
+ this.trackMap.set(trackKey, track);
429
+ }
430
+ track.cleanup();
431
+ return track;
432
+ }
433
+ /**
434
+ * Patch the props of this node.
435
+ * It will:
436
+ * 1. Iterate the props and patch it.
437
+ * 2. If the prop is a event handler, add a event listener to the first child of the node.
438
+ * 3. If the prop is a ref, set the first child of the node to the ref.
439
+ * 4. If the prop is a update handler, update the prop in the node's props.
440
+ * 5. If the prop is a normal prop, create a signal for it and then patch it.
441
+ * @param props The props to patch.
442
+ */
443
+ patchProps(props) {
444
+ var _a, _b;
445
+ if (!props) {
446
+ return;
447
+ }
448
+ for (const [key, prop] of Object.entries(props)) {
449
+ if (shared.startsWith(key, "on") && ((_a = this.rootNode) == null ? void 0 : _a.firstChild)) {
450
+ const event = key.slice(2).toLowerCase();
451
+ const cleanup = addEventListener(this.rootNode.nodes[0], event, prop);
452
+ this.emitter.add(cleanup);
453
+ } else if (key === "ref") {
454
+ prop.value = (_b = this.rootNode) == null ? void 0 : _b.firstChild;
455
+ } else if (shared.startsWith(key, "update")) {
456
+ this.props[key] = extractSignal(prop);
457
+ } else if (key !== "children") {
458
+ const track = this.getNodeTrack(key);
459
+ track.cleanup = signal.useEffect(() => {
460
+ this.proxyProps[key] = shared.isFunction(prop) ? prop() : prop;
461
+ });
462
+ }
463
+ }
464
+ this.props = props;
465
+ }
466
+ };
358
467
 
359
468
  // src/patch.ts
360
469
  function patchChildren(parent, childrenMap, nextChildren, before) {
361
470
  const result = /* @__PURE__ */ new Map();
362
471
  const children = Array.from(childrenMap.values());
363
- const childrenLength = children.length;
364
472
  if (childrenMap.size > 0 && nextChildren.length === 0) {
365
- if (parent.childNodes.length === childrenLength + (before ? 1 : 0)) {
366
- parent.innerHTML = "";
367
- if (before) {
368
- insertChild(parent, before);
369
- }
370
- } else {
371
- const range = document.createRange();
372
- const child = children[0];
373
- const start = isJsxElement(child) ? child.firstChild : child;
374
- range.setStartBefore(start);
375
- if (before) {
376
- range.setEndBefore(before);
377
- } else {
378
- range.setEndAfter(parent);
379
- }
380
- range.deleteContents();
381
- }
382
- children.forEach((node) => {
383
- if (isJsxElement(node)) {
384
- node.unmount();
385
- }
386
- });
473
+ clearChildren(parent, children, before);
387
474
  return result;
388
475
  }
389
476
  const replaces = [];
@@ -424,6 +511,30 @@ function patchChildren(parent, childrenMap, nextChildren, before) {
424
511
  });
425
512
  return result;
426
513
  }
514
+ function clearChildren(parent, children, before) {
515
+ if (parent.childNodes.length === children.length + (before ? 1 : 0)) {
516
+ parent.innerHTML = "";
517
+ if (before) {
518
+ insertChild(parent, before);
519
+ }
520
+ } else {
521
+ const range = document.createRange();
522
+ const child = children[0];
523
+ const start = isJsxElement(child) ? child.firstChild : child;
524
+ range.setStartBefore(start);
525
+ if (before) {
526
+ range.setEndBefore(before);
527
+ } else {
528
+ range.setEndAfter(parent);
529
+ }
530
+ range.deleteContents();
531
+ }
532
+ children.forEach((node) => {
533
+ if (isJsxElement(node)) {
534
+ node.unmount();
535
+ }
536
+ });
537
+ }
427
538
  function patch(parent, node, next) {
428
539
  if (node === next) {
429
540
  return node;
@@ -460,30 +571,34 @@ function getKey(node, index) {
460
571
  }
461
572
 
462
573
  // src/template-node.ts
463
- var TemplateNode = class _TemplateNode {
464
- constructor(template2, props, key) {
465
- this.template = template2;
574
+ var TemplateNode = class {
575
+ constructor(template, props, key) {
576
+ this.template = template;
466
577
  this.props = props;
467
578
  this.key = key;
468
579
  this.treeMap = /* @__PURE__ */ new Map();
469
580
  this.mounted = false;
470
581
  this.nodes = [];
471
- this.provides = {};
472
582
  this.trackMap = /* @__PURE__ */ new Map();
583
+ this.bindValueKeys = [];
473
584
  this.parent = null;
474
- this.key = this.key || props.key;
585
+ this.key || (this.key = props == null ? void 0 : props.key);
586
+ if (renderContext.isSSR) {
587
+ this.componentIndex = getComponentIndex(this.template);
588
+ }
589
+ }
590
+ addEventListener() {
591
+ }
592
+ removeEventListener() {
475
593
  }
476
594
  get firstChild() {
477
595
  var _a;
478
596
  return (_a = this.nodes[0]) != null ? _a : null;
479
597
  }
598
+ // is mounted
480
599
  get isConnected() {
481
600
  return this.mounted;
482
601
  }
483
- addEventListener() {
484
- }
485
- removeEventListener() {
486
- }
487
602
  mount(parent, before) {
488
603
  var _a;
489
604
  this.parent = parent;
@@ -491,6 +606,9 @@ var TemplateNode = class _TemplateNode {
491
606
  this.nodes.forEach((node) => insertChild(parent, node, before));
492
607
  return this.nodes;
493
608
  }
609
+ if (shared.isArray(this.template)) {
610
+ this.template = createTemplate(this.template.join(""));
611
+ }
494
612
  const cloneNode = this.template.content.cloneNode(true);
495
613
  const firstChild = cloneNode.firstChild;
496
614
  if ((_a = firstChild == null ? void 0 : firstChild.hasAttribute) == null ? void 0 : _a.call(firstChild, "_svg_")) {
@@ -500,23 +618,20 @@ var TemplateNode = class _TemplateNode {
500
618
  });
501
619
  }
502
620
  this.nodes = Array.from(cloneNode.childNodes);
503
- this.mapNodeTree(parent, cloneNode);
621
+ if (renderContext.isSSR) {
622
+ this.mapSSGNodeTree(parent);
623
+ } else {
624
+ this.mapNodeTree(parent, cloneNode);
625
+ }
504
626
  insertChild(parent, cloneNode, before);
505
- this.patchNodes(this.props);
627
+ this.patchProps(this.props);
506
628
  this.mounted = true;
507
629
  return this.nodes;
508
630
  }
509
631
  unmount() {
510
632
  this.trackMap.forEach((track) => {
511
- var _a, _b;
633
+ var _a;
512
634
  (_a = track.cleanup) == null ? void 0 : _a.call(track);
513
- (_b = track.lastNodes) == null ? void 0 : _b.forEach((node) => {
514
- if (track.isRoot) {
515
- removeChild(node);
516
- } else if (node instanceof _TemplateNode) {
517
- node.unmount();
518
- }
519
- });
520
635
  });
521
636
  this.trackMap.clear();
522
637
  this.treeMap.clear();
@@ -524,6 +639,52 @@ var TemplateNode = class _TemplateNode {
524
639
  this.nodes = [];
525
640
  this.mounted = false;
526
641
  }
642
+ patchProps(props) {
643
+ if (!props) return;
644
+ Object.entries(props).forEach(([key, value]) => {
645
+ const index = Number(key);
646
+ const node = this.treeMap.get(index);
647
+ if (node) {
648
+ this.patchProp(key, node, value, index === 0);
649
+ }
650
+ });
651
+ this.props = props;
652
+ }
653
+ inheritNode(node) {
654
+ this.mounted = node.mounted;
655
+ this.nodes = node.nodes;
656
+ this.trackMap = node.trackMap;
657
+ this.treeMap = node.treeMap;
658
+ const props = this.props;
659
+ this.props = node.props;
660
+ this.patchProps(props);
661
+ }
662
+ mapSSGNodeTree(parent) {
663
+ this.treeMap.set(0, parent);
664
+ const walk = (node) => {
665
+ var _a;
666
+ if (node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
667
+ if (node.nodeType === Node.COMMENT_NODE) {
668
+ const [type, index] = ((_a = node.textContent) == null ? void 0 : _a.split("-")) || "";
669
+ if (0 /* TEXT */ === +type && +index === this.componentIndex) {
670
+ const textNode = node.nextSibling;
671
+ this.treeMap.set(+index, textNode);
672
+ }
673
+ } else if (node.nodeType !== Node.TEXT_NODE) {
674
+ const { ci = "-1", hk } = (node == null ? void 0 : node.dataset) || {};
675
+ if (hk && +ci === this.componentIndex) {
676
+ this.treeMap.set(+hk, node);
677
+ }
678
+ }
679
+ }
680
+ let child = node.firstChild;
681
+ while (child) {
682
+ walk(child);
683
+ child = child.nextSibling;
684
+ }
685
+ };
686
+ walk(parent);
687
+ }
527
688
  mapNodeTree(parent, tree) {
528
689
  let index = 1;
529
690
  this.treeMap.set(0, parent);
@@ -539,17 +700,14 @@ var TemplateNode = class _TemplateNode {
539
700
  };
540
701
  walk(tree);
541
702
  }
542
- patchNodes(props) {
543
- for (const key in props) {
544
- const index = Number(key);
545
- const node = this.treeMap.get(index);
546
- if (node) {
547
- const value = this.props[key];
548
- this.patchNode(key, node, value, index === 0);
549
- }
550
- }
551
- this.props = props;
552
- }
703
+ /**
704
+ * Get a NodeTrack from the trackMap. If the track is not in the trackMap, create a new one.
705
+ * Then, call the cleanup function to remove any previously registered hooks.
706
+ * @param trackKey the key of the node track to get.
707
+ * @param trackLastNodes if true, the track will record the last nodes it has rendered.
708
+ * @param isRoot if true, the track will be treated as a root track.
709
+ * @returns the NodeTrack, cleaned up and ready to use.
710
+ */
553
711
  getNodeTrack(trackKey, trackLastNodes, isRoot) {
554
712
  var _a;
555
713
  let track = this.trackMap.get(trackKey);
@@ -567,16 +725,7 @@ var TemplateNode = class _TemplateNode {
567
725
  (_a = track.cleanup) == null ? void 0 : _a.call(track);
568
726
  return track;
569
727
  }
570
- inheritNode(node) {
571
- this.mounted = node.mounted;
572
- this.nodes = node.nodes;
573
- this.trackMap = node.trackMap;
574
- this.treeMap = node.treeMap;
575
- const props = this.props;
576
- this.props = node.props;
577
- this.patchNodes(props);
578
- }
579
- patchNode(key, node, props, isRoot) {
728
+ patchProp(key, node, props, isRoot) {
580
729
  for (const attr in props) {
581
730
  if (attr === "children" && props.children) {
582
731
  if (!shared.isArray(props.children)) {
@@ -600,7 +749,14 @@ var TemplateNode = class _TemplateNode {
600
749
  const track = this.getNodeTrack(`${key}:${attr}`);
601
750
  const listener = props[attr];
602
751
  track.cleanup = addEventListener(node, eventName, listener);
603
- } else if (!shared.startsWith(attr, "update")) {
752
+ } else {
753
+ const updateKey = `update${shared.capitalizeFirstLetter(attr)}`;
754
+ if (this.bindValueKeys.includes(attr)) {
755
+ break;
756
+ }
757
+ if (props[updateKey]) {
758
+ this.bindValueKeys.push(updateKey);
759
+ }
604
760
  const track = this.getNodeTrack(`${key}:${attr}`);
605
761
  const val = props[attr];
606
762
  const triggerValue = signal.isSignal(val) ? val : signal.useSignal(val);
@@ -610,9 +766,8 @@ var TemplateNode = class _TemplateNode {
610
766
  patchAttribute(track, node, attr, triggerValue.value);
611
767
  });
612
768
  let cleanupBind;
613
- const updateKey = `update${shared.capitalizeFirstLetter(attr)}`;
614
- if (props[updateKey]) {
615
- cleanupBind = binNode(node, (value) => {
769
+ if (props[updateKey] && !isComponent(attr)) {
770
+ cleanupBind = bindNode(node, (value) => {
616
771
  props[updateKey](value);
617
772
  });
618
773
  }
@@ -624,6 +779,52 @@ var TemplateNode = class _TemplateNode {
624
779
  }
625
780
  }
626
781
  };
782
+ function patchChild(track, parent, child, before) {
783
+ if (shared.isFunction(child)) {
784
+ track.cleanup = signal.useEffect(() => {
785
+ const nextNodes = shared.coerceArray(child()).map(coerceNode);
786
+ if (renderContext.isSSR) {
787
+ track.lastNodes = reconcileChildren(parent, nextNodes, before);
788
+ } else {
789
+ track.lastNodes = patchChildren(parent, track.lastNodes, nextNodes, before);
790
+ }
791
+ });
792
+ } else {
793
+ shared.coerceArray(child).forEach((node, index) => {
794
+ const newNode = coerceNode(node);
795
+ const key = getKey(newNode, index);
796
+ if (renderContext.isSSR) {
797
+ track.lastNodes = reconcileChildren(parent, [newNode], before);
798
+ } else {
799
+ track.lastNodes.set(key, newNode);
800
+ insertChild(parent, newNode, before);
801
+ }
802
+ });
803
+ }
804
+ }
805
+ function reconcileChildren(parent, nextNodes, before) {
806
+ const result = /* @__PURE__ */ new Map();
807
+ const textNodes = Array.from(parent.childNodes).filter(
808
+ (node) => {
809
+ var _a, _b;
810
+ return node.nodeType === Node.TEXT_NODE && ((_a = node.previousSibling) == null ? void 0 : _a.nodeType) === Node.COMMENT_NODE && ((_b = node.nextSibling) == null ? void 0 : _b.nodeType) === Node.COMMENT_NODE;
811
+ }
812
+ );
813
+ nextNodes.forEach((node, index) => {
814
+ const key = getKey(node, index);
815
+ if (node.nodeType === Node.TEXT_NODE) {
816
+ textNodes.forEach((ne) => {
817
+ if (node.textContent === ne.textContent) {
818
+ parent.replaceChild(node, ne);
819
+ }
820
+ });
821
+ } else {
822
+ insertChild(parent, node, before);
823
+ }
824
+ result.set(key, node);
825
+ });
826
+ return result;
827
+ }
627
828
  function patchAttribute(track, node, attr, data) {
628
829
  const element = node;
629
830
  if (!element.setAttribute) {
@@ -637,179 +838,101 @@ function patchAttribute(track, node, attr, data) {
637
838
  setAttribute(element, attr, data);
638
839
  }
639
840
  }
640
- function patchChild(track, parent, child, before) {
641
- if (shared.isFunction(child)) {
642
- track.cleanup = signal.useEffect(() => {
643
- const nextNodes = shared.coerceArray(child()).map(coerceNode);
644
- track.lastNodes = patchChildren(parent, track.lastNodes, nextNodes, before);
645
- });
646
- } else {
647
- shared.coerceArray(child).forEach((node, i) => {
648
- const newNode = coerceNode(node);
649
- track.lastNodes.set(String(i), newNode);
650
- insertChild(parent, newNode, before);
651
- });
841
+
842
+ // src/jsx-renderer.ts
843
+ function h(template, props, key) {
844
+ if (shared.isString(template)) {
845
+ if (isHtmlTagName(template)) {
846
+ template = convertToHtmlTag(template);
847
+ props = { "1": props };
848
+ } else if (template === "") {
849
+ props = { "0": props };
850
+ }
851
+ template = createTemplate(template);
652
852
  }
853
+ return shared.isFunction(template) ? new ComponentNode(template, props, key) : new TemplateNode(template, props, key);
854
+ }
855
+ function isComponent(node) {
856
+ return node instanceof ComponentNode;
857
+ }
858
+ function isJsxElement(node) {
859
+ return node instanceof ComponentNode || node instanceof TemplateNode;
860
+ }
861
+ function createTemplate(html) {
862
+ const template = document.createElement("template");
863
+ template.innerHTML = closeHtmlTags(html);
864
+ return template;
865
+ }
866
+ function Fragment(props) {
867
+ return props.children;
653
868
  }
654
869
  function onMount(cb) {
655
870
  var _a;
656
- throwIfOutsideComponent("onMounted");
657
- (_a = Hooks.ref) == null ? void 0 : _a.addHook("mounted", cb);
871
+ assertInsideComponent("onMounted");
872
+ (_a = LifecycleContext.ref) == null ? void 0 : _a.addHook("mounted", cb);
658
873
  }
659
874
  function onDestroy(cb) {
660
875
  var _a;
661
- throwIfOutsideComponent("onDestroy");
662
- (_a = Hooks.ref) == null ? void 0 : _a.addHook("destroy", cb);
876
+ assertInsideComponent("onDestroy");
877
+ (_a = LifecycleContext.ref) == null ? void 0 : _a.addHook("destroy", cb);
663
878
  }
664
- function throwIfOutsideComponent(hook, key) {
665
- if (!Hooks.ref) {
879
+ function assertInsideComponent(hookName, key) {
880
+ if (!LifecycleContext.ref && true) {
666
881
  console.error(
667
- `"${hook}"(key: ${shared.isSymbol(key) ? key.toString() : key}) can only be called within the component function body
882
+ `"${hookName}"(key: ${shared.isSymbol(key) ? key.toString() : key}) can only be called within the component function body
668
883
  and cannot be used in asynchronous or deferred calls.`
669
884
  );
670
885
  }
671
886
  }
672
887
  function useProvide(key, value) {
673
888
  var _a;
674
- throwIfOutsideComponent("useProvide", key);
675
- (_a = Hooks.ref) == null ? void 0 : _a.setContext(key, value);
889
+ assertInsideComponent("useProvide", key);
890
+ (_a = LifecycleContext.ref) == null ? void 0 : _a.setContext(key, value);
676
891
  }
677
892
  function useInject(key, defaultValue) {
678
893
  var _a;
679
- throwIfOutsideComponent("useInject", key);
680
- return ((_a = Hooks.ref) == null ? void 0 : _a.getContext(key)) || defaultValue;
894
+ assertInsideComponent("useInject", key);
895
+ return ((_a = LifecycleContext.ref) == null ? void 0 : _a.getContext(key)) || defaultValue;
681
896
  }
682
897
  function useRef() {
683
898
  const ref = signal.shallowSignal(null);
684
899
  return ref;
685
900
  }
686
- function generateAttributes(props) {
687
- return Object.entries(props).map(([key, value]) => {
688
- if (key === "children" || shared.isFunction(value)) return "";
689
- return `${key}="${shared.escape(String(value))}"`;
690
- }).filter(Boolean).join(" ");
691
- }
692
- function normalizeProps(props) {
693
- Object.keys(props).forEach((propKey) => {
694
- if (shared.isFunction(props[propKey])) {
695
- delete props[propKey];
696
- }
697
- if (signal.isSignal(props[propKey])) {
698
- props[propKey] = props[propKey].value;
699
- }
700
- });
901
+
902
+ // src/hydration.ts
903
+ function renderToString(component, props) {
904
+ renderContext.setSSG();
905
+ const ssrNode = new SSGNode(component, props || {});
906
+ const html = ssrNode.mount();
907
+ renderContext.setClient();
908
+ return html;
701
909
  }
702
- function handleChildResult(result, prop, key, tmpl, childNodesMap, path) {
703
- if (signal.isSignal(result)) {
704
- tmpl.template += result.value;
705
- } else if (result instanceof ServerNode) {
706
- const mapKey = path ? String(path) : `${key}`;
707
- if (!childNodesMap[mapKey]) childNodesMap[mapKey] = [];
708
- const childResult = result.mount();
709
- childNodesMap[mapKey].push(
710
- shared.isFunction(childResult) ? childResult(prop) : signal.isSignal(childResult) ? childResult.value : childResult
711
- );
712
- } else {
713
- tmpl.template += shared.isFunction(result) ? result(prop) : String(result);
910
+ function hydrate(component, container) {
911
+ const rootElement = typeof container === "string" ? document.querySelector(container) : container;
912
+ if (!rootElement) {
913
+ throw new Error(`Could not find container: ${container}`);
714
914
  }
915
+ renderContext.setSSR();
916
+ h(component).mount(rootElement);
917
+ renderContext.setClient();
715
918
  }
716
- var ServerNode = class _ServerNode extends Hooks {
717
- constructor(template2, props = {}, key) {
718
- super();
719
- this.template = template2;
720
- this.props = props;
721
- this.key = key;
722
- this.childNodesMap = {};
723
- this.processedTemplates = {};
724
- }
725
- /**
726
- * Mount and render the component
727
- */
728
- mount() {
729
- this.initRef();
730
- const output = this.render();
731
- this.removeRef();
732
- return output;
733
- }
734
- /**
735
- * Initialize template entries and props
736
- */
737
- initTemplates() {
738
- const templateCollection = Array.isArray(this.template) ? this.template.reduce((acc, tmpl, index) => {
739
- acc[index + 1] = { template: tmpl };
740
- return acc;
741
- }, {}) : this.template;
742
- if (shared.isObject(templateCollection)) {
743
- Object.entries(templateCollection).forEach(([key, tmpl]) => {
744
- const prop = __spreadValues({}, this.props[key]);
745
- normalizeProps(prop);
746
- if (prop.children) {
747
- prop.children.forEach((item) => {
748
- const [child, path] = shared.isArray(item) ? item : [item, null];
749
- if (shared.isFunction(child)) {
750
- const result = child(prop);
751
- handleChildResult(result, prop, key, tmpl, this.childNodesMap, path);
752
- } else {
753
- tmpl.template += signal.isSignal(child) ? child.value : String(child);
754
- }
755
- });
756
- }
757
- this.processedTemplates[key] = {
758
- template: tmpl.template,
759
- props: prop
760
- };
761
- });
762
- }
763
- }
764
- /**
765
- * Render component and its children into a string
766
- */
767
- render() {
768
- if (shared.isFunction(this.template)) {
769
- const root = this.template(this.props);
770
- return root instanceof _ServerNode ? root.mount() : String(root);
771
- }
772
- if (this.template instanceof _ServerNode) {
773
- return this.template.mount();
774
- }
775
- this.initTemplates();
776
- return Object.entries(this.processedTemplates).map(([key, { template: template2, props }]) => {
777
- let content = template2;
778
- if (props && Object.keys(props).length > 0) {
779
- content += ` ${generateAttributes(props)}`;
780
- }
781
- if (this.childNodesMap[key]) {
782
- content = content.replace("<!>", this.renderChildren(this.childNodesMap[key]));
783
- }
784
- return content;
785
- }).join("");
786
- }
787
- /**
788
- * Render child nodes into a string
789
- */
790
- renderChildren(children) {
791
- return shared.coerceArray(children).map(String).join("");
792
- }
793
- };
794
919
  function ssg(component, props) {
795
- return new ServerNode(component, props);
796
- }
797
- function renderToString(component, props) {
798
- return ssg(component, props).mount();
920
+ if (renderContext.isSSG) {
921
+ return new SSGNode(component, props);
922
+ }
923
+ return h(component, props);
799
924
  }
800
925
 
801
- exports.ComponentNode = ComponentNode;
802
926
  exports.Fragment = Fragment;
803
- exports.TemplateNode = TemplateNode;
804
927
  exports.h = h;
928
+ exports.hydrate = hydrate;
805
929
  exports.isComponent = isComponent;
806
930
  exports.isJsxElement = isJsxElement;
807
- exports.nextTick = nextTick;
808
931
  exports.onDestroy = onDestroy;
809
932
  exports.onMount = onMount;
810
933
  exports.renderToString = renderToString;
811
934
  exports.ssg = ssg;
812
- exports.template = template;
935
+ exports.template = createTemplate;
813
936
  exports.useInject = useInject;
814
937
  exports.useProvide = useProvide;
815
938
  exports.useRef = useRef;