UNPKG

choo-shortcache

Version:

choo nanocomponent cache shortcut

150 lines (133 loc) 4.09 kB
var assert = require('assert') var morph = require('./lib/morph') var TEXT_NODE = 3 // var DEBUG = false module.exports = nanomorph // Morph one tree into another tree // // no parent // -> same: diff and walk children // -> not same: replace and return // old node doesn't exist // -> insert new node // new node doesn't exist // -> delete old node // nodes are not the same // -> diff nodes and apply patch to old node // nodes are the same // -> walk all child nodes and append to old node function nanomorph (oldTree, newTree) { // if (DEBUG) { // console.log( // 'nanomorph\nold\n %s\nnew\n %s', // oldTree && oldTree.outerHTML, // newTree && newTree.outerHTML // ) // } assert.equal(typeof oldTree, 'object', 'nanomorph: oldTree should be an object') assert.equal(typeof newTree, 'object', 'nanomorph: newTree should be an object') var tree = walk(newTree, oldTree) // if (DEBUG) console.log('=> morphed\n %s', tree.outerHTML) return tree } // Walk and morph a dom tree function walk (newNode, oldNode) { // if (DEBUG) { // console.log( // 'walk\nold\n %s\nnew\n %s', // oldNode && oldNode.outerHTML, // newNode && newNode.outerHTML // ) // } if (!oldNode) { return newNode } else if (!newNode) { return null } else if (newNode.isSameNode && newNode.isSameNode(oldNode)) { return oldNode } else if (newNode.tagName !== oldNode.tagName) { return newNode } else { morph(newNode, oldNode) updateChildren(newNode, oldNode) return oldNode } } // Update the children of elements // (obj, obj) -> null function updateChildren (newNode, oldNode) { // if (DEBUG) { // console.log( // 'updateChildren\nold\n %s\nnew\n %s', // oldNode && oldNode.outerHTML, // newNode && newNode.outerHTML // ) // } var oldChild, newChild, morphed, oldMatch // The offset is only ever increased, and used for [i - offset] in the loop var offset = 0 for (var i = 0; ; i++) { oldChild = oldNode.childNodes[i] newChild = newNode.childNodes[i - offset] // if (DEBUG) { // console.log( // '===\n- old\n %s\n- new\n %s', // oldChild && oldChild.outerHTML, // newChild && newChild.outerHTML // ) // } // Both nodes are empty, do nothing if (!oldChild && !newChild) { break // There is no new child, remove old } else if (!newChild) { oldNode.removeChild(oldChild) i-- // There is no old child, add new } else if (!oldChild) { oldNode.appendChild(newChild) offset++ // Both nodes are the same, morph } else if (same(newChild, oldChild)) { morphed = walk(newChild, oldChild) if (morphed !== oldChild) { oldNode.replaceChild(morphed, oldChild) offset++ } // Both nodes do not share an ID or a placeholder, try reorder } else { oldMatch = null // Try and find a similar node somewhere in the tree for (var j = i; j < oldNode.childNodes.length; j++) { if (same(oldNode.childNodes[j], newChild)) { oldMatch = oldNode.childNodes[j] break } } // If there was a node with the same ID or placeholder in the old list if (oldMatch) { morphed = walk(newChild, oldMatch) if (morphed !== oldMatch) offset++ oldNode.insertBefore(morphed, oldChild) // It's safe to morph two nodes in-place if neither has an ID } else if (!newChild.id && !oldChild.id) { morphed = walk(newChild, oldChild) if (morphed !== oldChild) { oldNode.replaceChild(morphed, oldChild) offset++ } // Insert the node at the index if we couldn't morph or find a matching node } else { oldNode.insertBefore(newChild, oldChild) offset++ } } } } function same (a, b) { if (a.id) return a.id === b.id if (a.isSameNode) return a.isSameNode(b) if (a.tagName !== b.tagName) return false if (a.type === TEXT_NODE) return a.nodeValue === b.nodeValue return false }