Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/WebReflection/domdiff

Diffing the DOM without virtual DOM
https://github.com/WebReflection/domdiff

Last synced: about 2 months ago
JSON representation

Diffing the DOM without virtual DOM

Awesome Lists containing this project

README

        

# domdiff

[![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/WebReflection/donate) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/domdiff/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/domdiff?branch=master) [![Build Status](https://travis-ci.org/WebReflection/domdiff.svg?branch=master)](https://travis-ci.org/WebReflection/domdiff) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC)

A vDOM-less implementation of the [petit-dom](https://github.com/yelouafi/petit-dom) diffing logic, at the core of [hyperHTML](https://github.com/WebReflection/hyperHTML).

### V2 breaking change

* the good old snabdom diff logic has been 100% replaced
* lists with `null` or `undefined` 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

```js
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;` ( or `require('domdiff/cjs').default` )
* via bundlers/transpilers: `import domdiff from 'domdiff';` ( or `from 'domdiff/esm'` )

### Example

```js
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.

### A `{node: (generic, info) => node}` callback for complex data

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 appended
* `0` 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

[Example](https://codepen.io/WebReflection/pen/bYJVPd?editors=0010)

```js
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}
);

```