UNPKG

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