UNPKG

31.8 kBMarkdownView Raw
1<img alt="Snabbdom" src="readme-title.svg" width="356px">
2
3A virtual DOM library with focus on simplicity, modularity, powerful features
4and performance.
5
6---
7
8[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
9[![Build Status](https://travis-ci.org/snabbdom/snabbdom.svg?branch=master)](https://travis-ci.org/snabbdom/snabbdom)
10[![npm version](https://badge.fury.io/js/snabbdom.svg)](https://badge.fury.io/js/snabbdom)
11[![npm downloads](https://img.shields.io/npm/dm/snabbdom.svg)](https://www.npmjs.com/package/snabbdom)
12[![Join the chat at https://gitter.im/snabbdom/snabbdom](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snabbdom/snabbdom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
13
14[![Donate to our collective](https://opencollective.com/snabbdom/donate/button@2x.png?color=blue)](https://opencollective.com/snabbdom#section-contribute)
15
16Thanks to [Browserstack](https://www.browserstack.com/) for providing access to
17their great cross-browser testing tools.
18
19---
20
21## Introduction
22
23Virtual DOM is awesome. It allows us to express our application's view
24as a function of its state. But existing solutions were way way too
25bloated, too slow, lacked features, had an API biased towards OOP
26and/or lacked features I needed.
27
28Snabbdom consists of an extremely simple, performant and extensible
29core that is only ≈ 200 SLOC. It offers a modular architecture with
30rich functionality for extensions through custom modules. To keep the
31core simple, all non-essential functionality is delegated to modules.
32
33You can mold Snabbdom into whatever you desire! Pick, choose and
34customize the functionality you want. Alternatively you can just use
35the default extensions and get a virtual DOM library with high
36performance, small size and all the features listed below.
37
38## Features
39
40- Core features
41 - About 200 SLOC – you could easily read through the entire core and fully
42 understand how it works.
43 - Extendable through modules.
44 - A rich set of hooks available, both per vnode and globally for modules,
45 to hook into any part of the diff and patch process.
46 - Splendid performance. Snabbdom is among the fastest virtual DOM libraries.
47 - Patch function with a function signature equivalent to a reduce/scan
48 function. Allows for easier integration with a FRP library.
49- Features in modules
50 - `h` function for easily creating virtual DOM nodes.
51 - [SVG _just works_ with the `h` helper](#svg).
52 - Features for doing complex CSS animations.
53 - Powerful event listener functionality.
54 - [Thunks](#thunks) to optimize the diff and patch process even further.
55 - [JSX support, including TypeScript types](#jsx)
56- Third party features
57 - Server-side HTML output provided by [snabbdom-to-html](https://github.com/acstll/snabbdom-to-html).
58 - Compact virtual DOM creation with [snabbdom-helpers](https://github.com/krainboltgreene/snabbdom-helpers).
59 - Template string support using [snabby](https://github.com/jamen/snabby).
60 - Virtual DOM assertion with [snabbdom-looks-like](https://github.com/jvanbruegge/snabbdom-looks-like)
61
62## Example
63
64```mjs
65import {
66 init,
67 classModule,
68 propsModule,
69 styleModule,
70 eventListenersModule,
71 h,
72} from "snabbdom";
73
74const patch = init([
75 // Init patch function with chosen modules
76 classModule, // makes it easy to toggle classes
77 propsModule, // for setting properties on DOM elements
78 styleModule, // handles styling on elements with support for animations
79 eventListenersModule, // attaches event listeners
80]);
81
82const container = document.getElementById("container");
83
84const vnode = h("div#container.two.classes", { on: { click: someFn } }, [
85 h("span", { style: { fontWeight: "bold" } }, "This is bold"),
86 " and this is just normal text",
87 h("a", { props: { href: "/foo" } }, "I'll take you places!"),
88]);
89// Patch into empty DOM element – this modifies the DOM as a side effect
90patch(container, vnode);
91
92const newVnode = h(
93 "div#container.two.classes",
94 { on: { click: anotherEventHandler } },
95 [
96 h(
97 "span",
98 { style: { fontWeight: "normal", fontStyle: "italic" } },
99 "This is now italic type"
100 ),
101 " and this is still just normal text",
102 h("a", { props: { href: "/bar" } }, "I'll take you places!"),
103 ]
104);
105// Second `patch` invocation
106patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
107```
108
109## More examples
110
111- [Animated reordering of elements](http://snabbdom.github.io/snabbdom/examples/reorder-animation/)
112- [Hero transitions](http://snabbdom.github.io/snabbdom/examples/hero/)
113- [SVG Carousel](http://snabbdom.github.io/snabbdom/examples/carousel-svg/)
114
115---
116
117## Table of contents
118
119- [Core documentation](#core-documentation)
120 - [`init`](#init)
121 - [`patch`](#patch)
122 - [Unmounting](#unmounting)
123 - [`h`](#h)
124 - [`fragment`](#fragment-experimental) (experimental)
125 - [`tovnode`](#tovnode)
126 - [Hooks](#hooks)
127 - [Overview](#overview)
128 - [Usage](#usage)
129 - [The `init` hook](#the-init-hook)
130 - [The `insert` hook](#the-insert-hook)
131 - [The `remove` hook](#the-remove-hook)
132 - [The `destroy` hook](#the-destroy-hook)
133 - [Creating modules](#creating-modules)
134- [Modules documentation](#modules-documentation)
135 - [The class module](#the-class-module)
136 - [The props module](#the-props-module)
137 - [The attributes module](#the-attributes-module)
138 - [The dataset module](#the-dataset-module)
139 - [The style module](#the-style-module)
140 - [Custom properties (CSS variables)](#custom-properties-css-variables)
141 - [Delayed properties](#delayed-properties)
142 - [Set properties on `remove`](#set-properties-on-remove)
143 - [Set properties on `destroy`](#set-properties-on-destroy)
144 - [The eventlisteners module](#the-eventlisteners-module)
145- [SVG](#svg)
146 - [Classes in SVG Elements](#classes-in-svg-elements)
147- [Thunks](#thunks)
148- [JSX](#jsx)
149 - [TypeScript](#typescript)
150 - [Babel](#babel)
151- [Virtual Node](#virtual-node)
152 - [sel : String](#sel--string)
153 - [data : Object](#data--object)
154 - [children : Array<vnode>](#children--arrayvnode)
155 - [text : string](#text--string)
156 - [elm : Element](#elm--element)
157 - [key : string | number](#key--string--number)
158- [Structuring applications](#structuring-applications)
159- [Common errors](#common-errors)
160- [Opportunity for community feedback](#opportunity-for-community-feedback)
161
162## Core documentation
163
164The core of Snabbdom provides only the most essential functionality.
165It is designed to be as simple as possible while still being fast and
166extendable.
167
168### `init`
169
170The core exposes only one single function `init`. This `init`
171takes a list of modules and returns a `patch` function that uses the
172specified set of modules.
173
174```mjs
175import { classModule, styleModule } from "snabbdom";
176
177const patch = init([classModule, styleModule]);
178```
179
180### `patch`
181
182The `patch` function returned by `init` takes two arguments. The first
183is a DOM element or a vnode representing the current view. The second
184is a vnode representing the new, updated view.
185
186If a DOM element with a parent is passed, `newVnode` will be turned
187into a DOM node, and the passed element will be replaced by the
188created DOM node. If an old vnode is passed, Snabbdom will efficiently
189modify it to match the description in the new vnode.
190
191Any old vnode passed must be the resulting vnode from a previous call
192to `patch`. This is necessary since Snabbdom stores information in the
193vnode. This makes it possible to implement a simpler and more
194performant architecture. This also avoids the creation of a new old
195vnode tree.
196
197```mjs
198patch(oldVnode, newVnode);
199```
200
201#### Unmounting
202
203While there is no API specifically for removing a VNode tree from its mount point element, one way of almost achieving this is providing a comment VNode as the second argument to `patch`, such as:
204
205```mjs
206patch(
207 oldVnode,
208 h("!", {
209 hooks: {
210 post: () => {
211 /* patch complete */
212 },
213 },
214 })
215);
216```
217
218Of course, then there is still a single comment node at the mount point.
219
220### `h`
221
222It is recommended that you use `h` to create vnodes. It accepts a
223tag/selector as a string, an optional data object and an optional string or
224array of children.
225
226```mjs
227import { h } from "snabbdom";
228
229const vnode = h("div", { style: { color: "#000" } }, [
230 h("h1", "Headline"),
231 h("p", "A paragraph"),
232]);
233```
234
235### `fragment` (experimental)
236
237Caution: This feature is currently experimental and must be opted in.
238Its API may be changed without an major version bump.
239
240```mjs
241const patch = init(modules, undefined, {
242 experimental: {
243 fragments: true,
244 },
245});
246```
247
248Creates a virtual node that will be converted to a document fragment containing the given children.
249
250```mjs
251import { fragment, h } from "snabbdom";
252
253const vnode = fragment(["I am", h("span", [" a", " fragment"])]);
254```
255
256### `tovnode`
257
258Converts a DOM node into a virtual node. Especially good for patching over an pre-existing,
259server-side generated content.
260
261```mjs
262import {
263 init,
264 classModule,
265 propsModule,
266 styleModule,
267 eventListenersModule,
268 h,
269 toVNode,
270} from "snabbdom";
271
272const patch = init([
273 // Init patch function with chosen modules
274 classModule, // makes it easy to toggle classes
275 propsModule, // for setting properties on DOM elements
276 styleModule, // handles styling on elements with support for animations
277 eventListenersModule, // attaches event listeners
278]);
279
280const newVNode = h("div", { style: { color: "#000" } }, [
281 h("h1", "Headline"),
282 h("p", "A paragraph"),
283]);
284
285patch(toVNode(document.querySelector(".container")), newVNode);
286```
287
288### Hooks
289
290Hooks are a way to hook into the lifecycle of DOM nodes. Snabbdom
291offers a rich selection of hooks. Hooks are used both by modules to
292extend Snabbdom, and in normal code for executing arbitrary code at
293desired points in the life of a virtual node.
294
295#### Overview
296
297| Name | Triggered when | Arguments to callback |
298| ----------- | -------------------------------------------------- | ----------------------- |
299| `pre` | the patch process begins | none |
300| `init` | a vnode has been added | `vnode` |
301| `create` | a DOM element has been created based on a vnode | `emptyVnode, vnode` |
302| `insert` | an element has been inserted into the DOM | `vnode` |
303| `prepatch` | an element is about to be patched | `oldVnode, vnode` |
304| `update` | an element is being updated | `oldVnode, vnode` |
305| `postpatch` | an element has been patched | `oldVnode, vnode` |
306| `destroy` | an element is directly or indirectly being removed | `vnode` |
307| `remove` | an element is directly being removed from the DOM | `vnode, removeCallback` |
308| `post` | the patch process is done | none |
309
310The following hooks are available for modules: `pre`, `create`,
311`update`, `destroy`, `remove`, `post`.
312
313The following hooks are available in the `hook` property of individual
314elements: `init`, `create`, `insert`, `prepatch`, `update`,
315`postpatch`, `destroy`, `remove`.
316
317#### Usage
318
319To use hooks, pass them as an object to `hook` field of the data
320object argument.
321
322```mjs
323h("div.row", {
324 key: movie.rank,
325 hook: {
326 insert: (vnode) => {
327 movie.elmHeight = vnode.elm.offsetHeight;
328 },
329 },
330});
331```
332
333#### The `init` hook
334
335This hook is invoked during the patch process when a new virtual node
336has been found. The hook is called before Snabbdom has processed the
337node in any way. I.e., before it has created a DOM node based on the
338vnode.
339
340#### The `insert` hook
341
342This hook is invoked once the DOM element for a vnode has been
343inserted into the document _and_ the rest of the patch cycle is done.
344This means that you can do DOM measurements (like using
345[getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)
346in this hook safely, knowing that no elements will be changed
347afterwards that could affect the position of the inserted elements.
348
349#### The `remove` hook
350
351Allows you to hook into the removal of an element. The hook is called
352once a vnode is to be removed from the DOM. The handling function
353receives both the vnode and a callback. You can control and delay the
354removal with the callback. The callback should be invoked once the
355hook is done doing its business, and the element will only be removed
356once all `remove` hooks have invoked their callback.
357
358The hook is only triggered when an element is to be removed from its
359parent – not if it is the child of an element that is removed. For
360that, see the `destroy` hook.
361
362#### The `destroy` hook
363
364This hook is invoked on a virtual node when its DOM element is removed
365from the DOM or if its parent is being removed from the DOM.
366
367To see the difference between this hook and the `remove` hook,
368consider an example.
369
370```mjs
371const vnode1 = h("div", [h("div", [h("span", "Hello")])]);
372const vnode2 = h("div", []);
373patch(container, vnode1);
374patch(vnode1, vnode2);
375```
376
377Here `destroy` is triggered for both the inner `div` element _and_ the
378`span` element it contains. `remove`, on the other hand, is only
379triggered on the `div` element because it is the only element being
380detached from its parent.
381
382You can, for instance, use `remove` to trigger an animation when an
383element is being removed and use the `destroy` hook to additionally
384animate the disappearance of the removed element's children.
385
386### Creating modules
387
388Modules works by registering global listeners for [hooks](#hooks). A module is simply a dictionary mapping hook names to functions.
389
390```mjs
391const myModule = {
392 create: function (oldVnode, vnode) {
393 // invoked whenever a new virtual node is created
394 },
395 update: function (oldVnode, vnode) {
396 // invoked whenever a virtual node is updated
397 },
398};
399```
400
401With this mechanism you can easily augment the behaviour of Snabbdom.
402For demonstration, take a look at the implementations of the default
403modules.
404
405## Modules documentation
406
407This describes the core modules. All modules are optional. JSX examples assume you're using the [`jsx` pragma](#jsx) provided by this library.
408
409### The class module
410
411The class module provides an easy way to dynamically toggle classes on
412elements. It expects an object in the `class` data property. The
413object should map class names to booleans that indicates whether or
414not the class should stay or go on the vnode.
415
416```mjs
417h("a", { class: { active: true, selected: false } }, "Toggle");
418```
419
420In JSX, you can use `class` like this:
421
422```jsx
423<div class={{ foo: true, bar: true }} />
424// Renders as: <div class="foo bar"></div>
425```
426
427### The props module
428
429Allows you to set properties on DOM elements.
430
431```mjs
432h("a", { props: { href: "/foo" } }, "Go to Foo");
433```
434
435In JSX, you can use `props` like this:
436
437```jsx
438<input props={{ name: "foo" }} />
439// Renders as: <input name="foo" /> with input.name === "foo"
440```
441
442Properties can only be set. Not removed. Even though browsers allow addition and
443deletion of custom properties, deletion will not be attempted by this module.
444This makes sense, because native DOM properties cannot be removed. And
445if you are using custom properties for storing values or referencing
446objects on the DOM, then please consider using
447[data-\* attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes)
448instead. Perhaps via [the dataset module](#the-dataset-module).
449
450### The attributes module
451
452Same as props, but set attributes instead of properties on DOM elements.
453
454```mjs
455h("a", { attrs: { href: "/foo" } }, "Go to Foo");
456```
457
458In JSX, you can use `attrs` like this:
459
460```jsx
461<div attrs={{ "aria-label": "I'm a div" }} />
462// Renders as: <div aria-label="I'm a div"></div>
463```
464
465Attributes are added and updated using `setAttribute`. In case of an
466attribute that had been previously added/set and is no longer present
467in the `attrs` object, it is removed from the DOM element's attribute
468list using `removeAttribute`.
469
470In the case of boolean attributes (e.g. `disabled`, `hidden`,
471`selected` ...), the meaning doesn't depend on the attribute value
472(`true` or `false`) but depends instead on the presence/absence of the
473attribute itself in the DOM element. Those attributes are handled
474differently by the module: if a boolean attribute is set to a
475[falsy value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
476(`0`, `-0`, `null`, `false`,`NaN`, `undefined`, or the empty string
477(`""`)), then the attribute will be removed from the attribute list of
478the DOM element.
479
480### The dataset module
481
482Allows 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.
483
484```mjs
485h("button", { dataset: { action: "reset" } }, "Reset");
486```
487
488In JSX, you can use `dataset` like this:
489
490```jsx
491<div dataset={{ foo: "bar" }} />
492// Renders as: <div data-foo="bar"></div>
493```
494
495### The style module
496
497The style module is for making your HTML look slick and animate smoothly. At
498its core it allows you to set CSS properties on elements.
499
500```mjs
501h(
502 "span",
503 {
504 style: {
505 border: "1px solid #bada55",
506 color: "#c0ffee",
507 fontWeight: "bold",
508 },
509 },
510 "Say my name, and every colour illuminates"
511);
512```
513
514In JSX, you can use `style` like this:
515
516```jsx
517<div
518 style={{
519 border: "1px solid #bada55",
520 color: "#c0ffee",
521 fontWeight: "bold",
522 }}
523/>
524// Renders as: <div style="border: 1px solid #bada55; color: #c0ffee; font-weight: bold"></div>
525```
526
527#### Custom properties (CSS variables)
528
529CSS custom properties (aka CSS variables) are supported, they must be prefixed
530with `--`
531
532```mjs
533h(
534 "div",
535 {
536 style: { "--warnColor": "yellow" },
537 },
538 "Warning"
539);
540```
541
542#### Delayed properties
543
544You can specify properties as being delayed. Whenever these properties
545change, the change is not applied until after the next frame.
546
547```mjs
548h(
549 "span",
550 {
551 style: {
552 opacity: "0",
553 transition: "opacity 1s",
554 delayed: { opacity: "1" },
555 },
556 },
557 "Imma fade right in!"
558);
559```
560
561This makes it easy to declaratively animate the entry of elements.
562
563The `all` value of `transition-property` is not supported.
564
565#### Set properties on `remove`
566
567Styles set in the `remove` property will take effect once the element
568is about to be removed from the DOM. The applied styles should be
569animated with CSS transitions. Only once all the styles are done
570animating will the element be removed from the DOM.
571
572```mjs
573h(
574 "span",
575 {
576 style: {
577 opacity: "1",
578 transition: "opacity 1s",
579 remove: { opacity: "0" },
580 },
581 },
582 "It's better to fade out than to burn away"
583);
584```
585
586This makes it easy to declaratively animate the removal of elements.
587
588The `all` value of `transition-property` is not supported.
589
590#### Set properties on `destroy`
591
592```mjs
593h(
594 "span",
595 {
596 style: {
597 opacity: "1",
598 transition: "opacity 1s",
599 destroy: { opacity: "0" },
600 },
601 },
602 "It's better to fade out than to burn away"
603);
604```
605
606The `all` value of `transition-property` is not supported.
607
608### The eventlisteners module
609
610The event listeners module gives powerful capabilities for attaching
611event listeners.
612
613You can attach a function to an event on a vnode by supplying an
614object at `on` with a property corresponding to the name of the event
615you want to listen to. The function will be called when the event
616happens and will be passed the event object that belongs to it.
617
618```mjs
619function clickHandler(ev) {
620 console.log("got clicked");
621}
622h("div", { on: { click: clickHandler } });
623```
624
625In JSX, you can use `on` like this:
626
627```js
628<div on={{ click: clickHandler }} />
629```
630
631Snabbdom allows swapping event handlers between renders. This happens without
632actually touching the event handlers attached to the DOM.
633
634Note, however, that **you should be careful when sharing event
635handlers between vnodes**, because of the technique this module uses
636to avoid re-binding event handlers to the DOM. (And in general,
637sharing data between vnodes is not guaranteed to work, because modules
638are allowed to mutate the given data).
639
640In particular, you should **not** do something like this:
641
642```mjs
643// Does not work
644const sharedHandler = {
645 change: function (e) {
646 console.log("you chose: " + e.target.value);
647 },
648};
649h("div", [
650 h("input", {
651 props: { type: "radio", name: "test", value: "0" },
652 on: sharedHandler,
653 }),
654 h("input", {
655 props: { type: "radio", name: "test", value: "1" },
656 on: sharedHandler,
657 }),
658 h("input", {
659 props: { type: "radio", name: "test", value: "2" },
660 on: sharedHandler,
661 }),
662]);
663```
664
665For many such cases, you can use array-based handlers instead (described above).
666Alternatively, simply make sure each node is passed unique `on` values:
667
668```mjs
669// Works
670const sharedHandler = function (e) {
671 console.log("you chose: " + e.target.value);
672};
673h("div", [
674 h("input", {
675 props: { type: "radio", name: "test", value: "0" },
676 on: { change: sharedHandler },
677 }),
678 h("input", {
679 props: { type: "radio", name: "test", value: "1" },
680 on: { change: sharedHandler },
681 }),
682 h("input", {
683 props: { type: "radio", name: "test", value: "2" },
684 on: { change: sharedHandler },
685 }),
686]);
687```
688
689## SVG
690
691SVG just works when using the `h` function for creating virtual
692nodes. SVG elements are automatically created with the appropriate
693namespaces.
694
695```mjs
696const vnode = h("div", [
697 h("svg", { attrs: { width: 100, height: 100 } }, [
698 h("circle", {
699 attrs: {
700 cx: 50,
701 cy: 50,
702 r: 40,
703 stroke: "green",
704 "stroke-width": 4,
705 fill: "yellow",
706 },
707 }),
708 ]),
709]);
710```
711
712See also the [SVG example](./examples/svg) and the [SVG Carousel example](./examples/carousel-svg/).
713
714### Classes in SVG Elements
715
716Certain browsers (like IE &lt;=11) [do not support `classList` property in SVG elements](http://caniuse.com/#feat=classlist).
717Because the _class_ module internally uses `classList`, it will not work in this case unless you use a [classList polyfill](https://www.npmjs.com/package/classlist-polyfill).
718(If you don't want to use a polyfill, you can use the `class` attribute with the _attributes_ module).
719
720## Thunks
721
722The `thunk` function takes a selector, a key for identifying a thunk,
723a function that returns a vnode and a variable amount of state
724parameters. If invoked, the render function will receive the state
725arguments.
726
727`thunk(selector, key, renderFn, [stateArguments])`
728
729The `renderFn` is invoked only if the `renderFn` is changed or `[stateArguments]` array length or it's elements are changed.
730
731The `key` is optional. It should be supplied when the `selector` is
732not unique among the thunks siblings. This ensures that the thunk is
733always matched correctly when diffing.
734
735Thunks are an optimization strategy that can be used when one is
736dealing with immutable data.
737
738Consider a simple function for creating a virtual node based on a number.
739
740```mjs
741function numberView(n) {
742 return h("div", "Number is: " + n);
743}
744```
745
746The view depends only on `n`. This means that if `n` is unchanged,
747then creating the virtual DOM node and patching it against the old
748vnode is wasteful. To avoid the overhead we can use the `thunk` helper
749function.
750
751```mjs
752function render(state) {
753 return thunk("num", numberView, [state.number]);
754}
755```
756
757Instead of actually invoking the `numberView` function this will only
758place a dummy vnode in the virtual tree. When Snabbdom patches this
759dummy vnode against a previous vnode, it will compare the value of
760`n`. If `n` is unchanged it will simply reuse the old vnode. This
761avoids recreating the number view and the diff process altogether.
762
763The view function here is only an example. In practice thunks are only
764relevant if you are rendering a complicated view that takes
765significant computational time to generate.
766
767## JSX
768
769Note that JSX fragments are still experimental and must be opted in.
770See [`fragment`](#fragment-experimental) section for details.
771
772### TypeScript
773
774Add the following options to your `tsconfig.json`:
775
776```json
777{
778 "compilerOptions": {
779 "jsx": "react",
780 "jsxFactory": "jsx",
781 "jsxFragmentFactory": "Fragment"
782 }
783}
784```
785
786Then make sure that you use the `.tsx` file extension and import the `jsx` function and the `Fragment` function at the top of the file:
787
788```tsx
789import { Fragment, jsx, VNode } from "snabbdom";
790
791const node: VNode = (
792 <div>
793 <span>I was created with JSX</span>
794 </div>
795);
796
797const fragment: VNode = (
798 <>
799 <span>JSX fragments</span>
800 are experimentally supported
801 </>
802);
803```
804
805### Babel
806
807Add the following options to your babel configuration:
808
809```json
810{
811 "plugins": [
812 [
813 "@babel/plugin-transform-react-jsx",
814 {
815 "pragma": "jsx",
816 "pragmaFrag": "Fragment"
817 }
818 ]
819 ]
820}
821```
822
823Then import the `jsx` function and the `Fragment` function at the top of the file:
824
825```jsx
826import { Fragment, jsx } from "snabbdom";
827
828const node = (
829 <div>
830 <span>I was created with JSX</span>
831 </div>
832);
833
834const fragment = (
835 <>
836 <span>JSX fragments</span>
837 are experimentally supported
838 </>
839);
840```
841
842## Virtual Node
843
844**Properties**
845
846- [sel](#sel--string)
847- [data](#data--object)
848- [children](#children--array)
849- [text](#text--string)
850- [elm](#elm--element)
851- [key](#key--string--number)
852
853### sel : String
854
855The `.sel` property of a virtual node is the CSS selector passed to
856[`h()`](#snabbdomh) during creation. For example: `h('div#container', {}, [...])` will create a a virtual node which has `div#container` as
857its `.sel` property.
858
859### data : Object
860
861The `.data` property of a virtual node is the place to add information
862for [modules](#modules-documentation) to access and manipulate the
863real DOM element when it is created; Add styles, CSS classes,
864attributes, etc.
865
866The data object is the (optional) second parameter to [`h()`](#snabbdomh)
867
868For example `h('div', {props: {className: 'container'}}, [...])` will produce a virtual node with
869
870```mjs
871({
872 props: {
873 className: "container",
874 },
875});
876```
877
878as its `.data` object.
879
880### children : Array<vnode>
881
882The `.children` property of a virtual node is the third (optional)
883parameter to [`h()`](#snabbdomh) during creation. `.children` is
884simply an Array of virtual nodes that should be added as children of
885the parent DOM node upon creation.
886
887For example `h('div', {}, [ h('h1', {}, 'Hello, World') ])` will
888create a virtual node with
889
890```mjs
891[
892 {
893 sel: "h1",
894 data: {},
895 children: undefined,
896 text: "Hello, World",
897 elm: Element,
898 key: undefined,
899 },
900];
901```
902
903as its `.children` property.
904
905### text : string
906
907The `.text` property is created when a virtual node is created with
908only a single child that possesses text and only requires
909`document.createTextNode()` to be used.
910
911For example: `h('h1', {}, 'Hello')` will create a virtual node with
912`Hello` as its `.text` property.
913
914### elm : Element
915
916The `.elm` property of a virtual node is a pointer to the real DOM
917node created by snabbdom. This property is very useful to do
918calculations in [hooks](#hooks) as well as
919[modules](#modules-documentation).
920
921### key : string | number
922
923The `.key` property is created when a key is provided inside of your
924[`.data`](#data--object) object. The `.key` property is used to keep
925pointers to DOM nodes that existed previously to avoid recreating them
926if it is unnecessary. This is very useful for things like list
927reordering. A key must be either a string or a number to allow for
928proper lookup as it is stored internally as a key/value pair inside of
929an object, where `.key` is the key and the value is the
930[`.elm`](#elm--element) property created.
931
932If provided, the `.key` property must be unique among sibling elements.
933
934For example: `h('div', {key: 1}, [])` will create a virtual node
935object with a `.key` property with the value of `1`.
936
937## Structuring applications
938
939Snabbdom is a low-level virtual DOM library. It is unopinionated with
940regards to how you should structure your application.
941
942Here are some approaches to building applications with Snabbdom.
943
944- [functional-frontend-architecture](https://github.com/paldepind/functional-frontend-architecture) –
945 a repository containing several example applications that
946 demonstrates an architecture that uses Snabbdom.
947- [Cycle.js](https://cycle.js.org/) –
948 "A functional and reactive JavaScript framework for cleaner code"
949 uses Snabbdom
950- [Vue.js](http://vuejs.org/) use a fork of snabbdom.
951- [scheme-todomvc](https://github.com/amirouche/scheme-todomvc/) build
952 redux-like architecture on top of snabbdom bindings.
953- [kaiju](https://github.com/AlexGalays/kaiju) -
954 Stateful components and observables on top of snabbdom
955- [Tweed](https://tweedjs.github.io) –
956 An Object Oriented approach to reactive interfaces.
957- [Cyclow](http://cyclow.js.org) -
958 "A reactive frontend framework for JavaScript"
959 uses Snabbdom
960- [Tung](https://github.com/Reon90/tung) –
961 A JavaScript library for rendering html. Tung helps to divide html and JavaScript development.
962- [sprotty](https://github.com/theia-ide/sprotty) - "A web-based diagramming framework" uses Snabbdom.
963- [Mark Text](https://github.com/marktext/marktext) - "Realtime preview Markdown Editor" build on Snabbdom.
964- [puddles](https://github.com/flintinatux/puddles) -
965 "Tiny vdom app framework. Pure Redux. No boilerplate." - Built with :heart: on Snabbdom.
966- [Backbone.VDOMView](https://github.com/jcbrand/backbone.vdomview) - A [Backbone](http://backbonejs.org/) View with VirtualDOM capability via Snabbdom.
967- [Rosmaro Snabbdom starter](https://github.com/lukaszmakuch/rosmaro-snabbdom-starter) - Building user interfaces with state machines and Snabbdom.
968- [Pureact](https://github.com/irony/pureact) - "65 lines implementation of React incl Redux and hooks with only one dependency - Snabbdom"
969- [Snabberb](https://github.com/tobymao/snabberb) - A minimalistic Ruby framework using [Opal](https://github.com/opal/opal) and Snabbdom for building reactive views.
970- [WebCell](https://github.com/EasyWebApp/WebCell) - Web Components engine based on JSX & TypeScript
971
972Be sure to share it if you're building an application in another way
973using Snabbdom.
974
975## Common errors
976
977```text
978Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node':
979 The node before which the new node is to be inserted is not a child of this node.
980```
981
982The 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.
983
984```mjs
985const sharedNode = h("div", {}, "Selected");
986const vnode1 = h("div", [
987 h("div", {}, ["One"]),
988 h("div", {}, ["Two"]),
989 h("div", {}, [sharedNode]),
990]);
991const vnode2 = h("div", [
992 h("div", {}, ["One"]),
993 h("div", {}, [sharedNode]),
994 h("div", {}, ["Three"]),
995]);
996patch(container, vnode1);
997patch(vnode1, vnode2);
998```
999
1000You can fix this issue by creating a shallow copy of the object (here with object spread syntax):
1001
1002```mjs
1003const vnode2 = h("div", [
1004 h("div", {}, ["One"]),
1005 h("div", {}, [{ ...sharedNode }]),
1006 h("div", {}, ["Three"]),
1007]);
1008```
1009
1010Another solution would be to wrap shared vnodes in a factory function:
1011
1012```mjs
1013const sharedNode = () => h("div", {}, "Selected");
1014const vnode1 = h("div", [
1015 h("div", {}, ["One"]),
1016 h("div", {}, ["Two"]),
1017 h("div", {}, [sharedNode()]),
1018]);
1019```
1020
1021## Opportunity for community feedback
1022
1023Pull requests that the community may care to provide feedback on should be
1024merged after such opportunity of a few days was provided.