This commit is contained in:
nvms 2024-10-19 11:14:33 -04:00
parent 2c4e66bd8d
commit 0019cd64e5
3 changed files with 321 additions and 219 deletions

View File

@ -279,10 +279,10 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
return; return;
} }
const nextNode = el.nextSibling; const nextNode = el.nextSibling;
const parent = el.parentElement; const parent2 = el.parentElement;
const anchor = new Text(""); const anchor = new Text("");
parent.insertBefore(anchor, el); parent2.insertBefore(anchor, el);
parent.removeChild(el); parent2.removeChild(el);
const sourceExp = inMatch[2].trim(); const sourceExp = inMatch[2].trim();
let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim(); let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim();
let destructureBindings; let destructureBindings;
@ -352,7 +352,7 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
const mountBlock = (ctx2, ref2) => { const mountBlock = (ctx2, ref2) => {
const block = new Block({ element: el, parentContext: ctx2, replacementType: "replace", component, componentProps, allProps }); const block = new Block({ element: el, parentContext: ctx2, replacementType: "replace", component, componentProps, allProps });
block.key = ctx2.key; block.key = ctx2.key;
block.insert(parent, ref2); block.insert(parent2, ref2);
return block; return block;
}; };
ctx.effect(() => { ctx.effect(() => {
@ -385,7 +385,7 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
if (blocks[oldIndex + 1] !== nextBlock || // If the next has moved, it must move too if (blocks[oldIndex + 1] !== nextBlock || // If the next has moved, it must move too
prevMovedBlock === nextBlock) { prevMovedBlock === nextBlock) {
prevMovedBlock = block; prevMovedBlock = block;
block.insert(parent, nextBlock ? nextBlock.element : anchor); block.insert(parent2, nextBlock ? nextBlock.element : anchor);
} }
} }
} }
@ -399,28 +399,28 @@ var _for = (el, exp, ctx, component, componentProps, allProps) => {
// src/directives/if.ts // src/directives/if.ts
function _if(el, exp, ctx, component, componentProps, allProps) { function _if(el, exp, ctx, component, componentProps, allProps) {
const parent = el.parentElement; const parent2 = el.parentElement;
const anchor = new Comment(":if"); const anchor = new Comment(":if");
parent.insertBefore(anchor, el); parent2.insertBefore(anchor, el);
const branches = [{ exp, el }]; const branches = [{ exp, el }];
let elseEl; let elseEl;
let elseExp; let elseExp;
while (elseEl = el.nextElementSibling) { while (elseEl = el.nextElementSibling) {
elseExp = null; elseExp = null;
if (checkAndRemoveAttribute(elseEl, ":else") === "" || (elseExp = checkAndRemoveAttribute(elseEl, ":else-if"))) { if (checkAndRemoveAttribute(elseEl, ":else") === "" || (elseExp = checkAndRemoveAttribute(elseEl, ":else-if"))) {
parent.removeChild(elseEl); parent2.removeChild(elseEl);
branches.push({ exp: elseExp, el: elseEl }); branches.push({ exp: elseExp, el: elseEl });
} else { } else {
break; break;
} }
} }
const nextNode = el.nextSibling; const nextNode = el.nextSibling;
parent.removeChild(el); parent2.removeChild(el);
let block; let block;
let activeBranchIndex = -1; let activeBranchIndex = -1;
const removeActiveBlock = () => { const removeActiveBlock = () => {
if (block) { if (block) {
parent.insertBefore(anchor, block.element); parent2.insertBefore(anchor, block.element);
block.remove(); block.remove();
block = void 0; block = void 0;
} }
@ -432,8 +432,8 @@ function _if(el, exp, ctx, component, componentProps, allProps) {
if (i !== activeBranchIndex) { if (i !== activeBranchIndex) {
removeActiveBlock(); removeActiveBlock();
block = new Block({ element: el2, parentContext: ctx, replacementType: "replace", component, componentProps, allProps }); block = new Block({ element: el2, parentContext: ctx, replacementType: "replace", component, componentProps, allProps });
block.insert(parent, anchor); block.insert(parent2, anchor);
parent.removeChild(anchor); parent2.removeChild(anchor);
activeBranchIndex = i; activeBranchIndex = i;
} }
return; return;
@ -1015,14 +1015,14 @@ var Block = class {
this.componentProps[name] = value; this.componentProps[name] = value;
} }
} }
insert(parent, anchor = null) { insert(parent2, anchor = null) {
if (this.isFragment) { if (this.isFragment) {
if (this.start) { if (this.start) {
let node = this.start; let node = this.start;
let next; let next;
while (node) { while (node) {
next = node.nextSibling; next = node.nextSibling;
parent.insertBefore(node, anchor); parent2.insertBefore(node, anchor);
if (node === this.end) { if (node === this.end) {
break; break;
} }
@ -1031,12 +1031,12 @@ var Block = class {
} else { } else {
this.start = new Text(""); this.start = new Text("");
this.end = new Text(""); this.end = new Text("");
parent.insertBefore(this.end, anchor); parent2.insertBefore(this.end, anchor);
parent.insertBefore(this.start, this.end); parent2.insertBefore(this.start, this.end);
parent.insertBefore(this.element, this.end); parent2.insertBefore(this.element, this.end);
} }
} else { } else {
parent.insertBefore(this.element, anchor); parent2.insertBefore(this.element, anchor);
} }
} }
remove() { remove() {
@ -1047,12 +1047,12 @@ var Block = class {
} }
} }
if (this.start) { if (this.start) {
const parent = this.start.parentNode; const parent2 = this.start.parentNode;
let node = this.start; let node = this.start;
let next; let next;
while (node) { while (node) {
next = node.nextSibling; next = node.nextSibling;
parent.removeChild(node); parent2.removeChild(node);
if (node === this.end) { if (node === this.end) {
break; break;
} }
@ -1080,9 +1080,39 @@ function walk(node, context) {
} }
if (isElement(node)) { if (isElement(node)) {
let exp; let exp;
if (isComponent(node, context)) { const handleDirectives = (node2, context2, component, componentProps, allProps) => {
const component = context.app.getComponent(node.tagName.toLowerCase()); if (exp = checkAndRemoveAttribute(node2, ":teleport")) {
const allProps = Array.from(node.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({ return _teleport(node2, exp, context2);
}
if (exp = checkAndRemoveAttribute(node2, ":if")) {
return _if(node2, exp, context2, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node2, ":for")) {
return _for(node2, exp, context2, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node2, ":show")) {
new ShowDirective({ element: node2, context: context2, expression: exp });
}
if (exp = checkAndRemoveAttribute(node2, ":ref")) {
context2.scope[exp].value = node2;
}
if (exp = checkAndRemoveAttribute(node2, ":value")) {
new ValueDirective({ element: node2, context: context2, expression: exp });
}
if (exp = checkAndRemoveAttribute(node2, ":html")) {
context2.effect(() => {
const result = evalGet(context2.scope, exp);
if (result instanceof Element) {
node2.replaceChildren();
node2.append(result);
} else {
node2.innerHTML = result;
}
});
}
};
const processAttributes = (node2, component) => {
return Array.from(node2.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({
isMirror: isMirrorProp(attr.name), isMirror: isMirrorProp(attr.name),
isSpread: isSpreadProp(attr.name), isSpread: isSpreadProp(attr.name),
isBind: attr.name.includes("bind"), isBind: attr.name.includes("bind"),
@ -1091,6 +1121,10 @@ function walk(node, context) {
exp: attr.value, exp: attr.value,
value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : void 0 value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : void 0
})); }));
};
if (isComponent(node, context)) {
const component = context.app.getComponent(node.tagName.toLowerCase());
const allProps = processAttributes(node, component);
const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => {
if (isSpread) { if (isSpread) {
const spread = evalGet(context.scope, extractedName); const spread = evalGet(context.scope, extractedName);
@ -1102,20 +1136,10 @@ function walk(node, context) {
} }
return acc; return acc;
}, {}); }, {});
if (exp = checkAndRemoveAttribute(node, ":teleport")) { const next2 = handleDirectives(node, context, component, componentProps, allProps);
return _teleport(node, exp, context); if (next2) return next2;
}
if (exp = checkAndRemoveAttribute(node, ":if")) {
return _if(node, exp, context, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node, ":for")) {
return _for(node, exp, context, component, componentProps, allProps);
}
if (exp = checkAndRemoveAttribute(node, ":show")) {
new ShowDirective({ element: node, context, expression: exp });
}
const templates = findTemplateNodes(node); const templates = findTemplateNodes(node);
const block = new Block({ return new Block({
element: node, element: node,
parentContext: context, parentContext: context,
component, component,
@ -1124,27 +1148,10 @@ function walk(node, context) {
templates, templates,
componentProps, componentProps,
allProps allProps
}); }).element;
return block.element;
}
if (exp = checkAndRemoveAttribute(node, ":teleport")) {
return _teleport(node, exp, context);
}
if (exp = checkAndRemoveAttribute(node, ":if")) {
return _if(node, exp, context);
}
if (exp = checkAndRemoveAttribute(node, ":for")) {
return _for(node, exp, context);
}
if (exp = checkAndRemoveAttribute(node, ":ref")) {
context.scope[exp].value = node;
}
if (exp = checkAndRemoveAttribute(node, ":value")) {
new ValueDirective({ element: node, context, expression: exp });
}
if (exp = checkAndRemoveAttribute(node, ":show")) {
new ShowDirective({ element: node, context, expression: exp });
} }
const next = handleDirectives(node, context);
if (next) return next;
Array.from(node.attributes).forEach((attr) => { Array.from(node.attributes).forEach((attr) => {
if (isPropAttribute(attr.name)) { if (isPropAttribute(attr.name)) {
new AttributeDirective({ element: node, context, attr }); new AttributeDirective({ element: node, context, attr });
@ -1157,9 +1164,9 @@ function walk(node, context) {
} }
} }
function walkChildren(node, context) { function walkChildren(node, context) {
let child = node.firstChild; let child2 = node.firstChild;
while (child) { while (child2) {
child = walk(child, context) || child.nextSibling; child2 = walk(child2, context) || child2.nextSibling;
} }
} }
var evalFuncCache = {}; var evalFuncCache = {};
@ -1203,28 +1210,84 @@ function flattenRefs(scope) {
} }
return mapped; return mapped;
} }
var counter = { var child = {
template: html`<div>Animal: {{animal}} {{index}}</div>`,
props: { animal: { default: "cat" }, index: { default: 0 } },
main({ animal, index }) {
return { animal, index };
}
};
var parent = {
template: html` template: html`
<div> <div>
<div :teleport="body" :if="style.color === 'gray'">true</div> mirror, no bind:
<h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3> <child {animal} />
<button @click="increment">+</button> <hr />
<button @click="decrement">-</button> mirror, bind:
<child {animal:bind} />
<hr />
spread, no bind:
<child ...spread />
<hr />
spread, bind:
<child ...spread:bind />
<hr />
regular prop:
<child .animal="animal" />
<hr />
regular prop, bind:
<child .animal:bind="animal" />
<hr />
<div .id="animal">div has "id" set to animal.value</div>
<hr />
<div .id:bind="animal">div has "id" set and bound to animal.value</div>
<hr />
<div {animal}>div has "animal" set to animal.value</div>
<hr />
<div {animal:bind}>div has "animal" set and bound to animal.value</div>
<hr />
<div ...spread>div has "animal" spread</div>
<hr />
<div ...spread:bind>div has "animal" spread and bound</div>
<hr />
<hr />
<hr />
<hr />
if bool, mirror, no bind:
<child :if="bool" {animal} />
if bool, mirror, bind:
<child :if="bool" {animal:bind} />
<hr />
for list, mirror, no bind:
<child :for="item in list" {animal} />
<hr />
for list, mirror, bind:
<child :for="item in list" {animal:bind} />
if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
<div :if="bool">
<child :for="item in list" {animal} />
</div>
</div> </div>
`, `,
main() { main() {
const count = ref(0); const bool = ref(false);
const style = reactive({ color: "gray" }); const animal = ref("dog");
const increment = () => count.value++; const spread = reactive({ animal: "panther" });
const decrement = () => count.value--; const list = reactive([1, 2, 3]);
setInterval(() => { setTimeout(() => {
style.color = style.color === "gray" ? "white" : "gray"; spread.animal = "PANTHER!";
animal.value = "DOG!";
bool.value = true;
}, 500); }, 500);
return { count, increment, decrement, style }; setTimeout(() => {
animal.value = "DOG!!!!!";
}, 1e3);
return { animal, spread, bool, list };
} }
}; };
var app = new App(); var app = new App();
app.mount(counter, "#app"); app.register("child", child);
app.mount(parent, "#app");
export { export {
App, App,
Block, Block,

File diff suppressed because one or more lines are too long

View File

@ -417,10 +417,40 @@ function walk(node: Node, context: Context) {
if (isElement(node)) { if (isElement(node)) {
let exp: string | null; let exp: string | null;
if (isComponent(node, context)) { const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record<string, any>, allProps?: any[]) => {
const component = context.app.getComponent(node.tagName.toLowerCase()); if ((exp = checkAndRemoveAttribute(node, ":teleport"))) {
return _teleport(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
return _if(node, exp, context, component, componentProps, allProps);
}
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
return _for(node, exp, context, component, componentProps, allProps);
}
if ((exp = checkAndRemoveAttribute(node, ":show"))) {
new ShowDirective({ element: node, context, expression: exp });
}
if ((exp = checkAndRemoveAttribute(node, ":ref"))) {
context.scope[exp].value = node;
}
if ((exp = checkAndRemoveAttribute(node, ":value"))) {
new ValueDirective({ element: node, context, expression: exp });
}
if ((exp = checkAndRemoveAttribute(node, ":html"))) {
context.effect(() => {
const result = evalGet(context.scope, exp);
if (result instanceof Element) {
node.replaceChildren();
node.append(result);
} else {
node.innerHTML = result;
}
});
}
};
const allProps = Array.from(node.attributes) const processAttributes = (node: Element, component?: Component) => {
return Array.from(node.attributes)
.filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || (isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component))) .filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || (isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)))
.map((attr) => ({ .map((attr) => ({
isMirror: isMirrorProp(attr.name), isMirror: isMirrorProp(attr.name),
@ -431,6 +461,11 @@ function walk(node: Node, context: Context) {
exp: attr.value, exp: attr.value,
value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : undefined, value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : undefined,
})); }));
};
if (isComponent(node, context)) {
const component = context.app.getComponent(node.tagName.toLowerCase());
const allProps = processAttributes(node, component);
const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => {
if (isSpread) { if (isSpread) {
@ -444,25 +479,12 @@ function walk(node: Node, context: Context) {
return acc; return acc;
}, {}); }, {});
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) { const next = handleDirectives(node, context, component, componentProps, allProps);
return _teleport(node, exp, context); if (next) return next;
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
return _if(node, exp, context, component, componentProps, allProps);
}
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
return _for(node, exp, context, component, componentProps, allProps);
}
if ((exp = checkAndRemoveAttribute(node, ":show"))) {
new ShowDirective({ element: node, context, expression: exp });
}
const templates = findTemplateNodes(node); const templates = findTemplateNodes(node);
const block = new Block({ return new Block({
element: node, element: node,
parentContext: context, parentContext: context,
component, component,
@ -471,34 +493,11 @@ function walk(node: Node, context: Context) {
templates, templates,
componentProps, componentProps,
allProps, allProps,
}); }).element;
return block.element;
} }
if ((exp = checkAndRemoveAttribute(node, ":teleport"))) { const next = handleDirectives(node, context);
return _teleport(node, exp, context); if (next) return next;
}
if ((exp = checkAndRemoveAttribute(node, ":if"))) {
return _if(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":for"))) {
return _for(node, exp, context);
}
if ((exp = checkAndRemoveAttribute(node, ":ref"))) {
context.scope[exp].value = node;
}
if ((exp = checkAndRemoveAttribute(node, ":value"))) {
new ValueDirective({ element: node, context, expression: exp });
}
if ((exp = checkAndRemoveAttribute(node, ":show"))) {
new ShowDirective({ element: node, context, expression: exp });
}
Array.from(node.attributes).forEach((attr) => { Array.from(node.attributes).forEach((attr) => {
if (isPropAttribute(attr.name)) { if (isPropAttribute(attr.name)) {
@ -650,115 +649,155 @@ function flattenRefs(scope: any): any {
// ------------------------------------------------ // ------------------------------------------------
// Component pros, mirror and spread, bind and no bind // Component pros, mirror and spread, bind and no bind
// const child = { const child = {
// template: html`<div>Animal: {{animal}}</div>`, template: html`<div>Animal: {{animal}} {{index}}</div>`,
// props: { animal: { default: "cat" } }, props: { animal: { default: "cat" }, index: { default: 0 } },
// main({ animal }) { main({ animal, index }) {
// return { animal }; return { animal, index };
// }, },
// }; };
// const parent = { const parent = {
// template: html`
// <div>
// <div>asdf</div>
// mirror, no bind:
// <child {animal} />
// <hr />
// mirror, bind:
// <child {animal:bind} />
// <hr />
// spread, no bind:
// <child ...spread />
// <hr />
// spread, bind:
// <child ...spread:bind />
// <hr />
// regular prop:
// <child .animal="animal" />
// <hr />
// regular prop, bind:
// <child .animal:bind="animal" />
// <hr />
// <div .id="animal">div has "id" set to animal.value</div>
// <hr />
// <div .id:bind="animal">div has "id" set and bound to animal.value</div>
// <hr />
// <div {animal}>div has "animal" set to animal.value</div>
// <hr />
// <div {animal:bind}>div has "animal" set and bound to animal.value</div>
// <hr />
// <div ...spread>div has "animal" spread</div>
// <hr />
// <div ...spread:bind>div has "animal" spread and bound</div>
// <hr />
// <hr />
// <hr />
// <hr />
// if bool, mirror, no bind:
// <child :if="bool" {animal} />
// if bool, mirror, bind:
// <child :if="bool" {animal:bind} />
// <hr />
// for list, mirror, no bind:
// <child :for="item in list" {animal} />
// <hr />
// for list, mirror, bind:
// <child :for="item in list" {animal:bind} />
// if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
// <div :if="bool">
// <child :for="item in list" {animal} />
// </div>
// </div>
// `,
// main() {
// const bool = ref(false);
// const animal = ref("dog");
// const spread = reactive({ animal: "panther" });
// const list = reactive([1, 2, 3]);
// setTimeout(() => {
// spread.animal = "PANTHER!";
// animal.value = "DOG!";
// bool.value = true;
// }, 500);
// setTimeout(() => {
// animal.value = "DOG!!!!!";
// }, 1000);
// return { animal, spread, bool, list };
// },
// };
// const app = new App();
// app.register("child", child);
// app.mount(parent, "#app");
// ------------------------------------------------
// Event directive
const counter = {
template: html` template: html`
<div> <div>
<div :teleport="body" :if="style.color === 'gray'">true</div> mirror, no bind:
<h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3> <child {animal} />
<button @click="increment">+</button> <hr />
<button @click="decrement">-</button> mirror, bind:
<child {animal:bind} />
<hr />
spread, no bind:
<child ...spread />
<hr />
spread, bind:
<child ...spread:bind />
<hr />
regular prop:
<child .animal="animal" />
<hr />
regular prop, bind:
<child .animal:bind="animal" />
<hr />
<div .id="animal">div has "id" set to animal.value</div>
<hr />
<div .id:bind="animal">div has "id" set and bound to animal.value</div>
<hr />
<div {animal}>div has "animal" set to animal.value</div>
<hr />
<div {animal:bind}>div has "animal" set and bound to animal.value</div>
<hr />
<div ...spread>div has "animal" spread</div>
<hr />
<div ...spread:bind>div has "animal" spread and bound</div>
<hr />
<hr />
<hr />
<hr />
if bool, mirror, no bind:
<child :if="bool" {animal} />
if bool, mirror, bind:
<child :if="bool" {animal:bind} />
<hr />
for list, mirror, no bind:
<child :for="item in list" {animal} />
<hr />
for list, mirror, bind:
<child :for="item in list" {animal:bind} />
if bool, for list, mirror, no bind: these have the value "DOG!" because by the time for :for directive is evaluated, animal.value is "DOG!", and no longer "dog".
<div :if="bool">
<child :for="item in list" {animal} />
</div>
</div> </div>
`, `,
main() { main() {
const count = ref(0); const bool = ref(false);
const style = reactive({ color: "gray" }); const animal = ref("dog");
const increment = () => count.value++; const spread = reactive({ animal: "panther" });
const decrement = () => count.value--; const list = reactive([1, 2, 3]);
setInterval(() => { setTimeout(() => {
style.color = style.color === "gray" ? "white" : "gray"; spread.animal = "PANTHER!";
animal.value = "DOG!";
bool.value = true;
}, 500); }, 500);
return { count, increment, decrement, style }; setTimeout(() => {
animal.value = "DOG!!!!!";
}, 1000);
return { animal, spread, bool, list };
}, },
}; };
const app = new App(); const app = new App();
app.mount(counter, "#app"); app.register("child", child);
app.mount(parent, "#app");
// ------------------------------------------------
// Event directive
// const counter = {
// template: html`
// <div>
// <div :teleport="body" :if="style.color === 'gray'">true</div>
// <h3 {style:bind}>Count: {{count}}{{count >= 2 ? '!!!' : ''}}</h3>
// <button @click="increment">+</button>
// <button @click="decrement">-</button>
// </div>
// `,
// main() {
// const count = ref(0);
// const style = reactive({ color: "gray" });
// const increment = () => count.value++;
// const decrement = () => count.value--;
// setInterval(() => {
// style.color = style.color === "gray" ? "white" : "gray";
// }, 500);
// return { count, increment, decrement, style };
// },
// };
// const app = new App();
// app.mount(counter, "#app");
// ------------------------------------------------
// Template
// const main = {
// template: html`
// <div>
// <div :if="bool">
// <div :for="item in items">{{item}}</div>
// </div>
// </div>
// `,
// main() {
// const items = reactive([1, 2, 3, 4, 5]);
// const bool = ref(true);
// setInterval(() => (bool.value = !bool.value), 250);
// return { items, bool };
// },
// };
// const app = new App();
// app.mount(main, "#app");
// ------------------------------------------------
// :html
// const main = {
// template: html`<div :html="html"></div>`,
// main() {
// const html = ref("<h1>hello</h1>");
// setTimeout(() => {
// if (html.value === "<h1>hello</h1>") {
// html.value = "<h1>world</h1>";
// }
// }, 1000);
// return { html };
// },
// };
// const app = new App();
// app.mount(main, "#app");