domdiff
A vDOM-less implementation of the petit-dom diffing logic, at the core of hyperHTML.
V2 breaking change
- the good old snabdom diff logic has been 100% replaced
- lists with
null
orundefined
nodes are not allowed anymore
... but I guess having null nodes in the equation was quite possibly a bad idea in the first place ...
V2 Diffing Strategies:
- common prefixes
- common suffixes
- skip same lists
- add boundaries
- remove boundaries
- simple sub-sequences insertions and removals
- one to many and many to one replacements
- fast inverted list swap
- O(ND) algo with a limit of 50 attempts
- last fallback with a simplified Hunt Szymanski algorithm
The current goal is to have in about 1K the best DOM diffing library out there.
V1 breaking change
The signature has moved from parent, current[], future[], getNode(), beforeNode
to parent, current[], future[], {before, compare(), node()}
.
Signature
futureNodes = domdiff(
parentNode, // where changes happen
currentNodes, // Array of current items/nodes
futureNodes, // Array of future items/nodes (returned)
options // optional object with one of the following properties
// before: domNode
// compare(generic, generic) => true if same generic
// node(generic) => Node
);
How to import it:
- via CDN, as global variable:
https://unpkg.com/domdiff
- via ESM, as external module:
https://unpkg.com/domdiff/esm/index.js
- via CJS:
const EventTarget = require('domdiff').default;
( orrequire('domdiff/cjs').default
) - via bundlers/transpilers:
import domdiff from 'domdiff';
( orfrom 'domdiff/esm'
)
Example
var nodes = {
a: document.createTextNode('a'),
b: document.createTextNode('b'),
c: document.createTextNode('c')
};
var parentNode = document.createElement('p');
var childNodes = [nodes.a, nodes.c];
parentNode.append(...childNodes);
parentNode.textContent;
// "ac"
childNodes = domdiff(
parentNode,
childNodes,
[nodes.a, nodes.b, nodes.c]
);
parentNode.textContent;
// "abc"
Compatibility:
Every. JavaScript. Engine.
{node: (generic, info) => node}
callback for complex data
A The optional {node: (generic, info) => node}
is invoked per each operation on the DOM.
This can be useful to represent node through wrappers, whenever that is needed.
The passed info
value can be:
1
when the item/node is being appended0
when the item/node is being used as insert before reference-0
when the item/node is being used as insert after reference-1
when the item/node is being removed
function node(item, i) {
// case removal or case after
if ((1 / i) < 0) {
// case removal
if (i) {
// if the item has more than a node
// remove all other nodes at once
if (item.length > 1) {
const range = document.createRange();
range.setStartBefore(item[1]);
range.setEndAfter(item[item.length - 1]);
range.deleteContents();
}
// return the first node to be removed
return item[0];
}
// case after
else {
return item[item.length - 1];
}
}
// case insert
else if (i) {
const fragment = document.createDocumentFragment();
fragment.append(...item);
return fragment;
}
// case before
else {
return item[0];
}
}
const and = [document.createTextNode(' & ')];
const Bob = [
document.createTextNode('B'),
document.createTextNode('o'),
document.createTextNode('b')
];
const Lucy = [
document.createTextNode('L'),
document.createTextNode('u'),
document.createTextNode('c'),
document.createTextNode('y')
];
// clean the body for demo purpose
document.body.textContent = '';
let content = domdiff(
document.body,
[],
[Bob, and, Lucy],
{node}
);
// ... later on ...
content = domdiff(
document.body,
content,
[Lucy, and, Bob],
{node}
);
// clean up
domdiff(
document.body,
content,
[],
{node}
);