all repos — h3 @ a290f1d4e4ccf8a5fec59af4981205bd3ae5dfa3

A tiny, extremely minimalist JavaScript microframework.

Started re-implementing diffing algorithm
h3rald h3rald@h3rald.com
Sun, 09 Apr 2023 20:19:55 +0000
commit

a290f1d4e4ccf8a5fec59af4981205bd3ae5dfa3

parent

0c0e83cd257e58656aaabc4e95840e55a2e1ffe9

1 files changed, 55 insertions(+), 66 deletions(-)

jump to
M h3.jsh3.js

@@ -399,48 +399,36 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // Children - let childMap = mapChildren(oldvnode, newvnode); - let resultMap = [...Array(newvnode.children.length).keys()]; - while (!equal(childMap, resultMap)) { - let count = -1; - checkmap: for (const i of childMap) { - count++; - if (i === count) { - // Matching nodes; - continue; - } - switch (i) { - case PATCH: { - oldvnode.children[count].redraw({ - node: node.childNodes[count], - vnode: newvnode.children[count], - }); - break checkmap; - } - case INSERT: { - oldvnode.children.splice(count, 0, newvnode.children[count]); - const renderedNode = newvnode.children[count].render(); - node.insertBefore(renderedNode, node.childNodes[count]); - newvnode.children[count].$onrender && newvnode.children[count].$onrender(renderedNode); - break checkmap; - } - case DELETE: { - oldvnode.children.splice(count, 1); - node.removeChild(node.childNodes[count]); - break checkmap; - } - default: { - const vtarget = oldvnode.children.splice(i, 1)[0]; - oldvnode.children.splice(count, 0, vtarget); - const target = node.removeChild(node.childNodes[i]); - node.insertBefore(target, node.childNodes[count]); - break checkmap; - } - } + // Use the Levenshtein distance algorithm to compare the children of the + // two VNode objects + const distance = calculateLevenshteinDistance(this.children, newvnode.children); + + if (distance === 0) { + // If the distance is 0, the children are identical, so we don't need to + // do anything + return; + } + + // If the distance is greater than 0, we need to update the DOM + + // Remove nodes that are no longer present + for (let i = distance - 1; i < oldvnode.children.length; i++) { + node.removeChild(node.childNodes[i]); + } + + // Add new nodes that weren't present before + for (let i = distance - 1; i < newvnode.children.length; i++) { + //const refNode = i > 0 ? newvnode.children[i - 1].el.nextSibling : this.el.nextSibling; // <-- check! + const refNode = node.childNodes[i-1] + const newEl = node.children[i].render(); + node.children[i].$onrender && node.children[1].$onrender(); + node.insertBefore(newEl, refNode); + } + + // Update existing nodes + for (let i = distance - 1; i < Math.min(oldvnode.children.length, newvnode.children.length); i++) { + oldvnode.children[i].redraw(newvnode.children[i]); } - childMap = mapChildren(oldvnode, newvnode); - resultMap = [...Array(newvnode.children.length).keys()]; - } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) { oldvnode.$onrender = newvnode.$onrender;

@@ -454,33 +442,34 @@ }

} } -const mapChildren = (oldvnode, newvnode) => { - const newList = newvnode.children; - const oldList = oldvnode.children; - let map = []; - for (let nIdx = 0; nIdx < newList.length; nIdx++) { - let op = PATCH; - for (let oIdx = 0; oIdx < oldList.length; oIdx++) { - if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { - op = oIdx; // Same node found - break; - } - } - if (op < 0 && newList.length >= oldList.length && map.length >= oldList.length) { - op = INSERT; - } - map.push(op); +const calculateLevenshteinDistance = (a, b) => { + const m = a.length; + const n = b.length; + const dp = []; + + for (let i = 0; i <= m; i++) { + dp[i] = []; + dp[i][0] = i; } - const oldNodesFound = map.filter((c) => c >= 0); - if (oldList.length > newList.length) { - // Remove remaining nodes - [...Array(oldList.length - newList.length).keys()].forEach(() => map.push(DELETE)); - } else if (oldNodesFound.length === oldList.length) { - // All nodes not found are insertions - map = map.map((c) => (c < 0 ? INSERT : c)); + + for (let j = 0; j <= n; j++) { + dp[0][j] = j; } - return map; -}; + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + const cost = equal(a[i - 1], b[j - 1]) ? 0 : 1; + + dp[i][j] = Math.min( + dp[i - 1][j] + 1, // deletion + dp[i][j - 1] + 1, // insertion + dp[i - 1][j - 1] + cost // substitution + ); + } + } + + return dp[m][n]; +} /** * The code of the following class is heavily based on Storeon