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