From 31fea795bfae1e8fabef3ec703fdc7549cd20825 Mon Sep 17 00:00:00 2001 From: nvms Date: Sat, 19 Oct 2024 08:17:26 -0400 Subject: [PATCH] first --- .esr.yml | 25 + .gitignore | 1 + .prettierrc | 6 + bun.lockb | Bin 0 -> 33316 bytes package.json | 22 + public/index.html | 15 + public/index.js | 1215 +++++++++++++++++++++++++++++++ public/index.js.map | 7 + src/directives/attribute.ts | 113 +++ src/directives/event.ts | 40 + src/directives/for.ts | 156 ++++ src/directives/if.ts | 67 ++ src/directives/interpolation.ts | 78 ++ src/directives/teleport.ts | 32 + src/directives/value.ts | 111 +++ src/index.ts | 778 ++++++++++++++++++++ src/plugins/index.ts | 7 + src/plugins/router/index.ts | 25 + src/plugins/router/plugin.ts | 260 +++++++ src/reactivity/computed.ts | 28 + src/reactivity/effect.ts | 118 +++ src/reactivity/reactive.ts | 62 ++ src/reactivity/ref.ts | 41 ++ src/reactivity/unwrap.ts | 14 + src/reactivity/watch.ts | 31 + src/render.test.ts | 71 ++ src/util.ts | 230 ++++++ tsconfig.json | 8 + vitest.config.ts | 8 + 29 files changed, 3569 insertions(+) create mode 100644 .esr.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100755 bun.lockb create mode 100644 package.json create mode 100644 public/index.html create mode 100755 public/index.js create mode 100755 public/index.js.map create mode 100644 src/directives/attribute.ts create mode 100644 src/directives/event.ts create mode 100644 src/directives/for.ts create mode 100644 src/directives/if.ts create mode 100644 src/directives/interpolation.ts create mode 100644 src/directives/teleport.ts create mode 100644 src/directives/value.ts create mode 100644 src/index.ts create mode 100644 src/plugins/index.ts create mode 100644 src/plugins/router/index.ts create mode 100644 src/plugins/router/plugin.ts create mode 100644 src/reactivity/computed.ts create mode 100644 src/reactivity/effect.ts create mode 100644 src/reactivity/reactive.ts create mode 100644 src/reactivity/ref.ts create mode 100644 src/reactivity/unwrap.ts create mode 100644 src/reactivity/watch.ts create mode 100644 src/render.test.ts create mode 100644 src/util.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.esr.yml b/.esr.yml new file mode 100644 index 0000000..8cb2205 --- /dev/null +++ b/.esr.yml @@ -0,0 +1,25 @@ +bundle: true +platform: browser +format: esm +sourcemap: true +outdir: public + +watch: + paths: ['src/**/*.{ts,tsx,js,jsx,css,scss,html}', 'public/index.html'] + +serve: + html: public/index.html + port: 1234 + +build: + minify: true + minifyWhitespace: true + minifyIdentifiers: true + sourcemap: false + +run: + runtime: bun + sourcemap: false + +jsx: automatic +jsxFactory: React.createElement diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..d43e959 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 200, + "semi": true, + "singleQuote": false, + "tabWidth": 2 +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..444c71206986fa08e769edd6576f9f9eebd45300 GIT binary patch literal 33316 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 rZg*In|5p + + + + + + Application + {{ css }} + + +
+ {{ livereload }} + {{ js }} + + diff --git a/public/index.js b/public/index.js new file mode 100755 index 0000000..d6a04a3 --- /dev/null +++ b/public/index.js @@ -0,0 +1,1215 @@ +// src/util.ts +function stringToElement(template) { + const parser = new DOMParser(); + const doc = parser.parseFromString(template, "text/html"); + return doc.body.firstChild; +} +var isText = (node) => { + return node.nodeType === Node.TEXT_NODE; +}; +var isTemplate = (node) => { + return node.nodeName === "TEMPLATE"; +}; +var isElement = (node) => { + return node.nodeType === Node.ELEMENT_NODE; +}; +function isObject(value) { + return value !== null && typeof value === "object"; +} +function isArray(value) { + return Array.isArray(value); +} +function checkAndRemoveAttribute(el, attrName) { + const attributeValue = el.getAttribute(attrName); + if (attributeValue !== null) { + el.removeAttribute(attrName); + } + return attributeValue; +} +function findSlotNodes(element) { + const slots = []; + const findSlots = (node) => { + Array.from(node.childNodes).forEach((node2) => { + if (isElement(node2)) { + if (node2.nodeName === "SLOT") { + slots.push({ node: node2, name: node2.getAttribute("name") || "default" }); + } + if (node2.hasChildNodes()) { + findSlots(node2); + } + } + }); + }; + findSlots(element); + return slots; +} +function findTemplateNodes(element) { + const templates = []; + const findTemplates = (element2) => { + let defaultContentNodes = []; + Array.from(element2.childNodes).forEach((node) => { + if (isElement(node) || isText(node)) { + if (isElement(node) && node.nodeName === "TEMPLATE" && isTemplate(node)) { + templates.push({ targetSlotName: node.getAttribute("slot") || "", node }); + } else { + defaultContentNodes.push(node); + } + } + }); + if (defaultContentNodes.length > 0) { + const defaultTemplate = document.createElement("template"); + defaultTemplate.setAttribute("slot", "default"); + defaultContentNodes.forEach((node) => { + defaultTemplate.content.appendChild(node); + }); + templates.push({ targetSlotName: "default", node: defaultTemplate }); + } + }; + findTemplates(element); + return templates; +} +var nextTick = async (f) => { + await new Promise( + (r) => setTimeout( + (_) => requestAnimationFrame((_2) => { + f && f(); + r(); + }) + ) + ); +}; +function html(strings, ...values) { + const selfClosingTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; + let result = strings.reduce((acc, str, i) => acc + str + (values[i] || ""), ""); + result = result.replace(/<([a-zA-Z][^\s/>]*)\s*([^>]*?)\/>/g, (match, tagName, attributes) => { + if (selfClosingTags.includes(tagName.toLowerCase())) { + return match; + } + return `<${tagName} ${attributes}>`; + }); + return result; +} +function toDisplayString(value) { + return value == null ? "" : isObject(value) ? JSON.stringify(value, null, 2) : String(value); +} +function insertAfter(newNode, existingNode) { + if (existingNode.nextSibling) { + existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling); + } else { + existingNode?.parentNode?.appendChild(newNode); + } +} +function isPropAttribute(attrName) { + if (attrName.startsWith(".")) { + return true; + } + if (attrName.startsWith("{") && attrName.endsWith("}")) { + return true; + } + return false; +} +function isSpreadProp(attr) { + return attr.startsWith("..."); +} +function isMirrorProp(attr) { + return attr.startsWith("{") && attr.endsWith("}"); +} +function isRegularProp(attr) { + return attr.startsWith("."); +} +function isEventAttribute(attrName) { + return attrName.startsWith("@"); +} +function componentHasPropByName(name, component) { + return Object.keys(component?.props ?? {}).some((prop) => prop === name); +} +function extractAttributeName(attrName) { + return attrName.replace(/^\.\.\./, "").replace(/^\./, "").replace(/^{/, "").replace(/}$/, "").replace(/:bind$/, ""); +} +function dashToCamel(str) { + return str.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +} +function extractPropName(attrName) { + return dashToCamel(extractAttributeName(attrName)); +} +function classNames(_) { + const classes = []; + for (let i = 0; i < arguments.length; i++) { + const arg = arguments[i]; + if (!arg) continue; + const argType = typeof arg; + if (argType === "string" || argType === "number") { + classes.push(arg); + } else if (Array.isArray(arg)) { + if (arg.length) { + const inner = classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } + } else if (argType === "object") { + if (arg.toString === Object.prototype.toString) { + for (let key in arg) { + if (Object.hasOwnProperty.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } else { + classes.push(arg.toString()); + } + } + } + return classes.join(" "); +} + +// src/directives/attribute.ts +var AttributeDirective = class { + element; + context; + expression; + attr; + extractedAttributeName; + previousClasses = []; + previousStyles = {}; + is = { + sameNameProperty: false, + bound: false, + spread: false, + componentProp: false + }; + constructor({ element, context, attr }) { + this.element = element; + this.context = context; + this.expression = attr.value; + this.attr = attr; + this.extractedAttributeName = extractAttributeName(attr.name); + this.is = { + sameNameProperty: attr.name.startsWith("{") && attr.name.endsWith("}"), + bound: attr.name.includes(":bind"), + spread: attr.name.startsWith("..."), + componentProp: false + }; + if (this.is.sameNameProperty) { + this.expression = this.extractedAttributeName; + } + if (this.is.spread) { + this.expression = this.extractedAttributeName; + } + console.log("attribute", attr.name, "spread?", this.is.spread); + element.removeAttribute(attr.name); + if (this.is.bound) { + context.effect(this.update.bind(this)); + } else { + this.update(); + } + } + update() { + let value = evalGet(this.context.scope, this.expression); + if (this.is.spread && typeof value === "object") { + for (const [key, val] of Object.entries(value)) { + this.element.setAttribute(key, String(val)); + } + } else if ((typeof value === "object" || Array.isArray(value)) && this.extractedAttributeName === "class") { + value = classNames(value); + const next = value.split(" "); + const diff = next.filter((c) => !this.previousClasses.includes(c)).filter(Boolean); + const rm = this.previousClasses.filter((c) => !next.includes(c)); + diff.forEach((c) => { + this.previousClasses.push(c); + this.element.classList.add(c); + }); + rm.forEach((c) => { + this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c); + this.element.classList.remove(c); + }); + } else if (typeof value === "object" && this.extractedAttributeName === "style") { + const next = Object.keys(value); + const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style)); + next.forEach((style) => { + this.previousStyles[style] = value[style]; + this.element.style[style] = value[style]; + }); + rm.forEach((style) => { + this.previousStyles[style] = ""; + this.element.style[style] = ""; + }); + this.previousStyles = value; + } else { + this.element.setAttribute(this.extractedAttributeName, value); + } + } +}; + +// src/directives/event.ts +var EventDirective = class { + element; + context; + expression; + attr; + eventCount = 0; + constructor({ element, context, attr }) { + this.element = element; + this.context = context; + this.expression = attr.value; + this.attr = attr; + const eventName = attr.name.replace(/^@/, ""); + const parts = eventName.split("."); + this.element.addEventListener(parts[0], (event) => { + if (parts.includes("prevent")) event.preventDefault(); + if (parts.includes("stop")) event.stopPropagation(); + if (parts.includes("once") && this.eventCount > 0) return; + this.eventCount++; + const handler = evalGet(context.scope, attr.value); + if (typeof handler === "function") { + handler(event); + } + }); + element.removeAttribute(attr.name); + } +}; + +// src/directives/for.ts +var forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/; +var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; +var stripParensRE = /^\(|\)$/g; +var destructureRE = /^[{[]\s*((?:[\w_$]+\s*,?\s*)+)[\]}]$/; +var _for = (el, exp, ctx, component, componentProps, allProps) => { + const inMatch = exp.match(forAliasRE); + if (!inMatch) { + console.warn(`invalid :for expression: ${exp}`); + return; + } + const nextNode = el.nextSibling; + const parent = el.parentElement; + const anchor = new Text(""); + parent.insertBefore(anchor, el); + parent.removeChild(el); + const sourceExp = inMatch[2].trim(); + let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim(); + let destructureBindings; + let isArrayDestructure = false; + let indexExp; + let objIndexExp; + let keyAttr = "key"; + let keyExp = el.getAttribute(keyAttr) || el.getAttribute(keyAttr = ":key") || el.getAttribute(keyAttr = ":key:bind"); + if (keyExp) { + el.removeAttribute(keyAttr); + if (keyAttr === "key") keyExp = JSON.stringify(keyExp); + } + let match; + if (match = valueExp.match(forIteratorRE)) { + valueExp = valueExp.replace(forIteratorRE, "").trim(); + indexExp = match[1].trim(); + if (match[2]) { + objIndexExp = match[2].trim(); + } + } + if (match = valueExp.match(destructureRE)) { + destructureBindings = match[1].split(",").map((s) => s.trim()); + isArrayDestructure = valueExp[0] === "["; + } + let mounted = false; + let blocks; + let childCtxs; + let keyToIndexMap; + const createChildContexts = (source) => { + const map = /* @__PURE__ */ new Map(); + const ctxs = []; + if (isArray(source)) { + for (let i = 0; i < source.length; i++) { + ctxs.push(createChildContext(map, source[i], i)); + } + } else if (typeof source === "number") { + for (let i = 0; i < source; i++) { + ctxs.push(createChildContext(map, i + 1, i)); + } + } else if (isObject(source)) { + let i = 0; + for (const key in source) { + ctxs.push(createChildContext(map, source[key], i++, key)); + } + } + return [ctxs, map]; + }; + const createChildContext = (map, value, index, objKey) => { + const data = {}; + if (destructureBindings) { + destructureBindings.forEach((b, i) => data[b] = value[isArrayDestructure ? i : b]); + } else { + data[valueExp] = value; + } + if (objKey) { + indexExp && (data[indexExp] = objKey); + objIndexExp && (data[objIndexExp] = index); + } else { + indexExp && (data[indexExp] = index); + } + const childCtx = createScopedContext(ctx, data); + const key = keyExp ? evalGet(childCtx.scope, keyExp) : index; + map.set(key, index); + childCtx.key = key; + return childCtx; + }; + const mountBlock = (ctx2, ref2) => { + const block = new Block({ element: el, parentContext: ctx2, replacementType: "replace", component, componentProps, allProps }); + block.key = ctx2.key; + block.insert(parent, ref2); + return block; + }; + ctx.effect(() => { + const source = evalGet(ctx.scope, sourceExp); + const prevKeyToIndexMap = keyToIndexMap; + [childCtxs, keyToIndexMap] = createChildContexts(source); + if (!mounted) { + blocks = childCtxs.map((s) => mountBlock(s, anchor)); + mounted = true; + } else { + for (let i2 = 0; i2 < blocks.length; i2++) { + if (!keyToIndexMap.has(blocks[i2].key)) { + blocks[i2].remove(); + } + } + const nextBlocks = []; + let i = childCtxs.length; + let nextBlock; + let prevMovedBlock; + while (i--) { + const childCtx = childCtxs[i]; + const oldIndex = prevKeyToIndexMap.get(childCtx.key); + let block; + if (oldIndex == null) { + block = mountBlock(childCtx, nextBlock ? nextBlock.element : anchor); + } else { + block = blocks[oldIndex]; + Object.assign(block.context.scope, childCtx.scope); + if (oldIndex !== i) { + if (blocks[oldIndex + 1] !== nextBlock || // If the next has moved, it must move too + prevMovedBlock === nextBlock) { + prevMovedBlock = block; + block.insert(parent, nextBlock ? nextBlock.element : anchor); + } + } + } + nextBlocks.unshift(nextBlock = block); + } + blocks = nextBlocks; + } + }); + return nextNode; +}; + +// src/directives/if.ts +function _if(el, exp, ctx, component, componentProps, allProps) { + const parent = el.parentElement; + const anchor = new Comment(":if"); + parent.insertBefore(anchor, el); + const branches = [{ exp, el }]; + let elseEl; + let elseExp; + while (elseEl = el.nextElementSibling) { + elseExp = null; + if (checkAndRemoveAttribute(elseEl, ":else") === "" || (elseExp = checkAndRemoveAttribute(elseEl, ":else-if"))) { + parent.removeChild(elseEl); + branches.push({ exp: elseExp, el: elseEl }); + } else { + break; + } + } + const nextNode = el.nextSibling; + parent.removeChild(el); + let block; + let activeBranchIndex = -1; + const removeActiveBlock = () => { + if (block) { + parent.insertBefore(anchor, block.element); + block.remove(); + block = void 0; + } + }; + ctx.effect(() => { + for (let i = 0; i < branches.length; i++) { + const { exp: exp2, el: el2 } = branches[i]; + if (!exp2 || evalGet(ctx.scope, exp2)) { + if (i !== activeBranchIndex) { + removeActiveBlock(); + block = new Block({ element: el2, parentContext: ctx, replacementType: "replace", component, componentProps, allProps }); + block.insert(parent, anchor); + parent.removeChild(anchor); + activeBranchIndex = i; + } + return; + } + } + activeBranchIndex = -1; + removeActiveBlock(); + }); + return nextNode; +} + +// src/directives/interpolation.ts +var delims = /{{\s?(.*?)\s?}}/g; +var InterpolationDirective = class { + element; + context; + textNodes = /* @__PURE__ */ new Map(); + constructor({ element, context }) { + this.element = element; + this.context = context; + this.findNodes(); + this.textNodes.forEach((nodes, expression) => { + const trimmedExpression = expression.slice(2, -2).trim(); + nodes.forEach((node) => { + const getter = (exp = trimmedExpression) => evalGet(this.context.scope, exp, node); + context.effect(() => { + node.textContent = toDisplayString(getter()); + }); + }); + }); + } + findNodes() { + const textContent = this.element.textContent.trim(); + if (textContent?.match(delims)) { + const textNodes = textContent.split(/(\{\{\s?[^}]+\s?\}\})/g).filter(Boolean); + if (textNodes) { + let previousNode = this.element; + for (let i = 0; i < textNodes.length; i++) { + const textNode = textNodes[i]; + if (textNode.match(/\{\{\s?.+\s?\}\}/)) { + const newNode = document.createTextNode(textNode); + if (i === 0) { + this.element.replaceWith(newNode); + } else { + insertAfter(newNode, previousNode); + } + previousNode = newNode; + if (this.textNodes.has(textNode)) { + this.textNodes.get(textNode).push(newNode); + } else { + this.textNodes.set(textNode, [newNode]); + } + } else { + const newNode = document.createTextNode(textNodes[i]); + if (i === 0) { + this.element.replaceWith(newNode); + } else { + insertAfter(newNode, previousNode); + } + previousNode = newNode; + } + } + } + } + } + update() { + } +}; + +// src/directives/teleport.ts +function _teleport(el, exp, ctx) { + const anchor = new Comment(":teleport"); + el.replaceWith(anchor); + const target = document.querySelector(exp); + if (!target) { + console.warn(`teleport target not found: ${exp}`); + return; + } + nextTick(() => { + target.appendChild(el); + }); + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + mutation.removedNodes.forEach((removedNode) => { + if (removedNode.contains(anchor)) { + el.remove(); + observer.disconnect(); + } + }); + }); + }); + observer.observe(document.body, { childList: true, subtree: true }); + return anchor; +} + +// src/directives/value.ts +function isInput(element) { + return element instanceof HTMLInputElement; +} +function isTextarea(element) { + return element instanceof HTMLTextAreaElement; +} +function isSelect(element) { + return element instanceof HTMLSelectElement; +} +var ValueDirective = class { + element; + context; + expression; + inputType; + constructor({ element, context, expression }) { + this.element = element; + this.context = context; + this.expression = expression; + this.inputType = element.getAttribute("type"); + if (isInput(element)) { + switch (this.inputType) { + case "text": + case "password": + case "number": + case "color": + element.addEventListener("input", () => { + const value = this.inputType === "number" ? element.value ? parseFloat(element.value) : 0 : element.value; + evalSet(this.context.scope, expression, value); + }); + break; + case "checkbox": + element.addEventListener("change", (e) => { + evalSet(this.context.scope, expression, !!e.currentTarget.checked); + }); + break; + case "radio": + element.addEventListener("change", (e) => { + if (e.currentTarget.checked) { + evalSet(this.context.scope, expression, element.getAttribute("value")); + } + }); + break; + default: + break; + } + } + if (isTextarea(element)) { + element.addEventListener("input", () => { + evalSet(this.context.scope, expression, element.value); + }); + } + if (isSelect(element)) { + element.addEventListener("change", () => { + evalSet(this.context.scope, expression, element.value); + }); + } + context.effect(this.updateElementValue.bind(this)); + } + updateElementValue() { + const value = evalGet(this.context.scope, this.expression, this.element); + if (isInput(this.element)) { + switch (this.inputType) { + case "text": + case "password": + case "number": + case "color": + this.element.value = value; + break; + case "checkbox": + this.element.checked = !!value; + break; + case "radio": + this.element.checked = this.element.value === value; + break; + default: + break; + } + } + if (isTextarea(this.element)) { + this.element.value = value; + } + if (isSelect(this.element)) { + this.element.value = value; + } + } +}; + +// src/reactivity/effect.ts +var targetMap = /* @__PURE__ */ new WeakMap(); +var effectStack = []; +function track(target, key) { + const activeEffect = effectStack[effectStack.length - 1]; + if (!activeEffect) return; + let effectsMap = targetMap.get(target); + if (!effectsMap) + targetMap.set(target, effectsMap = /* @__PURE__ */ new Map()); + let effects = effectsMap.get(key); + if (!effects) effectsMap.set(key, effects = /* @__PURE__ */ new Set()); + if (!effects.has(activeEffect)) { + effects.add(activeEffect); + activeEffect.refs.push(effects); + } +} +function trigger(target, key) { + const effectsMap = targetMap.get(target); + if (!effectsMap) return; + const scheduled = /* @__PURE__ */ new Set(); + effectsMap.get(key)?.forEach((effect2) => { + scheduled.add(effect2); + }); + scheduled.forEach(run); +} +function stop2(effect2) { + if (effect2.active) cleanup(effect2); + effect2.active = false; +} +function start(effect2) { + if (!effect2.active) { + effect2.active = true; + run(effect2); + } +} +function run(effect2) { + if (!effect2.active) return; + if (effectStack.includes(effect2)) return; + cleanup(effect2); + let val; + try { + effectStack.push(effect2); + val = effect2.handler(); + } finally { + effectStack.pop(); + } + return val; +} +function cleanup(effect2) { + const { refs } = effect2; + if (refs.length) { + for (const ref2 of refs) { + ref2.delete(effect2); + } + } + refs.length = 0; +} +function effect(handler, opts = {}) { + const { lazy } = opts; + const newEffect = { + active: !lazy, + handler, + refs: [] + }; + run(newEffect); + return { + start: () => { + start(newEffect); + }, + stop: () => { + stop2(newEffect); + }, + toggle: () => { + if (newEffect.active) { + stop2(newEffect); + } else { + start(newEffect); + } + return newEffect.active; + } + }; +} + +// src/reactivity/computed.ts +var $computed = Symbol("computed"); +function isComputed(value) { + return isObject(value) && value[$computed]; +} + +// src/reactivity/ref.ts +var $ref = Symbol("ref"); +function isRef(value) { + return isObject(value) && !!value[$ref]; +} +function ref(value = null) { + if (isObject(value)) { + return isRef(value) ? value : reactive(value); + } + const result = { value, [$ref]: true }; + return new Proxy(result, { + get(target, key, receiver) { + const val = Reflect.get(target, key, receiver); + track(result, "value"); + return val; + }, + set(target, key, value2) { + const oldValue = target[key]; + if (oldValue !== value2) { + const success = Reflect.set(target, key, value2); + if (success) { + trigger(result, "value"); + } + } + return true; + } + }); +} + +// src/reactivity/reactive.ts +var $reactive = Symbol("reactive"); +function reactive(value) { + if (!isObject(value)) return ref(value); + if (value[$reactive]) return value; + value[$reactive] = true; + Object.keys(value).forEach((key) => { + if (isObject(value[key])) { + value[key] = reactive(value[key]); + } + }); + return new Proxy(value, reactiveProxyHandler()); +} +function reactiveProxyHandler() { + return { + deleteProperty(target, key) { + const had = Reflect.has(target, key); + const result = Reflect.deleteProperty(target, key); + if (had) trigger(target, key); + return result; + }, + get(target, key) { + track(target, key); + return Reflect.get(target, key); + }, + set(target, key, value) { + if (target[key] === value) return true; + let newObj = false; + if (isObject(value) && !isObject(target[key])) { + newObj = true; + } + if (Reflect.set(target, key, value)) { + trigger(target, key); + } + if (newObj) { + target[key] = reactive(target[key]); + } + return true; + } + }; +} + +// src/index.ts +function provide(key, value) { + if (!current.componentBlock) { + console.warn("Can't provide: no current component block"); + } + current.componentBlock.provides.set(key, value); +} +function inject(key) { + if (!current.componentBlock) { + console.warn("Can't inject: no current component block"); + } + let c = current.componentBlock; + while (c) { + if (c.provides.has(key)) { + return c.provides.get(key); + } + c = c.parentComponentBlock; + } + return void 0; +} +var App = class { + rootBlock; + registry = /* @__PURE__ */ new Map(); + plugins = /* @__PURE__ */ new Set(); + register(name, component) { + this.registry.set(name, component); + } + use(plugin, ...config) { + this.plugins.add(plugin); + plugin.use(this, ...config); + } + getComponent(tag) { + return this.registry.get(tag); + } + mount(component, target = "body", props = {}) { + const root = typeof target === "string" ? document.querySelector(target) : target; + const display = root.style.display; + root.style.display = "none"; + this.rootBlock = this._mount(component, root, props, true); + root.style.display = display; + return this.rootBlock; + } + _mount(component, target, props, isRoot = false) { + const parentContext = createContext({ app: this }); + if (props) { + parentContext.scope = reactive(props); + bindContextMethods(parentContext.scope); + } + parentContext.scope.$isRef = isRef; + parentContext.scope.$isComputed = isComputed; + const block = new Block({ + element: target, + parentContext, + component, + isRoot, + componentProps: props, + replacementType: "replaceChildren" + }); + return block; + } + unmount() { + this.rootBlock.teardown(); + } +}; +function createContext({ parentContext, app: app2 }) { + const context = { + app: app2 ? app2 : parentContext && parentContext.app ? parentContext.app : null, + scope: parentContext ? parentContext.scope : reactive({}), + blocks: [], + effects: [], + slots: [], + templates: parentContext ? parentContext.templates : [], + effect: (handler) => { + const e = effect(handler); + context.effects.push(e); + return e; + } + }; + return context; +} +var createScopedContext = (ctx, data = {}) => { + const parentScope = ctx.scope; + const mergedScope = Object.create(parentScope); + Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data)); + let proxy; + proxy = reactive( + new Proxy(mergedScope, { + set(target, key, val, receiver) { + if (receiver === proxy && !target.hasOwnProperty(key)) { + return Reflect.set(parentScope, key, val); + } + return Reflect.set(target, key, val, receiver); + } + }) + ); + bindContextMethods(proxy); + const out = { + ...ctx, + scope: { + ...ctx.scope, + ...proxy + } + }; + return out; +}; +function bindContextMethods(scope) { + for (const key of Object.keys(scope)) { + if (typeof scope[key] === "function") { + scope[key] = scope[key].bind(scope); + } + } +} +function mergeProps(props, defaultProps) { + const merged = {}; + Object.keys(defaultProps).forEach((defaultProp) => { + const propValue = props.hasOwnProperty(defaultProp) ? props[defaultProp] : defaultProps[defaultProp]?.default; + merged[defaultProp] = reactive(typeof propValue === "function" ? propValue() : propValue); + }); + return merged; +} +var current = { componentBlock: void 0 }; +var Block = class { + element; + context; + parentContext; + component; + provides = /* @__PURE__ */ new Map(); + parentComponentBlock; + componentProps; + allProps; + isFragment; + start; + end; + key; + constructor(opts) { + this.isFragment = opts.element instanceof HTMLTemplateElement; + this.parentComponentBlock = opts.parentComponentBlock; + if (opts.component) { + current.componentBlock = this; + this.element = stringToElement(opts.component.template); + } else { + if (this.isFragment) { + this.element = opts.element.content.cloneNode(true); + } else if (typeof opts.element === "string") { + this.element = stringToElement(opts.element); + } else { + this.element = opts.element.cloneNode(true); + opts.element.replaceWith(this.element); + } + } + if (opts.isRoot) { + this.context = opts.parentContext; + } else { + this.parentContext = opts.parentContext ? opts.parentContext : createContext({}); + this.parentContext.blocks.push(this); + this.context = createContext({ parentContext: opts.parentContext }); + } + if (opts.component) { + this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {}); + if (opts.component.main) { + this.context.scope = { + ...opts.component.main(this.componentProps) || {} + }; + } + } + opts.allProps?.forEach((prop) => { + if (prop.isBind) { + this.context.effect(() => { + let newValue; + if (prop.isSpread) { + const spreadProps = evalGet(this.parentContext.scope, prop.extractedName); + if (isObject(spreadProps)) { + Object.keys(spreadProps).forEach((key) => { + newValue = spreadProps[key]; + this.setProp(key, newValue); + }); + } + } else { + newValue = prop.isMirror ? evalGet(this.parentContext.scope, prop.extractedName) : evalGet(this.parentContext.scope, prop.exp); + this.setProp(prop.extractedName, newValue); + } + }); + } + }); + this.context.slots = findSlotNodes(this.element); + this.context.templates = opts.templates ?? []; + this.context.slots.forEach((slot) => { + const template = this.context.templates.find((t) => t.targetSlotName === slot.name); + if (template) { + const templateContents = template.node.content.cloneNode(true); + slot.node.replaceWith(templateContents); + } + }); + this.context.scope.$isRef = isRef; + this.context.scope.$isComputed = isComputed; + walk(this.element, this.context); + if (opts.component) { + if (opts.replacementType === "replace") { + if (opts.element instanceof HTMLElement) { + opts.element.replaceWith(this.element); + } + } else { + if (opts.element instanceof HTMLElement) { + opts.element.replaceChildren(this.element); + } + } + } + } + setProp(name, value) { + if (isRef(this.componentProps[name])) { + this.componentProps[name].value = value; + } else { + this.componentProps[name] = value; + } + } + insert(parent, anchor = null) { + if (this.isFragment) { + if (this.start) { + let node = this.start; + let next; + while (node) { + next = node.nextSibling; + parent.insertBefore(node, anchor); + if (node === this.end) { + break; + } + node = next; + } + } else { + this.start = new Text(""); + this.end = new Text(""); + parent.insertBefore(this.end, anchor); + parent.insertBefore(this.start, this.end); + parent.insertBefore(this.element, this.end); + } + } else { + parent.insertBefore(this.element, anchor); + } + } + remove() { + if (this.parentContext) { + const i = this.parentContext.blocks.indexOf(this); + if (i > -1) { + this.parentContext.blocks.splice(i, 1); + } + } + if (this.start) { + const parent = this.start.parentNode; + let node = this.start; + let next; + while (node) { + next = node.nextSibling; + parent.removeChild(node); + if (node === this.end) { + break; + } + node = next; + } + } else { + this.element.remove(); + } + this.teardown(); + } + teardown() { + this.context.blocks.forEach((block) => { + block.teardown(); + }); + this.context.effects.forEach(stop); + } +}; +function isComponent(element, context) { + return !!context.app.getComponent(element.tagName.toLowerCase()); +} +function walk(node, context) { + if (isText(node)) { + new InterpolationDirective({ element: node, context }); + return; + } + if (isElement(node)) { + let exp; + if (isComponent(node, context)) { + const component = context.app.getComponent(node.tagName.toLowerCase()); + const allProps = Array.from(node.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({ + isMirror: isMirrorProp(attr.name), + isSpread: isSpreadProp(attr.name), + isBind: attr.name.includes("bind"), + originalName: attr.name, + extractedName: extractPropName(attr.name), + exp: attr.value, + value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : void 0 + })); + const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { + if (isSpread) { + const spread = evalGet(context.scope, extractedName); + if (isObject(spread)) Object.assign(acc, spread); + } else if (isMirror) { + acc[extractedName] = evalGet(context.scope, extractedName); + } else { + acc[extractedName] = value; + } + return acc; + }, {}); + 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); + } + const templates = findTemplateNodes(node); + const block = new Block({ + element: node, + parentContext: context, + component, + replacementType: "replace", + parentComponentBlock: current.componentBlock, + templates, + componentProps, + allProps + }); + 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 }); + } + Array.from(node.attributes).forEach((attr) => { + if (isPropAttribute(attr.name)) { + new AttributeDirective({ element: node, context, attr }); + } + if (isEventAttribute(attr.name)) { + new EventDirective({ element: node, context, attr }); + } + }); + walkChildren(node, context); + } +} +function walkChildren(node, context) { + let child = node.firstChild; + while (child) { + child = walk(child, context) || child.nextSibling; + } +} +var evalFuncCache = {}; +function evalGet(scope, exp, el) { + if (!exp.trim()) return void 0; + return execute(scope, `const ___value = (${exp.trim()}); return ___value;`, el); +} +function evalSet(scope, exp, value) { + value = typeof value === "string" ? `"${value}"` : value; + return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false); +} +function execute(scope, exp, el, flatRefs = true) { + const newScope = flatRefs ? flattenRefs(scope) : scope; + const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp)); + try { + return fn(newScope, el); + } catch (e) { + console.warn(`Error evaluating expression: "${exp}":`); + console.error(e); + } +} +function toFunction(exp) { + try { + return new Function("$data", "$el", `with($data){${exp}}`); + } catch (e) { + console.error(`${e.message} in expression: ${exp}`); + return () => { + }; + } +} +function flattenRefs(scope) { + const mapped = {}; + for (const key in scope) { + if (scope.hasOwnProperty(key)) { + if (isRef(scope[key])) { + mapped[key] = scope[key].value; + } else { + mapped[key] = scope[key]; + } + } + } + return mapped; +} +var counter = { + template: html` +
+
true
+

Count: {{count}}{{count >= 2 ? '!!!' : ''}}

+ + +
+ `, + main() { + const count = ref(0); + const style = reactive({ color: "red" }); + const increment = () => count.value++; + const decrement = () => count.value--; + setInterval(() => { + if (style.color === "red") { + style.color = "blue"; + } else { + style.color = "red"; + } + }, 500); + return { count, increment, decrement, style }; + } +}; +var app = new App(); +app.mount(counter, "#app"); +export { + App, + Block, + createContext, + createScopedContext, + current, + evalGet, + evalSet, + inject, + provide +}; +//# sourceMappingURL=index.js.map diff --git a/public/index.js.map b/public/index.js.map new file mode 100755 index 0000000..a2c14d4 --- /dev/null +++ b/public/index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/util.ts", "../src/directives/attribute.ts", "../src/directives/event.ts", "../src/directives/for.ts", "../src/directives/if.ts", "../src/directives/interpolation.ts", "../src/directives/teleport.ts", "../src/directives/value.ts", "../src/reactivity/effect.ts", "../src/reactivity/computed.ts", "../src/reactivity/ref.ts", "../src/reactivity/reactive.ts", "../src/index.ts"], + "sourcesContent": ["import { Component } from \".\";\n\nexport function stringToElement(template: string): Element {\n const parser = new DOMParser();\n const doc = parser.parseFromString(template, \"text/html\");\n return doc.body.firstChild as Element;\n}\n\nexport const isText = (node: Node): node is Text => {\n return node.nodeType === Node.TEXT_NODE;\n};\n\nexport const isTemplate = (node: Node): node is HTMLTemplateElement => {\n return node.nodeName === \"TEMPLATE\";\n};\n\nexport const isElement = (node: Node): node is Element => {\n return node.nodeType === Node.ELEMENT_NODE;\n};\n\nexport function isObject(value: any): value is object {\n return value !== null && typeof value === \"object\";\n}\n\nexport function isArray(value: any): value is any[] {\n return Array.isArray(value);\n}\n\nexport function checkAndRemoveAttribute(el: Element, attrName: string): string | null {\n // Attempt to get the attribute value\n const attributeValue = el.getAttribute(attrName);\n\n // If attribute exists, remove it from the element\n if (attributeValue !== null) {\n el.removeAttribute(attrName);\n }\n\n // Return the value of the attribute or null if not present\n return attributeValue;\n}\n\nexport interface Slot {\n node: Element;\n name: string;\n}\n\nexport interface Template {\n targetSlotName: string;\n node: HTMLTemplateElement;\n}\n\nexport function findSlotNodes(element: Element): Slot[] {\n const slots: Slot[] = [];\n\n const findSlots = (node: Element) => {\n Array.from(node.childNodes).forEach((node) => {\n if (isElement(node)) {\n if (node.nodeName === \"SLOT\") {\n slots.push({ node, name: node.getAttribute(\"name\") || \"default\" });\n }\n\n if (node.hasChildNodes()) {\n findSlots(node);\n }\n }\n });\n };\n\n findSlots(element);\n\n return slots;\n}\n\nexport function findTemplateNodes(element: Element) {\n const templates: Template[] = [];\n\n const findTemplates = (element: Element) => {\n let defaultContentNodes: Node[] = [];\n\n Array.from(element.childNodes).forEach((node) => {\n if (isElement(node) || isText(node)) {\n if (isElement(node) && node.nodeName === \"TEMPLATE\" && isTemplate(node)) {\n templates.push({ targetSlotName: node.getAttribute(\"slot\") || \"\", node });\n } else {\n // Capture non-template top-level nodes and text nodes for default slot\n defaultContentNodes.push(node);\n }\n }\n });\n\n if (defaultContentNodes.length > 0) {\n // Create a template element with a default slot\n const defaultTemplate = document.createElement(\"template\");\n defaultTemplate.setAttribute(\"slot\", \"default\");\n\n defaultContentNodes.forEach((node) => {\n defaultTemplate.content.appendChild(node);\n });\n\n templates.push({ targetSlotName: \"default\", node: defaultTemplate });\n }\n };\n\n findTemplates(element);\n\n return templates;\n}\n\nexport const nextTick = async (f?: Function) => {\n await new Promise((r) =>\n setTimeout((_) =>\n requestAnimationFrame((_) => {\n f && f();\n r();\n }),\n ),\n );\n};\n\nexport function html(strings: TemplateStringsArray, ...values: any[]): string {\n // List of valid self-closing tags in HTML\n const selfClosingTags = [\"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\", \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\"];\n\n // Join the strings and values into a single template\n let result = strings.reduce((acc, str, i) => acc + str + (values[i] || \"\"), \"\");\n\n // Match non-HTML valid self-closing tags\n result = result.replace(/<([a-zA-Z][^\\s/>]*)\\s*([^>]*?)\\/>/g, (match, tagName, attributes) => {\n // If the tag is a valid self-closing tag, return it as is\n if (selfClosingTags.includes(tagName.toLowerCase())) {\n return match;\n }\n\n // Return the tag as an open/close tag preserving attributes\n return `<${tagName} ${attributes}>`;\n });\n\n return result;\n}\n\nexport function toDisplayString(value: unknown) {\n return value == null ? \"\" : isObject(value) ? JSON.stringify(value, null, 2) : String(value);\n}\n\nexport function insertAfter(newNode: Node, existingNode: Node) {\n if (existingNode.nextSibling) {\n existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);\n } else {\n existingNode?.parentNode?.appendChild(newNode);\n }\n}\n\nexport function isPropAttribute(attrName: string) {\n if (attrName.startsWith(\".\")) {\n return true;\n }\n\n if (attrName.startsWith(\"{\") && attrName.endsWith(\"}\")) {\n return true;\n }\n\n return false;\n}\n\nexport function isSpreadProp(attr: string) {\n return attr.startsWith(\"...\");\n}\n\nexport function isMirrorProp(attr: string) {\n return attr.startsWith(\"{\") && attr.endsWith(\"}\");\n}\n\nexport function isRegularProp(attr: string) {\n return attr.startsWith(\".\");\n}\n\nexport function isEventAttribute(attrName: string) {\n return attrName.startsWith(\"@\");\n}\n\nexport function componentHasPropByName(name: string, component: Component) {\n return Object.keys(component?.props ?? {}).some((prop) => prop === name);\n}\n\nexport function extractAttributeName(attrName: string) {\n return attrName\n .replace(/^\\.\\.\\./, \"\")\n .replace(/^\\./, \"\")\n .replace(/^{/, \"\")\n .replace(/}$/, \"\")\n .replace(/:bind$/, \"\");\n}\n\nfunction dashToCamel(str: string) {\n return str.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());\n}\n\nexport function extractPropName(attrName: string) {\n return dashToCamel(extractAttributeName(attrName));\n}\n\nexport function classNames(_: any) {\n const classes = [];\n for (let i = 0; i < arguments.length; i++) {\n const arg = arguments[i];\n if (!arg) continue;\n const argType = typeof arg;\n if (argType === \"string\" || argType === \"number\") {\n classes.push(arg);\n } else if (Array.isArray(arg)) {\n if (arg.length) {\n const inner = classNames.apply(null, arg);\n if (inner) {\n classes.push(inner);\n }\n }\n } else if (argType === \"object\") {\n if (arg.toString === Object.prototype.toString) {\n for (let key in arg) {\n if (Object.hasOwnProperty.call(arg, key) && arg[key]) {\n classes.push(key);\n }\n }\n } else {\n classes.push(arg.toString());\n }\n }\n }\n return classes.join(\" \");\n}\n", "import { Context, evalGet } from \"..\";\nimport { classNames, extractAttributeName } from \"../util\";\n\ninterface AttributeDirectiveOptions {\n element: Element;\n context: Context;\n attr: Attr;\n}\n\ninterface Is {\n sameNameProperty: boolean;\n bound: boolean;\n spread: boolean;\n componentProp: boolean;\n}\n\nexport class AttributeDirective {\n element: Element;\n context: Context;\n expression: string;\n attr: Attr;\n extractedAttributeName: string;\n\n previousClasses: string[] = [];\n previousStyles: { [key: string]: string } = {};\n\n is: Is = {\n sameNameProperty: false,\n bound: false,\n spread: false,\n componentProp: false,\n };\n\n constructor({ element, context, attr }: AttributeDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = attr.value;\n this.attr = attr;\n this.extractedAttributeName = extractAttributeName(attr.name);\n\n this.is = {\n sameNameProperty: attr.name.startsWith(\"{\") && attr.name.endsWith(\"}\"),\n bound: attr.name.includes(\":bind\"),\n spread: attr.name.startsWith(\"...\"),\n componentProp: false,\n };\n\n if (this.is.sameNameProperty) {\n this.expression = this.extractedAttributeName;\n }\n\n if (this.is.spread) {\n this.expression = this.extractedAttributeName;\n }\n\n console.log(\"attribute\", attr.name, \"spread?\", this.is.spread)\n\n element.removeAttribute(attr.name);\n\n if (this.is.bound) {\n context.effect(this.update.bind(this));\n } else {\n this.update();\n }\n }\n\n update() {\n let value = evalGet(this.context.scope, this.expression);\n\n if (this.is.spread && typeof value === \"object\") {\n for (const [key, val] of Object.entries(value)) {\n this.element.setAttribute(key, String(val));\n }\n } else if ((typeof value === \"object\" || Array.isArray(value)) && this.extractedAttributeName === \"class\") {\n value = classNames(value);\n const next = value.split(\" \");\n\n // If we now have classes that are not already on the element, add them now.\n // Remove classes that are no longer on the element.\n const diff = next.filter((c: string) => !this.previousClasses.includes(c)).filter(Boolean);\n const rm = this.previousClasses.filter((c) => !next.includes(c));\n\n diff.forEach((c: string) => {\n this.previousClasses.push(c);\n this.element.classList.add(c);\n });\n\n rm.forEach((c) => {\n this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c);\n this.element.classList.remove(c);\n });\n } else if (typeof value === \"object\" && this.extractedAttributeName === \"style\") {\n const next = Object.keys(value);\n const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style));\n\n next.forEach((style) => {\n this.previousStyles[style] = value[style];\n // @ts-ignore\n this.element.style[style] = value[style];\n });\n\n rm.forEach((style) => {\n this.previousStyles[style] = \"\";\n // @ts-ignore\n this.element.style[style] = \"\";\n });\n\n this.previousStyles = value;\n } else {\n this.element.setAttribute(this.extractedAttributeName, value);\n }\n }\n}\n", "import { Context, evalGet } from \"..\";\n\ninterface EventDirectiveOptions {\n element: Element;\n context: Context;\n attr: Attr;\n}\n\nexport class EventDirective {\n element: Element;\n context: Context;\n expression: string;\n attr: Attr;\n eventCount = 0;\n\n constructor({ element, context, attr }: EventDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = attr.value;\n this.attr = attr;\n\n const eventName = attr.name.replace(/^@/, \"\");\n const parts = eventName.split(\".\");\n\n this.element.addEventListener(parts[0], (event) => {\n if (parts.includes(\"prevent\")) event.preventDefault();\n if (parts.includes(\"stop\")) event.stopPropagation();\n if (parts.includes(\"once\") && this.eventCount > 0) return;\n\n this.eventCount++;\n\n const handler = evalGet(context.scope, attr.value);\n if (typeof handler === \"function\") {\n handler(event);\n }\n });\n\n element.removeAttribute(attr.name);\n }\n}\n", "import { Block, Component, Context, createScopedContext, evalGet } from \"..\";\nimport { isArray, isObject } from \"../util\";\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\nconst destructureRE = /^[{[]\\s*((?:[\\w_$]+\\s*,?\\s*)+)[\\]}]$/;\n\ntype KeyToIndexMap = Map;\n\nexport const _for = (el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) => {\n const inMatch = exp.match(forAliasRE);\n if (!inMatch) {\n console.warn(`invalid :for expression: ${exp}`);\n return;\n }\n\n const nextNode = el.nextSibling;\n\n const parent = el.parentElement!;\n const anchor = new Text(\"\");\n parent.insertBefore(anchor, el);\n parent.removeChild(el);\n\n const sourceExp = inMatch[2].trim();\n let valueExp = inMatch[1].trim().replace(stripParensRE, \"\").trim();\n let destructureBindings: string[] | undefined;\n let isArrayDestructure = false;\n let indexExp: string | undefined;\n let objIndexExp: string | undefined;\n\n let keyAttr = \"key\";\n let keyExp = el.getAttribute(keyAttr) || el.getAttribute((keyAttr = \":key\")) || el.getAttribute((keyAttr = \":key:bind\"));\n if (keyExp) {\n el.removeAttribute(keyAttr);\n if (keyAttr === \"key\") keyExp = JSON.stringify(keyExp);\n }\n\n let match: any;\n if ((match = valueExp.match(forIteratorRE))) {\n valueExp = valueExp.replace(forIteratorRE, \"\").trim();\n indexExp = match[1].trim();\n if (match[2]) {\n objIndexExp = match[2].trim();\n }\n }\n\n if ((match = valueExp.match(destructureRE))) {\n destructureBindings = match[1].split(\",\").map((s: string) => s.trim());\n isArrayDestructure = valueExp[0] === \"[\";\n }\n\n let mounted = false;\n let blocks: Block[];\n let childCtxs: Context[];\n let keyToIndexMap: Map;\n\n const createChildContexts = (source: unknown): [Context[], KeyToIndexMap] => {\n const map: KeyToIndexMap = new Map();\n const ctxs: Context[] = [];\n\n if (isArray(source)) {\n for (let i = 0; i < source.length; i++) {\n ctxs.push(createChildContext(map, source[i], i));\n }\n } else if (typeof source === \"number\") {\n for (let i = 0; i < source; i++) {\n ctxs.push(createChildContext(map, i + 1, i));\n }\n } else if (isObject(source)) {\n let i = 0;\n for (const key in source) {\n ctxs.push(createChildContext(map, source[key], i++, key));\n }\n }\n\n return [ctxs, map];\n };\n\n const createChildContext = (map: KeyToIndexMap, value: any, index: number, objKey?: string): Context => {\n const data: any = {};\n if (destructureBindings) {\n destructureBindings.forEach((b, i) => (data[b] = value[isArrayDestructure ? i : b]));\n } else {\n data[valueExp] = value;\n }\n if (objKey) {\n indexExp && (data[indexExp] = objKey);\n objIndexExp && (data[objIndexExp] = index);\n } else {\n indexExp && (data[indexExp] = index);\n }\n\n const childCtx = createScopedContext(ctx, data);\n const key = keyExp ? evalGet(childCtx.scope, keyExp) : index;\n map.set(key, index);\n childCtx.key = key;\n return childCtx;\n };\n\n const mountBlock = (ctx: Context, ref: Node) => {\n const block = new Block({ element: el, parentContext: ctx, replacementType: \"replace\", component, componentProps, allProps });\n block.key = ctx.key;\n block.insert(parent, ref);\n return block;\n };\n\n ctx.effect(() => {\n const source = evalGet(ctx.scope, sourceExp);\n const prevKeyToIndexMap = keyToIndexMap;\n [childCtxs, keyToIndexMap] = createChildContexts(source);\n if (!mounted) {\n blocks = childCtxs.map((s) => mountBlock(s, anchor));\n mounted = true;\n } else {\n for (let i = 0; i < blocks.length; i++) {\n if (!keyToIndexMap.has(blocks[i].key)) {\n blocks[i].remove();\n }\n }\n\n const nextBlocks: Block[] = [];\n let i = childCtxs.length;\n let nextBlock: Block | undefined;\n let prevMovedBlock: Block | undefined;\n while (i--) {\n const childCtx = childCtxs[i];\n const oldIndex = prevKeyToIndexMap.get(childCtx.key);\n let block: Block;\n if (oldIndex == null) {\n // new\n block = mountBlock(childCtx, nextBlock ? nextBlock.element : anchor);\n } else {\n // update\n block = blocks[oldIndex];\n Object.assign(block.context.scope, childCtx.scope);\n if (oldIndex !== i) {\n // moved\n if (\n blocks[oldIndex + 1] !== nextBlock ||\n // If the next has moved, it must move too\n prevMovedBlock === nextBlock\n ) {\n prevMovedBlock = block;\n block.insert(parent, nextBlock ? nextBlock.element : anchor);\n }\n }\n }\n nextBlocks.unshift((nextBlock = block));\n }\n blocks = nextBlocks;\n }\n });\n\n return nextNode;\n};\n", "import { Block, Component, Context, evalGet } from \"..\";\nimport { checkAndRemoveAttribute } from \"../util\";\n\ninterface Branch {\n exp?: string | null;\n el: Element;\n}\n\nexport function _if(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) {\n const parent = el.parentElement!;\n const anchor = new Comment(\":if\");\n\n parent.insertBefore(anchor, el);\n\n const branches: Branch[] = [{ exp, el }];\n\n let elseEl: Element | null;\n let elseExp: string | null;\n\n while ((elseEl = el.nextElementSibling)) {\n elseExp = null;\n\n if (checkAndRemoveAttribute(elseEl, \":else\") === \"\" || (elseExp = checkAndRemoveAttribute(elseEl, \":else-if\"))) {\n parent.removeChild(elseEl);\n branches.push({ exp: elseExp, el: elseEl });\n } else {\n break;\n }\n }\n\n const nextNode = el.nextSibling;\n parent.removeChild(el);\n\n let block: Block | undefined;\n let activeBranchIndex = -1;\n\n const removeActiveBlock = () => {\n if (block) {\n parent.insertBefore(anchor, block.element);\n block.remove();\n block = undefined;\n }\n };\n\n ctx.effect(() => {\n for (let i = 0; i < branches.length; i++) {\n const { exp, el } = branches[i];\n\n if (!exp || evalGet(ctx.scope, exp)) {\n if (i !== activeBranchIndex) {\n removeActiveBlock();\n block = new Block({ element: el, parentContext: ctx, replacementType: \"replace\", component, componentProps, allProps });\n block.insert(parent, anchor);\n parent.removeChild(anchor);\n activeBranchIndex = i;\n }\n\n return;\n }\n }\n\n activeBranchIndex = -1;\n removeActiveBlock();\n });\n\n return nextNode;\n}\n", "import { Context, evalGet } from \"../\";\nimport { insertAfter, toDisplayString } from \"../util\";\n\ninterface InterpolationDirectiveOptions {\n element: Text;\n context: Context;\n}\n\nconst delims = /{{\\s?(.*?)\\s?}}/g;\n\nexport class InterpolationDirective {\n element: Text;\n context: Context;\n textNodes: Map = new Map();\n\n constructor({ element, context }: InterpolationDirectiveOptions) {\n this.element = element;\n this.context = context;\n\n this.findNodes();\n\n this.textNodes.forEach((nodes, expression) => {\n const trimmedExpression = expression.slice(2, -2).trim();\n\n nodes.forEach((node) => {\n const getter = (exp = trimmedExpression) => evalGet(this.context.scope, exp, node);\n\n context.effect(() => {\n node.textContent = toDisplayString(getter());\n });\n });\n });\n }\n\n findNodes() {\n const textContent = this.element.textContent.trim();\n if (textContent?.match(delims)) {\n const textNodes = textContent.split(/(\\{\\{\\s?[^}]+\\s?\\}\\})/g).filter(Boolean);\n if (textNodes) {\n let previousNode = this.element;\n\n for (let i = 0; i < textNodes.length; i++) {\n const textNode = textNodes[i];\n\n if (textNode.match(/\\{\\{\\s?.+\\s?\\}\\}/)) {\n const newNode = document.createTextNode(textNode);\n\n if (i === 0) {\n this.element.replaceWith(newNode);\n } else {\n insertAfter(newNode, previousNode);\n }\n\n previousNode = newNode;\n\n if (this.textNodes.has(textNode)) {\n this.textNodes.get(textNode).push(newNode);\n } else {\n this.textNodes.set(textNode, [newNode]);\n }\n } else {\n const newNode = document.createTextNode(textNodes[i]);\n\n if (i === 0) {\n this.element.replaceWith(newNode);\n } else {\n insertAfter(newNode, previousNode);\n }\n\n previousNode = newNode;\n }\n }\n }\n }\n }\n\n update() {}\n}\n", "import { Context } from \"..\";\nimport { nextTick } from \"../util\";\n\nexport function _teleport(el: Element, exp: string, ctx: Context) {\n const anchor = new Comment(\":teleport\");\n el.replaceWith(anchor);\n\n const target = document.querySelector(exp);\n if (!target) {\n console.warn(`teleport target not found: ${exp}`);\n return;\n }\n\n nextTick(() => {\n target.appendChild(el);\n });\n\n const observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n mutation.removedNodes.forEach((removedNode) => {\n if (removedNode.contains(anchor)) {\n el.remove();\n observer.disconnect();\n }\n });\n });\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n\n return anchor;\n}\n", "import { Context, evalGet, evalSet } from \"..\";\n\ninterface ValueDirectiveOptions {\n element: Element;\n context: Context;\n expression: string;\n}\n\ntype SupportedModelType = \"text\" | \"checkbox\" | \"radio\" | \"number\" | \"password\" | \"color\";\n\nfunction isInput(element: Element): element is HTMLInputElement {\n return element instanceof HTMLInputElement;\n}\n\nfunction isTextarea(element: Element): element is HTMLTextAreaElement {\n return element instanceof HTMLTextAreaElement;\n}\n\nfunction isSelect(element: Element): element is HTMLSelectElement {\n return element instanceof HTMLSelectElement;\n}\n\nexport class ValueDirective {\n element: Element;\n context: Context;\n expression: string;\n inputType: SupportedModelType;\n\n constructor({ element, context, expression }: ValueDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = expression;\n this.inputType = element.getAttribute(\"type\") as SupportedModelType;\n\n // Element -> Context\n if (isInput(element)) {\n switch (this.inputType) {\n case \"text\":\n case \"password\":\n case \"number\":\n case \"color\":\n element.addEventListener(\"input\", () => {\n const value = this.inputType === \"number\" ? (element.value ? parseFloat(element.value) : 0) : element.value;\n evalSet(this.context.scope, expression, value);\n });\n break;\n\n case \"checkbox\":\n element.addEventListener(\"change\", (e: any) => {\n evalSet(this.context.scope, expression, !!e.currentTarget.checked);\n });\n break;\n case \"radio\":\n element.addEventListener(\"change\", (e: any) => {\n if (e.currentTarget.checked) {\n evalSet(this.context.scope, expression, element.getAttribute(\"value\"));\n }\n });\n break;\n default:\n break;\n }\n }\n\n if (isTextarea(element)) {\n element.addEventListener(\"input\", () => {\n evalSet(this.context.scope, expression, element.value);\n });\n }\n\n if (isSelect(element)) {\n element.addEventListener(\"change\", () => {\n evalSet(this.context.scope, expression, element.value);\n });\n }\n\n // Context -> Element\n context.effect(this.updateElementValue.bind(this));\n }\n\n updateElementValue() {\n const value = evalGet(this.context.scope, this.expression, this.element);\n\n if (isInput(this.element)) {\n switch (this.inputType) {\n case \"text\":\n case \"password\":\n case \"number\":\n case \"color\":\n this.element.value = value;\n break;\n case \"checkbox\":\n this.element.checked = !!value;\n break;\n case \"radio\":\n this.element.checked = this.element.value === value;\n break;\n default:\n break;\n }\n }\n\n if (isTextarea(this.element)) {\n this.element.value = value;\n }\n\n if (isSelect(this.element)) {\n this.element.value = value;\n }\n }\n}\n", "interface EffectOptions {\n lazy?: boolean;\n}\n\ninterface EffectFunction {\n active: boolean;\n handler: () => void;\n refs: Set[];\n}\n\ntype Effects = Set;\ntype EffectsMap = Map;\ntype TargetMap = WeakMap;\n\nconst targetMap: TargetMap = new WeakMap();\nconst effectStack: (EffectFunction | undefined)[] = [];\n\nexport function track(target: T, key: PropertyKey) {\n const activeEffect = effectStack[effectStack.length - 1];\n\n if (!activeEffect) return;\n\n let effectsMap = targetMap.get(target);\n if (!effectsMap)\n targetMap.set(target, (effectsMap = new Map() as EffectsMap));\n\n let effects = effectsMap.get(key);\n if (!effects) effectsMap.set(key, (effects = new Set()));\n\n if (!effects.has(activeEffect)) {\n effects.add(activeEffect);\n activeEffect.refs.push(effects);\n }\n}\n\nexport function trigger(target: any, key: PropertyKey) {\n const effectsMap = targetMap.get(target);\n if (!effectsMap) return;\n\n const scheduled = new Set();\n\n effectsMap.get(key)?.forEach((effect) => {\n scheduled.add(effect);\n });\n\n scheduled.forEach(run);\n}\n\nfunction stop(effect: EffectFunction) {\n if (effect.active) cleanup(effect);\n effect.active = false;\n}\n\nfunction start(effect: EffectFunction) {\n if (!effect.active) {\n effect.active = true;\n run(effect);\n }\n}\n\nfunction run(effect: EffectFunction): unknown {\n if (!effect.active) return;\n\n if (effectStack.includes(effect)) return;\n\n cleanup(effect);\n\n let val: unknown;\n\n try {\n effectStack.push(effect);\n val = effect.handler();\n } finally {\n effectStack.pop();\n }\n\n return val;\n}\n\nfunction cleanup(effect: EffectFunction) {\n const { refs } = effect;\n\n if (refs.length) {\n for (const ref of refs) {\n ref.delete(effect);\n }\n }\n\n refs.length = 0;\n}\n\nexport function effect(handler: () => void, opts: EffectOptions = {}) {\n const { lazy } = opts;\n const newEffect: EffectFunction = {\n active: !lazy,\n handler,\n refs: [],\n };\n\n run(newEffect);\n\n return {\n start: () => {\n start(newEffect);\n },\n stop: () => {\n stop(newEffect);\n },\n toggle: () => {\n if (newEffect.active) {\n stop(newEffect);\n } else {\n start(newEffect);\n }\n return newEffect.active;\n },\n };\n}\n", "import { isObject } from \"../util\";\nimport { effect } from \"./effect\";\n\nconst $computed = Symbol(\"computed\");\n\nexport type Computed = {\n readonly value: T;\n readonly [$computed]: true;\n};\n\nexport function isComputed(value: unknown): value is Computed {\n return isObject(value) && value[$computed];\n}\n\nexport function computed(getter: () => T): Computed {\n const ref = {\n get value(): T {\n return getter();\n },\n [$computed]: true,\n } as const;\n\n effect(() => {\n getter();\n });\n\n return ref;\n}\n", "import { isObject } from \"../util\";\nimport { track, trigger } from \"./effect\";\nimport { Reactive, reactive } from \"./reactive\";\n\nexport const $ref = Symbol(\"ref\");\n\nexport type Ref = {\n value: T;\n [$ref]: true;\n};\n\nexport function isRef(value: unknown): value is Ref {\n return isObject(value) && !!value[$ref];\n}\n\nexport function ref(value: T = null as unknown as T): Ref {\n if (isObject(value)) {\n // @ts-ignore\n return isRef(value) ? (value as Ref) : (reactive(value) as Reactive);\n }\n\n const result = { value, [$ref]: true };\n\n return new Proxy(result, {\n get(target: object, key: string | symbol, receiver: any) {\n const val = Reflect.get(target, key, receiver);\n track(result, \"value\");\n return val;\n },\n set(target: object, key: string | symbol, value: unknown) {\n const oldValue = target[key];\n if (oldValue !== value) {\n const success = Reflect.set(target, key, value);\n if (success) {\n trigger(result, \"value\");\n }\n }\n return true;\n },\n }) as Ref;\n}\n", "import { isObject } from \"../util\";\nimport { track, trigger } from \"./effect\";\nimport { ref } from \"./ref\";\n\nconst $reactive = Symbol(\"reactive\");\n\nexport type Reactive = T & { [$reactive]: true };\n\nexport function isReactive(\n value: unknown,\n): value is Reactive {\n return isObject(value) && !!value[$reactive];\n}\n\nexport function reactive(value: T): Reactive {\n // @ts-ignore\n if (!isObject(value)) return ref(value) as Reactive;\n if (value[$reactive]) return value as Reactive;\n\n value[$reactive] = true;\n\n Object.keys(value).forEach((key) => {\n if (isObject(value[key])) {\n value[key] = reactive(value[key]);\n }\n });\n\n return new Proxy(value, reactiveProxyHandler()) as Reactive;\n}\n\nfunction reactiveProxyHandler() {\n return {\n deleteProperty(target: object, key: string | symbol) {\n const had = Reflect.has(target, key);\n const result = Reflect.deleteProperty(target, key);\n if (had) trigger(target, key);\n return result;\n },\n get(target: object, key: string | symbol) {\n track(target, key);\n return Reflect.get(target, key);\n },\n set(target: object, key: string | symbol, value: unknown) {\n if (target[key] === value) return true;\n let newObj = false;\n\n if (isObject(value) && !isObject(target[key])) {\n newObj = true;\n }\n\n if (Reflect.set(target, key, value)) {\n trigger(target, key);\n }\n\n if (newObj) {\n target[key] = reactive(target[key]);\n }\n\n return true;\n },\n };\n}\n", "import { AttributeDirective } from \"./directives/attribute\";\nimport { EventDirective } from \"./directives/event\";\nimport { _for } from \"./directives/for\";\nimport { _if } from \"./directives/if\";\nimport { InterpolationDirective } from \"./directives/interpolation\";\nimport { _teleport } from \"./directives/teleport\";\nimport { ValueDirective } from \"./directives/value\";\nimport { Plugin } from \"./plugins\";\nimport { isComputed } from \"./reactivity/computed\";\nimport { effect as _effect } from \"./reactivity/effect\";\nimport { reactive } from \"./reactivity/reactive\";\nimport { isRef, ref } from \"./reactivity/ref\";\nimport {\n checkAndRemoveAttribute,\n componentHasPropByName,\n extractPropName,\n findSlotNodes,\n findTemplateNodes,\n html,\n isElement,\n isEventAttribute,\n isMirrorProp,\n isObject,\n isPropAttribute,\n isRegularProp,\n isSpreadProp,\n isText,\n Slot,\n stringToElement,\n Template,\n} from \"./util\";\n\nexport function provide(key: string, value: unknown) {\n if (!current.componentBlock) {\n console.warn(\"Can't provide: no current component block\");\n }\n\n current.componentBlock.provides.set(key, value);\n}\n\nexport function inject(key: string) {\n if (!current.componentBlock) {\n console.warn(\"Can't inject: no current component block\");\n }\n\n let c = current.componentBlock;\n\n while (c) {\n if (c.provides.has(key)) {\n return c.provides.get(key);\n }\n\n c = c.parentComponentBlock;\n }\n\n return undefined;\n}\n\nexport class App {\n rootBlock: Block;\n registry = new Map();\n plugins = new Set();\n\n register(name: string, component: Component) {\n this.registry.set(name, component);\n }\n\n use(plugin: Plugin, ...config: any[]) {\n this.plugins.add(plugin);\n plugin.use(this, ...config);\n }\n\n getComponent(tag: string) {\n return this.registry.get(tag);\n }\n\n mount(component: Component, target: string | HTMLElement = \"body\", props: Record = {}) {\n const root = typeof target === \"string\" ? (document.querySelector(target) as HTMLElement) : target;\n const display = root.style.display;\n root.style.display = \"none\";\n this.rootBlock = this._mount(component, root, props, true);\n root.style.display = display;\n return this.rootBlock;\n }\n\n private _mount(component: Component, target: HTMLElement, props: Record, isRoot = false) {\n const parentContext = createContext({ app: this });\n\n if (props) {\n parentContext.scope = reactive(props);\n bindContextMethods(parentContext.scope);\n }\n\n parentContext.scope.$isRef = isRef;\n parentContext.scope.$isComputed = isComputed;\n\n const block = new Block({\n element: target,\n parentContext,\n component,\n isRoot,\n componentProps: props,\n replacementType: \"replaceChildren\",\n });\n\n return block;\n }\n\n unmount() {\n this.rootBlock.teardown();\n }\n}\n\nexport interface Context {\n key?: any;\n app: App;\n scope: Record;\n blocks: Block[];\n effects: Array>;\n effect: typeof _effect;\n slots: Slot[];\n templates: Template[];\n}\n\ninterface CreateContextOptions {\n parentContext?: Context;\n app?: App;\n}\n\nexport function createContext({ parentContext, app }: CreateContextOptions): Context {\n const context: Context = {\n app: app ? app : parentContext && parentContext.app ? parentContext.app : null,\n scope: parentContext ? parentContext.scope : reactive({}),\n blocks: [],\n effects: [],\n slots: [],\n templates: parentContext ? parentContext.templates : [],\n effect: (handler: () => void) => {\n const e = _effect(handler);\n context.effects.push(e);\n return e;\n },\n };\n\n return context;\n}\n\nexport const createScopedContext = (ctx: Context, data = {}): Context => {\n const parentScope = ctx.scope;\n const mergedScope = Object.create(parentScope);\n Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data));\n let proxy: any;\n proxy = reactive(\n new Proxy(mergedScope, {\n set(target, key, val, receiver) {\n // when setting a property that doesn't exist on current scope,\n // do not create it on the current scope and fallback to parent scope.\n if (receiver === proxy && !target.hasOwnProperty(key)) {\n return Reflect.set(parentScope, key, val);\n }\n return Reflect.set(target, key, val, receiver);\n },\n }),\n );\n\n bindContextMethods(proxy);\n\n const out: Context = {\n ...ctx,\n scope: {\n ...ctx.scope,\n ...proxy,\n },\n };\n\n return out;\n};\n\nfunction bindContextMethods(scope: Record) {\n for (const key of Object.keys(scope)) {\n if (typeof scope[key] === \"function\") {\n scope[key] = scope[key].bind(scope);\n }\n }\n}\n\nfunction mergeProps(props: Record, defaultProps: Record) {\n const merged = {};\n\n Object.keys(defaultProps).forEach((defaultProp) => {\n const propValue = props.hasOwnProperty(defaultProp) ? props[defaultProp] : defaultProps[defaultProp]?.default;\n\n merged[defaultProp] = reactive(typeof propValue === \"function\" ? propValue() : propValue);\n });\n\n return merged;\n}\n\nexport interface Component {\n template: string;\n props?: Record;\n main?: (props?: Record) => Record | void;\n}\n\ninterface Current {\n componentBlock?: Block;\n}\n\nexport const current: Current = { componentBlock: undefined };\n\ninterface BlockOptions {\n element: Element;\n isRoot?: boolean;\n replacementType?: \"replace\" | \"replaceChildren\";\n componentProps?: Record;\n allProps?: Record;\n parentContext?: Context;\n component?: Component;\n parentComponentBlock?: Block;\n templates?: Template[];\n}\n\nexport class Block {\n element: Element;\n context: Context;\n parentContext: Context;\n component: Component;\n provides = new Map();\n parentComponentBlock: Block | undefined;\n componentProps: Record;\n allProps: Record;\n\n isFragment: boolean;\n start?: Text;\n end?: Text;\n key?: any;\n\n constructor(opts: BlockOptions) {\n this.isFragment = opts.element instanceof HTMLTemplateElement;\n this.parentComponentBlock = opts.parentComponentBlock;\n\n if (opts.component) {\n current.componentBlock = this;\n this.element = stringToElement(opts.component.template);\n } else {\n if (this.isFragment) {\n this.element = (opts.element as HTMLTemplateElement).content.cloneNode(true) as Element;\n } else if (typeof opts.element === \"string\") {\n this.element = stringToElement(opts.element);\n } else {\n this.element = opts.element.cloneNode(true) as Element;\n opts.element.replaceWith(this.element);\n }\n }\n\n if (opts.isRoot) {\n this.context = opts.parentContext;\n } else {\n this.parentContext = opts.parentContext ? opts.parentContext : createContext({});\n this.parentContext.blocks.push(this);\n this.context = createContext({ parentContext: opts.parentContext });\n }\n\n if (opts.component) {\n this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});\n\n if (opts.component.main) {\n this.context.scope = {\n ...(opts.component.main(this.componentProps) || {}),\n };\n }\n }\n\n opts.allProps?.forEach((prop) => {\n if (prop.isBind) {\n this.context.effect(() => {\n let newValue: unknown;\n\n if (prop.isSpread) {\n const spreadProps = evalGet(this.parentContext.scope, prop.extractedName);\n if (isObject(spreadProps)) {\n Object.keys(spreadProps).forEach((key) => {\n newValue = spreadProps[key];\n this.setProp(key, newValue);\n });\n }\n } else {\n newValue = prop.isMirror ? evalGet(this.parentContext.scope, prop.extractedName) : evalGet(this.parentContext.scope, prop.exp);\n this.setProp(prop.extractedName, newValue);\n }\n });\n }\n });\n\n // Capture slots\n this.context.slots = findSlotNodes(this.element);\n this.context.templates = opts.templates ?? [];\n\n // Put templates into slots\n this.context.slots.forEach((slot) => {\n const template = this.context.templates.find((t) => t.targetSlotName === slot.name);\n\n if (template) {\n const templateContents = template.node.content.cloneNode(true);\n slot.node.replaceWith(templateContents);\n }\n });\n\n this.context.scope.$isRef = isRef;\n this.context.scope.$isComputed = isComputed;\n\n walk(this.element, this.context);\n\n if (opts.component) {\n if (opts.replacementType === \"replace\") {\n if (opts.element instanceof HTMLElement) {\n opts.element.replaceWith(this.element);\n }\n } else {\n if (opts.element instanceof HTMLElement) {\n opts.element.replaceChildren(this.element);\n }\n }\n }\n }\n\n setProp(name: string, value: unknown) {\n if (isRef(this.componentProps[name])) {\n this.componentProps[name].value = value;\n } else {\n this.componentProps[name] = value;\n }\n }\n\n insert(parent: Element, anchor: Node | null = null) {\n if (this.isFragment) {\n if (this.start) {\n // Already inserted, moving\n let node: Node | null = this.start;\n let next: Node | null;\n\n while (node) {\n next = node.nextSibling;\n parent.insertBefore(node, anchor);\n\n if (node === this.end) {\n break;\n }\n\n node = next;\n }\n } else {\n this.start = new Text(\"\");\n this.end = new Text(\"\");\n\n parent.insertBefore(this.end, anchor);\n parent.insertBefore(this.start, this.end);\n parent.insertBefore(this.element, this.end);\n }\n } else {\n parent.insertBefore(this.element, anchor);\n }\n }\n\n remove() {\n if (this.parentContext) {\n const i = this.parentContext.blocks.indexOf(this);\n\n if (i > -1) {\n this.parentContext.blocks.splice(i, 1);\n }\n }\n\n if (this.start) {\n const parent = this.start.parentNode!;\n let node: Node | null = this.start;\n let next: Node | null;\n\n while (node) {\n next = node.nextSibling;\n parent.removeChild(node);\n\n if (node === this.end) {\n break;\n }\n\n node = next;\n }\n } else {\n // this.element.parentNode!.removeChild(this.element);\n this.element.remove();\n }\n\n this.teardown();\n }\n\n teardown() {\n this.context.blocks.forEach((block) => {\n block.teardown();\n });\n\n this.context.effects.forEach(stop);\n }\n}\n\nfunction isComponent(element: Element, context: Context) {\n return !!context.app.getComponent(element.tagName.toLowerCase());\n}\n\nfunction walk(node: Node, context: Context) {\n if (isText(node)) {\n new InterpolationDirective({ element: node, context });\n return;\n }\n\n if (isElement(node)) {\n let exp: string | null;\n\n if (isComponent(node, context)) {\n const component = context.app.getComponent(node.tagName.toLowerCase());\n\n const allProps = Array.from(node.attributes)\n .filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || (isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)))\n .map((attr) => ({\n isMirror: isMirrorProp(attr.name),\n isSpread: isSpreadProp(attr.name),\n isBind: attr.name.includes(\"bind\"),\n originalName: attr.name,\n extractedName: extractPropName(attr.name),\n exp: attr.value,\n value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : undefined,\n }));\n\n const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => {\n if (isSpread) {\n const spread = evalGet(context.scope, extractedName);\n if (isObject(spread)) Object.assign(acc, spread);\n } else if (isMirror) {\n acc[extractedName] = evalGet(context.scope, extractedName);\n } else {\n acc[extractedName] = value;\n }\n return acc;\n }, {});\n\n if ((exp = checkAndRemoveAttribute(node, \":teleport\"))) {\n return _teleport(node, exp, context);\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":if\"))) {\n return _if(node, exp, context, component, componentProps, allProps);\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":for\"))) {\n return _for(node, exp, context, component, componentProps, allProps);\n }\n\n const templates = findTemplateNodes(node);\n\n const block = new Block({\n element: node,\n parentContext: context,\n component,\n replacementType: \"replace\",\n parentComponentBlock: current.componentBlock,\n templates,\n componentProps,\n allProps,\n });\n\n return block.element;\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":teleport\"))) {\n return _teleport(node, exp, context);\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":if\"))) {\n return _if(node, exp, context);\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":for\"))) {\n return _for(node, exp, context);\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":ref\"))) {\n context.scope[exp].value = node;\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":value\"))) {\n new ValueDirective({ element: node, context, expression: exp });\n }\n\n Array.from(node.attributes).forEach((attr) => {\n if (isPropAttribute(attr.name)) {\n new AttributeDirective({ element: node, context, attr });\n }\n\n if (isEventAttribute(attr.name)) {\n new EventDirective({ element: node, context, attr });\n }\n });\n\n walkChildren(node, context);\n }\n}\n\nfunction walkChildren(node: Node, context: Context) {\n let child = node.firstChild;\n\n while (child) {\n child = walk(child, context) || child.nextSibling;\n }\n}\n\nconst evalFuncCache: Record = {};\n\nexport function evalGet(scope: any, exp: string, el?: Node) {\n if (!exp.trim()) return undefined;\n return execute(scope, `const ___value = (${exp.trim()}); return ___value;`, el);\n}\n\nexport function evalSet(scope: any, exp: string, value: unknown) {\n value = typeof value === \"string\" ? `\"${value}\"` : value;\n return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false);\n}\n\nfunction execute(scope: any, exp: string, el?: Node, flatRefs = true) {\n const newScope = flatRefs ? flattenRefs(scope) : scope;\n const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp));\n\n try {\n return fn(newScope, el);\n } catch (e) {\n console.warn(`Error evaluating expression: \"${exp}\":`);\n console.error(e);\n }\n}\n\n// Function to convert expression strings to functions\nfunction toFunction(exp: string) {\n try {\n return new Function(\"$data\", \"$el\", `with($data){${exp}}`);\n } catch (e) {\n console.error(`${(e as Error).message} in expression: ${exp}`);\n return () => {};\n }\n}\n\n// Map all ref properties in scope to their `.value`\nfunction flattenRefs(scope: any): any {\n const mapped = {};\n\n for (const key in scope) {\n if (scope.hasOwnProperty(key)) {\n // Check if the value is a Ref\n if (isRef(scope[key])) {\n mapped[key] = scope[key].value;\n } else {\n mapped[key] = scope[key];\n }\n }\n }\n return mapped;\n}\n\n// ------------------------------------------------\n// Slots, multiple default and named, :if and :for\n// const card = {\n// template: html`
\n//

\n// \n//
`,\n// };\n\n// const main = {\n// template: html`\n//
\n//

card below

\n// \n// card title\n// \n// \n//
\n// `,\n// };\n\n// const app = new App();\n// app.register(\"card\", card);\n// app.mount(main, \"#app\");\n\n// ------------------------------------------------\n// Slots, multiple default and named, :if and :for\n// const app = new App();\n\n// const parent = {\n// template: html`\n//
\n//

parent

\n// \n// \n//
\n//
\n//
1
\n//
2
\n//
3
\n//
\n//
\n// content 1 always shown\n//
\n// content 2, animals:\n//
animal: {{animal}}
\n//
\n\n// \n// \n//
\n//
\n// `,\n// main() {\n// const bool = ref(true);\n// const animals = reactive([\"dog\", \"cat\", \"bear\"]);\n\n// setInterval(() => {\n// bool.value = !bool.value;\n// }, 2000);\n\n// return { bool, animals };\n// },\n// };\n// const card = {\n// template: html`
\n//

card

\n//

\n// \n//
`,\n// };\n// app.register(\"card\", card);\n// const parentBlock = app.mount(parent, \"body\");\n// const cardBlock = parentBlock.context.blocks[0];\n\n// ------------------------------------------------\n// Component pros, mirror and spread, bind and no bind\n// const child = {\n// template: html`
Animal: {{animal}}
`,\n// props: { animal: { default: \"cat\" } },\n// main({ animal }) {\n// return { animal };\n// },\n// };\n\n// const parent = {\n// template: html`\n//
\n//
asdf
\n// mirror, no bind:\n// \n//
\n// mirror, bind:\n// \n//
\n// spread, no bind:\n// \n//
\n// spread, bind:\n// \n//
\n// regular prop:\n// \n//
\n// regular prop, bind:\n// \n//
\n//
div has \"id\" set to animal.value
\n//
\n//
div has \"id\" set and bound to animal.value
\n//
\n//
div has \"animal\" set to animal.value
\n//
\n//
div has \"animal\" set and bound to animal.value
\n//
\n//
div has \"animal\" spread
\n//
\n//
div has \"animal\" spread and bound
\n//
\n//
\n//
\n//
\n// if bool, mirror, no bind:\n// \n// if bool, mirror, bind:\n// \n//
\n// for list, mirror, no bind:\n// \n//
\n// for list, mirror, bind:\n// \n// 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\".\n//
\n// \n//
\n//
\n// `,\n// main() {\n// const bool = ref(false);\n// const animal = ref(\"dog\");\n// const spread = reactive({ animal: \"panther\" });\n// const list = reactive([1, 2, 3]);\n\n// setTimeout(() => {\n// spread.animal = \"PANTHER!\";\n// animal.value = \"DOG!\";\n// bool.value = true;\n// }, 500);\n\n// setTimeout(() => {\n// animal.value = \"DOG!!!!!\";\n// }, 1000);\n\n// return { animal, spread, bool, list };\n// },\n// };\n\n// const app = new App();\n// app.register(\"child\", child);\n// app.mount(parent, \"#app\");\n\n// ------------------------------------------------\n// Event directive\nconst counter = {\n template: html`\n
\n
true
\n

Count: {{count}}{{count >= 2 ? '!!!' : ''}}

\n \n \n
\n `,\n main() {\n const count = ref(0);\n const style = reactive({ color: \"red\" });\n const increment = () => count.value++;\n const decrement = () => count.value--;\n\n setInterval(() => {\n if (style.color === \"red\") {\n style.color = \"blue\";\n } else {\n style.color = \"red\";\n }\n }, 500);\n\n return { count, increment, decrement, style };\n },\n};\n\nconst app = new App();\napp.mount(counter, \"#app\");\n\n// ------------------------------------------------\n// Vue SFC syntax of the above counter component\n\n// \n\n// \n"], + "mappings": "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", + "names": ["node", "element", "_", "ctx", "ref", "i", "exp", "el", "effect", "stop", "ref", "value", "app"] +} diff --git a/src/directives/attribute.ts b/src/directives/attribute.ts new file mode 100644 index 0000000..b39e075 --- /dev/null +++ b/src/directives/attribute.ts @@ -0,0 +1,113 @@ +import { Context, evalGet } from ".."; +import { classNames, extractAttributeName } from "../util"; + +interface AttributeDirectiveOptions { + element: Element; + context: Context; + attr: Attr; +} + +interface Is { + sameNameProperty: boolean; + bound: boolean; + spread: boolean; + componentProp: boolean; +} + +export class AttributeDirective { + element: Element; + context: Context; + expression: string; + attr: Attr; + extractedAttributeName: string; + + previousClasses: string[] = []; + previousStyles: { [key: string]: string } = {}; + + is: Is = { + sameNameProperty: false, + bound: false, + spread: false, + componentProp: false, + }; + + constructor({ element, context, attr }: AttributeDirectiveOptions) { + this.element = element; + this.context = context; + this.expression = attr.value; + this.attr = attr; + this.extractedAttributeName = extractAttributeName(attr.name); + + this.is = { + sameNameProperty: attr.name.startsWith("{") && attr.name.endsWith("}"), + bound: attr.name.includes(":bind"), + spread: attr.name.startsWith("..."), + componentProp: false, + }; + + if (this.is.sameNameProperty) { + this.expression = this.extractedAttributeName; + } + + if (this.is.spread) { + this.expression = this.extractedAttributeName; + } + + console.log("attribute", attr.name, "spread?", this.is.spread) + + element.removeAttribute(attr.name); + + if (this.is.bound) { + context.effect(this.update.bind(this)); + } else { + this.update(); + } + } + + update() { + let value = evalGet(this.context.scope, this.expression); + + if (this.is.spread && typeof value === "object") { + for (const [key, val] of Object.entries(value)) { + this.element.setAttribute(key, String(val)); + } + } else if ((typeof value === "object" || Array.isArray(value)) && this.extractedAttributeName === "class") { + value = classNames(value); + const next = value.split(" "); + + // If we now have classes that are not already on the element, add them now. + // Remove classes that are no longer on the element. + const diff = next.filter((c: string) => !this.previousClasses.includes(c)).filter(Boolean); + const rm = this.previousClasses.filter((c) => !next.includes(c)); + + diff.forEach((c: string) => { + this.previousClasses.push(c); + this.element.classList.add(c); + }); + + rm.forEach((c) => { + this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c); + this.element.classList.remove(c); + }); + } else if (typeof value === "object" && this.extractedAttributeName === "style") { + const next = Object.keys(value); + const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style)); + + next.forEach((style) => { + this.previousStyles[style] = value[style]; + // @ts-ignore + this.element.style[style] = value[style]; + }); + + rm.forEach((style) => { + this.previousStyles[style] = ""; + // @ts-ignore + this.element.style[style] = ""; + }); + + this.previousStyles = value; + } else { + this.element.setAttribute(this.extractedAttributeName, value); + } + } +} diff --git a/src/directives/event.ts b/src/directives/event.ts new file mode 100644 index 0000000..c9e8386 --- /dev/null +++ b/src/directives/event.ts @@ -0,0 +1,40 @@ +import { Context, evalGet } from ".."; + +interface EventDirectiveOptions { + element: Element; + context: Context; + attr: Attr; +} + +export class EventDirective { + element: Element; + context: Context; + expression: string; + attr: Attr; + eventCount = 0; + + constructor({ element, context, attr }: EventDirectiveOptions) { + this.element = element; + this.context = context; + this.expression = attr.value; + this.attr = attr; + + const eventName = attr.name.replace(/^@/, ""); + const parts = eventName.split("."); + + this.element.addEventListener(parts[0], (event) => { + if (parts.includes("prevent")) event.preventDefault(); + if (parts.includes("stop")) event.stopPropagation(); + if (parts.includes("once") && this.eventCount > 0) return; + + this.eventCount++; + + const handler = evalGet(context.scope, attr.value); + if (typeof handler === "function") { + handler(event); + } + }); + + element.removeAttribute(attr.name); + } +} diff --git a/src/directives/for.ts b/src/directives/for.ts new file mode 100644 index 0000000..7ef6646 --- /dev/null +++ b/src/directives/for.ts @@ -0,0 +1,156 @@ +import { Block, Component, Context, createScopedContext, evalGet } from ".."; +import { isArray, isObject } from "../util"; + +const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/; +const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; +const stripParensRE = /^\(|\)$/g; +const destructureRE = /^[{[]\s*((?:[\w_$]+\s*,?\s*)+)[\]}]$/; + +type KeyToIndexMap = Map; + +export const _for = (el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) => { + const inMatch = exp.match(forAliasRE); + if (!inMatch) { + console.warn(`invalid :for expression: ${exp}`); + return; + } + + const nextNode = el.nextSibling; + + const parent = el.parentElement!; + const anchor = new Text(""); + parent.insertBefore(anchor, el); + parent.removeChild(el); + + const sourceExp = inMatch[2].trim(); + let valueExp = inMatch[1].trim().replace(stripParensRE, "").trim(); + let destructureBindings: string[] | undefined; + let isArrayDestructure = false; + let indexExp: string | undefined; + let objIndexExp: string | undefined; + + let keyAttr = "key"; + let keyExp = el.getAttribute(keyAttr) || el.getAttribute((keyAttr = ":key")) || el.getAttribute((keyAttr = ":key:bind")); + if (keyExp) { + el.removeAttribute(keyAttr); + if (keyAttr === "key") keyExp = JSON.stringify(keyExp); + } + + let match: any; + if ((match = valueExp.match(forIteratorRE))) { + valueExp = valueExp.replace(forIteratorRE, "").trim(); + indexExp = match[1].trim(); + if (match[2]) { + objIndexExp = match[2].trim(); + } + } + + if ((match = valueExp.match(destructureRE))) { + destructureBindings = match[1].split(",").map((s: string) => s.trim()); + isArrayDestructure = valueExp[0] === "["; + } + + let mounted = false; + let blocks: Block[]; + let childCtxs: Context[]; + let keyToIndexMap: Map; + + const createChildContexts = (source: unknown): [Context[], KeyToIndexMap] => { + const map: KeyToIndexMap = new Map(); + const ctxs: Context[] = []; + + if (isArray(source)) { + for (let i = 0; i < source.length; i++) { + ctxs.push(createChildContext(map, source[i], i)); + } + } else if (typeof source === "number") { + for (let i = 0; i < source; i++) { + ctxs.push(createChildContext(map, i + 1, i)); + } + } else if (isObject(source)) { + let i = 0; + for (const key in source) { + ctxs.push(createChildContext(map, source[key], i++, key)); + } + } + + return [ctxs, map]; + }; + + const createChildContext = (map: KeyToIndexMap, value: any, index: number, objKey?: string): Context => { + const data: any = {}; + if (destructureBindings) { + destructureBindings.forEach((b, i) => (data[b] = value[isArrayDestructure ? i : b])); + } else { + data[valueExp] = value; + } + if (objKey) { + indexExp && (data[indexExp] = objKey); + objIndexExp && (data[objIndexExp] = index); + } else { + indexExp && (data[indexExp] = index); + } + + const childCtx = createScopedContext(ctx, data); + const key = keyExp ? evalGet(childCtx.scope, keyExp) : index; + map.set(key, index); + childCtx.key = key; + return childCtx; + }; + + const mountBlock = (ctx: Context, ref: Node) => { + const block = new Block({ element: el, parentContext: ctx, replacementType: "replace", component, componentProps, allProps }); + block.key = ctx.key; + block.insert(parent, ref); + return block; + }; + + ctx.effect(() => { + const source = evalGet(ctx.scope, sourceExp); + const prevKeyToIndexMap = keyToIndexMap; + [childCtxs, keyToIndexMap] = createChildContexts(source); + if (!mounted) { + blocks = childCtxs.map((s) => mountBlock(s, anchor)); + mounted = true; + } else { + for (let i = 0; i < blocks.length; i++) { + if (!keyToIndexMap.has(blocks[i].key)) { + blocks[i].remove(); + } + } + + const nextBlocks: Block[] = []; + let i = childCtxs.length; + let nextBlock: Block | undefined; + let prevMovedBlock: Block | undefined; + while (i--) { + const childCtx = childCtxs[i]; + const oldIndex = prevKeyToIndexMap.get(childCtx.key); + let block: Block; + if (oldIndex == null) { + // new + block = mountBlock(childCtx, nextBlock ? nextBlock.element : anchor); + } else { + // update + block = blocks[oldIndex]; + Object.assign(block.context.scope, childCtx.scope); + if (oldIndex !== i) { + // moved + if ( + blocks[oldIndex + 1] !== nextBlock || + // If the next has moved, it must move too + prevMovedBlock === nextBlock + ) { + prevMovedBlock = block; + block.insert(parent, nextBlock ? nextBlock.element : anchor); + } + } + } + nextBlocks.unshift((nextBlock = block)); + } + blocks = nextBlocks; + } + }); + + return nextNode; +}; diff --git a/src/directives/if.ts b/src/directives/if.ts new file mode 100644 index 0000000..24b59bd --- /dev/null +++ b/src/directives/if.ts @@ -0,0 +1,67 @@ +import { Block, Component, Context, evalGet } from ".."; +import { checkAndRemoveAttribute } from "../util"; + +interface Branch { + exp?: string | null; + el: Element; +} + +export function _if(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) { + const parent = el.parentElement!; + const anchor = new Comment(":if"); + + parent.insertBefore(anchor, el); + + const branches: Branch[] = [{ exp, el }]; + + let elseEl: Element | null; + let elseExp: string | null; + + while ((elseEl = el.nextElementSibling)) { + elseExp = null; + + if (checkAndRemoveAttribute(elseEl, ":else") === "" || (elseExp = checkAndRemoveAttribute(elseEl, ":else-if"))) { + parent.removeChild(elseEl); + branches.push({ exp: elseExp, el: elseEl }); + } else { + break; + } + } + + const nextNode = el.nextSibling; + parent.removeChild(el); + + let block: Block | undefined; + let activeBranchIndex = -1; + + const removeActiveBlock = () => { + if (block) { + parent.insertBefore(anchor, block.element); + block.remove(); + block = undefined; + } + }; + + ctx.effect(() => { + for (let i = 0; i < branches.length; i++) { + const { exp, el } = branches[i]; + + if (!exp || evalGet(ctx.scope, exp)) { + if (i !== activeBranchIndex) { + removeActiveBlock(); + block = new Block({ element: el, parentContext: ctx, replacementType: "replace", component, componentProps, allProps }); + block.insert(parent, anchor); + parent.removeChild(anchor); + activeBranchIndex = i; + } + + return; + } + } + + activeBranchIndex = -1; + removeActiveBlock(); + }); + + return nextNode; +} diff --git a/src/directives/interpolation.ts b/src/directives/interpolation.ts new file mode 100644 index 0000000..e6cb688 --- /dev/null +++ b/src/directives/interpolation.ts @@ -0,0 +1,78 @@ +import { Context, evalGet } from "../"; +import { insertAfter, toDisplayString } from "../util"; + +interface InterpolationDirectiveOptions { + element: Text; + context: Context; +} + +const delims = /{{\s?(.*?)\s?}}/g; + +export class InterpolationDirective { + element: Text; + context: Context; + textNodes: Map = new Map(); + + constructor({ element, context }: InterpolationDirectiveOptions) { + this.element = element; + this.context = context; + + this.findNodes(); + + this.textNodes.forEach((nodes, expression) => { + const trimmedExpression = expression.slice(2, -2).trim(); + + nodes.forEach((node) => { + const getter = (exp = trimmedExpression) => evalGet(this.context.scope, exp, node); + + context.effect(() => { + node.textContent = toDisplayString(getter()); + }); + }); + }); + } + + findNodes() { + const textContent = this.element.textContent.trim(); + if (textContent?.match(delims)) { + const textNodes = textContent.split(/(\{\{\s?[^}]+\s?\}\})/g).filter(Boolean); + if (textNodes) { + let previousNode = this.element; + + for (let i = 0; i < textNodes.length; i++) { + const textNode = textNodes[i]; + + if (textNode.match(/\{\{\s?.+\s?\}\}/)) { + const newNode = document.createTextNode(textNode); + + if (i === 0) { + this.element.replaceWith(newNode); + } else { + insertAfter(newNode, previousNode); + } + + previousNode = newNode; + + if (this.textNodes.has(textNode)) { + this.textNodes.get(textNode).push(newNode); + } else { + this.textNodes.set(textNode, [newNode]); + } + } else { + const newNode = document.createTextNode(textNodes[i]); + + if (i === 0) { + this.element.replaceWith(newNode); + } else { + insertAfter(newNode, previousNode); + } + + previousNode = newNode; + } + } + } + } + } + + update() {} +} diff --git a/src/directives/teleport.ts b/src/directives/teleport.ts new file mode 100644 index 0000000..906d1fb --- /dev/null +++ b/src/directives/teleport.ts @@ -0,0 +1,32 @@ +import { Context } from ".."; +import { nextTick } from "../util"; + +export function _teleport(el: Element, exp: string, ctx: Context) { + const anchor = new Comment(":teleport"); + el.replaceWith(anchor); + + const target = document.querySelector(exp); + if (!target) { + console.warn(`teleport target not found: ${exp}`); + return; + } + + nextTick(() => { + target.appendChild(el); + }); + + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + mutation.removedNodes.forEach((removedNode) => { + if (removedNode.contains(anchor)) { + el.remove(); + observer.disconnect(); + } + }); + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); + + return anchor; +} diff --git a/src/directives/value.ts b/src/directives/value.ts new file mode 100644 index 0000000..6a7ddc3 --- /dev/null +++ b/src/directives/value.ts @@ -0,0 +1,111 @@ +import { Context, evalGet, evalSet } from ".."; + +interface ValueDirectiveOptions { + element: Element; + context: Context; + expression: string; +} + +type SupportedModelType = "text" | "checkbox" | "radio" | "number" | "password" | "color"; + +function isInput(element: Element): element is HTMLInputElement { + return element instanceof HTMLInputElement; +} + +function isTextarea(element: Element): element is HTMLTextAreaElement { + return element instanceof HTMLTextAreaElement; +} + +function isSelect(element: Element): element is HTMLSelectElement { + return element instanceof HTMLSelectElement; +} + +export class ValueDirective { + element: Element; + context: Context; + expression: string; + inputType: SupportedModelType; + + constructor({ element, context, expression }: ValueDirectiveOptions) { + this.element = element; + this.context = context; + this.expression = expression; + this.inputType = element.getAttribute("type") as SupportedModelType; + + // Element -> Context + if (isInput(element)) { + switch (this.inputType) { + case "text": + case "password": + case "number": + case "color": + element.addEventListener("input", () => { + const value = this.inputType === "number" ? (element.value ? parseFloat(element.value) : 0) : element.value; + evalSet(this.context.scope, expression, value); + }); + break; + + case "checkbox": + element.addEventListener("change", (e: any) => { + evalSet(this.context.scope, expression, !!e.currentTarget.checked); + }); + break; + case "radio": + element.addEventListener("change", (e: any) => { + if (e.currentTarget.checked) { + evalSet(this.context.scope, expression, element.getAttribute("value")); + } + }); + break; + default: + break; + } + } + + if (isTextarea(element)) { + element.addEventListener("input", () => { + evalSet(this.context.scope, expression, element.value); + }); + } + + if (isSelect(element)) { + element.addEventListener("change", () => { + evalSet(this.context.scope, expression, element.value); + }); + } + + // Context -> Element + context.effect(this.updateElementValue.bind(this)); + } + + updateElementValue() { + const value = evalGet(this.context.scope, this.expression, this.element); + + if (isInput(this.element)) { + switch (this.inputType) { + case "text": + case "password": + case "number": + case "color": + this.element.value = value; + break; + case "checkbox": + this.element.checked = !!value; + break; + case "radio": + this.element.checked = this.element.value === value; + break; + default: + break; + } + } + + if (isTextarea(this.element)) { + this.element.value = value; + } + + if (isSelect(this.element)) { + this.element.value = value; + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..675dc6d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,778 @@ +import { AttributeDirective } from "./directives/attribute"; +import { EventDirective } from "./directives/event"; +import { _for } from "./directives/for"; +import { _if } from "./directives/if"; +import { InterpolationDirective } from "./directives/interpolation"; +import { _teleport } from "./directives/teleport"; +import { ValueDirective } from "./directives/value"; +import { Plugin } from "./plugins"; +import { isComputed } from "./reactivity/computed"; +import { effect as _effect } from "./reactivity/effect"; +import { reactive } from "./reactivity/reactive"; +import { isRef, ref } from "./reactivity/ref"; +import { + checkAndRemoveAttribute, + componentHasPropByName, + extractPropName, + findSlotNodes, + findTemplateNodes, + html, + isElement, + isEventAttribute, + isMirrorProp, + isObject, + isPropAttribute, + isRegularProp, + isSpreadProp, + isText, + Slot, + stringToElement, + Template, +} from "./util"; + +export function provide(key: string, value: unknown) { + if (!current.componentBlock) { + console.warn("Can't provide: no current component block"); + } + + current.componentBlock.provides.set(key, value); +} + +export function inject(key: string) { + if (!current.componentBlock) { + console.warn("Can't inject: no current component block"); + } + + let c = current.componentBlock; + + while (c) { + if (c.provides.has(key)) { + return c.provides.get(key); + } + + c = c.parentComponentBlock; + } + + return undefined; +} + +export class App { + rootBlock: Block; + registry = new Map(); + plugins = new Set(); + + register(name: string, component: Component) { + this.registry.set(name, component); + } + + use(plugin: Plugin, ...config: any[]) { + this.plugins.add(plugin); + plugin.use(this, ...config); + } + + getComponent(tag: string) { + return this.registry.get(tag); + } + + mount(component: Component, target: string | HTMLElement = "body", props: Record = {}) { + const root = typeof target === "string" ? (document.querySelector(target) as HTMLElement) : target; + const display = root.style.display; + root.style.display = "none"; + this.rootBlock = this._mount(component, root, props, true); + root.style.display = display; + return this.rootBlock; + } + + private _mount(component: Component, target: HTMLElement, props: Record, isRoot = false) { + const parentContext = createContext({ app: this }); + + if (props) { + parentContext.scope = reactive(props); + bindContextMethods(parentContext.scope); + } + + parentContext.scope.$isRef = isRef; + parentContext.scope.$isComputed = isComputed; + + const block = new Block({ + element: target, + parentContext, + component, + isRoot, + componentProps: props, + replacementType: "replaceChildren", + }); + + return block; + } + + unmount() { + this.rootBlock.teardown(); + } +} + +export interface Context { + key?: any; + app: App; + scope: Record; + blocks: Block[]; + effects: Array>; + effect: typeof _effect; + slots: Slot[]; + templates: Template[]; +} + +interface CreateContextOptions { + parentContext?: Context; + app?: App; +} + +export function createContext({ parentContext, app }: CreateContextOptions): Context { + const context: Context = { + app: app ? app : parentContext && parentContext.app ? parentContext.app : null, + scope: parentContext ? parentContext.scope : reactive({}), + blocks: [], + effects: [], + slots: [], + templates: parentContext ? parentContext.templates : [], + effect: (handler: () => void) => { + const e = _effect(handler); + context.effects.push(e); + return e; + }, + }; + + return context; +} + +export const createScopedContext = (ctx: Context, data = {}): Context => { + const parentScope = ctx.scope; + const mergedScope = Object.create(parentScope); + Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data)); + let proxy: any; + proxy = reactive( + new Proxy(mergedScope, { + set(target, key, val, receiver) { + // when setting a property that doesn't exist on current scope, + // do not create it on the current scope and fallback to parent scope. + if (receiver === proxy && !target.hasOwnProperty(key)) { + return Reflect.set(parentScope, key, val); + } + return Reflect.set(target, key, val, receiver); + }, + }), + ); + + bindContextMethods(proxy); + + const out: Context = { + ...ctx, + scope: { + ...ctx.scope, + ...proxy, + }, + }; + + return out; +}; + +function bindContextMethods(scope: Record) { + for (const key of Object.keys(scope)) { + if (typeof scope[key] === "function") { + scope[key] = scope[key].bind(scope); + } + } +} + +function mergeProps(props: Record, defaultProps: Record) { + const merged = {}; + + Object.keys(defaultProps).forEach((defaultProp) => { + const propValue = props.hasOwnProperty(defaultProp) ? props[defaultProp] : defaultProps[defaultProp]?.default; + + merged[defaultProp] = reactive(typeof propValue === "function" ? propValue() : propValue); + }); + + return merged; +} + +export interface Component { + template: string; + props?: Record; + main?: (props?: Record) => Record | void; +} + +interface Current { + componentBlock?: Block; +} + +export const current: Current = { componentBlock: undefined }; + +interface BlockOptions { + element: Element; + isRoot?: boolean; + replacementType?: "replace" | "replaceChildren"; + componentProps?: Record; + allProps?: Record; + parentContext?: Context; + component?: Component; + parentComponentBlock?: Block; + templates?: Template[]; +} + +export class Block { + element: Element; + context: Context; + parentContext: Context; + component: Component; + provides = new Map(); + parentComponentBlock: Block | undefined; + componentProps: Record; + allProps: Record; + + isFragment: boolean; + start?: Text; + end?: Text; + key?: any; + + constructor(opts: BlockOptions) { + this.isFragment = opts.element instanceof HTMLTemplateElement; + this.parentComponentBlock = opts.parentComponentBlock; + + if (opts.component) { + current.componentBlock = this; + this.element = stringToElement(opts.component.template); + } else { + if (this.isFragment) { + this.element = (opts.element as HTMLTemplateElement).content.cloneNode(true) as Element; + } else if (typeof opts.element === "string") { + this.element = stringToElement(opts.element); + } else { + this.element = opts.element.cloneNode(true) as Element; + opts.element.replaceWith(this.element); + } + } + + if (opts.isRoot) { + this.context = opts.parentContext; + } else { + this.parentContext = opts.parentContext ? opts.parentContext : createContext({}); + this.parentContext.blocks.push(this); + this.context = createContext({ parentContext: opts.parentContext }); + } + + if (opts.component) { + this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {}); + + if (opts.component.main) { + this.context.scope = { + ...(opts.component.main(this.componentProps) || {}), + }; + } + } + + opts.allProps?.forEach((prop) => { + if (prop.isBind) { + this.context.effect(() => { + let newValue: unknown; + + if (prop.isSpread) { + const spreadProps = evalGet(this.parentContext.scope, prop.extractedName); + if (isObject(spreadProps)) { + Object.keys(spreadProps).forEach((key) => { + newValue = spreadProps[key]; + this.setProp(key, newValue); + }); + } + } else { + newValue = prop.isMirror ? evalGet(this.parentContext.scope, prop.extractedName) : evalGet(this.parentContext.scope, prop.exp); + this.setProp(prop.extractedName, newValue); + } + }); + } + }); + + // Capture slots + this.context.slots = findSlotNodes(this.element); + this.context.templates = opts.templates ?? []; + + // Put templates into slots + this.context.slots.forEach((slot) => { + const template = this.context.templates.find((t) => t.targetSlotName === slot.name); + + if (template) { + const templateContents = template.node.content.cloneNode(true); + slot.node.replaceWith(templateContents); + } + }); + + this.context.scope.$isRef = isRef; + this.context.scope.$isComputed = isComputed; + + walk(this.element, this.context); + + if (opts.component) { + if (opts.replacementType === "replace") { + if (opts.element instanceof HTMLElement) { + opts.element.replaceWith(this.element); + } + } else { + if (opts.element instanceof HTMLElement) { + opts.element.replaceChildren(this.element); + } + } + } + } + + setProp(name: string, value: unknown) { + if (isRef(this.componentProps[name])) { + this.componentProps[name].value = value; + } else { + this.componentProps[name] = value; + } + } + + insert(parent: Element, anchor: Node | null = null) { + if (this.isFragment) { + if (this.start) { + // Already inserted, moving + let node: Node | null = this.start; + let next: Node | null; + + while (node) { + next = node.nextSibling; + parent.insertBefore(node, anchor); + + if (node === this.end) { + break; + } + + node = next; + } + } else { + this.start = new Text(""); + this.end = new Text(""); + + parent.insertBefore(this.end, anchor); + parent.insertBefore(this.start, this.end); + parent.insertBefore(this.element, this.end); + } + } else { + parent.insertBefore(this.element, anchor); + } + } + + remove() { + if (this.parentContext) { + const i = this.parentContext.blocks.indexOf(this); + + if (i > -1) { + this.parentContext.blocks.splice(i, 1); + } + } + + if (this.start) { + const parent = this.start.parentNode!; + let node: Node | null = this.start; + let next: Node | null; + + while (node) { + next = node.nextSibling; + parent.removeChild(node); + + if (node === this.end) { + break; + } + + node = next; + } + } else { + // this.element.parentNode!.removeChild(this.element); + this.element.remove(); + } + + this.teardown(); + } + + teardown() { + this.context.blocks.forEach((block) => { + block.teardown(); + }); + + this.context.effects.forEach(stop); + } +} + +function isComponent(element: Element, context: Context) { + return !!context.app.getComponent(element.tagName.toLowerCase()); +} + +function walk(node: Node, context: Context) { + if (isText(node)) { + new InterpolationDirective({ element: node, context }); + return; + } + + if (isElement(node)) { + let exp: string | null; + + if (isComponent(node, context)) { + const component = context.app.getComponent(node.tagName.toLowerCase()); + + const allProps = Array.from(node.attributes) + .filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || (isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component))) + .map((attr) => ({ + isMirror: isMirrorProp(attr.name), + isSpread: isSpreadProp(attr.name), + isBind: attr.name.includes("bind"), + originalName: attr.name, + extractedName: extractPropName(attr.name), + exp: attr.value, + value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name)) : attr.value ? evalGet(context.scope, attr.value) : undefined, + })); + + const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { + if (isSpread) { + const spread = evalGet(context.scope, extractedName); + if (isObject(spread)) Object.assign(acc, spread); + } else if (isMirror) { + acc[extractedName] = evalGet(context.scope, extractedName); + } else { + acc[extractedName] = value; + } + return acc; + }, {}); + + 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); + } + + const templates = findTemplateNodes(node); + + const block = new Block({ + element: node, + parentContext: context, + component, + replacementType: "replace", + parentComponentBlock: current.componentBlock, + templates, + componentProps, + allProps, + }); + + 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 }); + } + + Array.from(node.attributes).forEach((attr) => { + if (isPropAttribute(attr.name)) { + new AttributeDirective({ element: node, context, attr }); + } + + if (isEventAttribute(attr.name)) { + new EventDirective({ element: node, context, attr }); + } + }); + + walkChildren(node, context); + } +} + +function walkChildren(node: Node, context: Context) { + let child = node.firstChild; + + while (child) { + child = walk(child, context) || child.nextSibling; + } +} + +const evalFuncCache: Record = {}; + +export function evalGet(scope: any, exp: string, el?: Node) { + if (!exp.trim()) return undefined; + return execute(scope, `const ___value = (${exp.trim()}); return ___value;`, el); +} + +export function evalSet(scope: any, exp: string, value: unknown) { + value = typeof value === "string" ? `"${value}"` : value; + return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false); +} + +function execute(scope: any, exp: string, el?: Node, flatRefs = true) { + const newScope = flatRefs ? flattenRefs(scope) : scope; + const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp)); + + try { + return fn(newScope, el); + } catch (e) { + console.warn(`Error evaluating expression: "${exp}":`); + console.error(e); + } +} + +// Function to convert expression strings to functions +function toFunction(exp: string) { + try { + return new Function("$data", "$el", `with($data){${exp}}`); + } catch (e) { + console.error(`${(e as Error).message} in expression: ${exp}`); + return () => {}; + } +} + +// Map all ref properties in scope to their `.value` +function flattenRefs(scope: any): any { + const mapped = {}; + + for (const key in scope) { + if (scope.hasOwnProperty(key)) { + // Check if the value is a Ref + if (isRef(scope[key])) { + mapped[key] = scope[key].value; + } else { + mapped[key] = scope[key]; + } + } + } + return mapped; +} + +// ------------------------------------------------ +// Slots, multiple default and named, :if and :for +// const card = { +// template: html`
+//

+// +//
`, +// }; + +// const main = { +// template: html` +//
+//

card below

+// +// card title +// +// +//
+// `, +// }; + +// const app = new App(); +// app.register("card", card); +// app.mount(main, "#app"); + +// ------------------------------------------------ +// Slots, multiple default and named, :if and :for +// const app = new App(); + +// const parent = { +// template: html` +//
+//

parent

+// +// +//
+//
+//
1
+//
2
+//
3
+//
+//
+// content 1 always shown +//
+// content 2, animals: +//
animal: {{animal}}
+//
+ +// +// +//
+//
+// `, +// main() { +// const bool = ref(true); +// const animals = reactive(["dog", "cat", "bear"]); + +// setInterval(() => { +// bool.value = !bool.value; +// }, 2000); + +// return { bool, animals }; +// }, +// }; +// const card = { +// template: html`
+//

card

+//

+// +//
`, +// }; +// app.register("card", card); +// const parentBlock = app.mount(parent, "body"); +// const cardBlock = parentBlock.context.blocks[0]; + +// ------------------------------------------------ +// Component pros, mirror and spread, bind and no bind +// const child = { +// template: html`
Animal: {{animal}}
`, +// props: { animal: { default: "cat" } }, +// main({ animal }) { +// return { animal }; +// }, +// }; + +// const parent = { +// template: html` +//
+//
asdf
+// mirror, no bind: +// +//
+// mirror, bind: +// +//
+// spread, no bind: +// +//
+// spread, bind: +// +//
+// regular prop: +// +//
+// regular prop, bind: +// +//
+//
div has "id" set to animal.value
+//
+//
div has "id" set and bound to animal.value
+//
+//
div has "animal" set to animal.value
+//
+//
div has "animal" set and bound to animal.value
+//
+//
div has "animal" spread
+//
+//
div has "animal" spread and bound
+//
+//
+//
+//
+// if bool, mirror, no bind: +// +// if bool, mirror, bind: +// +//
+// for list, mirror, no bind: +// +//
+// for list, mirror, 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". +//
+// +//
+//
+// `, +// 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` +
+
true
+

Count: {{count}}{{count >= 2 ? '!!!' : ''}}

+ + +
+ `, + main() { + const count = ref(0); + const style = reactive({ color: "red" }); + const increment = () => count.value++; + const decrement = () => count.value--; + + setInterval(() => { + if (style.color === "red") { + style.color = "blue"; + } else { + style.color = "red"; + } + }, 500); + + return { count, increment, decrement, style }; + }, +}; + +const app = new App(); +app.mount(counter, "#app"); + +// ------------------------------------------------ +// Vue SFC syntax of the above counter component + +// + +// diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..d66d782 --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,7 @@ +import { App } from ".."; + +export interface Plugin { + use: (app: App, ...config: any[]) => void; + destroy: () => void; + compile: (element: Element) => void; +} diff --git a/src/plugins/router/index.ts b/src/plugins/router/index.ts new file mode 100644 index 0000000..105169b --- /dev/null +++ b/src/plugins/router/index.ts @@ -0,0 +1,25 @@ +import { Component } from "../.."; + +export type Route = { + view?: string; + path: string; + component: Component; + componentFallback?: Component; + props?: Record; + children?: Route[]; + + beforeEnter?: () => boolean | Promise; + redirectTo?: string; +}; + +export type RouteExpression = { + regex: RegExp; + params: string[]; + path: string; + route: Route; +}; + +export type RouteMatch = { + match: RouteExpression; + parents: Route[]; +}; diff --git a/src/plugins/router/plugin.ts b/src/plugins/router/plugin.ts new file mode 100644 index 0000000..a40743d --- /dev/null +++ b/src/plugins/router/plugin.ts @@ -0,0 +1,260 @@ +import { pathToRegexp } from "path-to-regexp"; +import { Route, RouteExpression, RouteMatch } from "."; +import { Plugin } from ".."; +import { App, Block, Component, current } from "../.."; +import { reactive } from "../../reactivity/reactive"; +import { unwrap } from "../../reactivity/unwrap"; +import { html } from "../../util"; + +const activeRouters = new Set(); + +const link = { + template: html` +
+ LINK + + `, + props: { href: { default: "#" } }, + main({ href }: { href: string }) { + const go = (e: Event) => { + e.preventDefault(); + + activeRouters.forEach((router) => { + router.doRouteChange(unwrap(href as unknown) as string); + }); + }; + + const classes = reactive({ "router-link": true }); + + return { go, classes, href }; + }, +}; + +async function runEnterTransition(enter: () => boolean | Promise): Promise { + return await enter(); +} + +const canEnterRoute = async (route: Route) => { + if (route.beforeEnter) { + return await runEnterTransition(route.beforeEnter); + } + return true; +}; + +const maybeRedirectRoute = (route: Route) => { + if (route.redirectTo) { + activeRouters.forEach((plugin) => plugin.doRouteChange(route.redirectTo)); + } +}; + +export class RouterPlugin implements Plugin { + app: App; + routes: Route[] = []; + pathExpressions = new Map(); + lastPath = "/"; + knownRouterViews = new Map(); + knownRouterViewNames = new Map(); + populatedRouterViews = new Map(); + + constructor(routes: Route[] = []) { + this.routes = routes; + } + + use(app: App, ...config: any[]) { + this.app = app; + this.app.register("router-link", link); + + window.addEventListener("popstate", this.onHistoryEvent.bind(this)); + window.addEventListener("pushstate", this.onHistoryEvent.bind(this)); + window.addEventListener("load", this.onHistoryEvent.bind(this)); + + for (const route of this.routes) { + this.cacheRouteExpression(route); + } + + this.lastPath = `${location.pathname}${location.search}`; + window.history.replaceState({}, "", this.lastPath); + + activeRouters.add(this); + } + + compile(element: Element) { + if (element.nodeType === Node.ELEMENT_NODE && element.nodeName === "ROUTER-VIEW" && !this.knownRouterViews.has(element) && current.componentBlock) { + this.knownRouterViews.set(element, current.componentBlock); + this.knownRouterViewNames.set(element.getAttribute("name")?.trim() || "", element); + } + } + + onHistoryEvent(e: PopStateEvent | Event) { + e.preventDefault(); + e.stopImmediatePropagation(); + + // @ts-ignore + const path = new URL(e.currentTarget.location.href).pathname; + + if (e.type === "load") { + window.history.replaceState({}, "", this.lastPath); + } else if (e.type === "pushstate") { + window.history.replaceState({}, "", path); + } else if (e.type === "popstate") { + window.history.replaceState({}, "", path); + } + + this.lastPath = path; + + const matches = this.getMatchesForURL(path); + this.applyMatches(matches); + } + + doRouteChange(to: string) { + window.history.pushState({}, "", to); + const matches = this.getMatchesForURL(`${location.pathname}${location.search}`); + this.applyMatches(matches); + } + + getMatchesForURL(url: string): RouteMatch[] { + let matches: RouteMatch[] = []; + + const matchRoutes = (routes: Route[], parentPath: string = "", previousParents = []): RouteMatch[] => { + let parents = []; + + for (const route of routes) { + parents.push(route); + const path = `${parentPath}${route.path}`.replace(/\/\//g, "/"); + const match = this.getPathMatch(path, url); + if (match) matches.push({ match, parents: [...previousParents, ...parents] }); + if (route.children?.length) { + matchRoutes(route.children, path, [...previousParents, ...parents]); + parents = []; + } + } + + return matches; + }; + matches = matchRoutes(this.routes); + return matches; + } + + /** + * getRouteExpression takes a path like "/users/:id" and returns a regex + * and an array of params that match the path. + * "/users/:id" => { regex: /^\/users\/([^\/]+)\?jwt=(\w)$/, params: ["id"], query: ["jwt"] } + */ + getRouteExpression(path: string, route: Route): RouteExpression { + if (this.pathExpressions.has(path)) return this.pathExpressions.get(path); + + const params = []; + const regex = pathToRegexp(path, params, { strict: false, sensitive: false, end: true }); + const expression = { regex, params, path, route }; + this.pathExpressions.set(path, expression); + return expression; + } + + /** + * + * @param path A path like /foo/bar/:id + * @param url A url like /foo/bar/1234 + * @returns A RouteExpression if the URL matches the regex cached for @param path, null otherwise. + */ + getPathMatch(path: string, url: string): RouteExpression | null { + if (this.pathExpressions.get(path)) { + const match = this.pathExpressions.get(path).regex.exec(url); + if (match) { + return this.pathExpressions.get(path); + } + } + + return null; + } + + async applyMatches(matches: RouteMatch[] | null) { + if (!matches) return; + + const usedRouterViews = new Set(); + + const renderRoutes = async (routeChain: Route[], rootNode?: Element) => { + for (const route of routeChain) { + if (route.view) { + const viewNode = this.knownRouterViewNames.get(route.view); + if (viewNode && (await canEnterAndRenderRoute(viewNode, route))) { + continue; + } + } else if (rootNode && (await canEnterAndRenderRoute(rootNode, route))) { + continue; + } + } + }; + + const canEnterAndRenderRoute = async (node: Element, route: Route) => { + const canEnter = await canEnterRoute(route); + if (canEnter) { + renderRouteAtNode(node, route); + return true; + } else { + if (route.componentFallback) { + renderRouteAtNode(node, route, route.componentFallback); + } else { + maybeRedirectRoute(route); + } + + return false; + } + }; + + const renderRouteAtNode = (node: Element, route: Route, component?: Component) => { + if (!usedRouterViews.has(node) || this.populatedRouterViews.get(node)?.route !== route) { + const div = document.createElement("div"); + node.replaceChildren(div); + + const target = div.parentElement; + + const block = new Block({ + element: div, + component: component ? component : route.component, + replacementType: "replaceChildren", + parentContext: current.componentBlock.context, + }); + + target.replaceChild(block.element, div); + + this.populatedRouterViews.set(node, { block, route }); + + usedRouterViews.add(node); + } + }; + + for (const match of matches) { + const routeChain = [...match.parents, match.match.route]; + const uniqueRouteChain = routeChain.filter((route, index, self) => index === self.findIndex((r) => r.path === route.path)); + const rootNode = this.knownRouterViewNames.get("") ?? null; + await renderRoutes(uniqueRouteChain, rootNode); + } + + // Clean up stale views + for (const node of this.knownRouterViews.keys()) { + if (!usedRouterViews.has(node) && this.populatedRouterViews.has(node)) { + const entry = this.populatedRouterViews.get(node); + if (entry) { + entry.block.teardown(); + this.populatedRouterViews.delete(node); + } + } + } + } + + cacheRouteExpression(route: Route, parentPath: string = "") { + const path = `${parentPath}${route.path}`.replace(/\/\//g, "/"); + this.getRouteExpression(path, route); + if (route.children?.length) { + route.children.forEach((child) => { + this.cacheRouteExpression(child, path); + }); + } + } + + destroy() { + window.removeEventListener("popstate", this.onHistoryEvent.bind(this)); + window.removeEventListener("pushstate", this.onHistoryEvent.bind(this)); + window.removeEventListener("load", this.onHistoryEvent.bind(this)); + } +} diff --git a/src/reactivity/computed.ts b/src/reactivity/computed.ts new file mode 100644 index 0000000..bf70223 --- /dev/null +++ b/src/reactivity/computed.ts @@ -0,0 +1,28 @@ +import { isObject } from "../util"; +import { effect } from "./effect"; + +const $computed = Symbol("computed"); + +export type Computed = { + readonly value: T; + readonly [$computed]: true; +}; + +export function isComputed(value: unknown): value is Computed { + return isObject(value) && value[$computed]; +} + +export function computed(getter: () => T): Computed { + const ref = { + get value(): T { + return getter(); + }, + [$computed]: true, + } as const; + + effect(() => { + getter(); + }); + + return ref; +} diff --git a/src/reactivity/effect.ts b/src/reactivity/effect.ts new file mode 100644 index 0000000..14ba028 --- /dev/null +++ b/src/reactivity/effect.ts @@ -0,0 +1,118 @@ +interface EffectOptions { + lazy?: boolean; +} + +interface EffectFunction { + active: boolean; + handler: () => void; + refs: Set[]; +} + +type Effects = Set; +type EffectsMap = Map; +type TargetMap = WeakMap; + +const targetMap: TargetMap = new WeakMap(); +const effectStack: (EffectFunction | undefined)[] = []; + +export function track(target: T, key: PropertyKey) { + const activeEffect = effectStack[effectStack.length - 1]; + + if (!activeEffect) return; + + let effectsMap = targetMap.get(target); + if (!effectsMap) + targetMap.set(target, (effectsMap = new Map() as EffectsMap)); + + let effects = effectsMap.get(key); + if (!effects) effectsMap.set(key, (effects = new Set())); + + if (!effects.has(activeEffect)) { + effects.add(activeEffect); + activeEffect.refs.push(effects); + } +} + +export function trigger(target: any, key: PropertyKey) { + const effectsMap = targetMap.get(target); + if (!effectsMap) return; + + const scheduled = new Set(); + + effectsMap.get(key)?.forEach((effect) => { + scheduled.add(effect); + }); + + scheduled.forEach(run); +} + +function stop(effect: EffectFunction) { + if (effect.active) cleanup(effect); + effect.active = false; +} + +function start(effect: EffectFunction) { + if (!effect.active) { + effect.active = true; + run(effect); + } +} + +function run(effect: EffectFunction): unknown { + if (!effect.active) return; + + if (effectStack.includes(effect)) return; + + cleanup(effect); + + let val: unknown; + + try { + effectStack.push(effect); + val = effect.handler(); + } finally { + effectStack.pop(); + } + + return val; +} + +function cleanup(effect: EffectFunction) { + const { refs } = effect; + + if (refs.length) { + for (const ref of refs) { + ref.delete(effect); + } + } + + refs.length = 0; +} + +export function effect(handler: () => void, opts: EffectOptions = {}) { + const { lazy } = opts; + const newEffect: EffectFunction = { + active: !lazy, + handler, + refs: [], + }; + + run(newEffect); + + return { + start: () => { + start(newEffect); + }, + stop: () => { + stop(newEffect); + }, + toggle: () => { + if (newEffect.active) { + stop(newEffect); + } else { + start(newEffect); + } + return newEffect.active; + }, + }; +} diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts new file mode 100644 index 0000000..6d706fb --- /dev/null +++ b/src/reactivity/reactive.ts @@ -0,0 +1,62 @@ +import { isObject } from "../util"; +import { track, trigger } from "./effect"; +import { ref } from "./ref"; + +const $reactive = Symbol("reactive"); + +export type Reactive = T & { [$reactive]: true }; + +export function isReactive( + value: unknown, +): value is Reactive { + return isObject(value) && !!value[$reactive]; +} + +export function reactive(value: T): Reactive { + // @ts-ignore + if (!isObject(value)) return ref(value) as Reactive; + if (value[$reactive]) return value as Reactive; + + value[$reactive] = true; + + Object.keys(value).forEach((key) => { + if (isObject(value[key])) { + value[key] = reactive(value[key]); + } + }); + + return new Proxy(value, reactiveProxyHandler()) as Reactive; +} + +function reactiveProxyHandler() { + return { + deleteProperty(target: object, key: string | symbol) { + const had = Reflect.has(target, key); + const result = Reflect.deleteProperty(target, key); + if (had) trigger(target, key); + return result; + }, + get(target: object, key: string | symbol) { + track(target, key); + return Reflect.get(target, key); + }, + set(target: object, key: string | symbol, value: unknown) { + if (target[key] === value) return true; + let newObj = false; + + if (isObject(value) && !isObject(target[key])) { + newObj = true; + } + + if (Reflect.set(target, key, value)) { + trigger(target, key); + } + + if (newObj) { + target[key] = reactive(target[key]); + } + + return true; + }, + }; +} diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts new file mode 100644 index 0000000..5193c0c --- /dev/null +++ b/src/reactivity/ref.ts @@ -0,0 +1,41 @@ +import { isObject } from "../util"; +import { track, trigger } from "./effect"; +import { Reactive, reactive } from "./reactive"; + +export const $ref = Symbol("ref"); + +export type Ref = { + value: T; + [$ref]: true; +}; + +export function isRef(value: unknown): value is Ref { + return isObject(value) && !!value[$ref]; +} + +export function ref(value: T = null as unknown as T): Ref { + if (isObject(value)) { + // @ts-ignore + return isRef(value) ? (value as Ref) : (reactive(value) as Reactive); + } + + const result = { value, [$ref]: true }; + + return new Proxy(result, { + get(target: object, key: string | symbol, receiver: any) { + const val = Reflect.get(target, key, receiver); + track(result, "value"); + return val; + }, + set(target: object, key: string | symbol, value: unknown) { + const oldValue = target[key]; + if (oldValue !== value) { + const success = Reflect.set(target, key, value); + if (success) { + trigger(result, "value"); + } + } + return true; + }, + }) as Ref; +} diff --git a/src/reactivity/unwrap.ts b/src/reactivity/unwrap.ts new file mode 100644 index 0000000..11d2591 --- /dev/null +++ b/src/reactivity/unwrap.ts @@ -0,0 +1,14 @@ +import { isComputed } from "./computed"; +import { isRef } from "./ref"; + +export function unwrap(value: unknown) { + if (isRef(value) || isComputed(value)) { + return value.value; + } + + if (typeof value === "function") { + return value(); + } + + return value; +} diff --git a/src/reactivity/watch.ts b/src/reactivity/watch.ts new file mode 100644 index 0000000..aac22ad --- /dev/null +++ b/src/reactivity/watch.ts @@ -0,0 +1,31 @@ +import { Computed, isComputed } from "./computed"; +import { effect } from "./effect"; +import { Reactive } from "./reactive"; +import { isRef, Ref } from "./ref"; +import { unwrap } from "./unwrap"; + +export type WatchCallback = (newVal: unknown, oldVal: unknown) => void; + +type Watchable = + | Function + | Ref + | Reactive + | Computed; + +export function watch(getter: Watchable, callback: WatchCallback) { + if (typeof getter !== "function" && !isRef(getter) && !isComputed(getter)) { + console.warn( + "'watch' expects: ref, computed or function that returns one of those.", + ); + return; + } + + let oldVal = unwrap(getter); + + effect(() => { + const newVal = unwrap(getter); + if (newVal === oldVal) return; + callback(newVal, oldVal); + oldVal = newVal; + }); +} diff --git a/src/render.test.ts b/src/render.test.ts new file mode 100644 index 0000000..3155d1c --- /dev/null +++ b/src/render.test.ts @@ -0,0 +1,71 @@ +import { expect, test } from "vitest"; +import { App, Block, createContext } from "."; +import { html } from "./util"; + +test("mounts", () => { + const app = new App(); + const main = { template: "
Hello
" }; + app.mount(main, "body"); + expect(document.body.innerHTML).toBe("
Hello
"); +}); + +test("when element is an HTMLTemplateElement, block.isFragment should be true", () => { + document.body.innerHTML = ``; + const templateElement = document.body.firstElementChild as HTMLTemplateElement; + const parentContext = createContext({ app: new App() }); + const block = new Block({ + element: templateElement, + parentContext: parentContext, + }); + expect(block.isFragment).toBe(true); +}); + +test("block captures slots", () => { + const app = new App(); + const main = { template: html`
` }; + const block = app.mount(main, "body"); + expect(document.body.innerHTML).toBe(`
`); + expect(block.context.slots.length).toBe(2); + expect(block.context.slots[0].name).toBeNull(); + expect(block.context.slots[1].name).toBe("x"); +}); + +test("block captures templates", () => { + const app = new App(); + + const parent = { + template: html` +
+