diffHTML |

Easily diff HTML with tagged template strings and transitions
MIT License

About

diffHTML is a JavaScript view library well suited for architecting production components for the web. It is powered by an efficient object pooling system which provides stable memory consumption. Transition hooks are embedded into the rendering flow allowing them to be scheduled asynchronously. This makes them very useful for animating elements in and out of the DOM. The overall filesize is relatively small, but can be extended through lightweight middleware, modeled after connect.

This library was influenced and inspired by existing projects like: React, virtual-dom, bel, and yo-yo. Disclaimer: Before deciding for or against this tool, look into these libraries first. They have vibrant communities to help you when you inevitably get stuck or have a question. With diffHTML you'll have a much smaller community to draw support from.

Install using  

npm install diffhtml --save

Hello world example
diff.innerHTML(document.body, '<h1>Hello world!</h1>')

Features

While the surface area of diffHTML is minimal, it packs a ton:

File size

Pretty darn tiny once compressed at 9.9kb minified + gzip. If you are transpiling or not using HTML, you can omit the parser by requiring diffhtml/runtime instead, bringing the size down to 8.5kb!

  # Full build (includes parser)
   81K Jun 23 23:27 diffhtml.js
   43K Jun 23 23:28 diffhtml.min.js
  9.9K Jun 23 23:27 diffhtml.min.js.gz

  # Runtime build (excludes parser)
   71K Jun 23 23:28 diffhtml-runtime.js
   38K Jun 23 23:28 diffhtml-runtime.min.js
  8.5K Jun 23 23:27 diffhtml-runtime.min.js.gz

Browser compatibility

We aim for broad compatibility amongst devices and their various browsers. The output bundles are valid ES5, but may require the Babel polyfill to be loaded in unsupported browsers.

Supporting legacy browsers

diffHTML uses many modern browser features, such as Set, which are not available in all browsers. If you wish to use diffHTML in older browsers, make sure you have the Babel polyfill loaded first.

IE (9/10/11) Edge (13) Firefox (47) Chrome (51) Safari (9.1) Opera (38) iOS iPhone Safari (9.3) Android Chrome (50)

API

Changelog

1.0.0 (Unreleased)

Virtual trees

In diffHTML a Virtual Tree is a JavaScript object schema for describing existing DOM Nodes and Attributes found in an HTML document. A tree is generated every render cycle and compared to the previously calculated tree. Objects are recycled and pooled since they are continuously built up and discarded. These simple objects represent the bare amount of information necessary for diffHTML to properly patch the DOM.

The Virtual Element object is much lighter than its respective DOM Node, as it represents only: rawNodeName, nodeName, nodeValue, key, attributes, childNodes. Contrast this to the hundreds of properties and methods on a real DOM Node. Also note that Virtual Trees contain no circular references. Two attributes that may stand out to you, rawNodeName and key, are necessary for making optimizations and enforcing correctness during the synchronization stage. The first is used to reference the exact casing as written, as nodeName is always lowercased. The key is used for sibling elements when you want to have precise control over which elements are added, moved, and removed.

You can create Virtual Elements using the API method: createElement.

Example markup and respective Virtual Element JSON
diff.createElement('<div class="test">Hello world</div>');
{
  "rawNodeName": "DIV",
  "nodeName": "div",
  "nodeValue": "",
  "nodeType": 1,
  "key": "",
  "attributes": [{ "class": "test" }],
  "childNodes": [{
    "rawNodeName": "#text",
    "nodeName": "#text",
    "nodeValue": "Hello world",
    "nodeType": 3,
    "key": "",
    "attributes": [],
    "childNodes": []
  }]
}

As stated before, DOM Nodes aren't the only objects that are "virtualized", attributes are as well. This is due to how frequently they are used. Every attribute has a name and value, making them easy to reuse. The above example's attribute would be created using the API like so:

diff.createAttribute('class', 'test')
Example Virtual Attribute JSON, it looks exactly like above
{ "class": "test" }
You may wonder what the difference is between using the raw object and calling createAttribute. The main difference is that using createAttribute will pull from the object pooling system. This is much more efficient.

Object pooling

One of the most

Render transactions

Coming soon...

Tagged templates

JSX has shown that developers like to declaratively describe their UI in their component's source. For instance take a look at React's hello world:

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('example')
);
This shows markup being embedded directly into the component. Note this isn't exactly HTML per-se, but a similar flavor that leans closer to strict XHTML than HTML. It also deals purely with properties and not attributes. This is fairly far and away from standards and to use JSX effectively you'll need to align your tooling around it.

There is another way, however,

Transitions

Coming soon...

Middleware

Middleware is a minimalist way to gain access to the internals of the render transaction cycle. It was designed with inspiration from: Redux and connect. From Redux I saw an elegant way of returning functions to opt-in more into the render flow. I chose the use method name from connect. It is concise and familiar, simple, and

What makes this approach great is that it follows a common, existing, pattern. This puts it in harmony with other frontend libraries like: VueJS and choo.

Consuming & creating middleware

Middleware in diffHTML are just a collection of closures. They run in order and whenever a return value is found, it is assumed to be a function and is pushed into an array for the next render stage. It is recommended to use arrow functions, since they are significantly cleaner looking when nested.

An example middleware

const middleware = start => sync => patch => finish => {
  console.log('A render transaction completed');
};

Register middleware by invoking the use method.

import { use } from 'diffhtml';

const unsubscribe = diff.use(middleware);
Unsubscribe a middleware
unsubscribe();

The return value of invoking the use method is an unsubscribe function. When you invoke this, it will remove the middleware from being invoked during renders. It will also invoke the unsubscribe method on the middleware function object, if it exists.

An example of adding cleanup logic to our previous middleware:

const middleware = start => sync => patch => finish => {
  console.log('A render transaction completed');
};

// Attach a handler that is called whenever this middleware is removed from
// diffHTML.
middleware.unsubscribe = () => {
  console.log('This middleware was removed from diffHTML');
};

Logging

https://github.com/tbranyen/diffhtml-logger

A useful middleware for gaining insight into your renders. Provides relevant logs for the start and end of each render transaction.

Inline transition hooks

https://github.com/tbranyen/diffhtml-inline-transitions

This middleware makes it possible to treat diffHTML transition hooks like you would DOM Events when using the tagged template helper.


Babel transform

https://github.com/tbranyen/transform-tagged-diffhtml

This Babel transform optimizes your render tree by parsing the markdown at build time instead of runtime.

Browser prollyfill

The very first version of diffHTML was a method on the Element prototype `diffHTML`