{"id":13452009,"url":"https://github.com/wavesoft/dot-dom","last_synced_at":"2025-04-04T11:14:16.157Z","repository":{"id":46829703,"uuid":"80664559","full_name":"wavesoft/dot-dom","owner":"wavesoft","description":".dom is a tiny (512 byte) template engine that uses virtual DOM and some of react principles","archived":false,"fork":false,"pushed_at":"2021-09-23T06:58:44.000Z","size":1423,"stargazers_count":809,"open_issues_count":14,"forks_count":45,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-28T10:08:17.632Z","etag":null,"topics":["es6","es6-javascript","iot","javascript","template-engine","virtual-dom","web"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wavesoft.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":"2017-02-01T21:09:54.000Z","updated_at":"2025-02-23T21:34:28.000Z","dependencies_parsed_at":"2022-08-27T18:52:12.501Z","dependency_job_id":null,"html_url":"https://github.com/wavesoft/dot-dom","commit_stats":null,"previous_names":["wavesoft/dotdom"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesoft%2Fdot-dom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesoft%2Fdot-dom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesoft%2Fdot-dom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesoft%2Fdot-dom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wavesoft","download_url":"https://codeload.github.com/wavesoft/dot-dom/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247166168,"owners_count":20894654,"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","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":["es6","es6-javascript","iot","javascript","template-engine","virtual-dom","web"],"created_at":"2024-07-31T07:01:09.894Z","updated_at":"2025-04-04T11:14:16.112Z","avatar_url":"https://github.com/wavesoft.png","language":"JavaScript","readme":"# .dom [![npm](https://badgen.net/npm/v/dot-dom)](https://www.npmjs.com/package/dot-dom) [![size](https://badgen.net/badgesize/normal/https://cdn.jsdelivr.net/npm/dot-dom@0.3.1/dist/dotdom.min.js.gz)](https://cdn.jsdelivr.net/npm/dot-dom@0.3.1/dist/dotdom.min.js.gz) [![install size](https://badgen.net/packagephobia/install/dot-dom)](https://packagephobia.now.sh/result?p=dot-dom) [![Build Status](https://badgen.net/travis/wavesoft/dot-dom)](https://travis-ci.org/wavesoft/dot-dom) [![Try it in codepen.io](https://img.shields.io/badge/Try%20it-codepen.io-blue.svg)](https://codepen.io/wavesoft/pen/wvwgOpz?editors=0010) [![Gitter](https://badges.gitter.im/wavesoft/dot-dom.svg)](https://gitter.im/wavesoft/dot-dom?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\n\u003e A tiny (512 byte) virtual DOM template engine for embedded projects\n\n| \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/edge.png\" alt=\"IE / Edge\" width=\"16px\" height=\"16px\" /\u003e IE / Edge | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/firefox.png\" alt=\"Firefox\" width=\"16px\" height=\"16px\" /\u003e Firefox | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome.png\" alt=\"Chrome\" width=\"16px\" height=\"16px\" /\u003e Chrome | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari.png\" alt=\"Safari\" width=\"16px\" height=\"16px\" /\u003e Safari | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/opera.png\" alt=\"Opera\" width=\"16px\" height=\"16px\" /\u003e Opera | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari-ios.png\" alt=\"iOS Safari\" width=\"16px\" height=\"16px\" /\u003e iOS Safari | \u003cimg src=\"https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome-android.png\" alt=\"Chrome for Android\" width=\"16px\" height=\"16px\" /\u003e Chrome for Android |\n| --------- | --------- | --------- | --------- | --------- | --------- | --------- |\n| Edge 14+ | 45+ | 49+ | 10+ | 37+ | 10.2+ | 55+\n\n**.dom** borrows some concepts from React.js (such as the re-usable Components and the Virtual DOM) and tries to replicate them with the smallest possible footprint, exploiting the ES6 javascript features.\n\nWhy? Because with such library you can create powerful GUIs in tight space environments, such as IoT devices, where saving even an extra byte actually matters!\n\n### Features\n\n* _Tiny by design_ : The library should never exceed the 512 bytes in size. The goal is not to have yet another template engine, but to have as many features as possible in 512 bytes. If a new feature is needed, an other must be sacraficed or the scope must be reduced.\n\n* _Built for the future_ : The library is heavily exploiting the ES6 specifications, meaning that it's **not** supported by older browsers. Currently it's supported by the 90% of the browsers in the market, but expect this to be close to 100% within the next year.\n\n* _Declarative_ : Describe your HTML DOM in a structured, natural manner, helping you create powerful yet readable user interfaces.\n\n* _Component-Oriented_ : Just like React.js, **.dom** promotes the use of functional components.\n\n* _\"Write less\" accelerators_ : The library API is designed specifically to have short function names and accelerators, allowing you to describe your views with less code.\n\n### Projects Using `.dom`\n\n* [Open Graph Image as a Service](https://github.com/styfle/og-image) - [demo](https://og-image.now.sh/)\n\nAre you using `.dom` in your project? Fork this repository and add yours on the list!\n\n\n## Installation\n\nFor minimum footprint, include `dotdom.min.js.gz` (512b) to your project.\n\n```html\n\u003cscript src=\"dotdom.min.js.gz\" /\u003e\n```\n\nAlternatively you can just include the minified version of the library directly before your script. Just copy-paste the [minified code](https://raw.githubusercontent.com/wavesoft/dot-dom/master/dotdom.min.js).\n\n## Examples\n\nIf you already know React.js, the following examples can help you understand how\nthe .dom primitives relate to React.\n\n#### 1. Plain DOM\n\nRendering a very simple DOM structure.\n\n\u003ctable width=\"100%\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eReact\u003c/th\u003e\n    \u003cth\u003e.dom\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nReactDOM.render(\n  React.createElement('div', null, 'Hello world'),\n  document.body\n);\n\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nR(\n  H('div', 'Hello world'),\n  document.body\n)\n\u003c/pre\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### 2. Stateless Component\n\nCreating a component on which you can pass properties.\n\n\u003ctable width=\"100%\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eReact\u003c/th\u003e\n    \u003cth\u003e.dom\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nfunction Hello(props) {\n    return React.createElement(\n      'div', null, `Hello ${props.toWhat}`\n    );\n  }\n\u003cbr /\u003e\nReactDOM.render(\n  React.createElement(\n    Hello, {toWhat: 'World'}, null\n  ),\n  document.body\n);\n\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nfunction Hello(props) {\n  return H('div', `Hello ${props.toWhat}`);\n}\n\u003cbr /\u003e\nR(\n  H(Hello, {toWhat: 'World'}),\n  document.body\n)\n\u003c/pre\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### 3. Stateful Component\n\nCreating components that can maintain their own state.\n\n\u003ctable width=\"100%\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eReact\u003c/th\u003e\n    \u003cth\u003e.dom\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nclass Clickable extends React.Component {\n  constructor() {\n    super(...arguments);\n    this.state = {\n      clicks: 0\n    };\n  }\n\u003cbr /\u003e\n  render() {\n    const {clicks} = this.state;\n\u003cbr /\u003e\n    return React.createElement(\n      'button', {\n        onClick() {\n          this.setState({clicks: clicks+1})\n        }\n      }, `Clicked ${clicks} times`\n    );\n  }\n}\n\u003cbr /\u003e\nReactDOM.render(\n  React.createElement('div', null,\n    React.createElement(Clickable, null, null),\n    React.createElement(Clickable, null, null)\n  ),\n  document.body\n);\n\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nfunction Clickable(props, state, setState) {\n  const {clicks=0} = state;\n\u003cbr /\u003e\n  return H('button',\n    {\n      onclick() {\n        setState({clicks: clicks+1})\n      }\n    },\n    `Clicked ${clicks} times`\n  );\n}\n\u003cbr /\u003e\nR(\n  H('div',\n    H(Clickable),\n    H(Clickable)\n  ),\n  document.body\n)\n\u003c/pre\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### 4. Life-Cycle Component Events\n\nThe component can also subscribe to life-cycle events:\n\n\u003ctable width=\"100%\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eReact\u003c/th\u003e\n    \u003cth\u003e.dom\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nclass WithLifeCycle extends React.Component {\n  constructor() {\n    super(...arguments);\n    this.state = {\n      mounted: \"no\"\n    };\n  }\n\u003cbr /\u003e\n  componentDidMount() {\n    this.setState({ mounted: \"yes\" })\n  }\n\u003cbr /\u003e\n  render() {\n    const {mounted} = this.state;\n\u003cbr /\u003e\n    return React.createElement(\n      'div', null, `mounted = ${mounted}`\n    );\n  }\n}\n\u003cbr /\u003e\nReactDOM.render(\n  React.createElement('div', null,\n    React.createElement(WithLifeCycle, null, null),\n  ),\n  document.body\n);\n\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nfunction WithLifeCycle(props, state, setState, hooks) {\n  const {mounted = \"no\"} = state;\n  hooks.m.push(() =\u003e {\n    setState({ mounted: \"yes\" })\n  });\n\u003cbr /\u003e\n  return H('div',\n    `mounted = ${mounted}`\n  );\n}\n\u003cbr /\u003e\nR(\n  H('div', H(WithLifeCycle)),\n  document.body\n)\n\u003c/pre\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### 5. Keyed Updates\n\nKeyed updates is a useful [reconciliation](https://reactjs.org/docs/reconciliation.html) feature from React that enables the rendering engine to take smart decisions on which elements to update.\n\nA particularly useful case is when you are rendering a dynamic list of elements. Since the rendering engine does not understand _which_ element has changed, it ends-up with wrong updates.\n\nTo solve this issue, the VDOM engines use a `key` property that uniquely identifies an element in the tree. However **.dom** solves it, by keeping a copy of the element state in the VDom element instance itself.\n\nThis means that you don't need any `key` property, just make sure you return the same VDom instance as before.\n\nIf you are creating dynamic elements (eg. an array of vdom elements), **.dom** might have trouble detecting the correct update order. \n\n\u003ctable width=\"100%\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eReact\u003c/th\u003e\n    \u003cth\u003e.dom\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nclass Clickable extends React.Component {\n  constructor() {\n    super(...arguments);\n    this.state = {\n      clicks: 0\n    };\n  }\n\u003cbr /\u003e\n  render() {\n    const {clicks} = this.state;\n    const {ket} = this.props;\n\u003cbr /\u003e\n    return React.createElement(\n      'button', {\n        onClick() {\n          this.setState({clicks: clicks+1})\n        }\n      }, `clicks=${clicks}, key=${key}`\n    );\n  }\n}\n\u003cbr /\u003e\nconst list = [\"first\", \"second\", \"third\"];\nconst components = list.map(key =\u003e \n  React.createElement(Clickable, {key}, null);\n\u003cbr /\u003e\nReactDOM.render(\n  React.createElement('div', null,\n    components\n  ),\n  document.body\n);\n\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"top\"\u003e\n\u003cpre lang=\"javascript\"\u003e\nfunction Clickable(props, state, setState) {\n  const {clicks=0} = state;\n  const {key} = props;\n\u003cbr /\u003e\n  return H('button',\n    {\n      onclick() {\n        setState({clicks: clicks+1})\n      }\n    },\n    `clicks=${clicks}, key=${key}`\n  );\n}\n\u003cbr /\u003e\nconst list = [\"first\", \"second\", \"third\"];\nconst components = list.map(key =\u003e \n  H(Clickable, {key});\n\u003cbr /\u003e\nR(\n  H('div', components),\n  document.body\n)\n\u003c/pre\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nNote that the solution above will correctly update the stateful components, even if their order has changed. However, if you want the complete, React-Like functionality that updates individual keys, you can use the `Keyed` plug-in.\n\n```js\nfunction Container(props, state) {\n  const {components} = props;\n  // The function `K` accepts the component state and an array of components that\n  // contain the `key` property, and returns the same array of components, with their\n  // state correctly manipulated.\n  return H(\"div\", K(state, components));\n}\n```\n\n#### 6. Raw (Unreconciled) Nodes\n\nYou can create raw (unreconciled) VDom nodes (eg. that carry an arbitrary HTML content) by setting the `.r` property of the hooks object to any truthy value.\n\nThis will disable further reconciliation to the child nodes, and therefore keep your contents intact.\n\n```js\nfunction Description(props, state, setState, hooks) {\n  const { html } = props;\n  hooks.r = 1; // Enable raw mode\n  return H('div', {\n    innerHTML: html\n  })\n}\n```\n\n## API Reference\n\n### Render `R( VNode, DOMElement )`\n\n```js\nR( H('div', 'Hello'), document.body )\n```\n\nRenders the given VNode tree to the given DOM element. Further updates from\nstateful components will only occur on their immediate children.\n\n### Create Element `H( tagName | function, [properties], [children ...])`\n\n```js\nH( 'tag' )\nH( 'tag', {prop: \"value\"})\nH( 'tag', H( 'child' ))\nH( 'tag', {prop: \"value\"}, H( 'child' ))\nH( Component, {prop: \"value\"} )\n```\n\nCreates a VNode element. If a string is passed as the first argument, it will\ncreate a HTML element. If a function is given, it will create a stateful\ncomponent.\n\nProperties and children are optional and they can be omitted.\n\n#### Functional Components\n\nInstead of a tag name you can provide a function that returns a Virtual DOM\naccording to some higher-level logic. Such function have the following signature:\n\n```js\nconst Component = (props, state, setState, hooks) {\n\n  // Return your Virtual DOM\n  return div( ... )\n}\n```\n\nThe `props` property contains the properties object as given when the component\nwas created.\n\nThe `state` is initialized to an empty object `{}` and it's updated by calling\nthe `setState({ newState })` method. The latter will also trigger an update to\nthe component and it's children.\n\nYou can also assign properties to the `state` object directly if you don't want\nto cause an update.\n\nThe `hooks` object can be used when you want to register handlers to the component life-cycle methods.\n\n#### Component Life-Cycle\n\nSimilar to React, the **.dom** components have a life-cycle:\n\n  * They are **mounted** when their root DOM element is placed on the document.\n  * They are **unmounted** when their root DOM element is removed from the document.\n  * The yare **updated** when the state, the properties, or the rendered DOM has changed.\n\nTo access the life-cycle methods you need to use the fourth argument on your component function. More specifically you have to push your handling function in either of the following fields:\n\n```js\nconst Component = (props, state, setState, hooks) {\n  hooks.m.push((domElement) =\u003e {\n    // '.m' is called when the component is mounted\n  });\n  hooks.u.push(() =\u003e {\n    // `.u` is called when the component is unmounted\n  });\n  hooks.d.push((domElement, previousDomElement) =\u003e {\n    // `.d` is called when the component is updated\n  });\n  ...\n}\n```\n\n### Tag Shorthand `tag( [properties], [children ...] )`\n\n```js\nconst {div, span, a} = H;\n\ndiv( 'hello', span( 'world' ) )\ndiv( 'click', a({href: '#'}, 'Here'), 'to continue')\n```\n\nA shorthand function can be extracted as a property from the `H` function. Such\nshorthands behave exactly like `H`, but with the tag name already populated.\n\nIt's recommended to use a deconstructuring assignment in the beginning of your\nscript in order to help javascript minifiers further optimize the result:\n\n```\nconst {div, span, a, button} = H;\n```\n\n### Tag + Class Shorthand `tag.class( [properties], [children ...] )`\n\n```js\nconst {h1, span, p} = H;\n\nh1.short( 'short header', span.strong( 'strong text' ) )\nbutton.primary({onclick: handleClick}, 'Primary Action')\np.bold.italic( twitterPost )\n```\n\nInstead of providing the `className` as a property, you can use the `.className` shorthand in combination with the shorthand tag methods.\n\nThis is the same as calling `div({className: 'className'})` and the function interface is exactly the same as above.\n\n*Note:* You can add more than one class by concatenating more than one `.class` to the tag. For example: `div.foo.bar` is the same as `div({className: 'foo bar'})`.\n\n## Caveats\n\nSince the project's focus is the small size, it is lacking sanity checks. This makes it susceptible to errors. Be **very careful** with the following caveats:\n\n* You cannot trigger an update with a property removal. You **must** set the new property to an empty value instead. For example:\n\n  ```js\n  // Wrong\n  R(div({className: 'foo'}), document.body);\n  R(div({}), document.body);\n\n  // Correct\n  R(div({className: 'foo'}), document.body);\n  R(div({className: ''}), document.body);\n  ```\n\n* You **must** never use a property named `$` in your components. Doing so, will make the property object to be considered as a Virtual DOM Node and will lead to unexpected results.\n\n  ```js\n  // *NEVER* do this!\n  R(H(MyComponent, {$: 'Foo'}), document.body)\n  ```\n\n## Plugin Reference\n\n### Keyed Update List `K(state, components)`\n\n\u003e In `plugin-keyed.min.js`\n\nEnsures the state of the components in the list is synchronized, according to their `key` property. This enables you to do react-like keyed updates like so:\n\n```js\nfunction ValueRenderer(...) { \n  ...\n}\n\nfunction MyComponent(props, state) {\n  const { values } = props;\n  const components = values.map(value =\u003e {\n    H(ValueRenderer, {\n      key: value,\n      value: value\n    });\n  })\n\n  // Synchronize state of components, based on their key\n  return H('div', K(state, components))\n}\n```\n\n## Contribution\n\nAre you interested in contributing to **.dom**? You are more than welcome! Just be sure to follow the guidelines:\n\n1. Install a local development environment (you will need node.js **6.x** or later)\n\n  ```\n  npm install\n  ```\n\n2. **Always** run the following when you think you are ready for a pull request:\n\n  ```\n  npm test \u0026\u0026 npm run build \u0026\u0026 ls -l dotdom.min.js.gz\n  ```\n\n3. If tests pass and the size of `dotdom.min.js.gz` is smaller than or equal to 512 bytes, create a pull request. Otherwise reduce your scope or think of another implementation in order to bring it back down to 512 bytes.\n\n4. Make sure to properly comments your code, since you will most probably have to do some extreme javascript hacking. The gudeliens are the following:\n\n  ```js\n  /**\n   * Functions are commented as JSDoc blocks\n   *\n   * @param {VNode|Array\u003cVNode\u003e} vnodes - The node on an array of nodes to render\n   * ...\n   */\n  global.R = render = (\n    vnodes,                                                           // Flat-code comments start on column 70 and\n    dom,                                                              // wrap after column 120.\n\n    /* Logical separations can be commented like this */\n\n    ...\n  ```\n# License\n\nLicensed under the [Apache License, Version 2.0](https://raw.githubusercontent.com/wavesoft/dot-dom/master/LICENSE)\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwavesoft%2Fdot-dom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwavesoft%2Fdot-dom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwavesoft%2Fdot-dom/lists"}