Precise DOM morphing
morphing typescript dom

Use an array for matches

+41 -31
+41 -31
src/morphlex.ts
··· 286 286 const candidateNodes: Set<ChildNode> = new Set() 287 287 const candidateElements: Set<Element> = new Set() 288 288 289 - const unmatchedNodes: Set<ChildNode> = new Set() 290 - const unmatchedElements: Set<Element> = new Set() 291 - 292 - const matches: Map<ChildNode, ChildNode> = new Map() 289 + const matches: Array<ChildNode | null> = new Array(toChildNodes.length).fill(null) 293 290 294 291 for (const candidate of fromChildNodes) { 295 292 if (isElement(candidate)) candidateElements.add(candidate) 296 293 else candidateNodes.add(candidate) 297 294 } 298 295 299 - for (const node of toChildNodes) { 300 - if (isElement(node)) unmatchedElements.add(node) 301 - else unmatchedNodes.add(node) 302 - } 296 + // Match elements by isEqualNode 297 + for (let i = 0; i < toChildNodes.length; i++) { 298 + const element = toChildNodes[i]! 299 + if (!isElement(element)) continue 303 300 304 - // Match elements by isEqualNode 305 - for (const element of unmatchedElements) { 306 301 for (const candidate of candidateElements) { 307 302 if (candidate.isEqualNode(element)) { 308 - matches.set(element, candidate) 309 - unmatchedElements.delete(element) 303 + matches[i] = candidate 310 304 candidateElements.delete(candidate) 311 305 break 312 306 } ··· 314 308 } 315 309 316 310 // Match by exact id 317 - for (const element of unmatchedElements) { 311 + for (let i = 0; i < toChildNodes.length; i++) { 312 + if (matches[i]) continue 313 + const element = toChildNodes[i]! 314 + if (!isElement(element)) continue 315 + 318 316 const id = element.id 319 317 if (id === "") continue 320 318 321 319 for (const candidate of candidateElements) { 322 320 if (element.localName === candidate.localName && id === candidate.id) { 323 - matches.set(element, candidate) 324 - unmatchedElements.delete(element) 321 + matches[i] = candidate 325 322 candidateElements.delete(candidate) 326 323 break 327 324 } ··· 329 326 } 330 327 331 328 // Match by idSet 332 - for (const element of unmatchedElements) { 329 + for (let i = 0; i < toChildNodes.length; i++) { 330 + if (matches[i]) continue 331 + const element = toChildNodes[i]! 333 332 if (!isElement(element)) continue 333 + 334 334 const idSet = this.idMap.get(element) 335 335 if (!idSet) continue 336 336 ··· 340 340 if (candidateIdSet) { 341 341 for (const id of idSet) { 342 342 if (candidateIdSet.has(id)) { 343 - matches.set(element, candidate) 344 - unmatchedElements.delete(element) 343 + matches[i] = candidate 345 344 candidateElements.delete(candidate) 346 345 break candidateLoop 347 346 } ··· 352 351 } 353 352 354 353 // Match by huristics 355 - for (const element of unmatchedElements) { 354 + for (let i = 0; i < toChildNodes.length; i++) { 355 + if (matches[i]) continue 356 + const element = toChildNodes[i]! 356 357 if (!isElement(element)) continue 358 + 357 359 const name = element.getAttribute("name") 358 360 const href = element.getAttribute("href") 359 361 const src = element.getAttribute("src") ··· 366 368 (href !== "" && href === candidate.getAttribute("href")) || 367 369 (src !== "" && src === candidate.getAttribute("src"))) 368 370 ) { 369 - matches.set(element, candidate) 370 - unmatchedElements.delete(element) 371 + matches[i] = candidate 371 372 candidateElements.delete(candidate) 372 373 break 373 374 } ··· 375 376 } 376 377 377 378 // Match by tagName 378 - for (const element of unmatchedElements) { 379 + for (let i = 0; i < toChildNodes.length; i++) { 380 + if (matches[i]) continue 381 + const element = toChildNodes[i]! 382 + if (!isElement(element)) continue 383 + 379 384 const localName = element.localName 380 385 381 386 for (const candidate of candidateElements) { ··· 384 389 // Treat inputs with different type as though they are different tags. 385 390 continue 386 391 } 387 - matches.set(element, candidate) 388 - unmatchedElements.delete(element) 392 + matches[i] = candidate 389 393 candidateElements.delete(candidate) 390 394 break 391 395 } ··· 393 397 } 394 398 395 399 // Match nodes by isEqualNode 396 - for (const node of unmatchedNodes) { 400 + for (let i = 0; i < toChildNodes.length; i++) { 401 + if (matches[i]) continue 402 + const node = toChildNodes[i]! 403 + if (isElement(node)) continue 404 + 397 405 for (const candidate of candidateNodes) { 398 406 if (candidate.isEqualNode(node)) { 399 - matches.set(node, candidate) 400 - unmatchedNodes.delete(node) 407 + matches[i] = candidate 401 408 candidateNodes.delete(candidate) 402 409 break 403 410 } ··· 405 412 } 406 413 407 414 // Match by nodeType 408 - for (const node of unmatchedNodes) { 415 + for (let i = 0; i < toChildNodes.length; i++) { 416 + if (matches[i]) continue 417 + const node = toChildNodes[i]! 418 + if (isElement(node)) continue 419 + 409 420 const nodeType = node.nodeType 410 421 411 422 for (const candidate of candidateNodes) { 412 423 if (nodeType === candidate.nodeType) { 413 - matches.set(node, candidate) 414 - unmatchedNodes.delete(node) 424 + matches[i] = candidate 415 425 candidateNodes.delete(candidate) 416 426 break 417 427 } ··· 422 432 let insertionPoint: ChildNode | null = parent.firstChild 423 433 for (let i = 0; i < toChildNodes.length; i++) { 424 434 const node = toChildNodes[i]! 425 - const match = matches.get(node) 435 + const match = matches[i] 426 436 if (match) { 427 437 moveBefore(parent, match, insertionPoint) 428 438 this.morphOneToOne(match, node)