Precise DOM morphing
morphing typescript dom

Support merging `<head>`

+30 -4
+13 -3
dist/morphlex.js
··· 26 26 // This is where we actually morph the nodes. The `morph` function exists to set up the `idMap`. 27 27 function morphNodes(node, ref, idMap) { 28 28 if (isElement(node) && isElement(ref) && node.tagName === ref.tagName) { 29 - // We need to check if the element is an input, option, or textarea here, because they have 30 - // special attributes not covered by the isEqualNode check. 31 29 if (node.hasAttributes() || ref.hasAttributes()) morphAttributes(node, ref); 32 - if (node.hasChildNodes() || ref.hasChildNodes()) morphChildNodes(node, ref, idMap); 30 + if (isHead(node) && isHead(ref)) { 31 + const refChildNodes = new Map(); 32 + for (const child of ref.children) refChildNodes.set(child.outerHTML, child); 33 + for (const child of node.children) { 34 + const key = child.outerHTML; 35 + const refChild = refChildNodes.get(key); 36 + refChild ? refChildNodes.delete(key) : child.remove(); 37 + } 38 + for (const refChild of refChildNodes.values()) node.appendChild(refChild.cloneNode(true)); 39 + } else if (node.hasChildNodes() || ref.hasChildNodes()) morphChildNodes(node, ref, idMap); 33 40 } else { 34 41 if (isText(node) && isText(ref)) { 35 42 if (node.textContent !== ref.textContent) node.textContent = ref.textContent; ··· 124 131 } 125 132 function isTextArea(element) { 126 133 return element.localName === "textarea"; 134 + } 135 + function isHead(element) { 136 + return element.localName === "head"; 127 137 } 128 138 function isParentNode(node) { 129 139 return node.nodeType === 1 || node.nodeType === 9 || node.nodeType === 11;
+17 -1
src/morphlex.ts
··· 17 17 readonly hasAttribute: (name: string) => boolean; 18 18 readonly hasAttributes: () => boolean; 19 19 readonly hasChildNodes: () => boolean; 20 + readonly children: ReadOnlyNodeList<Element>; 20 21 }); 21 22 22 23 // Maps a node to a read-only node list of nodes of that type ··· 65 66 function morphNodes(node: ChildNode, ref: ReadOnlyNode<ChildNode>, idMap: IdMap): void { 66 67 if (isElement(node) && isElement(ref) && node.tagName === ref.tagName) { 67 68 if (node.hasAttributes() || ref.hasAttributes()) morphAttributes(node, ref); 68 - if (node.hasChildNodes() || ref.hasChildNodes()) morphChildNodes(node, ref, idMap); 69 + if (isHead(node) && isHead(ref)) { 70 + const refChildNodes: Map<string, ReadOnlyNode<Element>> = new Map(); 71 + for (const child of ref.children) refChildNodes.set(child.outerHTML, child); 72 + for (const child of node.children) { 73 + const key = child.outerHTML; 74 + const refChild = refChildNodes.get(key); 75 + refChild ? refChildNodes.delete(key) : child.remove(); 76 + } 77 + for (const refChild of refChildNodes.values()) node.appendChild(refChild.cloneNode(true)); 78 + } else if (node.hasChildNodes() || ref.hasChildNodes()) morphChildNodes(node, ref, idMap); 69 79 } else { 70 80 if (isText(node) && isText(ref)) { 71 81 if (node.textContent !== ref.textContent) node.textContent = ref.textContent; ··· 198 208 function isTextArea(element: ReadOnlyNode<Element>): element is ReadOnlyNode<HTMLTextAreaElement>; 199 209 function isTextArea(element: Element | ReadOnlyNode<Element>): boolean { 200 210 return element.localName === "textarea"; 211 + } 212 + 213 + function isHead(element: Element): element is HTMLHeadElement; 214 + function isHead(element: ReadOnlyNode<Element>): element is ReadOnlyNode<HTMLHeadElement>; 215 + function isHead(element: Element | ReadOnlyNode<Element>): boolean { 216 + return element.localName === "head"; 201 217 } 202 218 203 219 function isParentNode(node: Node): node is ParentNode;