diff --git a/.changeset/funny-eyes-rescue.md b/.changeset/funny-eyes-rescue.md new file mode 100644 index 0000000000..5fe80d5ade --- /dev/null +++ b/.changeset/funny-eyes-rescue.md @@ -0,0 +1,6 @@ +--- +"@marko/translator-interop-class-tags": patch +"marko": patch +--- + +Update tags api compat layer. diff --git a/.changeset/thick-parents-vanish.md b/.changeset/thick-parents-vanish.md new file mode 100644 index 0000000000..1896931cf1 --- /dev/null +++ b/.changeset/thick-parents-vanish.md @@ -0,0 +1,5 @@ +--- +"@marko/runtime-tags": patch +--- + +Support namespaced tags (svg/math). diff --git a/.sizes.json b/.sizes.json index bea2028700..cfb21259b7 100644 --- a/.sizes.json +++ b/.sizes.json @@ -7,8 +7,8 @@ { "name": "*", "total": { - "min": 18085, - "brotli": 6624 + "min": 18364, + "brotli": 6749 } }, { @@ -18,12 +18,12 @@ "brotli": 144 }, "runtime": { - "min": 4178, - "brotli": 1803 + "min": 4409, + "brotli": 1898 }, "total": { - "min": 4367, - "brotli": 1947 + "min": 4598, + "brotli": 2042 } }, { @@ -34,11 +34,11 @@ }, "runtime": { "min": 3688, - "brotli": 1661 + "brotli": 1656 }, "total": { "min": 3799, - "brotli": 1761 + "brotli": 1756 } }, { @@ -48,12 +48,12 @@ "brotli": 541 }, "runtime": { - "min": 7879, - "brotli": 3253 + "min": 8145, + "brotli": 3363 }, "total": { - "min": 9019, - "brotli": 3794 + "min": 9285, + "brotli": 3904 } }, { @@ -63,12 +63,12 @@ "brotli": 478 }, "runtime": { - "min": 8972, - "brotli": 3643 + "min": 9253, + "brotli": 3772 }, "total": { - "min": 9920, - "brotli": 4121 + "min": 10201, + "brotli": 4250 } } ] diff --git a/.sizes/dom.js b/.sizes/dom.js index 8ebaa48269..227713563b 100644 --- a/.sizes/dom.js +++ b/.sizes/dom.js @@ -1,4 +1,4 @@ -// size: 18085 (min) 6624 (brotli) +// size: 18364 (min) 6749 (brotli) var empty = [], rest = Symbol(); function attrTag(attrs2) { @@ -128,10 +128,10 @@ var registeredValues = {}, Render = class { n = []; o = {}; - z = { _: registeredValues }; + A = { _: registeredValues }; constructor(renders, runtimeId, renderId) { - (this.A = renders), - (this.B = runtimeId), + (this.B = renders), + (this.C = runtimeId), (this.p = renderId), (this.q = renders[renderId]), this.s(); @@ -141,7 +141,7 @@ var registeredValues = {}, } s() { let data2 = this.q, - serializeContext = this.z, + serializeContext = this.A, scopeLookup = this.o, visits = data2.v, branchIds = new Set(), @@ -226,7 +226,7 @@ var registeredValues = {}, { $global: $global } = scopeLookup; $global || ((scopeLookup.$global = $global = scopes.$ || {}), - ($global.runtimeId = this.B), + ($global.runtimeId = this.C), ($global.renderId = this.p)); for (let scopeId in scopes) if ("$" !== scopeId) { @@ -251,7 +251,7 @@ var registeredValues = {}, } } else i === len || "string" != typeof resumes[i] - ? delete this.A[this.p] + ? delete this.B[this.p] : registeredValues[resumes[i++]]( scopeLookup[resumeData], scopeLookup[resumeData], @@ -294,7 +294,7 @@ function init(runtimeId = "M") { }); } function registerSubscriber(id, signal) { - return register(id, signal.C), signal; + return register(id, signal.D), signal; } function nodeRef(id, key) { return register(id, (scope) => () => scope[key]); @@ -554,9 +554,11 @@ function normalizeBoolProp(value2) { function toValueProp(it) { return it.value; } -var parser = document.createElement("template"); -function parseHTML(html2) { - return (parser.innerHTML = html2), parser.content; +var parsers = {}; +function parseHTML(html2, ns) { + let parser = (parsers[ns] ||= document.createElementNS(ns, "template")), + content = ((parser.innerHTML = html2), parser.content || parser); + return content.firstChild || content.appendChild(new Text()), content; } function attr(element, name, value2) { setAttribute(element, name, normalizeAttrValue(value2)); @@ -727,11 +729,18 @@ function attrsEvents(scope, nodeAccessor) { } function html(scope, value2, accessor) { let firstChild = scope[accessor], + parentNode = firstChild.parentNode, lastChild = scope[accessor + "-"] || firstChild, - newContent = parseHTML(value2 || 0 === value2 ? value2 + "" : ""); - (scope[accessor] = newContent.firstChild), + newContent = parseHTML( + value2 || 0 === value2 ? value2 + "" : "", + parentNode.namespaceURI, + ); + insertChildNodes( + parentNode, + firstChild, + (scope[accessor] = newContent.firstChild), (scope[accessor + "-"] = newContent.lastChild), - firstChild.parentNode.insertBefore(newContent, firstChild), + ), removeChildNodes(firstChild, lastChild); } function props(scope, nodeIndex, index) { @@ -766,6 +775,20 @@ function removeChildNodes(startNode, endNode) { current.remove(), (current = next); } } +function insertChildNodes(parentNode, referenceNode, startNode, endNode) { + parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode); +} +function toInsertNode(startNode, endNode) { + if (startNode === endNode) return startNode; + let parent = new DocumentFragment(), + stop = endNode.nextSibling, + current = startNode; + for (; current !== stop; ) { + let next = current.nextSibling; + parent.appendChild(current), (current = next); + } + return parent; +} var pendingScopes = []; function createScope($global) { let scope = { g: 1, $global: $global }; @@ -779,9 +802,9 @@ function destroyBranch(branch) { branch.t?.k?.delete(branch), destroyNestedBranches(branch); } function destroyNestedBranches(branch) { - (branch.D = 1), + (branch.E = 1), branch.k?.forEach(destroyNestedBranches), - branch.E?.forEach((scope) => { + branch.F?.forEach((scope) => { for (let id in scope.h) scope.h[id]?.abort(); }); } @@ -789,12 +812,7 @@ function removeAndDestroyBranch(branch) { destroyBranch(branch), removeChildNodes(branch.a, branch.b); } function insertBranchBefore(branch, parentNode, nextSibling) { - let current = branch.a, - stop = branch.b.nextSibling; - for (; current !== stop; ) { - let next = current.nextSibling; - parentNode.insertBefore(current, nextSibling), (current = next); - } + insertChildNodes(parentNode, nextSibling, branch.a, branch.b); } var walker = document.createTreeWalker(document); function trimWalkString(walkString) { @@ -851,27 +869,41 @@ function walkInternal(walkCodes, scope, currentWalkIndex) { } return currentWalkIndex; } -function createBranchScopeWithRenderer(renderer, $global, parentScope) { +function createBranchScopeWithRenderer( + renderer, + $global, + parentScope, + parentNode, +) { let branch = createBranch($global, renderer.u || parentScope, parentScope); - return initBranch(renderer, branch), branch; + return initBranch(renderer, branch, parentNode), branch; } function createBranchScopeWithTagNameOrRenderer( tagNameOrRenderer, $global, parentScope, + parentNode, ) { if ("string" != typeof tagNameOrRenderer) return createBranchScopeWithRenderer( tagNameOrRenderer, $global, parentScope, + parentNode, ); let branch = createBranch($global, parentScope, parentScope); return ( (branch[0] = branch.a = branch.b = - document.createElement(tagNameOrRenderer)), + document.createElementNS( + "svg" === tagNameOrRenderer + ? "http://www.w3.org/2000/svg" + : "math" === tagNameOrRenderer + ? "http://www.w3.org/1998/Math/MathML" + : parentNode.namespaceURI, + tagNameOrRenderer, + )), branch ); } @@ -889,15 +921,13 @@ function createBranch($global, ownerScope, parentScope) { branch ); } -function initBranch(renderer, branch) { - let dom = renderer.l(); - return ( - walk(11 === dom.nodeType ? dom.firstChild : dom, renderer.F, branch), - (branch.a = 11 === dom.nodeType ? dom.firstChild : dom), - (branch.b = 11 === dom.nodeType ? dom.lastChild : dom), - renderer.x && queueRender(branch, renderer.x), - dom - ); +function initBranch(renderer, branch, parentNode) { + let clone = renderer.l(parentNode.namespaceURI), + cloneParent = clone.parentNode; + walk(cloneParent?.firstChild || clone, renderer.G, branch), + (branch.a = cloneParent?.firstChild || clone), + (branch.b = cloneParent?.lastChild || clone), + renderer.x && queueRender(branch, renderer.x); } function dynamicTagAttrs(nodeAccessor, getContent, inputIsArgs) { return (scope, attrsOrOp) => { @@ -927,12 +957,12 @@ function createRendererWithOwner(template, rawWalks, setup, getArgs) { walks = rawWalks ? trimWalkString(rawWalks) : " "; return (owner) => ({ j: id, - G: template, - F: walks, + y: template, + G: walks, x: setup, l: _clone, u: owner, - H: void 0, + J: void 0, get d() { return (args ||= getArgs?.()); }, @@ -941,21 +971,19 @@ function createRendererWithOwner(template, rawWalks, setup, getArgs) { function createRenderer(template, walks, setup, getArgs) { return createRendererWithOwner(template, walks, setup, getArgs)(); } -function _clone() { - return (this.H ||= (function (html2) { - let content = parseHTML(html2); - if (content.firstChild) { - if ( - content.firstChild === content.lastChild && - 8 !== content.firstChild.nodeType - ) - return content.firstChild; - let fragment = new DocumentFragment(); - return fragment.appendChild(content), fragment; - } - return new Text(); - })(this.G)).cloneNode(!0); +function _clone(ns) { + return ((cloneCache[ns] ||= {})[this.y] ||= (function (html2, ns) { + let { firstChild: firstChild, lastChild: lastChild } = parseHTML(html2, ns), + parent = document.createElementNS(ns, "t"); + return ( + insertChildNodes(parent, null, firstChild, lastChild), + firstChild === lastChild && firstChild.nodeType < 8 + ? () => firstChild.cloneNode(!0) + : () => parent.cloneNode(!0).firstChild + ); + })(this.y, ns))(); } +var cloneCache = {}; var conditional = function (nodeAccessor, fn, getIntersection) { let rendererAccessor = nodeAccessor + "(", intersection2 = @@ -978,6 +1006,7 @@ var conditional = function (nodeAccessor, fn, getIntersection) { newRenderer, scope.$global, scope, + prevBranch.b.parentNode, ) : getEmptyBranch(scope[nodeAccessor]); insertBranchBefore( @@ -1028,6 +1057,7 @@ function setConditionalRendererOnlyChild(scope, nodeAccessor, newRenderer) { newRenderer, scope.$global, scope, + referenceNode, ) : void 0; (referenceNode.textContent = ""), @@ -1074,29 +1104,30 @@ function loop(nodeAccessor, renderer, forEach) { let newMap, newArray, afterReference, - parentNode, referenceNode = scope[nodeAccessor], - referenceIsMarker = - 8 === referenceNode.nodeType || 3 === referenceNode.nodeType, + referenceIsMarker = referenceNode.nodeType > 1, oldMap = scope[nodeAccessor + "("] || (referenceIsMarker ? emptyMarkerMap : emptyMap), oldArray = scope[nodeAccessor + "!"] || Array.from(oldMap.values()), + parentNode = referenceIsMarker + ? referenceNode.parentNode || oldArray[0].a.parentNode + : referenceNode, needsReconciliation = !0; - if ( - (forEach(valueOrOp, (key, args) => { - let branch = oldMap.get(key); - branch || - (branch = createBranchScopeWithRenderer( - renderer, - scope.$global, - scope, - )), - params && params(branch, args), - newMap - ? (newMap.set(key, branch), newArray.push(branch)) - : ((newMap = new Map([[key, branch]])), (newArray = [branch])); - }), + forEach(valueOrOp, (key, args) => { + let branch = oldMap.get(key); + branch || + (branch = createBranchScopeWithRenderer( + renderer, + scope.$global, + scope, + parentNode, + )), + params && params(branch, args), + newMap + ? (newMap.set(key, branch), newArray.push(branch)) + : ((newMap = new Map([[key, branch]])), (newArray = [branch])); + }), newMap || (referenceIsMarker ? ((newMap = emptyMarkerMap), @@ -1107,134 +1138,141 @@ function loop(nodeAccessor, renderer, forEach) { (newMap = emptyMap), (newArray = emptyArray), (needsReconciliation = !1))), - needsReconciliation) - ) { - if (referenceIsMarker) { - oldMap === emptyMarkerMap && getEmptyBranch(referenceNode); - let oldLastChild = oldArray[oldArray.length - 1]; - (afterReference = oldLastChild.b.nextSibling), - (parentNode = oldLastChild.a.parentNode); - } else (afterReference = null), (parentNode = referenceNode); - !(function (parent, oldBranches, newBranches, afterReference) { - let i, - j, - k, - nextSibling, - oldBranch, - newBranch, - oldStart = 0, - newStart = 0, - oldEnd = oldBranches.length - 1, - newEnd = newBranches.length - 1, - oldStartBranch = oldBranches[oldStart], - newStartBranch = newBranches[newStart], - oldEndBranch = oldBranches[oldEnd], - newEndBranch = newBranches[newEnd]; - outer: { - for (; oldStartBranch === newStartBranch; ) { - if ( - (++oldStart, ++newStart, oldStart > oldEnd || newStart > newEnd) - ) - break outer; - (oldStartBranch = oldBranches[oldStart]), - (newStartBranch = newBranches[newStart]); - } - for (; oldEndBranch === newEndBranch; ) { - if ((--oldEnd, --newEnd, oldStart > oldEnd || newStart > newEnd)) - break outer; - (oldEndBranch = oldBranches[oldEnd]), - (newEndBranch = newBranches[newEnd]); + needsReconciliation && + (referenceIsMarker + ? (oldMap === emptyMarkerMap && getEmptyBranch(referenceNode), + (afterReference = oldArray[oldArray.length - 1].b.nextSibling)) + : (afterReference = null), + (function (parent, oldBranches, newBranches, afterReference) { + let i, + j, + k, + nextSibling, + oldBranch, + newBranch, + oldStart = 0, + newStart = 0, + oldEnd = oldBranches.length - 1, + newEnd = newBranches.length - 1, + oldStartBranch = oldBranches[oldStart], + newStartBranch = newBranches[newStart], + oldEndBranch = oldBranches[oldEnd], + newEndBranch = newBranches[newEnd]; + outer: { + for (; oldStartBranch === newStartBranch; ) { + if ( + (++oldStart, ++newStart, oldStart > oldEnd || newStart > newEnd) + ) + break outer; + (oldStartBranch = oldBranches[oldStart]), + (newStartBranch = newBranches[newStart]); + } + for (; oldEndBranch === newEndBranch; ) { + if ((--oldEnd, --newEnd, oldStart > oldEnd || newStart > newEnd)) + break outer; + (oldEndBranch = oldBranches[oldEnd]), + (newEndBranch = newBranches[newEnd]); + } } - } - if (oldStart > oldEnd) { - if (newStart <= newEnd) { - (k = newEnd + 1), - (nextSibling = - k < newBranches.length ? newBranches[k].a : afterReference); + if (oldStart > oldEnd) { + if (newStart <= newEnd) { + (k = newEnd + 1), + (nextSibling = + k < newBranches.length ? newBranches[k].a : afterReference); + do { + insertBranchBefore( + newBranches[newStart++], + parent, + nextSibling, + ); + } while (newStart <= newEnd); + } + } else if (newStart > newEnd) do { - insertBranchBefore(newBranches[newStart++], parent, nextSibling); - } while (newStart <= newEnd); - } - } else if (newStart > newEnd) - do { - removeAndDestroyBranch(oldBranches[oldStart++]); - } while (oldStart <= oldEnd); - else { - let oldLength = oldEnd - oldStart + 1, - newLength = newEnd - newStart + 1, - aNullable = oldBranches, - sources = new Array(newLength); - for (i = 0; i < newLength; ++i) sources[i] = -1; - let pos = 0, - synced = 0, - keyIndex = new Map(); - for (j = newStart; j <= newEnd; ++j) keyIndex.set(newBranches[j], j); - for (i = oldStart; i <= oldEnd && synced < newLength; ++i) - (oldBranch = oldBranches[i]), - (j = keyIndex.get(oldBranch)), - void 0 !== j && - ((pos = pos > j ? 2147483647 : j), - ++synced, - (newBranch = newBranches[j]), - (sources[j - newStart] = i), - (aNullable[i] = null)); - if (oldLength === oldBranches.length && 0 === synced) { - for (; newStart < newLength; ++newStart) - insertBranchBefore(newBranches[newStart], parent, afterReference); - for (; oldStart < oldLength; ++oldStart) - removeAndDestroyBranch(oldBranches[oldStart]); - } else { - for (i = oldLength - synced; i > 0; ) - (oldBranch = aNullable[oldStart++]), - null !== oldBranch && (removeAndDestroyBranch(oldBranch), i--); - if (2147483647 === pos) { - let seq = (function (a) { - let u, - v, - p = a.slice(), - result = []; - result.push(0); - for (let i = 0, il = a.length; i < il; ++i) { - if (-1 === a[i]) continue; - let j = result[result.length - 1]; - if (a[j] < a[i]) (p[i] = j), result.push(i); - else { - for (u = 0, v = result.length - 1; u < v; ) { - let c = ((u + v) / 2) | 0; - a[result[c]] < a[i] ? (u = c + 1) : (v = c); + removeAndDestroyBranch(oldBranches[oldStart++]); + } while (oldStart <= oldEnd); + else { + let oldLength = oldEnd - oldStart + 1, + newLength = newEnd - newStart + 1, + aNullable = oldBranches, + sources = new Array(newLength); + for (i = 0; i < newLength; ++i) sources[i] = -1; + let pos = 0, + synced = 0, + keyIndex = new Map(); + for (j = newStart; j <= newEnd; ++j) + keyIndex.set(newBranches[j], j); + for (i = oldStart; i <= oldEnd && synced < newLength; ++i) + (oldBranch = oldBranches[i]), + (j = keyIndex.get(oldBranch)), + void 0 !== j && + ((pos = pos > j ? 2147483647 : j), + ++synced, + (newBranch = newBranches[j]), + (sources[j - newStart] = i), + (aNullable[i] = null)); + if (oldLength === oldBranches.length && 0 === synced) { + for (; newStart < newLength; ++newStart) + insertBranchBefore( + newBranches[newStart], + parent, + afterReference, + ); + for (; oldStart < oldLength; ++oldStart) + removeAndDestroyBranch(oldBranches[oldStart]); + } else { + for (i = oldLength - synced; i > 0; ) + (oldBranch = aNullable[oldStart++]), + null !== oldBranch && + (removeAndDestroyBranch(oldBranch), i--); + if (2147483647 === pos) { + let seq = (function (a) { + let u, + v, + p = a.slice(), + result = []; + result.push(0); + for (let i = 0, il = a.length; i < il; ++i) { + if (-1 === a[i]) continue; + let j = result[result.length - 1]; + if (a[j] < a[i]) (p[i] = j), result.push(i); + else { + for (u = 0, v = result.length - 1; u < v; ) { + let c = ((u + v) / 2) | 0; + a[result[c]] < a[i] ? (u = c + 1) : (v = c); + } + a[i] < a[result[u]] && + (u > 0 && (p[i] = result[u - 1]), (result[u] = i)); } - a[i] < a[result[u]] && - (u > 0 && (p[i] = result[u - 1]), (result[u] = i)); } - } - for (u = result.length, v = result[u - 1]; u-- > 0; ) - (result[u] = v), (v = p[v]); - return result; - })(sources); - for ( - j = seq.length - 1, k = newBranches.length, i = newLength - 1; - i >= 0; - --i - ) - -1 === sources[i] || j < 0 || i !== seq[j] - ? ((pos = i + newStart), + for (u = result.length, v = result[u - 1]; u-- > 0; ) + (result[u] = v), (v = p[v]); + return result; + })(sources); + for ( + j = seq.length - 1, k = newBranches.length, i = newLength - 1; + i >= 0; + --i + ) + -1 === sources[i] || j < 0 || i !== seq[j] + ? ((pos = i + newStart), + (newBranch = newBranches[pos++]), + (nextSibling = + pos < k ? newBranches[pos].a : afterReference), + insertBranchBefore(newBranch, parent, nextSibling)) + : --j; + } else if (synced !== newLength) + for (k = newBranches.length, i = newLength - 1; i >= 0; --i) + -1 === sources[i] && + ((pos = i + newStart), (newBranch = newBranches[pos++]), (nextSibling = pos < k ? newBranches[pos].a : afterReference), - insertBranchBefore(newBranch, parent, nextSibling)) - : --j; - } else if (synced !== newLength) - for (k = newBranches.length, i = newLength - 1; i >= 0; --i) - -1 === sources[i] && - ((pos = i + newStart), - (newBranch = newBranches[pos++]), - (nextSibling = pos < k ? newBranches[pos].a : afterReference), - insertBranchBefore(newBranch, parent, nextSibling)); + insertBranchBefore(newBranch, parent, nextSibling)); + } } - } - })(parentNode, oldArray, newArray, afterReference); - } - (scope[nodeAccessor + "("] = newMap), + })(parentNode, oldArray, newArray, afterReference)), + (scope[nodeAccessor + "("] = newMap), (scope[nodeAccessor + "!"] = newArray); }; } @@ -1382,7 +1420,7 @@ function dynamicClosure( (helperSignal._ = (scope, value2) => { _signal(scope, value2), subscribe(scope); }), - (helperSignal.C = subscribe), + (helperSignal.D = subscribe), helperSignal ); } @@ -1436,7 +1474,7 @@ function queueSource(scope, signal, value2) { } function queueRender(scope, signal, value2) { let i = pendingRenders.length, - render = { m: scope, I: signal, J: value2, y: i }; + render = { m: scope, H: signal, I: value2, z: i }; for (pendingRenders.push(render); i; ) { let parentIndex = (i - 1) >> 1, parent = pendingRenders[parentIndex]; @@ -1502,7 +1540,7 @@ function runRenders() { } pendingRenders[i] = item; } - render.m.c?.D || render.I(render.m, render.J); + render.m.c?.E || render.H(render.m, render.I); } !(function () { for (let scope of pendingScopes) scope.g = 0; @@ -1510,7 +1548,7 @@ function runRenders() { })(); } function comparePendingRenders(a, b) { - return getBranchDepth(a) - getBranchDepth(b) || a.y - b.y; + return getBranchDepth(a) - getBranchDepth(b) || a.z - b.z; } function getBranchDepth(render) { return render.m.c?.f || 0; @@ -1521,7 +1559,7 @@ function resetAbortSignal(scope, id) { } function getAbortSignal(scope, id) { return ( - scope.c && (scope.c.E ||= new Set()).add(scope), + scope.c && (scope.c.F ||= new Set()).add(scope), ((scope.h ||= {})[id] ||= new AbortController()).signal ); } @@ -1596,12 +1634,12 @@ var classIdToBranch = new Map(), ? (applyArgs(branch, MARK), (existing = !0)) : ((branch = component.scope = createScope(out.global)), (branch._ = renderer.u), - initBranch(renderer, branch)), + initBranch(renderer, branch, document.body)), applyArgs(branch, args); })), !existing) ) - return branch.a === branch.b ? branch.a : branch.a.parentNode; + return toInsertNode(branch.a, branch.b); }, }; function noop() {} @@ -1615,7 +1653,6 @@ var createTemplate = (templateId, ...rendererArgs) => { }; function mount(input = {}, reference, position) { let branch, - dom, parentNode = reference, nextSibling = null, { $global: $global } = input; @@ -1639,11 +1676,11 @@ function mount(input = {}, reference, position) { let args = this.d, effects = prepareEffects(() => { (branch = createScope($global)), - (dom = initBranch(this, branch)), + initBranch(this, branch, parentNode), args?.(branch, [input]); }); return ( - parentNode.insertBefore(dom, nextSibling), + insertChildNodes(parentNode, nextSibling, branch.a, branch.b), runEffects(effects), { update: (newInput) => { diff --git a/.sizes/name-cache.json b/.sizes/name-cache.json index 051e6cddbf..4160627b9f 100644 --- a/.sizes/name-cache.json +++ b/.sizes/name-cache.json @@ -1 +1 @@ -{"vars":{"props":{"$empty":"e","$rest":"t","$attrTag":"n","$attrTags":"r","$attrTagIterator":"i","$forIn":"l","$forOf":"o","$forTo":"f","$isScheduled":"u","$port2":"a","$flushAndWaitFrame":"c","$triggerMacroTask":"s","$createScope":"d","$emptyScope":"h","$getEmptyScope":"g","$destroyScope":"p","$_destroyScope":"v","$onDestroy":"b","$removeAndDestroyScope":"m","$insertBefore":"y","$registeredValues":"k","$Render":"w","$isResuming":"C","$register":"A","$registerBoundSignal":"S","$init":"N","$registerSubscriber":"x","$nodeRef":"$","$MARK":"M","$CLEAN":"E","$DIRTY":"I","$state":"T","$value":"_","$accessorId":"O","$intersection":"B","$defaultGetOwnerScope":"V","$closure":"j","$dynamicClosure":"R","$childClosures":"q","$dynamicSubscribers":"D","$setTagVar":"P","$tagVarSignal":"W","$setTagVarChange":"L","$tagVarSignalChange":"z","$renderBodyClosures":"F","$tagIdsByGlobal":"U","$nextTagId":"G","$inChild":"J","$intersections":"X","$effect":"Z","$pendingSignals":"H","$pendingEffects":"K","$rendering":"Q","$queueEffect":"Y","$run":"ee","$prepareEffects":"te","$runEffects":"ne","$runSignals":"re","$resetAbortSignal":"ie","$getAbortSignal":"le","$stringifyClassObject":"oe","$NON_DIMENSIONAL":"fe","$stringifyStyleObject":"ue","$toDelimitedString":"ae","$isEventHandler":"ce","$getEventHandlerName":"se","$normalizeDynamicRenderer":"de","$elementHandlersByEvent":"he","$defaultDelegator":"ge","$on":"pe","$createDelegator":"ve","$handleDelegated":"be","$stripSpacesAndPunctuation":"me","$controllable_input_checked":"ye","$controllable_input_checked_effect":"ke","$controllable_input_checkedValue":"we","$controllable_input_checkedValue_effect":"Ce","$controllable_input_value":"Ae","$controllable_input_value_effect":"Se","$controllable_select_value":"Ne","$controllable_select_value_effect":"xe","$setSelectOptions":"$e","$controllable_detailsOrDialog_open":"Me","$controllable_detailsOrDialog_open_effect":"Ee","$inputType":"Ie","$setValueAndUpdateSelection":"Te","$setCheckboxValue":"_e","$delegateFormControl":"Oe","$formChangeHandlers":"Be","$syncControllable":"Ve","$onFormChange":"je","$onFormReset":"Re","$hasValueChanged":"qe","$hasCheckboxChanged":"De","$hasSelectChanged":"Pe","$hasFormElementChanged":"We","$normalizeStrProp":"Le","$normalizeBoolProp":"ze","$toValueProp":"Fe","$fallback":"Ue","$parser":"Ge","$parseHTML":"Je","$attr":"Xe","$setAttribute":"Ze","$classAttr":"He","$styleAttr":"Ke","$data":"Qe","$attrs":"Ye","$hasAttrAlias":"et","$partialAttrs":"tt","$attrsInternal":"nt","$attrsEvents":"rt","$html":"it","$props":"lt","$normalizeAttrValue":"ot","$lifecycle":"ft","$walker":"ut","$trimWalkString":"at","$walk":"ct","$walkInternal":"st","$createScopeWithRenderer":"dt","$createScopeWithTagNameOrRenderer":"ht","$initRenderer":"gt","$dynamicTagAttrs":"pt","$createRendererWithOwner":"vt","$createRenderer":"bt","$_clone":"mt","$conditional":"yt","$inConditionalScope":"kt","$conditionalOnlyChild":"wt","$setConditionalRendererOnlyChild":"Ct","$emptyMarkerMap":"At","$emptyMarkerArray":"St","$emptyMap":"Nt","$emptyArray":"xt","$loopOf":"$t","$loopIn":"Mt","$loopTo":"Et","$loop":"It","$inLoopScope":"Tt","$bySecondArg":"_t","$byFirstArg":"Ot","$isDifferentRenderer":"Bt","$classIdToScope":"Vt","$compat":"jt","$noop":"Rt","$createTemplate":"qt","$mount":"Dt","$parseHTMLOrSingleNode":"Wt","$marker":"Ft","$_clickCount_effect":"Qt","$_clickCount":"Yt","$_setup_":"zt","$_expr_comment_comments_id$ifBody":"ss","$_id$ifBody":"as","$_comment_comments$ifBody":"ts","$_ifBody":"ns","$_expr_input_path_i$forBody":"os","$_if$forBody":"cs","$_open$forBody_effect":"is","$_open$forBody":"ms","$_id$forBody":"ls","$_i$forBody":"us","$_comment_comments$forBody":"es","$_comment_text$forBody":"ds","$_comment$forBody":"rs","$_params_2$forBody":"bs","$_input_path$forBody":"ps","$_for":"hs","$_input_path_":"vs","$_input_comments_":"fs","$_input_$1":"js","$_input_":"ks","$_params__":"Ss","$noop2":"Pt","$textContent":"Lt","$normalizeString":"Ut","$contentClosures":"Gt","$_expr_comment_comments_id$if_content":"Zs","$_id$if_content":"$s","$_comment_comments$if_content":"_s","$_if_content":"gs","$_expr_input_path_i$for_content":"ws","$_if$for_content":"xs","$_open$for_content_effect":"Bs","$_open$for_content":"Ds","$_id$for_content":"Es","$_i$for_content":"Hs","$_comment_comments$for_content":"Xs","$_comment_text$for_content":"qs","$_comment$for_content":"ys","$_params_2$for_content":"zs","$_input_path$for_content":"As","$pendingSignal":"Ht","$scopeIsConnected":"Jt","$sortScopeByDOMPosition":"Xt","$ownerStartNode":"Zt","$queueSource":"Kt","$pendingScopes":"en","$loopClosure":"tn","$conditionalClosure":"nn","$queueRender":"rn","$abort":"on","$destroyBranch":"ln","$removeAndDestroyBranch":"un","$createBranchScopeWithRenderer":"fn","$createBranchScopeWithTagNameOrRenderer":"an","$createBranch":"cn","$pendingRender":"sn","$runRenders":"dn","$comparePendingRenders":"hn","$doc":"gn","$pendingRenders":"pn","$getBranchId":"bn","$getBranchDepth":"vn","$initBranch":"yn","$emptyBranch":"mn","$getEmptyBranch":"kn","$insertBranchBefore":"Cn","$destroyNestedBranches":"wn","$removeChildNodes":"An","$classIdToBranch":"Sn"}}} \ No newline at end of file +{"vars":{"props":{"$empty":"e","$rest":"t","$attrTag":"n","$attrTags":"r","$attrTagIterator":"i","$forIn":"l","$forOf":"o","$forTo":"f","$isScheduled":"u","$port2":"a","$flushAndWaitFrame":"c","$triggerMacroTask":"s","$createScope":"d","$emptyScope":"h","$getEmptyScope":"g","$destroyScope":"p","$_destroyScope":"v","$onDestroy":"b","$removeAndDestroyScope":"m","$insertBefore":"y","$registeredValues":"k","$Render":"w","$isResuming":"C","$register":"A","$registerBoundSignal":"S","$init":"N","$registerSubscriber":"x","$nodeRef":"$","$MARK":"M","$CLEAN":"E","$DIRTY":"I","$state":"T","$value":"_","$accessorId":"O","$intersection":"B","$defaultGetOwnerScope":"V","$closure":"j","$dynamicClosure":"R","$childClosures":"q","$dynamicSubscribers":"D","$setTagVar":"P","$tagVarSignal":"W","$setTagVarChange":"L","$tagVarSignalChange":"z","$renderBodyClosures":"F","$tagIdsByGlobal":"U","$nextTagId":"G","$inChild":"J","$intersections":"X","$effect":"Z","$pendingSignals":"H","$pendingEffects":"K","$rendering":"Q","$queueEffect":"Y","$run":"ee","$prepareEffects":"te","$runEffects":"ne","$runSignals":"re","$resetAbortSignal":"ie","$getAbortSignal":"le","$stringifyClassObject":"oe","$NON_DIMENSIONAL":"fe","$stringifyStyleObject":"ue","$toDelimitedString":"ae","$isEventHandler":"ce","$getEventHandlerName":"se","$normalizeDynamicRenderer":"de","$elementHandlersByEvent":"he","$defaultDelegator":"ge","$on":"pe","$createDelegator":"ve","$handleDelegated":"be","$stripSpacesAndPunctuation":"me","$controllable_input_checked":"ye","$controllable_input_checked_effect":"ke","$controllable_input_checkedValue":"we","$controllable_input_checkedValue_effect":"Ce","$controllable_input_value":"Ae","$controllable_input_value_effect":"Se","$controllable_select_value":"Ne","$controllable_select_value_effect":"xe","$setSelectOptions":"$e","$controllable_detailsOrDialog_open":"Me","$controllable_detailsOrDialog_open_effect":"Ee","$inputType":"Ie","$setValueAndUpdateSelection":"Te","$setCheckboxValue":"_e","$delegateFormControl":"Oe","$formChangeHandlers":"Be","$syncControllable":"Ve","$onFormChange":"je","$onFormReset":"Re","$hasValueChanged":"qe","$hasCheckboxChanged":"De","$hasSelectChanged":"Pe","$hasFormElementChanged":"We","$normalizeStrProp":"Le","$normalizeBoolProp":"ze","$toValueProp":"Fe","$fallback":"Ue","$parser":"Ge","$parseHTML":"Je","$attr":"Xe","$setAttribute":"Ze","$classAttr":"He","$styleAttr":"Ke","$data":"Qe","$attrs":"Ye","$hasAttrAlias":"et","$partialAttrs":"tt","$attrsInternal":"nt","$attrsEvents":"rt","$html":"it","$props":"lt","$normalizeAttrValue":"ot","$lifecycle":"ft","$walker":"ut","$trimWalkString":"at","$walk":"ct","$walkInternal":"st","$createScopeWithRenderer":"dt","$createScopeWithTagNameOrRenderer":"ht","$initRenderer":"gt","$dynamicTagAttrs":"pt","$createRendererWithOwner":"vt","$createRenderer":"bt","$_clone":"mt","$conditional":"yt","$inConditionalScope":"kt","$conditionalOnlyChild":"wt","$setConditionalRendererOnlyChild":"Ct","$emptyMarkerMap":"At","$emptyMarkerArray":"St","$emptyMap":"Nt","$emptyArray":"xt","$loopOf":"$t","$loopIn":"Mt","$loopTo":"Et","$loop":"It","$inLoopScope":"Tt","$bySecondArg":"_t","$byFirstArg":"Ot","$isDifferentRenderer":"Bt","$classIdToScope":"Vt","$compat":"jt","$noop":"Rt","$createTemplate":"qt","$mount":"Dt","$parseHTMLOrSingleNode":"Wt","$marker":"Ft","$_clickCount_effect":"Qt","$_clickCount":"Yt","$_setup_":"zt","$_expr_comment_comments_id$ifBody":"ss","$_id$ifBody":"as","$_comment_comments$ifBody":"ts","$_ifBody":"ns","$_expr_input_path_i$forBody":"os","$_if$forBody":"cs","$_open$forBody_effect":"is","$_open$forBody":"ms","$_id$forBody":"ls","$_i$forBody":"us","$_comment_comments$forBody":"es","$_comment_text$forBody":"ds","$_comment$forBody":"rs","$_params_2$forBody":"bs","$_input_path$forBody":"ps","$_for":"hs","$_input_path_":"vs","$_input_comments_":"fs","$_input_$1":"js","$_input_":"ks","$_params__":"Ss","$noop2":"Pt","$textContent":"Lt","$normalizeString":"Ut","$contentClosures":"Gt","$_expr_comment_comments_id$if_content":"Zs","$_id$if_content":"$s","$_comment_comments$if_content":"_s","$_if_content":"gs","$_expr_input_path_i$for_content":"ws","$_if$for_content":"xs","$_open$for_content_effect":"Bs","$_open$for_content":"Ds","$_id$for_content":"Es","$_i$for_content":"Hs","$_comment_comments$for_content":"Xs","$_comment_text$for_content":"qs","$_comment$for_content":"ys","$_params_2$for_content":"zs","$_input_path$for_content":"As","$pendingSignal":"Ht","$scopeIsConnected":"Jt","$sortScopeByDOMPosition":"Xt","$ownerStartNode":"Zt","$queueSource":"Kt","$pendingScopes":"en","$loopClosure":"tn","$conditionalClosure":"nn","$queueRender":"rn","$abort":"on","$destroyBranch":"ln","$removeAndDestroyBranch":"un","$createBranchScopeWithRenderer":"fn","$createBranchScopeWithTagNameOrRenderer":"an","$createBranch":"cn","$pendingRender":"sn","$runRenders":"dn","$comparePendingRenders":"hn","$doc":"gn","$pendingRenders":"pn","$getBranchId":"bn","$getBranchDepth":"vn","$initBranch":"yn","$emptyBranch":"mn","$getEmptyBranch":"kn","$insertBranchBefore":"Cn","$destroyNestedBranches":"wn","$removeChildNodes":"An","$classIdToBranch":"Sn","$getParseKind":"Nn","$insertChildNodes":"xn","$toInsertNode":"$n","$parseCache":"Mn","$parseHTMLChild":"In","$parsers":"_n","$cloneCache":"En"}}} \ No newline at end of file diff --git a/packages/runtime-class/src/runtime/helpers/tags-compat/runtime-dom.js b/packages/runtime-class/src/runtime/helpers/tags-compat/runtime-dom.js index cff5cd4f55..ff64397a93 100644 --- a/packages/runtime-class/src/runtime/helpers/tags-compat/runtime-dom.js +++ b/packages/runtime-class/src/runtime/helpers/tags-compat/runtime-dom.js @@ -121,20 +121,13 @@ exports.p = function (domCompat) { }; } newRenderer = domCompat.createRenderer( - (scope) => { - if (skipAttrs) { - renderAndMorph(scope, rendererFromAnywhere, renderer, {}); - } - }, - () => { - const realFragment = document.createDocumentFragment(); - ___createFragmentNode(null, null, realFragment); - return realFragment; - }, - (scope, input) => { - if (domCompat.isOp(input)) return; - renderAndMorph(scope, rendererFromAnywhere, renderer, input); - }, + (scope) => + skipAttrs && + renderAndMorph(scope, rendererFromAnywhere, renderer, {}), + () => ___createFragmentNode().startNode, + (scope, input) => + domCompat.isOp(input) || + renderAndMorph(scope, rendererFromAnywhere, renderer, input), ); rendererCache.set(renderer, newRenderer); } diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/.name-cache.json b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/.name-cache.json new file mode 100644 index 0000000000..6df0d6a808 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/.name-cache.json @@ -0,0 +1,25 @@ +{ + "vars": { + "props": { + "$_$": "e", + "$init": "t", + "$_Child_content2": "r", + "$_Child_content": "i", + "$_input_value$Parent_content": "n", + "$_setup$Parent_content": "a", + "$_Parent_content": "c", + "$_expr_Parent_Child_effect": "o", + "$_expr_Parent_Child": "s", + "$_Parent_input": "m", + "$_dynamicTagName3": "d", + "$_Child_input2": "f", + "$_dynamicTagName2": "l", + "$_Child_input": "g", + "$_dynamicTagName": "u", + "$_Child_effect": "h", + "$_Child": "b", + "$_Parent_effect": "y", + "$_Parent": "A" + } + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr-sanitized.expected.md new file mode 100644 index 0000000000..52cc3776c7 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr-sanitized.expected.md @@ -0,0 +1,326 @@ +# Render `{"value":""}` + +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr.expected.md new file mode 100644 index 0000000000..b7c3c81ba2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/csr.expected.md @@ -0,0 +1,390 @@ +# Render `{"value":"
"}` + +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + +# Mutations +``` +INSERT div +UPDATE div/svg/a0[ns] null => "http://www.w3.org/2000/svg" +UPDATE div/svg/a1[ns] null => "http://www.w3.org/2000/svg" +UPDATE div/math/a0[ns] null => "http://www.w3.org/1998/Math/MathML" +UPDATE div/math/a1[ns] null => "http://www.w3.org/1998/Math/MathML" +UPDATE div/div/a[ns] null => "http://www.w3.org/1999/xhtml" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + +# Mutations +``` +INSERT div/svg1 +REMOVE div after div/math +INSERT #text +INSERT div/svg1/a +REMOVE #text after div/svg1/a +UPDATE div/svg1/a[ns] null => "http://www.w3.org/2000/svg" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + +# Mutations +``` +INSERT div/div +REMOVE svg after div/math +INSERT #text +INSERT div/div/a +REMOVE #text after div/div/a +UPDATE div/div/a[ns] null => "http://www.w3.org/1999/xhtml" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + +# Mutations +``` +INSERT div/svg1 +REMOVE div after div/math +INSERT #text +INSERT div/svg1/a +REMOVE #text after div/svg1/a +UPDATE div/svg1/a[ns] null => "http://www.w3.org/2000/svg" +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` + +# Mutations +``` +INSERT div/svg0/#text +REMOVE a after div/svg0/a +INSERT div/math/#text +REMOVE a after div/math/a +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + +# Mutations +``` +INSERT div/svg0/a1 +REMOVE #text after div/svg0/a0 +INSERT div/svg0/a1/#text +UPDATE div/svg0/a1[href] null => "#bar" +INSERT div/math/a1 +REMOVE #text after div/math/a0 +INSERT div/math/a1/#text +UPDATE div/math/a1[href] null => "#bar" +UPDATE div/svg0/a1[ns] null => "http://www.w3.org/2000/svg" +UPDATE div/math/a1[ns] null => "http://www.w3.org/1998/Math/MathML" +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` + +# Mutations +``` +INSERT div/svg0/#text +REMOVE a after div/svg0/a +INSERT div/math/#text +REMOVE a after div/math/a +``` \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.hydrate.js b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.hydrate.js new file mode 100644 index 0000000000..49ecf55890 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.hydrate.js @@ -0,0 +1,72 @@ +// size: 1030 (min) 429 (brotli) +const _Child_content2 = _$.register("a0", _$.createRendererWithOwner("Hi", "")), + _Child_content = _$.register("a1", _$.createRendererWithOwner("Hi", "")), + _input_value$Parent_content = _$.registerSubscriber( + "a2", + _$.dynamicClosure((_scope, input_value) => _$.html(_scope, input_value, 0)), + ), + _setup$Parent_content = (_scope) => { + _input_value$Parent_content._(_scope, _scope._[10]); + }, + _Parent_content = _$.register( + "a3", + _$.createRendererWithOwner(" ", " ", _setup$Parent_content), + ), + _expr_Parent_Child_effect = _$.effect( + "a4", + (_scope, { 11: Parent, 12: Child }) => { + for (const node of _scope[0].querySelectorAll("a")) + node.getAttribute("ns") !== node.namespaceURI && + node.setAttribute("ns", node.namespaceURI); + }, + ), + _expr_Parent_Child = _$.intersection(2, (_scope) => { + _expr_Parent_Child_effect(_scope); + }), + _Parent_input = _$.dynamicTagAttrs(5, _Parent_content), + _dynamicTagName3 = _$.conditional( + 5, + (_scope) => _Parent_input(_scope, () => ({})), + () => _Parent_input, + ), + _Child_input2 = _$.dynamicTagAttrs(4, _Child_content2), + _dynamicTagName2 = _$.conditional( + 4, + (_scope) => _Child_input2(_scope, () => ({ href: "#bar" })), + () => _Child_input2, + ), + _Child_input = _$.dynamicTagAttrs(2, _Child_content), + _dynamicTagName = _$.conditional( + 2, + (_scope) => _Child_input(_scope, () => ({ href: "#bar" })), + () => _Child_input, + ), + _Child_effect = _$.effect("a5", (_scope, { 12: Child }) => + _$.on(_scope[7], "click", function () { + _Child(_scope, "a" === Child ? null : "a"); + }), + ), + _Child = _$.state( + 12, + (_scope, Child) => { + _Child_effect(_scope), + _dynamicTagName(_scope, Child || _Child_content(_scope)), + _dynamicTagName2(_scope, Child || _Child_content2(_scope)); + }, + () => + _$.intersections([_expr_Parent_Child, _dynamicTagName, _dynamicTagName2]), + ), + _Parent_effect = _$.effect("a6", (_scope, { 11: Parent }) => + _$.on(_scope[6], "click", function () { + _Parent(_scope, "div" === Parent ? "svg" : "div"); + }), + ), + _Parent = _$.state( + 11, + (_scope, Parent) => { + _Parent_effect(_scope), + _dynamicTagName3(_scope, Parent || _Parent_content(_scope)); + }, + () => _$.intersections([_expr_Parent_Child, _dynamicTagName3]), + ); +init(); diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.js new file mode 100644 index 0000000000..2f90aea6ec --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/dom.expected/template.js @@ -0,0 +1,70 @@ +export const _template_ = "
"; +export const _walks_ = /* get, next(2), replace, over(1), replace, out(1), next(1), replace, over(1), replace, out(1), replace, over(1), get, over(1), get, out(1) */" E%b%lD%b%l%b b l"; +import * as _$ from "@marko/runtime-tags/debug/dom"; +const _Child_content2 = _$.register("__tests__/template.marko_3_renderer", /* @__PURE__ */_$.createRendererWithOwner("Hi", "")); +const _Child_content = _$.register("__tests__/template.marko_2_renderer", /* @__PURE__ */_$.createRendererWithOwner("Hi", "")); +const _input_value$Parent_content = _$.registerSubscriber("__tests__/template.marko_1_input_value/subscriber", /* @__PURE__ */_$.dynamicClosure((_scope, input_value) => _$.html(_scope, input_value, "#text/0"))); +const _setup$Parent_content = _scope => { + _input_value$Parent_content._(_scope, _scope._["input_value"]); +}; +const _Parent_content = _$.register("__tests__/template.marko_1_renderer", /* @__PURE__ */_$.createRendererWithOwner(" ", /* get */" ", _setup$Parent_content)); +const _expr_Parent_Child_effect = _$.effect("__tests__/template.marko_0_Parent_Child", (_scope, { + Parent, + Child +}) => { + Parent; + Child; + for (const node of _scope["#div/0"].querySelectorAll("a")) { + if (node.getAttribute("ns") !== node.namespaceURI) { + node.setAttribute("ns", node.namespaceURI); + } + } +}); +const _expr_Parent_Child = /* @__PURE__ */_$.intersection(2, _scope => { + const { + Parent, + Child + } = _scope; + _expr_Parent_Child_effect(_scope); +}); +const _Parent_input = _$.dynamicTagAttrs("#text/5", _Parent_content); +const _dynamicTagName3 = /* @__PURE__ */_$.conditional("#text/5", _scope => _Parent_input(_scope, () => ({})), () => _Parent_input); +const _Child_input2 = _$.dynamicTagAttrs("#text/4", _Child_content2); +const _dynamicTagName2 = /* @__PURE__ */_$.conditional("#text/4", _scope => _Child_input2(_scope, () => ({ + href: "#bar" +})), () => _Child_input2); +const _Child_input = _$.dynamicTagAttrs("#text/2", _Child_content); +const _dynamicTagName = /* @__PURE__ */_$.conditional("#text/2", _scope => _Child_input(_scope, () => ({ + href: "#bar" +})), () => _Child_input); +const _Child_effect = _$.effect("__tests__/template.marko_0_Child", (_scope, { + Child +}) => _$.on(_scope["#button/7"], "click", function () { + _Child(_scope, Child === "a" ? null : "a"); +})); +const _Child = /* @__PURE__ */_$.state("Child", (_scope, Child) => { + _Child_effect(_scope); + _dynamicTagName(_scope, Child || _Child_content(_scope)); + _dynamicTagName2(_scope, Child || _Child_content2(_scope)); +}, () => _$.intersections([_expr_Parent_Child, _dynamicTagName, _dynamicTagName2])); +const _Parent_effect = _$.effect("__tests__/template.marko_0_Parent", (_scope, { + Parent +}) => _$.on(_scope["#button/6"], "click", function () { + _Parent(_scope, Parent === "div" ? "svg" : "div"); +})); +const _Parent = /* @__PURE__ */_$.state("Parent", (_scope, Parent) => { + _Parent_effect(_scope); + _dynamicTagName3(_scope, Parent || _Parent_content(_scope)); +}, () => _$.intersections([_expr_Parent_Child, _dynamicTagName3])); +export const _input_value_ = /* @__PURE__ */_$.value("input_value", (_scope, input_value) => { + _$.html(_scope, input_value, "#text/1"); + _$.html(_scope, input_value, "#text/3"); + _input_value$Parent_content(_scope, input_value); +}); +export const _input_ = /* @__PURE__ */_$.value("input", (_scope, input) => _input_value_(_scope, input.value)); +export const _params__ = /* @__PURE__ */_$.value("_params_", (_scope, _params_) => _input_(_scope, _params_[0])); +export function _setup_(_scope) { + _Parent(_scope, "div"); + _Child(_scope, "a"); +} +export default /* @__PURE__ */_$.createTemplate("__tests__/template.marko", _template_, _walks_, _setup_, () => _params__); \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/html.expected/template.js new file mode 100644 index 0000000000..74e09bafa2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/html.expected/template.js @@ -0,0 +1,54 @@ +import * as _$ from "@marko/runtime-tags/debug/html"; +const _renderer = /* @__PURE__ */_$.createRenderer((input, _tagVar) => { + const _scope0_id = _$.nextScopeId(); + const Parent = "div"; + const Child = "a"; + const el = _$.nodeRef(); + _$.write(`
${_$.toString(input.value)}${_$.markResumeNode(_scope0_id, "#text/1")}`); + const _dynamicScope = _$.peekNextScope(); + _$.dynamicTagInput(_scope0_id, "#text/2", Child, { + href: "#bar" + }, _$.register(/* @__PURE__ */_$.createRenderer(() => { + const _scope2_id = _$.nextScopeId(); + _$.write("Hi"); + }), "__tests__/template.marko_2_renderer", _scope0_id)); + _$.write(`${_$.toString(input.value)}${_$.markResumeNode(_scope0_id, "#text/3")}`); + const _dynamicScope2 = _$.peekNextScope(); + _$.dynamicTagInput(_scope0_id, "#text/4", Child, { + href: "#bar" + }, _$.register(/* @__PURE__ */_$.createRenderer(() => { + const _scope3_id = _$.nextScopeId(); + _$.write("Hi"); + }), "__tests__/template.marko_3_renderer", _scope0_id)); + _$.write(""); + const _dynamicScope3 = _$.peekNextScope(); + _$.dynamicTagInput(_scope0_id, "#text/5", Parent, {}, _$.register(/* @__PURE__ */_$.createRenderer(() => { + const _scope1_id = _$.nextScopeId(); + _$.write(`${_$.toString(input.value)}${_$.markResumeNode(_scope1_id, "#text/0")}`); + _$.writeEffect(_scope1_id, "__tests__/template.marko_1_input_value/subscriber"); + _$.debug(_$.writeScope(_scope1_id, { + "_": _$.ensureScopeWithId(_scope0_id) + }), "__tests__/template.marko", "12:3"); + _$.resumeClosestBranch(_scope1_id); + }), "__tests__/template.marko_1_renderer", _scope0_id)); + _$.write(`${_$.markResumeNode(_scope0_id, "#button/6")}${_$.markResumeNode(_scope0_id, "#button/7")}
${_$.markResumeNode(_scope0_id, "#div/0")}`); + _$.writeEffect(_scope0_id, "__tests__/template.marko_0_Parent_Child"); + _$.writeEffect(_scope0_id, "__tests__/template.marko_0_Child"); + _$.writeEffect(_scope0_id, "__tests__/template.marko_0_Parent"); + _$.debug(_$.writeScope(_scope0_id, { + "input_value": input.value, + "Parent": Parent, + "Child": Child, + "#text/2!": _$.writeExistingScope(_dynamicScope), + "#text/2(": _$.normalizeDynamicRenderer(Child), + "#text/4!": _$.writeExistingScope(_dynamicScope2), + "#text/4(": _$.normalizeDynamicRenderer(Child), + "#text/5!": _$.writeExistingScope(_dynamicScope3), + "#text/5(": _$.normalizeDynamicRenderer(Parent) + }), "__tests__/template.marko", 0, { + "Parent": "1:5", + "Child": "2:5" + }); + _$.resumeClosestBranch(_scope0_id); +}); +export default /* @__PURE__ */_$.createTemplate("__tests__/template.marko", _renderer); \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume-sanitized.expected.md new file mode 100644 index 0000000000..52cc3776c7 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume-sanitized.expected.md @@ -0,0 +1,326 @@ +# Render `{"value":"
"}` + +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` + + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + + Hi + + + + + + Hi + + + + + + + +
+``` + + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html +
+ + + Hi + + + + Hi + + + + + + +
+``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume.expected.md new file mode 100644 index 0000000000..04814f03ce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/resume.expected.md @@ -0,0 +1,503 @@ +# Render `{"value":"
"}` + +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + +
+ + + +
+ + + + + +
+ + + + +``` + +# Mutations +``` +UPDATE html/body/div/svg/a0[ns] null => "http://www.w3.org/2000/svg" +UPDATE html/body/div/svg/a1[ns] null => "http://www.w3.org/2000/svg" +UPDATE html/body/div/math/a0[ns] null => "http://www.w3.org/1998/Math/MathML" +UPDATE html/body/div/math/a1[ns] null => "http://www.w3.org/1998/Math/MathML" +UPDATE html/body/div/div/a[ns] null => "http://www.w3.org/1999/xhtml" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + + + + + + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/svg1 +REMOVE div after html/body/div/math +INSERT #text +INSERT html/body/div/svg1/a +REMOVE #text after html/body/div/svg1/a +UPDATE html/body/div/svg1/a[ns] null => "http://www.w3.org/2000/svg" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + +
+ +
+ + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/div +REMOVE svg after html/body/div/math +INSERT #text +INSERT html/body/div/div/a +REMOVE #text after html/body/div/div/a +UPDATE html/body/div/div/a[ns] null => "http://www.w3.org/1999/xhtml" +``` + +# Render +```js +container.querySelector(".toggle-parent").click(); +``` +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + + + + + + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/svg1 +REMOVE div after html/body/div/math +INSERT #text +INSERT html/body/div/svg1/a +REMOVE #text after html/body/div/svg1/a +UPDATE html/body/div/svg1/a[ns] null => "http://www.w3.org/2000/svg" +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html + + + +
+ + + + Hi + + + + + + Hi + + + + + + + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/svg0/#text +REMOVE a after html/body/div/svg0/#comment0 +INSERT html/body/div/math/#text +REMOVE a after html/body/div/math/#comment0 +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + + + + + + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/svg0/a1 +REMOVE #text after html/body/div/svg0/#comment0 +INSERT html/body/div/svg0/a1/#text +UPDATE html/body/div/svg0/a1[href] null => "#bar" +INSERT html/body/div/math/a1 +REMOVE #text after html/body/div/math/#comment0 +INSERT html/body/div/math/a1/#text +UPDATE html/body/div/math/a1[href] null => "#bar" +UPDATE html/body/div/svg0/a1[ns] null => "http://www.w3.org/2000/svg" +UPDATE html/body/div/math/a1[ns] null => "http://www.w3.org/1998/Math/MathML" +``` + +# Render +```js +container.querySelector(".toggle-child").click(); +``` +```html + + + +
+ + + + Hi + + + + + + Hi + + + + + + + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html/body/div/svg0/#text +REMOVE a after html/body/div/svg0/#comment0 +INSERT html/body/div/math/#text +REMOVE a after html/body/div/math/#comment0 +``` \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr-sanitized.expected.md new file mode 100644 index 0000000000..4f2546fc1a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr-sanitized.expected.md @@ -0,0 +1,40 @@ +# Render End +```html +
+ + + + Hi + + + + + + Hi + + +
+ +
+ + +
+``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr.expected.md new file mode 100644 index 0000000000..600e3d7a1f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/__snapshots__/ssr.expected.md @@ -0,0 +1,97 @@ +# Write +```html +
HiHi
+``` + +# Render End +```html + + + +
+ + + + + Hi + + + + + + + + Hi + + + +
+ + + +
+ + + + + +
+ + + + +``` + +# Mutations +``` +INSERT html +INSERT html/head +INSERT html/body +INSERT html/body/div +INSERT html/body/div/svg +INSERT html/body/div/svg/a0 +INSERT html/body/div/svg/#comment0 +INSERT html/body/div/svg/a1 +INSERT html/body/div/svg/a1/#text +INSERT html/body/div/svg/#comment1 +INSERT html/body/div/math +INSERT html/body/div/math/a0 +INSERT html/body/div/math/#comment0 +INSERT html/body/div/math/a1 +INSERT html/body/div/math/a1/#text +INSERT html/body/div/math/#comment1 +INSERT html/body/div/div +INSERT html/body/div/div/a +INSERT html/body/div/div/#comment0 +INSERT html/body/div/div/#comment1 +INSERT html/body/div/#comment0 +INSERT html/body/div/button0 +INSERT html/body/div/button0/#text +INSERT html/body/div/#comment1 +INSERT html/body/div/button1 +INSERT html/body/div/button1/#text +INSERT html/body/div/#comment2 +INSERT html/body/#comment +INSERT html/body/script +INSERT html/body/script/#text +``` \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/template.marko b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/template.marko new file mode 100644 index 0000000000..403a5ebd68 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/template.marko @@ -0,0 +1,30 @@ +let/Parent = "div" +let/Child = "a" +div/el + svg + -- $!{input.value} + Child href="#bar" -- Hi + + math + -- $!{input.value} + Child href="#bar" -- Hi + + Parent -- + $!{input.value} + + button.toggle-parent onClick() { + Parent = Parent === "div" ? "svg" : "div"; + } -- Toggle Parent + + button.toggle-child onClick() { + Child = Child === "a" ? null : "a"; + } -- Toggle Child + +script -- + Parent; + Child; + for (const node of el().querySelectorAll("a")) { + if (node.getAttribute("ns") !== node.namespaceURI) { + node.setAttribute("ns", node.namespaceURI!); + } + } diff --git a/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/test.ts b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/test.ts new file mode 100644 index 0000000000..e3821e93a4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/namespaced-tags/test.ts @@ -0,0 +1,19 @@ +export const steps = [ + { + value: "", + }, + clickParent, + clickParent, + clickParent, + clickChild, + clickChild, + clickChild, +]; + +function clickParent(container: Element) { + (container.querySelector(".toggle-parent") as HTMLButtonElement).click(); +} + +function clickChild(container: Element) { + (container.querySelector(".toggle-child") as HTMLButtonElement).click(); +} diff --git a/packages/runtime-tags/src/dom/compat.ts b/packages/runtime-tags/src/dom/compat.ts index def2302346..ceaf9ba071 100644 --- a/packages/runtime-tags/src/dom/compat.ts +++ b/packages/runtime-tags/src/dom/compat.ts @@ -4,12 +4,12 @@ import { SET_SCOPE_REGISTER_ID, } from "../common/compat-meta"; import type { BranchScope } from "../common/types"; -import { createScope } from "../dom"; import { patchConditionals } from "./control-flow"; +import { toInsertNode } from "./dom"; import { prepareEffects, queueEffect, runEffects } from "./queue"; import { createRenderer, initBranch, type Renderer } from "./renderer"; import { getRegisteredWithScope, register } from "./resume"; -import { destroyBranch } from "./scope"; +import { createScope, destroyBranch } from "./scope"; import { CLEAN, DIRTY, MARK } from "./signals"; const classIdToBranch = new Map(); @@ -101,7 +101,7 @@ export const compat = { // TODO: this should be createBranch branch = component.scope = createScope(out.global) as BranchScope; branch._ = renderer.___owner; - initBranch(renderer, branch); + initBranch(renderer, branch, document.body); } else { applyArgs(branch, MARK); existing = true; @@ -110,9 +110,7 @@ export const compat = { }); if (!existing) { - return branch.___startNode === branch.___endNode - ? branch.___startNode - : branch.___startNode.parentNode; + return toInsertNode(branch.___startNode, branch.___endNode); } }, }; diff --git a/packages/runtime-tags/src/dom/control-flow.ts b/packages/runtime-tags/src/dom/control-flow.ts index 46317f8afe..8f71536a55 100644 --- a/packages/runtime-tags/src/dom/control-flow.ts +++ b/packages/runtime-tags/src/dom/control-flow.ts @@ -4,6 +4,7 @@ import { type Accessor, AccessorChar, type BranchScope, + NodeType, type Scope, } from "../common/types"; import { reconcile } from "./reconcile"; @@ -73,7 +74,12 @@ export function setConditionalRenderer( (scope[nodeAccessor + AccessorChar.ConditionalScope] as BranchScope) || getEmptyBranch(scope[nodeAccessor] as Comment); const newBranch = newRenderer - ? createBranchScopeWithTagNameOrRenderer(newRenderer, scope.$global, scope) + ? createBranchScopeWithTagNameOrRenderer( + newRenderer, + scope.$global, + scope, + prevBranch.___endNode.parentNode!, + ) : (getEmptyBranch(scope[nodeAccessor] as Comment) as BranchScope); insertBranchBefore( newBranch, @@ -134,7 +140,12 @@ export function setConditionalRendererOnlyChild( ] as BranchScope; const referenceNode = scope[nodeAccessor] as Element; const newBranch = newRenderer - ? createBranchScopeWithTagNameOrRenderer(newRenderer, scope.$global, scope) + ? createBranchScopeWithTagNameOrRenderer( + newRenderer, + scope.$global, + scope, + referenceNode, + ) : undefined; referenceNode.textContent = ""; @@ -214,10 +225,7 @@ function loop( } const referenceNode = scope[nodeAccessor] as Element | Comment | Text; - // TODO: compiler should use only comment so the text check can be removed - const referenceIsMarker = - referenceNode.nodeType === 8 /* Comment */ || - referenceNode.nodeType === 3; /* Text */ + const referenceIsMarker = referenceNode.nodeType > NodeType.Element; const oldMap = (scope[nodeAccessor + AccessorChar.LoopScopeMap] as Map< unknown, @@ -226,16 +234,25 @@ function loop( const oldArray = (scope[nodeAccessor + AccessorChar.LoopScopeArray] as BranchScope[]) || Array.from(oldMap.values()); + const parentNode = ( + referenceIsMarker + ? referenceNode.parentNode || oldArray[0].___startNode.parentNode + : referenceNode + ) as Element; let newMap!: Map; let newArray!: BranchScope[]; let afterReference: Node | null; - let parentNode: ParentNode; let needsReconciliation = true; forEach(valueOrOp, (key, args) => { let branch = oldMap.get(key); if (!branch) { - branch = createBranchScopeWithRenderer(renderer, scope.$global, scope); + branch = createBranchScopeWithRenderer( + renderer, + scope.$global, + scope, + parentNode, + ); // TODO: once we can track moves // needsReconciliation = true; } else { @@ -277,10 +294,8 @@ function loop( } const oldLastChild = oldArray[oldArray.length - 1]; afterReference = oldLastChild.___endNode.nextSibling; - parentNode = oldLastChild.___startNode.parentNode!; } else { afterReference = null; - parentNode = referenceNode as Element; } reconcile(parentNode, oldArray, newArray!, afterReference); } diff --git a/packages/runtime-tags/src/dom/dom.ts b/packages/runtime-tags/src/dom/dom.ts index f6c9df15e2..ff71144bd3 100644 --- a/packages/runtime-tags/src/dom/dom.ts +++ b/packages/runtime-tags/src/dom/dom.ts @@ -8,7 +8,6 @@ import { type Accessor, AccessorChar, ControlledType, - NodeType, type Scope, } from "../common/types"; import { getAbortSignal } from "./abort-signal"; @@ -28,10 +27,6 @@ import { import { on } from "./event"; import { parseHTML } from "./parse-html"; -export function isDocumentFragment(node: Node): node is DocumentFragment { - return node.nodeType === NodeType.DocumentFragment; -} - export function attr(element: Element, name: string, value: unknown) { setAttribute(element, name, normalizeAttrValue(value)); } @@ -261,17 +256,22 @@ export function attrsEvents(scope: Scope, nodeAccessor: Accessor) { export function html(scope: Scope, value: unknown, accessor: Accessor) { const firstChild = scope[accessor] as ChildNode; + const parentNode = firstChild.parentNode!; const lastChild = (scope[ accessor + AccessorChar.DynamicPlaceholderLastChild ] || firstChild) as ChildNode; const newContent = parseHTML( - value || value === 0 ? value + "" : "", // TODO: is the comment needed + value || value === 0 ? value + "" : "", + (parentNode as Element).namespaceURI!, ); - scope[accessor] = newContent.firstChild; - scope[accessor + AccessorChar.DynamicPlaceholderLastChild] = - newContent.lastChild; - firstChild.parentNode!.insertBefore(newContent, firstChild); + insertChildNodes( + parentNode, + firstChild, + (scope[accessor] = newContent.firstChild), + (scope[accessor + AccessorChar.DynamicPlaceholderLastChild] = + newContent.lastChild), + ); removeChildNodes(firstChild, lastChild); } @@ -336,3 +336,26 @@ export function removeChildNodes(startNode: ChildNode, endNode: ChildNode) { current = next!; } } + +export function insertChildNodes( + parentNode: ParentNode, + referenceNode: Node | null, + startNode: Node, + endNode: Node, +) { + parentNode.insertBefore(toInsertNode(startNode, endNode), referenceNode); +} + +export function toInsertNode(startNode: Node, endNode: Node) { + if (startNode === endNode) return startNode; + const parent = new DocumentFragment(); + const stop = endNode.nextSibling; + let current = startNode; + while (current !== stop) { + const next = current.nextSibling; + parent.appendChild(current); + current = next!; + } + + return parent; +} diff --git a/packages/runtime-tags/src/dom/parse-html.ts b/packages/runtime-tags/src/dom/parse-html.ts index 7a22621be4..b9e7322c4b 100644 --- a/packages/runtime-tags/src/dom/parse-html.ts +++ b/packages/runtime-tags/src/dom/parse-html.ts @@ -1,27 +1,12 @@ -const parser = /* @__PURE__ */ document.createElement("template"); - -export function parseHTML(html: string) { - parser.innerHTML = html; - return parser.content; -} - -export function parseHTMLOrSingleNode(html: string) { - const content = parseHTML(html); - if (content.firstChild) { - if ( - content.firstChild === content.lastChild && - // If the firstChild is a comment it's possible its - // a single replaced node, in which case the walker can't replace - // the node itself. - content.firstChild.nodeType !== 8 /* Node.COMMENT_NODE */ - ) { - return content.firstChild; - } - - const fragment = new DocumentFragment(); - fragment.appendChild(content); - return fragment; - } - - return new Text(); +const parsers: Record = {}; +export function parseHTML(html: string, ns: string) { + const parser = (parsers[ns] ||= document.createElementNS(ns, "template")); + const content = + ((parser.innerHTML = html), + (parser as HTMLTemplateElement).content || parser); + if (!content.firstChild) content.appendChild(new Text()); + return content as (DocumentFragment | Element) & { + firstChild: ChildNode; + lastChild: ChildNode; + }; } diff --git a/packages/runtime-tags/src/dom/renderer.ts b/packages/runtime-tags/src/dom/renderer.ts index ce43dc316b..eaa036f075 100644 --- a/packages/runtime-tags/src/dom/renderer.ts +++ b/packages/runtime-tags/src/dom/renderer.ts @@ -6,8 +6,8 @@ import { type Scope, } from "../common/types"; import { setConditionalRendererOnlyChild } from "./control-flow"; -import { attrs } from "./dom"; -import { parseHTMLOrSingleNode } from "./parse-html"; +import { attrs, insertChildNodes } from "./dom"; +import { parseHTML } from "./parse-html"; import { queueRender } from "./queue"; import { createScope } from "./scope"; import { CLEAN, DIRTY, MARK, type Signal, type SignalOp } from "./signals"; @@ -18,7 +18,7 @@ export type Renderer = { ___template: string; ___walks: string; ___setup: SetupFn | undefined; - ___clone: () => Node; + ___clone: (ns: string) => ChildNode; ___sourceNode: Node | undefined; ___args: Signal | undefined; ___owner: Scope | undefined; @@ -30,6 +30,7 @@ export function createBranchScopeWithRenderer( renderer: Renderer, $global: Scope["$global"], parentScope: Scope, + parentNode: ParentNode, ) { const branch = createBranch( $global, @@ -39,7 +40,7 @@ export function createBranchScopeWithRenderer( if (MARKO_DEBUG) { branch.___renderer = renderer; } - initBranch(renderer, branch); + initBranch(renderer, branch, parentNode); return branch; } @@ -47,12 +48,14 @@ export function createBranchScopeWithTagNameOrRenderer( tagNameOrRenderer: Renderer | string, $global: Scope["$global"], parentScope: Scope, + parentNode: ParentNode, ) { if (typeof tagNameOrRenderer !== "string") { return createBranchScopeWithRenderer( tagNameOrRenderer, $global, parentScope, + parentNode, ); } @@ -60,7 +63,14 @@ export function createBranchScopeWithTagNameOrRenderer( branch[MARKO_DEBUG ? `#${tagNameOrRenderer}/0` : 0] = branch.___startNode = branch.___endNode = - document.createElement(tagNameOrRenderer); + document.createElementNS( + tagNameOrRenderer === "svg" + ? "http://www.w3.org/2000/svg" + : tagNameOrRenderer === "math" + ? "http://www.w3.org/1998/Math/MathML" + : (parentNode as Element).namespaceURI, + tagNameOrRenderer, + ); return branch; } @@ -85,25 +95,19 @@ function createBranch( return branch; } -export function initBranch(renderer: Renderer, branch: BranchScope) { - const dom = renderer.___clone(); - walk( - dom.nodeType === NodeType.DocumentFragment ? dom.firstChild! : dom, - renderer.___walks, - branch, - ); - branch.___startNode = - dom.nodeType === NodeType.DocumentFragment - ? dom.firstChild! - : (dom as ChildNode); - branch.___endNode = - dom.nodeType === NodeType.DocumentFragment - ? dom.lastChild! - : (dom as ChildNode); +export function initBranch( + renderer: Renderer, + branch: BranchScope, + parentNode: ParentNode, +) { + const clone = renderer.___clone((parentNode as Element).namespaceURI!); + const cloneParent = clone.parentNode; + walk(cloneParent?.firstChild || clone, renderer.___walks, branch); + branch.___startNode = cloneParent?.firstChild || (clone as ChildNode); + branch.___endNode = cloneParent?.lastChild || (clone as ChildNode); if (renderer.___setup) { queueRender(branch, renderer.___setup); } - return dom; } export function dynamicTagAttrs( @@ -194,8 +198,26 @@ export function createRenderer( return createRendererWithOwner(template, walks, setup, getArgs)(); } -function _clone(this: Renderer) { - return (this.___sourceNode ||= parseHTMLOrSingleNode( +function _clone(this: Renderer, ns: string) { + return ((cloneCache[ns] ||= {})[this.___template] ||= createCloneableHTML( this.___template, - )).cloneNode(true); + ns, + ))(); +} + +const cloneCache: Partial< + Record>> +> = {}; +function createCloneableHTML(html: string, ns: string) { + const { firstChild, lastChild } = parseHTML(html, ns); + const parent = document.createElementNS(ns, "t") as ParentNode & { + firstChild: ChildNode; + lastChild: ChildNode; + }; + insertChildNodes(parent, null, firstChild, lastChild); + return ( + firstChild === lastChild && firstChild!.nodeType < NodeType.Comment + ? () => firstChild.cloneNode(true) + : () => parent.cloneNode(true).firstChild + ) as () => ChildNode; } diff --git a/packages/runtime-tags/src/dom/scope.ts b/packages/runtime-tags/src/dom/scope.ts index 767891f9cc..e6253c4301 100644 --- a/packages/runtime-tags/src/dom/scope.ts +++ b/packages/runtime-tags/src/dom/scope.ts @@ -1,5 +1,5 @@ import type { BranchScope, Scope } from "../common/types"; -import { removeChildNodes } from "./dom"; +import { insertChildNodes, removeChildNodes } from "./dom"; let pendingScopes: Scope[] = []; let debugID = 0; @@ -58,11 +58,10 @@ export function insertBranchBefore( parentNode: ParentNode, nextSibling: Node | null, ) { - let current = branch.___startNode as Node; - const stop = branch.___endNode.nextSibling; - while (current !== stop) { - const next = current.nextSibling; - parentNode.insertBefore(current, nextSibling); - current = next!; - } + insertChildNodes( + parentNode, + nextSibling, + branch.___startNode, + branch.___endNode, + ); } diff --git a/packages/runtime-tags/src/dom/template.ts b/packages/runtime-tags/src/dom/template.ts index 598b50e733..b19fa3ea20 100644 --- a/packages/runtime-tags/src/dom/template.ts +++ b/packages/runtime-tags/src/dom/template.ts @@ -5,6 +5,7 @@ import type { TemplateInput, TemplateInstance, } from "../common/types"; +import { insertChildNodes } from "./dom"; import { prepareEffects, runEffects } from "./queue"; import { createRenderer, initBranch, type Renderer } from "./renderer"; import { register } from "./resume"; @@ -35,7 +36,7 @@ function mount( reference: Node, position?: InsertPosition, ): TemplateInstance { - let branch!: BranchScope, dom!: Node; + let branch!: BranchScope; let parentNode = reference as ParentNode; let nextSibling: Node | null = null; let { $global } = input; @@ -78,11 +79,16 @@ function mount( const args = this.___args; const effects = prepareEffects(() => { branch = createScope($global) as BranchScope; - dom = initBranch(this, branch); + initBranch(this, branch, parentNode); args?.(branch, [input]); }); - parentNode.insertBefore(dom, nextSibling); + insertChildNodes( + parentNode, + nextSibling, + branch.___startNode, + branch.___endNode, + ); runEffects(effects); return {