Skip to content

Library for efficiently diffing and patching virtual and real DOM fragments.

License

Notifications You must be signed in to change notification settings

skatejs/dom-diff

Repository files navigation

Dom Diff

Skate's DOM Diff is a virtual DOM library for diffing, patching and converting between virtual and real DOM trees.

Sauce Test Status

  • Serialise to and from read DOM trees
  • Diff virtual trees and patch real trees
  • Web worker support
npm install skatejs-dom-diff

Usage

Where options are accepted, you may provide:

  • done If specified, diffing is performed in a web worker and this callback is called when it's done.

diff(source, target, options)

Diffs two virtual trees.

/** @jsx h **/
import { diff, h } from 'skatejs-dom-diff';

const source = <div><span>source</span></div>;
const target = <div><span>target</span></div>;
const instructions = diff(source, target);

The patchInstructions is an array that can be passed to patch() to update the source tree. Before passing the instructions to patch(), however, your source tree must be associated to real DOM nodes. This can be done by using mount() or by converting them to a tree using toDom().

fragment([virtualNodeOrNodes])

Creates a virtual fragment. You can pass nothing to create an empty fragment:

import { fragment } from 'skatejs-dom-diff';

const vFrag = fragment();

A single virtual node:

import { fragment } from 'skatejs-dom-diff';

const vFrag = fragment(<div />);

An array of virtual nodes:

import { fragment } from 'skatejs-dom-diff';

const vFrag = fragment([<div />, <span />]);

Or even a virtual fragment:

import { fragment } from 'skatejs-dom-diff';

const vFrag = fragment(fragment(<div />));

h(name, props, ...childrenOrText)

Creates a virtual node.

// <div class="my-class">text or...<span /></div>
h('div', { className: 'my-class' }, 'text or...', h('span'));

Or you could just use JSX:

/** @jsx h **/
// <div class="my-class">text or...<span /></div>
<div className="my-class">text or...<span /></div>

Attributes

By default, h only sets properties, but you can specify attributes you want to set by passing the special attributes prop:

// <div class="my-class" />
<div attributes={{ class: 'my-class' }} />

Aria attributes

You can pass aria-* attributes using attributes but you can also specify the aria prop:

// <div aria-label="my label" />
<div aria={{ label: 'my label' }} />

Data attributes

Like the aria prop, you can also use the data prop:

// <div data-something="my data" />
<div data={{ something: 'my data' }} />

Events

Events are bound using the special events prop:

const click = e => doSomethingWith(e);
<div events={{ click }} />

merge()

The merge() function is convenience for calling diff() and patch() sequentially. As with diff(), you must ensure the source virtual tree has been associated to real nodes first.

/** @jsx h **/
import { diff, h } from 'skatejs-dom-diff';

const source = <div><span>source</span></div>;
const target = <div><span>target</span></div>;
const dom = mount(source);
merge(source, target);

mount(vdom[, root])

Mounts the vdom to the real root DOM node. It returns the root node. If the root node was not specified, it automatically creates a <div /> and returns it.

/** @jsx h **/
import { h, mount } from 'skatejs-dom-diff';

const div = mount(<p>some text</p>);

Is the same thing as:

/** @jsx h **/
import { h, mount } from 'skatejs-dom-diff';

const div = document.createElement('div');
mount(<p>some text</p>, div);

It's more than likely that you'll just mount it directly to the document:

/** @jsx h **/
import { h, mount } from 'skatejs-dom-diff';

mount(<p>some text</p>, document.getElementById('app'));

patch()

Takes instructiosn created using diff() and performs them on the associated DOM nodes that each instructions is for.

/** @jsx h **/
import { diff, h, mount, patch } from 'skatejs-dom-diff';

const source = <p>source tree</p>;
const target = <p>target tree</p>;
const instructions = diff(source, target);

mount(source, document.getElementById('app'));
patch(instructions);

render()

A highly convenient function for continually rendering a given template.

/** @jsx h **/
import { h, render } from 'skatejs-dom-diff';

const root = document.getElementById('app');
const renderer = render((root) => (
  <p>{root.someProp}</p>
));

// Set the prop to render with
root.someProp = 'test 1';

// Initial render: <p>test 1</p>
renderer(root);

// Update the prop
root.someProp = 'test 2';

// Re-render: <p>test 2</p>
renderer(root);

text()

Returns a virtual text node:

import { text } from 'skatejs-dom-diff';

const vText = text('my text node');

toDom()

Convers a virtual tree to a real DOM tree, event listeners and all:

import { toDom } from 'skatejs-dom-diff';

const vdom = <p>I will soon be real!</p>
const dom = toDom(vdom);

// <p>I will soon be real!</p>
console.log(dom.outerHTML);

toVdom()

Converts a real DOM tree into a virtual tree. It only copies over attributes. Event listeners can't be copied because the standard DOM APIs don't provide a way to get bound listeners.

Properties currently aren't copied either, but is being worked on.

import { toVdom } from 'skatejs-dom-diff';

const dom = document.createElement('p');
dom.textContent = 'I will soon be fake!';

const vdom = toVdom(dom);

types

The types of patches that can occur. Currently these are:

import { types } from 'skatejs-dom-diff';

const {
  APPEND_CHILD,
  REMOVE_CHILD,
  REMOVE_ATTRIBUTE,
  REPLACE_CHILD,
  SET_ATTRIBUTE,
  SET_EVENT,
  SET_PROPERTY,
  TEXT_CONTENT
} = types;

Web workers

You can tell the differ to do its work in a web worker simply by passing a done callback option to any of the three major entry functions (diff(), merge(), render()).

diff(source, target, options)

In the case of diff(), it's called once the diffing algorithm has finished in the worker and passed the instructions. The patch instructions are the only argument passed into the callback.

/** @jsx h */
import { h, diff } from 'skatejs-dom-diff';

function done (instructions) {
  patch(instructions);
}
diff(<p>source</p>, <p>target</p>, { done });

merge(source, target, options)

For done(), it's passed in the same exact way. The only difference is that it's called after the patch is performed but it's still passed the instructions that were performed by the patch algorithm.

/** @jsx h */
import { h, merge } from 'skatejs-dom-diff';

function done (instructions) {
  // The DOM has been updated, do what you want here.
}
merge(<p>source</p>, <p>target</p>, { done });

render(source, target, options)

And for render(), it is the same as the merge() function. So once the vDOM is rendered and DOM is patched, done() is called with the instructions that were performed.

import { h, render } from 'skatejs-dom-diff';

function done (instructions) {
  // Renering and patching is done...
}
const root = document.createElement('div');
const doRender = render((root) => (
  <div>{root.test}</div>
));

div.test = 'initial text';
doRender(div, done);

div.test = 'updated text';
doRender(div, done);

About

Library for efficiently diffing and patching virtual and real DOM fragments.

Resources

License

Stars

Watchers

Forks

Packages

No packages published