UNPKG

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