{"id":33211869,"url":"https://domvm.github.io/domvm/","last_synced_at":"2025-11-21T05:03:32.939Z","repository":{"id":3088595,"uuid":"45707115","full_name":"domvm/domvm","owner":"domvm","description":"DOM ViewModel - A thin, fast, dependency-free vdom view layer","archived":false,"fork":false,"pushed_at":"2022-07-26T20:29:04.000Z","size":8492,"stargazers_count":612,"open_issues_count":12,"forks_count":27,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-10-22T16:27:44.232Z","etag":null,"topics":["fast","lightweight","minimal","vdom","view-layer","virtual-dom"],"latest_commit_sha":null,"homepage":"https://domvm.github.io/domvm/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/domvm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-06T20:56:36.000Z","updated_at":"2025-07-23T21:22:44.000Z","dependencies_parsed_at":"2022-09-11T01:50:44.109Z","dependency_job_id":null,"html_url":"https://github.com/domvm/domvm","commit_stats":null,"previous_names":["leeoniya/domvm"],"tags_count":63,"template":false,"template_full_name":null,"purl":"pkg:github/domvm/domvm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domvm%2Fdomvm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domvm%2Fdomvm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domvm%2Fdomvm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domvm%2Fdomvm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/domvm","download_url":"https://codeload.github.com/domvm/domvm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domvm%2Fdomvm/sbom","scorecard":{"id":351416,"data":{"date":"2025-08-11","repo":{"name":"github.com/domvm/domvm","commit":"67de1a0cdf1879ad87926dafde0b8961f660c906"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T08:22:47.623Z","repository_id":3088595,"created_at":"2025-08-18T08:22:47.623Z","updated_at":"2025-08-18T08:22:47.623Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285560057,"owners_count":27192467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-21T02:00:06.175Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fast","lightweight","minimal","vdom","view-layer","virtual-dom"],"created_at":"2025-11-16T12:00:20.548Z","updated_at":"2025-11-21T05:03:32.932Z","avatar_url":"https://github.com/domvm.png","language":"JavaScript","funding_links":[],"categories":["Frameworks"],"sub_categories":["Rest of the Pack"],"readme":"\u003ch2\u003e\u003ca href=\"https://github.com/domvm/domvm\"\u003edomvm (DOM ViewModel)\u003c/a\u003e\u003cimg src=\"domvm.png\" alt=\"domvm logo\" height=\"64\" align=\"right\"\u003e\u003c/h2\u003e\n\nA thin, fast, dependency-free vdom view layer _(MIT Licensed)_\n\n---\n### Introduction\n\ndomvm is a flexible, pure-js view layer for building high performance web applications.\nLike jQuery, it'll happily fit into any existing codebase without introducing new tooling or requiring major architectural changes.\n\n- It's zero-dependency and requires no compilation or tooling; one `\u003cscript\u003e` tag is all that's needed.\n- It's small: [~6k gz](/dist/README.md), fast: [just 20%](https://krausest.github.io/js-framework-benchmark/current.html) slower vs painfully imperative vanilla DOM code. [2x faster SSR](/demos/bench/ssr) vs React v16.\n- Its entire, practical API can be mastered in under 1 hour by both, OO graybeards and FRP hipsters. Obvious explicit behavior, debuggable plain JS templates, optional statefulness and interchangable imperative/declarative components.\n- It's well-suited for building [simple widgets](https://domvm.github.io/domvm/demos/playground/#calendar) and [complex, fault-tolerant applications](https://domvm.github.io/domvm/demos/ThreaditJS).\n- Supports down to IE11 with a tiny [Promise shim](https://github.com/RubenVerborgh/promiscuous).\n\nTo use domvm you should be comfortable with JavaScript and the DOM; the following code should be fairly self-explanatory:\n\n```js\nvar el = domvm.defineElement,\n    cv = domvm.createView;\n\nvar HelloView = {\n    render: function(vm, data) {\n        return el(\"h1\", {style: \"color: red;\"}, \"Hello \" + data.name);\n    }\n};\n\nvar data = {name: \"Leon\"};\n\nvar vm = cv(HelloView, data).mount(document.body);\n```\n\n---\n### [Demo Playground](https://domvm.github.io/domvm/demos)\n\n![demo playground](/playground.png)\n\n---\n### Documentation\n\n- [What domvm Is Not](#what-domvm-is-not)\n- [Builds](#builds)\n- [Changelog](#changelog)\n- [Tests](#tests)\n- [Installation](#usage)\n- [DEVMODE](#devmode)\n- [Templates](#templates)\n- [Views](#views)\n- [Parents \u0026 Roots](#parents--roots)\n- [Sub-views vs Sub-templates](#sub-views-vs-sub-templates)\n- [Event Listeners](#event-listeners)\n- [Autoredraw](#autoredraw)\n- [Streams](#streams)\n- [Refs \u0026 Data](#refs--data)\n- [Keys \u0026 DOM Recycling](#keys--dom-recycling)\n- [Hello World++](#hello-world)\n- [Emit System](#emit-system)\n- [Lifecycle Hooks](#lifecycle-hooks)\n- [Third-party Integration](#third-party-integration)\n- [Extending ViewModel \u0026 VNode](#extending-viewmodel--vnode)\n- [createContext](#createcontext)\n- [Isomorphism \u0026 SSR](#isomorphism--ssr)\n- [Optimizations](#optimizations)\n- WIP: https://github.com/domvm/domvm/issues/156\n\n---\n### What domvm Is Not\n\nAs a view layer, domvm does not include some things you would find in a larger framework.\nThis gives you the freedom to choose libs you already know or prefer for common tasks.\ndomvm provides a small, common surface for integration of routers, streams and immutable libs.\nSome minimalist libs that work well:\n\n- Routing: [domvm-router](https://github.com/domvm/domvm-router), [riot/route](https://github.com/riot/route), [rlite](https://github.com/chrisdavies/rlite), [navigo](https://github.com/krasimir/navigo)\n- Ajax/fetch/XHR: [xr](https://github.com/radiosilence/xr), [alite](https://github.com/chrisdavies/alite)\n- Streams: [flyd](https://github.com/paldepind/flyd), [xstream](https://github.com/staltz/xstream)\n- Immutable stores: [Freezer](https://github.com/arqex/freezer), [MobX](https://github.com/mobxjs/mobx)\n- Vendor prefixing: [cssprefix](https://github.com/anseki/cssprefix), [prefixfree](https://github.com/LeaVerou/prefixfree)\n- CSS-in-JS: [linaria](https://github.com/callstack/linaria), [stylis.js](https://github.com/thysultan/stylis.js), [j2c](https://github.com/j2css/j2c), [emotion](https://github.com/tkh44/emotion), [oh boy...](https://github.com/MicheleBertoli/css-in-js)\n\nMany [/demos](/demos) are examples of how to use these libs in your apps.\n\n---\n### Builds\n\ndomvm comes in [several builds](https://github.com/domvm/domvm/tree/master/dist) of increasing size and features. The `nano` build is a good starting point and is sufficient for most cases.\n\n---\n### Changelog\n\nChanges between versions are documented in [Releases](https://github.com/domvm/domvm/releases).\n\n---\n### Tests\n\n- Tests run in a browser: https://domvm.github.io/domvm/test/\n- Coverage reports are generated via `npm run covtest \u0026\u0026 npm run covreport`\n- Current coverage is [85% - 90%](/test/coverage.txt)\n\n---\n### Installation\n\n**Browser**\n\n```html\n\u003cscript src=\"dist/nano/domvm.nano.iife.min.js\"\u003e\u003c/script\u003e\n```\n\n**Node**\n\n```js\nvar domvm = require(\"domvm\");   // the \"full\" build\n```\n\n---\n### DEVMODE\n\nIf you're new to domvm, the [dev build](/dist/dev/domvm.dev.js) is recommended for development \u0026 learning to avoid common mistakes; watch the console for warnings and advice.\n\nThere are a couple config options:\n\n- `domvm.DEVMODE.mutations = false` will disable DOM mutation logging.\n- `domvm.DEVMODE.warnings = false` will disable all warnings.\n- `domvm.DEVMODE.verbose = false` will suppress the explanations, but still leave the error names \u0026 object info.\n- `domvm.DEVMODE.UNKEYED_INPUT = false` will disable only these warnings. The full list can be found in [devmode.js](/src/view/addons/devmode.js).\n\nDue to the runtime nature of DEVMODE heuristics, some warnings may be false positives (where the observed behavior is intentional). If you feel an error message can be improved, open an issue!\n\nWhile not DEVMODE-specific, you may find it useful to toggle always-sychronous redraw during testing and benchmarks:\n\n```js\ndomvm.cfg({\n    syncRedraw: true\n});\n```\n\n---\n### Templates\n\nMost of your domvm code will consist of templates for creating virtual-dom trees, which in turn are used to render and redraw the DOM.\ndomvm exposes several factory functions to get this done. Commonly this is called [hyperscript](https://github.com/hyperhype/hyperscript).\n\nFor convenience, we'll alias each factory function with a short variable:\n\n```js\nvar el = domvm.defineElement,\n    tx = domvm.defineText,\n    cm = domvm.defineComment,\n    sv = domvm.defineSvgElement,\n    vw = domvm.defineView,\n    iv = domvm.injectView,\n    ie = domvm.injectElement,\n    cv = domvm.createView;\n```\n\nUsing `defineText` is not required since domvm will convert all numbers and strings into `defineText` vnodes automatically.\n\nBelow is a dense reference of most template semantics.\n\n```js\nel(\"p\", \"Hello\")                                            // plain tags\nel(\"textarea[rows=10]#foo.bar.baz\", \"Hello\")                // attr, id \u0026 class shorthands\nel(\".kitty\", \"Hello\")                                       // \"div\" can be omitted from tags\n\nel(\"input\",  {type: \"checkbox\",    checked: true})          // boolean attrs\nel(\"input\",  {type: \"checkbox\", \".checked\": true})          // set property instead of attr\n\nel(\"button\", {onclick: myFn}, \"Hello\")                      // event handlers\nel(\"button\", {onclick: [myFn, arg1, arg2]}, \"Hello\")        // parameterized\n\nel(\"p\",      {style: \"font-size: 10pt;\"}, \"Hello\")          // style can be a string\nel(\"p\",      {style: {fontSize: \"10pt\"}}, \"Hello\")          // or an object (camelCase only)\nel(\"div\",    {style: {width: 35}},        \"Hello\")          // \"px\" will be added when needed\n\nel(\"h1\", [                                                  // attrs object is optional\n    el(\"em\", \"Important!\"),\n    \"foo\", 123,                                             // plain values\n    ie(myElement),                                          // inject existing DOM nodes\n    el(\"br\"),                                               // void tags without content\n    \"\", [], null, undefined, false,                         // these will be auto-removed\n    NaN, true, {}, Infinity,                                // these will be coerced to strings\n    [                                                       // nested arrays will get flattened\n        el(\".foo\", {class: \"bar\"}, [                        // short \u0026 attr class get merged: .foo.bar\n            \"Baz\",\n            el(\"hr\"),\n        ])\n    ],\n])\n\nel(\"#ui\", [\n    vw(NavBarView, navbar),                                 // sub-view w/data\n    vw(PanelView, panel, \"panelA\"),                         // sub-view w/data \u0026 key\n    iv(someOtherVM, newData),                               // injected external ViewModel\n])\n\n// special _* props\n\nel(\"p\", {_key: \"myParag\"}, \"Some text\")                     // keyed nodes\nel(\"p\", {_data: {foo: 123}}, \"Some text\")                   // per-node data (faster than attr)\n\nel(\"p\", {_ref: \"myParag\"}, \"Some text\")                     // named refs (vm.refs.myParag)\nel(\"p\", {_ref: \"pets.james\"}, \"Some text\")                  // namespaced (vm.refs.pets.james)\n\nel(\"p\", {_hooks: {willRemove: ...}}, \"Some text\")           // lifecycle hooks\n\nel(\"div\", {_flags: ...}, \"Some text\")                       // optimization flags\n```\n\n#### Spread children\n\n`micro`+ builds additionally provide two factories for defining child elements using a `...children` spread rather than an explicit array.\n\n```js\nvar el = domvm.defineElementSpread,\n    sv = domvm.defineSvgElementSpread;\n\nel(\"ul\",\n    el(\"li\", 1),\n    el(\"li\", 2),\n    el(\"li\", 3)\n);\n```\n\n#### JSX\n\nWhile not all of domvm's features can be accommodated by JSX syntax, it's possible to cover a fairly large subset via a `defineElementSpread` pragma.\nPlease refer to demos and examples in the [JSX wiki](https://github.com/domvm/domvm/wiki/JSX).\n\n---\n### Views\n\nWhat React calls \"components\", domvm calls \"views\".\nA view definition can be a plain object or a named closure (for isolated working scope, internal view state or helper functions).\nThe closure must return a template-generating `render` function or an object containing the same:\n\n\u003c!--\nHowever, domvm's views can be initialized both imperatively and declaratively prior to being composed within other views or being rendered to the DOM.\nThis opens the door to much more interesting architectural patterns when needed, without resorting to non-idiomatic framework hacks.\n--\u003e\n\n```js\nvar el = domvm.defineElement;\n\nfunction MyView(vm) {                                       // named view closure\n    return function() {                                         // render()\n        return el(\"div\", \"Hello World!\");                           // template\n    };\n}\n\nfunction YourView(vm) {\n    return {\n        render: function() {\n            return el(\"div\", \"Hello World!\");\n        }\n    };\n}\n\nvar SomeView = {\n    init: function(vm) {\n        // ...\n    },\n    render: function() {\n        return el(\"div\", \"Hello World!\");\n    }\n};\n```\n\nViews can accept external `data` to render (à la React's `props`):\n\n```js\nfunction MyView(vm) {\n    return function(vm, data) {\n        return el(\"div\", \"Hello \" + data.firstName + \"!\");\n    };\n}\n```\n\n`vm` is this views's `ViewModel`; it's the created instance of `MyView` and serves the same purpose as `this` within an ES6 React component.\nThe `vm` provides the control surface/API to this view and can expose a user-defined API for external view manipulation.\n\nRendering a view to the DOM is called mounting. To mount a top-level view, we create it from a view definition:\n\n```js\nvar data = {\n    firstName: \"Leon\"\n};\n\nvar vm = cv(MyView, data);\n\nvm.mount(document.body);            // appends into target\n```\n\nBy default, `.mount(container)` will append the view into the container. Alternatively, to use an existing placeholder element:\n\n```js\nvar placeholder = document.getElementById(\"widget\");\n\nvm.mount(placeholder, true);        // empties \u0026 assimilates placeholder\n```\n\nWhen your data changes, you can request to redraw the view, optionally passing a boolean `sync` flag to force a synchronous redraw.\n\n```js\nvm.redraw(sync);\n```\n\nIf you need to *replace* a view's data (as with immutable structures), you should use `vm.update`, which will also redraw.\n\n```js\nvm.update(newData, sync);\n```\n\nViews can be nested either declaratively or by injecting an already-initialized view:\n\n```js\nvar el = domvm.defineElement,\n    vw = domvm.defineView,\n    iv = domvm.injectView;\n\nfunction ViewA(vm) {\n    return function(vm, dataA) {\n        return el(\"div\", [\n            el(\"strong\", dataA.test),\n            vw(ViewB, dataA.dataB),               // implicit/declarative view\n            iv(data.viewC),                       // injected explicit view\n        ]);\n    };\n}\n\nfunction ViewB(vm) {\n    return function(vm, dataB) {\n        return el(\"em\", dataB.test2);\n    };\n}\n\nfunction ViewC(vm) {\n    return function(vm, dataC) {\n        return el(\"em\", dataC.test3);\n    };\n}\n\nvar dataC = {\n    test3: 789,\n};\n\nvar dataA = {\n    test: 123,\n    dataB: {\n        test2: 456,\n    },\n    viewC: cv(ViewC, dataC),\n};\n\nvar vmA = cv(ViewA, dataA).mount(document.body);\n```\n\nNotes:\n\n- `render()` must return a single dom vnode. There is no support yet for views returning fragments/arrays, other views or `null`. These capabilities do not add much value to domvm's API (see [Issue #207](https://github.com/domvm/domvm/issues/207)).\n\n#### Options\n\n`cv` and `vw` have four arguments: `(view, data, key, opts)`.\nThe fourth `opts` arg can be used to pass in any additional data into the view constructor/init without having to cram it into `data`.\nSeveral reserved options are handled automatically by domvm that correspond to existing `vm.cfg({...})` options (documented in other sections):\n\n- `init` (same as using `{init:...}` in views defs)\n- `diff`\n- `hooks`\n- `onevent`\n- `onemit`\n\nThis can simplify sub-view internals when externally-defined opts are passed in, avoiding some boilerplate inside views, eg. `vm.cfg({hooks: opts.hooks})`.\n\n#### ES6/ES2015 Classes\n\nClass views are not supported because domvm avoids use of `this` in its public APIs. To keep all functions pure, each is invoked with a `vm` argument. Not only does this compress better, but also avoids much ambiguity. Everything that can be done with classes can be done better with domvm's plain object views, ES6 modules, `Object.assign()` and/or `Object.create()`. See [#194](https://github.com/domvm/domvm/issues/194#issuecomment-352231381) \u0026 [#147](https://github.com/domvm/domvm/issues/147#issuecomment-307845459) for more details.\n\nTODO: create Wiki page showing ES6 class equivalents:\n\n- extend via ES6 module import \u0026 `Object.assign({}, base, current)`\n- super() via ES6 module import and passing vm instance\n- invoking additional \"methods\" via `vm.view.*` from handlers or view closure\n\n---\n### Parents \u0026 Roots\n\nYou can access any view's parent view via `vm.parent()` and the great granddaddy of any view hierarchy via `vm.root()` shortcut.\nSo, logically, to redraw the entire UI tree from any subview, invoke `vm.root().redraw()`.\nFor traversing the vtree, there's also `vm.body()` which gets the next level of descendant views (not necessarily direct children).\n`vnode.body` and `vnode.parent` complete the picture.\n\n---\n### Sub-views vs Sub-templates\n\nA core benefit of template composition is code reusability (DRY, component architecture). In domvm composition can be realized using either sub-views or sub-templates, often interchangeably. Sub-templates should generally be preferred over sub-views for the purposes of code reuse, keeping in mind that like sub-views, normal vnodes:\n\n- Can be keyed to prevent undesirable DOM reuse\n- Can subscribe to numerous lifecycle hooks\n- Can hold data, which can then be accessed from event handlers\n\nSub-views carry a bit of performance overhead and should be used when the following are needed:\n\n- Large building blocks\n- Complex private state\n- Numerous specific helper functions\n- Isolated redraw (as a perf optimization)\n- Synchronized redraw of disjoint views\n\nAs an example, the distinction can be discussed in terms of the [calendar demo](https://domvm.github.io/domvm/demos/playground/#calendar).\nIts implementation is a single monolithic view with internal sub-template generating functions.\nSome may prefer to split up the months into a sub-view called MonthView, which would bring the total view count to 13.\nOthers may be tempted to split each day into a DayView, but this would be a mistake as it would create 504 + 12 + 1 views, each incuring a slight performance hit for no reason.\nOn the other hand, if you have a full-page month view with 31 days and multiple interactive events in the day cells, then 31 sub-views are well-justified.\n\nThe general advice is, restrict your views to complex, building-block-level, stateful components and use sub-template generators for readability and DRY purposes; a button should not be a view.\n\n---\n### Event Listeners\n\n**Basic** listeners are bound directly and are defined by plain functions. Like vanilla DOM, they receive only the event as an argument. If you need high performance such as `mousemove`, `drag`, `scroll` or other events, use basic listeners.\n\n```js\nfunction filter(e) {\n    // ...\n}\n\nel(\"input\", {oninput: filter});\n```\n\n**Parameterized** listeners are defined using arrays and executed by a single, document-level, capturing proxy handler. They:\n\n- Can pass through additional args and receive `(...args, e, node, vm, data)`\n- Will invoke global and vm-level `onevent` callbacks\n- Will call `e.preventDefault()` \u0026 `e.stopPropagation()` if `false` is returned\n\n```js\nfunction cellClick(foo, bar, e, node, vm, data) {}\n\nel(\"td\", {onclick: [cellClick, \"foo\", \"bar\"]}, \"moo\");\n```\n\nView-level and global `onevent` callbacks:\n\n```js\n// global\ndomvm.cfg({\n    onevent: function(e, node, vm, data, args) {\n        // ...\n    }\n});\n\n// vm-level\nvm.cfg({\n    onevent: function(e, node, vm, data, args) {\n        // ...\n    }\n});\n```\n\n---\n### Autoredraw\n\nIs calling `vm.redraw()` everywhere a nuisance to you?\n\nThere's an easy way to implement autoredraw yourself via a global or vm-level `onevent` which fires after all **parameterized** [event listeners](#event-listeners).\nThe [onevent demo](https://domvm.github.io/domvm/demos/playground/#onevent) demonstrates a basic full app autoredraw:\n\n```js\ndomvm.cfg({\n    onevent: function(e, node, vm, data, args) {\n        vm.root().redraw();\n    }\n});\n```\n\nYou can get as creative as you want, including adding your own semantics to prevent redraw on a case-by-case basis by setting and checking for `e.redraw = false`.\nOr maybe having a Promise piggyback on `e.redraw = new Promise(...)` that will resolve upon deep data being fetched.\nYou can maybe implement filtering by event type so that a flood of `mousemove` events, doesnt result in a redraw flood. Etc..\n\n---\n### Streams\n\nAnother way to implement view reactivity and autoredraw is by using streams. By providing streams to your templates rather than values, views will autoredraw whenever streams change. domvm does not provide its own stream implementation but instead exposes a simple adapter to plug in your favorite stream lib.\n\ndomvm's templates support streams in the following contexts:\n\n- view data: `vw(MyView, dataStream...)` and `cv(MyView, dataStream...)`\n- simple body: `el(\"#total\", cartTotalStream)`\n- attr value: `el(\"input[type=checkbox]\", {checked: checkedStream})`\n- css value: `el(\"div\", {style: {background: colorStream}})`\n\nA stream adapter for [flyd](https://github.com/paldepind/flyd) looks like this:\n\n```js\ndomvm.cfg({\n    stream: {\n        val: function(v, accum) {\n            if (flyd.isStream(v)) {\n                accum.push(v);\n                return v();\n            }\n            else\n                return v;\n        },\n        on: function(accum, vm) {\n            let calls = 0;\n\n            const s = flyd.combine(function() {\n                if (++calls == 2) {\n                    vm.redraw();\n                    s.end(true);\n                }\n            }, accum);\n\n            return s;\n        },\n        off: function(s) {\n            s.end(true);\n        }\n    }\n});\n```\n\n- `val` accepts any value and, if that value is a stream, appends it to the provided accumulator array; then returns the stream's current value, else the original object. called multiple times per redraw.\n- `on` accepts the accumulater array (now filled with streams) and returns a dependent stream that will invoke `vm.redraw()` once and end (ignoring initial stream creation). this can also be implemented via a `.drop(1).take(1).map(...)` pattern, if supported by your stream lib (see https://github.com/paldepind/flyd/issues/176#issuecomment-385141469). called once per redraw.\n- `off` accepts the dependent stream created by `on` and ends it. called once per unmount.\n\nAn extensive demo can be found in the [streams playground](https://domvm.github.io/domvm/demos/playground/#streams).\n\nNotes:\n\n- Streams must never create, cache or reuse domvm's vnodes (`defineElement()`, etc.) since this *will* cause memory leaks and major bugs.\n\n---\n### Refs \u0026 Data\n\nLike React, it's possible to access the live DOM from event listeners, etc via `refs`. In addition, domvm's refs can be namespaced:\n\n```js\nfunction View(vm) {\n    function sayPet(e) {\n        var vnode = vm.refs.pets.fluffy;\n        alert(fluffy.el.value);\n    }\n\n    return function() {\n        return el(\"form\", [\n            el(\"button\", {onclick: sayPet}, \"Say Pet!\"),\n            el(\"input\", {_ref: \"pets.fluffy\"}),\n        ]);\n    };\n}\n```\n\nVNodes can hold arbitrary data, which obviates the need for slow `data-*` attributes and keeps your DOM clean:\n\n```js\nfunction View(vm) {\n    function clickMe(e, node) {\n        console.log(node.data.myVal);\n    }\n\n    return function() {\n        return el(\"form\", [\n            el(\"button\", {onclick: [clickMe], _data: {myVal: 123}}, \"Click!\"),\n        ]);\n    };\n}\n```\n\nNotes:\n\n`vm.state` \u0026 `vm.api` are userspace-reserved and initialized to `null`.\nYou may use them to expose view state or view methods as you see fit without fear of collisions with internal domvm properties \u0026 methods (present or future).\n\n---\n### Keys \u0026 DOM Recycling\n\nLike React [and any dom-reusing lib worth its salt], domvm sometimes needs keys to assure you of deterministic DOM recycling - ensuring similar sibling DOM elements are not reused in unpredictable ways during mutation.\nIn contrast to other libs, keys in domvm are more flexible and often already implicit.\n\n- Both vnodes and views may be keyed: `el('div', {_key: \"a\"})`, `vw(MyView, {...}, \"a\")`\n- Keys do not need to be strings; they can be numbers, objects or functions\n- Not all siblings need to be keyed - just those you need determinism for\n- Attrs and special attrs that should be unique anyhow will establish keys:\n  - `_key` (explicit)\n  - `_ref` (must be unique within a view)\n  - `id` (should already be unique per document)\n  - `name` or `name`+`value` for radios and checkboxes (should already be unique per form)\n\n---\n### Hello World++\n\n**Try it:** https://domvm.github.io/domvm/demos/playground/#stepper1\n\n```js\nvar el = domvm.defineElement;                       // element VNode creator\n\nfunction StepperView(vm, stepper) {                 // view closure (called once during init)\n    function add(num) {\n        stepper.value += num;\n        vm.redraw();\n    }\n\n    function set(e) {\n        stepper.value = +e.target.value;\n    }\n\n    return function() {                             // template renderer (called on each redraw)\n        return el(\"#stepper\", [\n            el(\"button\", {onclick: [add, -1]}, \"-\"),\n            el(\"input[type=number]\", {value: stepper.value, oninput: set}),\n            el(\"button\", {onclick: [add, +1]}, \"+\"),\n        ]);\n    };\n}\n\nvar stepper = {                                     // some external model/data/state\n    value: 1\n};\n\nvar vm = cv(StepperView, stepper);    // create ViewModel, passing model\n\nvm.mount(document.body);                            // mount into document\n```\n\nThe above example is simple and decoupled. It provides a UI to modify our stepper object which itself needs no awareness of any visual representation.\nBut what if we want to modify the stepper using an API and still have the UI reflect these changes. For this we need to add some coupling.\nOne way to accomplish this is to beef up our stepper with an API and give it awareness of its view(s) which it will redraw.\nThe end result is a lightly-coupled domain model that:\n\n1. Holds state, as needed.\n2. Exposes an API that can be used programmatically and is UI-consistent.\n3. Exposes view(s) which utilize the API and can be composed within other views.\n\nIt is *this* fully capable, view-augmented domain model that domvm's author considers a truely reusable \"component\".\n\n**Try it:** https://domvm.github.io/domvm/demos/playground/#stepper2\n\n```js\nvar el = domvm.defineElement;\n\nfunction Stepper() {\n    this.value = 1;\n\n    this.add = function(num) {\n        this.value += num;\n        this.view.redraw();\n    };\n\n    this.set = function(num) {\n        this.value = +num;\n        this.view.redraw();\n    };\n\n    this.view = cv(StepperView, this);\n}\n\nfunction StepperView(vm, stepper) {\n    function add(val) {\n        stepper.add(val);\n    }\n\n    function set(e) {\n        stepper.set(e.target.value);\n    }\n\n    return function() {\n        return el(\"#stepper\", [\n            el(\"button\", {onclick: [add, -1]}, \"-\"),\n            el(\"input[type=number]\", {value: stepper.value, oninput: set}),\n            el(\"button\", {onclick: [add, +1]}, \"+\"),\n        ]);\n    };\n}\n\nvar stepper = new Stepper();\n\nstepper.view.mount(document.body);\n\n// now let's use the stepper's API to increment\nvar i = 0;\nvar it = setInterval(function() {\n    stepper.add(1);\n\n    if (i++ == 20)\n        clearInterval(it);\n}, 250);\n```\n\n---\n### Emit System\n\nEmit is similar to DOM events, but works explicitly within the vdom tree and is user-triggerd.\nCalling `vm.emit(evName, ...args)` on a view will trigger an event that bubbles up through the view hierarchy.\nWhen an emit listener is matched, it is invoked and the bubbling stops.\nLike parameterized events, the `vm` and `data` args reflect the originating view of the event.\n\n```js\n// listen\nvm.cfg({\n    onemit: {\n        myEvent: function(arg1, arg2, vm, data) {\n            // ... do stuff\n        }\n    }\n});\n\n// trigger\nvm.emit(\"myEvent\", arg1, arg2);\n```\n\nThere is also a global emit listener which fires for all emit events.\n\n```js\ndomvm.cfg({\n    onemit: {\n        myEvent: function(arg1, arg2, vm, data) {\n            // ... do stuff\n        }\n    }\n});\n```\n\n---\n### Lifecycle Hooks\n\n**Demo:** [lifecycle-hooks](https://domvm.github.io/domvm/demos/playground/#lifecycle-hooks) different hooks animate in/out with different colors.\n\n#### Node-level\n\nUsage: `el(\"div\", {_key: \"...\", _hooks: {...}}, \"Hello\")`\n\n- `will`/`didInsert(newNode)` - initial insert\n- `will`/`didRecycle(oldNode, newNode)` - reuse \u0026 patch\n- `will`/`didReinsert(newNode)` - detach \u0026 move\n- `will`/`didRemove(oldNode)`\n\nWhile not required, it is strongly advised that your hook-handling vnodes are [uniquely keyed](#keys--dom-recycling) as shown above, to ensure deterministic DOM recycling and hook invocation.\n\n#### View-level\n\nUsage: `vm.cfg({hooks: {willMount: ...}})` or `return {render: ..., hooks: {willMount: ...}}`\n\n- `willUpdate(vm, data)` - before views's data is replaced\n- `will`/`didRedraw(vm, data)`\n- `will`/`didMount(vm, data)` - dom insertion\n- `will`/`didUnmount(vm, data)` - dom removal\n\nNotes:\n\n- `did*` hooks fire after a forced DOM repaint.\n- `willRemove` \u0026 `willUnmount` hooks can return a Promise to delay the removal/unmounting allowing you to CSS transition, etc.\n\n---\n### Third-Party Integration\n\nSeveral facilities exist to interoperate with third-party libraries.\n\n#### Non-interference\n\nFirst, domvm will not touch attrs that are not specified or managed in your templates.\nIn addition, elements not created by domvm will be ignored by the reconciler, as long as their ancestors continue to remain in the DOM.\nHowever, the position of any inserted third-party DOM element amongst its siblings cannot be guaranteed.\n\n#### will/didInsert Hooks\n\nYou can use normal DOM methods to insert elements into elements managed by domvm by using [will/didInsert hooks](#lifecycle-hooks).\nSee the [Embed Tweets](https://domvm.github.io/domvm/demos/playground/#embed-tweets) demo.\n\n#### injectElement\n\n`domvm.injectElement(elem)` allows you to insert any already-created third-party element into a template, deterministically manage its position and fire lifecycle hooks.\n\n#### innerHTML\n\nYou can set the innerHTML of an element created by domvm using a normal `.`-prefixed property attribute:\n\n```js\nel(\"div\", {\".innerHTML\": \"\u003cp\u003eFoo\u003c/p\u003e\"});\n```\n\nHowever, it's **strongly recommended** for security reasons to use `domvm.injectElement()` after parsing the html string via the browser's native [DOMParser API](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser).\n\n---\n### Extending ViewModel \u0026 VNode\n\nIf needed, you may extend some of domvm's internal class prototypes in your app to add helper methods, etc.\nThe following are available:\n\n- `domvm.ViewModel.prototype`\n- `domvm.VNode.prototype`\n\n---\n### createContext\n\n[This demo](https://domvm.github.io/domvm/demos/playground/#context) in the playground shows how to implement `VNode.prototype.pull()` - a close analog to React's `createContext` - a feature designed to alleviate [prop drilling](https://daveceddia.com/context-api-vs-redux/) without resorting to globals.\n\n---\n### Isomorphism \u0026 SSR\n\nLike React's `renderToString`, domvm can generate html and then hydrate it on the client.\nIn `server` \u0026 `full` builds, `vm.html()` can generate html.\nIn `client` \u0026 `full` builds, `vm.attach(target)` should be used to hydrate the rendered DOM.\n\n```js\nvar el = domvm.defineElement;\n\nfunction View() {\n    function sayHi(e) {\n        alert(\"Hi!\");\n    }\n\n    return function(vm, data) {\n        return el(\"body\", {onclick: sayHi}, \"Hello \" + data.name);\n    }\n}\n\nvar data = {name: \"Leon\"};\n\n// return this generated \u003cbody\u003eHello Leon\u003c/body\u003e from the server\nvar html = cv(View, data).html();\n\n// then hydrate on the client to bind event handlers, etc.\nvar vm = cv(View, data).attach(document.body);\n```\n\nNotes:\n\n- `target` must be the DOM element which corresponds to the top-level/root virtual node of the view you're attaching\n- Whitespace in the generated HTML is significant; indented, formatted or pretty-printed markup will *not* attach properly\n- The HTML parsing spec requires that an implicit `\u003ctbody\u003e` DOM node is created if `\u003ctr\u003e`s are nested directly within `\u003ctable\u003e`. This causes problems when no corresponding `\u003ctbody\u003e` is defined in the vtree. Therefore, when attaching tables via SSR, it is necessary to explicitly define `\u003ctbody\u003e` vnodes via `el(\"tbody\",...)` and avoid creating `\u003ctr\u003e` children of `\u003ctable\u003e` nodes. See [Issue #192](https://github.com/domvm/domvm/issues/192#issuecomment-350600764)\n\n---\n### Optimizations\n\nBefore you continue...\n\n- Recognize that domvm with no optimizations is able to rebuild and diff a full vtree and reconcile a DOM of 3,000 nodes in \u003c 1.5ms. See [0% dbmonster bench](https://domvm.github.io/domvm/demos/bench/dbmonster/).\n- Make sure you've read and understood [Sub-views vs Sub-templates](#sub-views-vs-sub-templates).\n- Ensure you're not manually caching \u0026 reusing old vnodes or holding references to them in your app code. They're meant to be discarded by the GC; let them go.\n- Profile your code to be certain that domvm is the bottleneck and not something else in your app. e.g. [Issue #173](https://github.com/domvm/domvm/issues/173).\n  - When using the DEVMODE build, are the logged DOM operations close to what you expect?\n  - Are you rendering an enormous DOM that's already difficult for browsers to deal with? Run `document.querySelectorAll(\"*\").length` in the devtools console. Live node counts over 10,000 should be evaluated for refactoring.\n  - Are you calling `vm.redraw()` from unthrottled event listeners such as `mousemove`, `scroll`, `resize`, `drag`, `touchmove`?\n  - Are you using `requestAnimationFrame()` where appropriate?\n  - Are you using event delegation where appropriate to avoid binding thousands of event listeners?\n  - Are you properly using CSS3 transforms, transitions and animation to do effects and animations rather than calling `vm.redraw()` at 60fps?\n  - Do thousands of nodes or views have lifecycle hooks?\n- Finally, understand that optimizations can only reduce the work needed to regenerate the vtree, diff and reconcile the DOM; the performed DOM operations will always be identical and near-optimal. In the vast majority of cases, the lowest-hanging fruit will be in the above advice.\n\nStill here? You must be a glutton for punishment, hell-bent on rendering enormous grids or tabular data ;) Very well, then...\n\n#### Isolated Redraw\n\nLet's start with the obvious.\nDo you need to redraw everything or just a sub-view?\n`vm.redraw()` lets you to redraw only specific views.\n\n#### Flatten Nested Arrays\n\nWhile domvm will flatten nested arrays in your templates, you may get a small boost by doing it yourself via `Array.concat()` before returning your templates from `render()`.\n\n#### Old VTree Reuse\n\nIf a view is static or is known to not have changed since the last redraw, `render()` can return the existing old vnode to short-circuit the vtree regeneration, diffing and dom reconciliation.\n\n```js\nfunction View(vm) {\n    return function(vm) {\n        if (noChanges)\n            return vm.node;\n        else\n            return el(\"div\", \"Hello World\");\n    };\n}\n```\n\nThe mechanism for determining if changes may exist is up to you, including caching old data within the closure and doing diffing on each redraw. Speaking of diffing...\n\n#### View Change Assessment\n\nSimilar to React's `shouldComponentUpdate()`, `vm.cfg({diff:...})` is able to short-circuit redraw calls.\nIt provides a caching layer that does shallow comparison before every `render()` call and may return an array or object to shallow-compare for changes.\n\n```js\nfunction View(vm) {\n    vm.cfg({\n        diff: function(vm, data) {\n            return [data.foo.bar, data.baz];\n        }\n    });\n\n    return function(vm, data) {\n        return el(\"div\", {class: data.baz}, \"Hello World, \" + data.foo.bar);\n    };\n}\n```\n\n`diff` may also return a plain value that's the result of your own DIY comparison, but is most useful for static views where no complex diff is required at all and a simple `===` will suffice.\n\nWith a plain-object view, it looks like this:\n\n```js\nvar StaticView = {\n    diff: function(vm, data) {\n        return 0;\n    },\n    render: function(vm, data) {},\n};\n```\n\nNotes:\n\nIf you intend to do a simple diff of an object by its identity, then it's preferable to return it wrapped in an array to avoid domvm also diffing all of its enumerable keys when `oldObj !== newObj`.\nThis is a micro-optimization and will not affect the resulting behavior.\nAlso, see [Issue #148](https://github.com/domvm/domvm/issues/148).\n\n#### VNode Patching\n\nVNodes can be patched on an individual basis, and this can be done without having to patch the children, too.\nThis makes mutating attributes, classes and styles much faster when the children have no changes.\n\n```js\nvar vDiv = el(\"div\", {class: \"foo\", style: \"color: red;\"}, [\n    el(\"div\", \"Mooo\")\n]);\n\nvDiv.patch({class: \"bar\", style: \"color: blue;\"});\n```\n\nDOM patching can also be done via a full vnode rebuild:\n\n```js\nfunction makeDiv(klass) {\n    return el(\"div\", {class: klass, style: \"color: red;\"}, [\n        el(\"div\", \"Mooo\")\n    ]);\n}\n\nvar vDiv = makeDiv(\"foo\");\n\nvDiv.patch(makeDiv(\"bar\"));\n```\n\n`vnode.patch(vnode|attrs, doRepaint)` can be called with a `doRepaint = true` arg to force a DOM update.\nThis is typically useful in cases when a CSS transition must start from a new state and should not be batched with any followup `patch()` calls.\nYou can see this used in the [lifecycle-hooks demo](https://domvm.github.io/domvm/demos/playground/#lifecycle-hooks).\n\n#### Fixed Structures\n\nLet's say you have a bench like dbmonster in this repo.\nIt's a huge grid that has a fixed structure. No elements are ever inserted, removed or reordered.\nIn fact, the only mutations that ever happen are `textContent` of the cells and patching of attrs like `class`, and `style`.\n\nThere's a lot of work that domvm's DOM reconciler can avoid doing here, but you have to tell it that the structure of the DOM will not change.\nThis is accomplished with a `domvm.FIXED_BODY` vnode flag on all nodes whose `body` will never change in shallow structure.\n\n```js\nvar Table = {\n    render: function() {\n        return el(\"table\", {_flags: domvm.FIXED_BODY}, [\n            el(\"tr\", {_flags: domvm.FIXED_BODY}, [\n                el(\"td\", {_flags: domvm.FIXED_BODY}, \"Hello\"),\n                el(\"td\", {_flags: domvm.FIXED_BODY}, \"World\"),\n            ])\n        ]);\n    }\n};\n```\n\nThis is rather tedious, so there's an easier way to get it done. The fourth argument to `defineElement()` is `flags`, so we create an additional element factory and use it normally:\n\n```js\nfunction fel(tag, arg1, arg2) {\n    return domvm.defineElement(tag, arg1, arg2, domvm.FIXED_BODY);\n}\n\nvar Table = {\n    render: function() {\n        return fel(\"table\", [\n            fel(\"tr\", [\n                fel(\"td\", \"Hello\"),\n                fel(\"td\", \"World\"),\n            ])\n        ]);\n    }\n};\n```\n\n#### Fully-Keyed Lists\n\nIn domvm, the term \"list\", implies that child elements are shallow-homogenous (the same views or elements with the same DOM tags).\ndomvm does not require that child arrays are fully-keyed, but if they *are*, you can slightly simplify domvm's job of matching up the old vtree by *only* testing keys.\nThis is done by setting the `domvm.KEYED_LIST` vnode flag on the parent.\n\n#### Lazy Lists\n\nLazy lists allow for old vtree reuse in the absence of changes at the vnode level without having to refactor into more expensive views that return existing vnodes.\nThis mostly saves on memory allocations. Lazy lists may be created for both, keyed and non-keyed lists.\nTo these lists, you will need:\n\n- A list-item generating function, which you should have anyways as the callback passed to a `Array.map` iterator. Only `defineElement()` and `defineView()` nodes are currently supported.\n- For keyed lists, a key-generating function that allows for matching up proper items in the old vtree.\n- A `diff` function which allows a lazy list to determine if an item has changed and needs a new vnode generated or can have its vnode reused.\n- Create a `domvm.list()` iterator/generator using the above.\n- Provide `{_key: key}` for `defineElement()` vnodes or `vw(ItemView, item, key)` for `defineView()` vnodes.\n\nWhile a bit involved, the resulting code is quite terse and not as daunting as it sounds: https://domvm.github.io/domvm/demos/playground/#lazy-list\n\n#### Special Attrs\n\nVNodes use `attrs` objects to also pass special properties: `_key`, `_ref`, `_hooks`, `_data`, `_flags`. If you only need to pass one of these special options to a vnode and have not actual attributes to set, you can avoid allocating attrs objects by assigning them directly to the created vnodes.\n\nInstead of creating attrs objects just to set a key:\n\n```js\nel(\"ul\", [\n    el(\"li\", {_key: \"foo\"}, \"hello\"),\n    el(\"li\", {_key: \"bar\"}, \"world\"),\n])\n```\n\nCreate a helper to set the key directly:\n\n```js\nfunction keyed(key, vnode) {\n    vnode.key = key;\n    return vnode;\n}\n\nel(\"ul\", [\n    keyed(\"foo\", el(\"li\", \"hello\")),\n    keyed(\"bar\", el(\"li\", \"world\")),\n])\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/domvm.github.io%2Fdomvm%2F","html_url":"https://awesome.ecosyste.ms/projects/domvm.github.io%2Fdomvm%2F","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/domvm.github.io%2Fdomvm%2F/lists"}