Precise DOM morphing
morphing typescript dom

Performance improvements

+28 -9
+28 -9
src/morphlex.ts
··· 129 } 130 131 private morphOneToOne(from: ChildNode, to: ChildNode): void { 132 if (!(this.options.beforeNodeMorphed?.(from, to) ?? true)) return 133 134 const pair: PairOfNodes<ChildNode> = [from, to] ··· 167 } 168 169 private morphAttributes([from, to]: PairOfMatchingElements<Element>): void { 170 - // Remove any excess attributes from the element that aren’t present in the reference. 171 - for (const { name, value } of from.attributes) { 172 - if (!to.hasAttribute(name) && (this.options.beforeAttributeUpdated?.(from, name, null) ?? true)) { 173 - from.removeAttribute(name) 174 - this.options.afterAttributeUpdated?.(from, name, value) 175 - } 176 - } 177 178 - // Copy attributes from the reference to the element, if they don’t already match. 179 - for (const { name, value } of to.attributes) { 180 const oldValue = from.getAttribute(name) 181 if (oldValue !== value && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) { 182 from.setAttribute(name, value) 183 this.options.afterAttributeUpdated?.(from, name, oldValue) 184 } 185 } 186 } ··· 264 const toChildNode = toChildNodes[i]! 265 266 if (fromChildNode) { 267 if (isElement(toChildNode)) { 268 this.searchSiblingsToMorphChildElement(fromChildNode, toChildNode, from) 269 } else {
··· 129 } 130 131 private morphOneToOne(from: ChildNode, to: ChildNode): void { 132 + // Fast path: if nodes are exactly the same object, skip morphing 133 + if (from.isSameNode?.(to)) return 134 + 135 if (!(this.options.beforeNodeMorphed?.(from, to) ?? true)) return 136 137 const pair: PairOfNodes<ChildNode> = [from, to] ··· 170 } 171 172 private morphAttributes([from, to]: PairOfMatchingElements<Element>): void { 173 + const toAttrs = to.attributes 174 + const fromAttrs = from.attributes 175 176 + // First pass: update/add attributes from reference (iterate forwards) 177 + for (let i = 0; i < toAttrs.length; i++) { 178 + const attr = toAttrs[i]! 179 + const name = attr.name 180 + const value = attr.value 181 const oldValue = from.getAttribute(name) 182 + 183 if (oldValue !== value && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) { 184 from.setAttribute(name, value) 185 this.options.afterAttributeUpdated?.(from, name, oldValue) 186 + } 187 + } 188 + 189 + // Second pass: remove excess attributes (iterate backwards for efficiency) 190 + for (let i = fromAttrs.length - 1; i >= 0; i--) { 191 + const attr = fromAttrs[i]! 192 + const name = attr.name 193 + const value = attr.value 194 + 195 + if (!to.hasAttribute(name) && (this.options.beforeAttributeUpdated?.(from, name, null) ?? true)) { 196 + from.removeAttribute(name) 197 + this.options.afterAttributeUpdated?.(from, name, value) 198 } 199 } 200 } ··· 278 const toChildNode = toChildNodes[i]! 279 280 if (fromChildNode) { 281 + // Fast path: if nodes are exactly the same, skip morphing 282 + if (fromChildNode.isSameNode?.(toChildNode)) { 283 + continue 284 + } 285 + 286 if (isElement(toChildNode)) { 287 this.searchSiblingsToMorphChildElement(fromChildNode, toChildNode, from) 288 } else {