UNPKG

23.5 kBMarkdownView Raw
1# Snabbdom
2
3A virtual DOM library with focus on simplicity, modularity, powerful features
4and performance.
5
6[![Join the chat at https://gitter.im/paldepind/snabbdom](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/paldepind/snabbdom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
8## Table of contents
9
10* [Introduction](#introduction)
11* [Features](#features)
12* [Inline example](#inline-example)
13* [Examples](#examples)
14* [Core documentation](#core-documentation)
15* [Modules documentation](#modules-documentation)
16* [Helpers](#helpers)
17* [Virtual Node documentation](#virtual-node)
18* [Structuring applications](#structuring-applications)
19
20## Why
21
22Virtual DOM is awesome. It allows us to express our application's view
23as a function of its state. But existing solutions were way way too
24bloated, too slow, lacked features, had an API biased towards OOP
25and/or lacked features I needed.
26
27## Introduction
28
29Snabbdom consists of an extremely simple, performant and extensible
30core that is only ≈ 200 SLOC. It offers a modular architecture with
31rich functionality for extensions through custom modules. To keep the
32core simple, all non-essential functionality is delegated to modules.
33
34You can mold Snabbdom into whatever you desire! Pick, choose and
35customize the functionality you want. Alternatively you can just use
36the default extensions and get a virtual DOM library with high
37performance, small size and all the features listed below.
38
39## Features
40
41* Core features
42 * About 200 SLOC – you could easily read through the entire core and fully
43 understand how it works.
44 * Extendable through modules.
45 * A rich set of hooks available, both per vnode and globally for modules,
46 to hook into any part of the diff and patch process.
47 * Splendid performance. Snabbdom is among the fastest virtual DOM libraries
48 in the [Virtual DOM Benchmark](http://vdom-benchmark.github.io/vdom-benchmark/).
49 * Patch function with a function signature equivalent to a reduce/scan
50 function. Allows for easier integration with a FRP library.
51* Features in modules
52 * `h` function for easily creating virtual DOM nodes.
53 * [SVG _just works_ with the `h` helper](#svg).
54 * Features for doing complex CSS animations.
55 * Powerful event listener functionality.
56 * [Thunks](#thunks) to optimize the diff and patch process even further.
57 * JSX support thanks to [snabbdom-jsx](https://github.com/yelouafi/snabbdom-jsx).
58 There is also a Babel plugin [babel-snabbdom-jsx](https://github.com/finnsson/babel-snabbdom-jsx).
59 * Server-side HTML output provided by
60 [snabbdom-to-html](https://github.com/acstll/snabbdom-to-html).
61 * Compact virtual DOM creation with [snabbdom-helpers](https://github.com/krainboltgreene/snabbdom-helpers).
62 * Template string support using [snabby](https://github.com/jamen/snabby).
63
64## Inline example
65
66```javascript
67var snabbdom = require('snabbdom');
68var patch = snabbdom.init([ // Init patch function with chosen modules
69 require('snabbdom/modules/class').default, // makes it easy to toggle classes
70 require('snabbdom/modules/props').default, // for setting properties on DOM elements
71 require('snabbdom/modules/style').default, // handles styling on elements with support for animations
72 require('snabbdom/modules/eventlisteners').default, // attaches event listeners
73]);
74var h = require('snabbdom/h').default; // helper function for creating vnodes
75
76var container = document.getElementById('container');
77
78var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
79 h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
80 ' and this is just normal text',
81 h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
82]);
83// Patch into empty DOM element – this modifies the DOM as a side effect
84patch(container, vnode);
85
86var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
87 h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
88 ' and this is still just normal text',
89 h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
90]);
91// Second `patch` invocation
92patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
93```
94
95## Examples
96
97* [Animated reordering of elements](http://snabbdom.github.io/snabbdom/examples/reorder-animation/)
98* [Hero transitions](http://snabbdom.github.io/snabbdom/examples/hero/)
99* [SVG Carousel](http://snabbdom.github.io/snabbdom/examples/carousel-svg/)
100
101## Core documentation
102
103The core of Snabbdom provides only the most essential functionality.
104It is designed to be as simple as possible while still being fast and
105extendable.
106
107### `snabbdom.init`
108
109The core exposes only one single function `snabbdom.init`. This `init`
110takes a list of modules and returns a `patch` function that uses the
111specified set of modules.
112
113```javascript
114var patch = snabbdom.init([
115 require('snabbdom/modules/class').default,
116 require('snabbdom/modules/style').default,
117]);
118```
119
120### `patch`
121
122The `patch` function returned by `init` takes two arguments. The first
123is a DOM element or a vnode representing the current view. The second
124is a vnode representing the new, updated view.
125
126If a DOM element with a parent is passed, `newVnode` will be turned
127into a DOM node, and the passed element will be replaced by the
128created DOM node. If an old vnode is passed, Snabbdom will efficiently
129modify it to match the description in the new vnode.
130
131Any old vnode passed must be the resulting vnode from a previous call
132to `patch`. This is necessary since Snabbdom stores information in the
133vnode. This makes it possible to implement a simpler and more
134performant architecture. This also avoids the creation of a new old
135vnode tree.
136
137```javascript
138patch(oldVnode, newVnode);
139```
140
141### `snabbdom/h`
142
143It is recommended that you use `snabbdom/h` to create vnodes. `h` accepts a
144tag/selector as a string, an optional data object and an optional string or
145array of children.
146
147```javascript
148var h = require('snabbdom/h').default;
149var vnode = h('div', {style: {color: '#000'}}, [
150 h('h1', 'Headline'),
151 h('p', 'A paragraph'),
152]);
153```
154
155### Hooks
156
157Hooks are a way to hook into the lifecycle of DOM nodes. Snabbdom
158offers a rich selection of hooks. Hooks are used both by modules to
159extend Snabbdom, and in normal code for executing arbitrary code at
160desired points in the life of a virtual node.
161
162#### Overview
163
164| Name | Triggered when | Arguments to callback |
165| ----------- | -------------- | ----------------------- |
166| `pre` | the patch process begins | none |
167| `init` | a vnode has been added | `vnode` |
168| `create` | a DOM element has been created based on a vnode | `emptyVnode, vnode` |
169| `insert` | an element has been inserted into the DOM | `vnode` |
170| `prepatch` | an element is about to be patched | `oldVnode, vnode` |
171| `update` | an element is being updated | `oldVnode, vnode` |
172| `postpatch` | an element has been patched | `oldVnode, vnode` |
173| `destroy` | an element is directly or indirectly being removed | `vnode` |
174| `remove` | an element is directly being removed from the DOM | `vnode, removeCallback` |
175| `post` | the patch process is done | none |
176
177The following hooks are available for modules: `pre`, `create`,
178`update`, `destroy`, `remove`, `post`.
179
180The following hooks are available in the `hook` property of individual
181elements: `init`, `create`, `insert`, `prepatch`, `update`,
182`postpatch`, `destroy`, `remove`.
183
184#### Usage
185
186To use hooks, pass them as an object to `hook` field of the data
187object argument.
188
189```javascript
190h('div.row', {
191 key: movie.rank,
192 hook: {
193 insert: (vnode) => { movie.elmHeight = vnode.elm.offsetHeight; }
194 }
195});
196```
197
198#### The `init` hook
199
200This hook is invoked during the patch process when a new virtual node
201has been found. The hook is called before Snabbdom has processed the
202node in any way. I.e., before it has created a DOM node based on the
203vnode.
204
205#### The `insert` hook
206
207This hook is invoked once the DOM element for a vnode has been
208inserted into the document _and_ the rest of the patch cycle is done.
209This means that you can do DOM measurements (like using
210[getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)
211in this hook safely, knowing that no elements will be changed
212afterwards that could affect the position of the inserted elements.
213
214#### The `remove` hook
215
216Allows you to hook into the removal of an element. The hook is called
217once a vnode is to be removed from the DOM. The handling function
218receives both the vnode and a callback. You can control and delay the
219removal with the callback. The callback should be invoked once the
220hook is done doing its business, and the element will only be removed
221once all `remove` hooks have invoked their callback.
222
223The hook is only triggered when an element is to be removed from its
224parent – not if it is the child of an element that is removed. For
225that, see the `destroy` hook.
226
227#### The `destroy` hook
228
229This hook is invoked on a virtual node when its DOM element is removed
230from the DOM or if its parent is being removed from the DOM.
231
232To see the difference between this hook and the `remove` hook,
233consider an example.
234
235```js
236var vnode1 = h('div', [h('div', [h('span', 'Hello')])]);
237var vnode2 = h('div', []);
238patch(container, vnode1);
239patch(vnode1, vnode2);
240```
241
242Here `destroy` is triggered for both the inner `div` element _and_ the
243`span` element it contains. `remove`, on the other hand, is only
244triggered on the `div` element because it is the only element being
245detached from its parent.
246
247You can, for instance, use `remove` to trigger an animation when an
248element is being removed and use the `destroy` hook to additionally
249animate the disappearance of the removed element's children.
250
251### Creating modules
252
253Modules works by registering global listeners for [hooks](#hooks). A module is simply a dictionary mapping hook names to functions.
254
255```javascript
256var myModule = {
257 create: function(oldVnode, vnode) {
258 // invoked whenever a new virtual node is created
259 },
260 update: function(oldVnode, vnode) {
261 // invoked whenever a virtual node is updated
262 }
263};
264```
265
266With this mechanism you can easily augment the behaviour of Snabbdom.
267For demonstration, take a look at the implementations of the default
268modules.
269
270## Modules documentation
271
272This describes the core modules. All modules are optional.
273
274### The class module
275
276The class module provides an easy way to dynamically toggle classes on
277elements. It expects an object in the `class` data property. The
278object should map class names to booleans that indicates whether or
279not the class should stay or go on the vnode.
280
281```javascript
282h('a', {class: {active: true, selected: false}}, 'Toggle');
283```
284
285### The props module
286
287Allows you to set properties on DOM elements.
288
289```javascript
290h('a', {props: {href: '/foo'}}, 'Go to Foo');
291```
292
293### The attributes module
294
295Same as props, but set attributes instead of properties on DOM elements.
296
297```javascript
298h('a', {attrs: {href: '/foo'}}, 'Go to Foo');
299```
300
301Attributes are added and updated using `setAttribute`. In case of an
302attribute that had been previously added/set and is no longer present
303in the `attrs` object, it is removed from the DOM element's attribute
304list using `removeAttribute`.
305
306In the case of boolean attributes (e.g. `disabled`, `hidden`,
307`selected` ...), the meaning doesn't depend on the attribute value
308(`true` or `false`) but depends instead on the presence/absence of the
309attribute itself in the DOM element. Those attributes are handled
310differently by the module: if a boolean attribute is set to a
311[falsy value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
312(`0`, `-0`, `null`, `false`,`NaN`, `undefined`, or the empty string
313(`""`)), then the attribute will be removed from the attribute list of
314the DOM element.
315
316### The style module
317
318The style module is for making your HTML look slick and animate smoothly. At
319its core it allows you to set CSS properties on elements.
320
321```javascript
322h('span', {
323 style: {border: '1px solid #bada55', color: '#c0ffee', fontWeight: 'bold'}
324}, 'Say my name, and every colour illuminates');
325```
326
327Note that the style module does not remove style attributes if they
328are removed as properties from the style object. To remove a style,
329you should instead set it to the empty string.
330
331```javascript
332h('div', {
333 style: {position: shouldFollow ? 'fixed' : ''}
334}, 'I, I follow, I follow you');
335```
336
337#### Custom properties (CSS variables)
338
339CSS custom properties (aka CSS variables) are supported, they must be prefixed
340with `--`
341
342```javascript
343h('div', {
344 style: {'--warnColor': 'yellow'}
345}, 'Warning');
346```
347
348#### Delayed properties
349
350You can specify properties as being delayed. Whenever these properties
351change, the change is not applied until after the next frame.
352
353```javascript
354h('span', {
355 style: {opacity: '0', transition: 'opacity 1s', delayed: {opacity: '1'}}
356}, 'Imma fade right in!');
357```
358
359This makes it easy to declaratively animate the entry of elements.
360
361#### Set properties on `remove`
362
363Styles set in the `remove` property will take effect once the element
364is about to be removed from the DOM. The applied styles should be
365animated with CSS transitions. Only once all the styles are done
366animating will the element be removed from the DOM.
367
368```javascript
369h('span', {
370 style: {opacity: '1', transition: 'opacity 1s',
371 remove: {opacity: '0'}}
372}, 'It\'s better to fade out than to burn away');
373```
374
375This makes it easy to declaratively animate the removal of elements.
376
377#### Set properties on `destroy`
378
379```javascript
380h('span', {
381 style: {opacity: '1', transition: 'opacity 1s',
382 destroy: {opacity: '0'}}
383}, 'It\'s better to fade out than to burn away');
384```
385
386### Eventlisteners module
387
388The event listeners module gives powerful capabilities for attaching
389event listeners.
390
391You can attach a function to an event on a vnode by supplying an
392object at `on` with a property corresponding to the name of the event
393you want to listen to. The function will be called when the event
394happens and will be passed the event object that belongs to it.
395
396```javascript
397function clickHandler(ev) { console.log('got clicked'); }
398h('div', {on: {click: clickHandler}});
399```
400
401Very often, however, you're not really interested in the event object
402itself. Often you have some data associated with the element that
403triggers an event and you want that data passed along instead.
404
405Consider a counter application with three buttons, one to increment
406the counter by 1, one to increment the counter by 2 and one to
407increment the counter by 3. You don't really care exactly which button
408was pressed. Instead you're interested in what number was associated
409with the clicked button. The event listeners module allows one to
410express that by supplying an array at the named event property. The
411first element in the array should be a function that will be invoked
412with the value in the second element once the event occurs.
413
414```javascript
415function clickHandler(number) { console.log('button ' + number + ' was clicked!'); }
416h('div', [
417 h('a', {on: {click: [clickHandler, 1]}}),
418 h('a', {on: {click: [clickHandler, 2]}}),
419 h('a', {on: {click: [clickHandler, 3]}}),
420]);
421```
422
423Snabbdom allows swapping event handlers between renders. This happens without
424actually touching the event handlers attached to the DOM.
425
426Note, however, that **you should be careful when sharing event
427handlers between vnodes**, because of the technique this module uses
428to avoid re-binding event handlers to the DOM. (And in general,
429sharing data between vnodes is not guaranteed to work, because modules
430are allowed to mutate the given data).
431
432In particular, you should **not** do something like this:
433
434```javascript
435// Does not work
436var sharedHandler = {
437 change: function(e){ console.log('you chose: ' + e.target.value); }
438};
439h('div', [
440 h('input', {props: {type: 'radio', name: 'test', value: '0'},
441 on: sharedHandler}),
442 h('input', {props: {type: 'radio', name: 'test', value: '1'},
443 on: sharedHandler}),
444 h('input', {props: {type: 'radio', name: 'test', value: '2'},
445 on: sharedHandler})
446]);
447```
448
449For many such cases, you can use array-based handlers instead (described above).
450Alternatively, simply make sure each node is passed unique `on` values:
451
452```javascript
453// Works
454var sharedHandler = function(e){ console.log('you chose: ' + e.target.value); };
455h('div', [
456 h('input', {props: {type: 'radio', name: 'test', value: '0'},
457 on: {change: sharedHandler}}),
458 h('input', {props: {type: 'radio', name: 'test', value: '1'},
459 on: {change: sharedHandler}}),
460 h('input', {props: {type: 'radio', name: 'test', value: '2'},
461 on: {change: sharedHandler}})
462]);
463```
464
465## Helpers
466
467### SVG
468
469SVG just works when using the `h` function for creating virtual
470nodes. SVG elements are automatically created with the appropriate
471namespaces.
472
473```javascript
474var vnode = h('div', [
475 h('svg', {attrs: {width: 100, height: 100}}, [
476 h('circle', {attrs: {cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow'}})
477 ])
478]);
479```
480
481See also the [SVG example](./examples/svg) and the [SVG Carousel example](./examples/carousel-svg/).
482
483#### Using Classes
484Due to a bug in certain browsers like IE 11 and below and UC Browser, SVG Objects in these browsers do not support classlist property. Hence, the classes module (which uses classlist property internally) will not work for these browsers.
485
486Also, using snabbdom/h to create an element by passing a className along with the element type will not work as className property is read-only for SVG elements.
487
488You can add classes to SVG elements for both of these cases by using the attributes module as shown below:-
489```javascript
490h('text', {
491 attrs: {
492 x: xPos,
493 y: yPos,
494 dy: "5",
495 class: 'text_class'
496 }},
497 text
498);
499```
500
501### Thunks
502
503The `thunk` function takes a selector, a key for identifying a thunk,
504a function that returns a vnode and a variable amount of state
505parameters. If invoked, the render function will receive the state
506arguments.
507
508`thunk(selector, key, renderFn, [stateArguments])`
509
510The `key` is optional. It should be supplied when the `selector` is
511not unique among the thunks siblings. This ensures that the thunk is
512always matched correctly when diffing.
513
514Thunks are an optimization strategy that can be used when one is
515dealing with immutable data.
516
517Consider a simple function for creating a virtual node based on a number.
518
519```js
520function numberView(n) {
521 return h('div', 'Number is: ' + n);
522}
523```
524
525The view depends only on `n`. This means that if `n` is unchanged,
526then creating the virtual DOM node and patching it against the old
527vnode is wasteful. To avoid the overhead we can use the `thunk` helper
528function.
529
530```js
531function render(state) {
532 return thunk('num', numberView, [state.number]);
533}
534```
535
536Instead of actually invoking the `numberView` function this will only
537place a dummy vnode in the virtual tree. When Snabbdom patches this
538dummy vnode against a previous vnode, it will compare the value of
539`n`. If `n` is unchanged it will simply reuse the old vnode. This
540avoids recreating the number view and the diff process altogether.
541
542The view function here is only an example. In practice thunks are only
543relevant if you are rendering a complicated view that takes
544significant computational time to generate.
545
546## Virtual Node
547**Properties**
548 - [sel](#sel--string)
549 - [data](#data--object)
550 - [children](#children--array)
551 - [text](#text--string)
552 - [elm](#elm--element)
553 - [key](#key--string--number)
554
555#### sel : String
556
557The `.sel` property of a virtual node is the CSS selector passed to
558[`h()`](#snabbdomh) during creation. For example: `h('div#container',
559{}, [...])` will create a a virtual node which has `div#container` as
560its `.sel` property.
561
562#### data : Object
563
564The `.data` property of a virtual node is the place to add information
565for [modules](#modules-documentation) to access and manipulate the
566real DOM element when it is created; Add styles, CSS classes,
567attributes, etc.
568
569The data object is the (optional) second parameter to [`h()`](#snabbdomh)
570
571For example `h('div', {props: {className: 'container'}}, [...])` will produce a virtual node with
572```js
573{
574 "props": {
575 className: "container"
576 }
577}
578```
579as its `.data` object.
580
581#### children : Array<vnode>
582
583The `.children` property of a virtual node is the third (optional)
584parameter to [`h()`](#snabbdomh) during creation. `.children` is
585simply an Array of virtual nodes that should be added as children of
586the parent DOM node upon creation.
587
588For example `h('div', {}, [ h('h1', {}, 'Hello, World') ])` will
589create a virtual node with
590
591```js
592[
593 {
594 sel: 'h1',
595 data: {},
596 children: undefined,
597 text: 'Hello, World',
598 elm: Element,
599 key: undefined,
600 }
601]
602```
603
604as its `.children` property.
605
606#### text : string
607
608The `.text` property is created when a virtual node is created with
609only a single child that possesses text and only requires
610`document.createTextNode()` to be used.
611
612For example: `h('h1', {}, 'Hello')` will create a virtual node with
613`Hello` as its `.text` property.
614
615#### elm : Element
616
617The `.elm` property of a virtual node is a pointer to the real DOM
618node created by snabbdom. This property is very useful to do
619calculations in [hooks](#hooks) as well as
620[modules](#modules-documentation).
621
622#### key : string | number
623
624The `.key` property is created when a key is provided inside of your
625[`.data`](#data--object) object. The `.key` property is used to keep
626pointers to DOM nodes that existed previously to avoid recreating them
627if it is unnecessary. This is very useful for things like list
628reordering. A key must be either a string or a number to allow for
629proper lookup as it is stored internally as a key/value pair inside of
630an object, where `.key` is the key and the value is the
631[`.elm`](#elm--element) property created.
632
633For example: `h('div', {key: 1}, [])` will create a virtual node
634object with a `.key` property with the value of `1`.
635
636
637## Structuring applications
638
639Snabbdom is a low-level virtual DOM library. It is unopinionated with
640regards to how you should structure your application.
641
642Here are some approaches to building applications with Snabbdom.
643
644* [functional-frontend-architecture](https://github.com/paldepind/functional-frontend-architecture) –
645 a repository containing several example applications that
646 demonstrates an architecture that uses Snabbdom.
647* [Cycle.js](https://cycle.js.org/) –
648 "A functional and reactive JavaScript framework for cleaner code"
649 uses Snabbdom
650* [Vue.js](http://vuejs.org/) use a fork of snabbdom.
651* [scheme-todomvc](https://github.com/amirouche/scheme-todomvc/) build
652 redux-like architecture on top of snabbdom bindings.
653* [kaiju](https://github.com/AlexGalays/kaiju) -
654 Stateful components and observables on top of snabbdom
655* [Tweed](https://tweedjs.github.io) –
656 An Object Oriented approach to reactive interfaces.
657
658Be sure to share it if you're building an application in another way
659using Snabbdom.
660