UNPKG

16.9 kBMarkdownView Raw
1# .dom [![npm](https://badgen.net/npm/v/dot-dom)](https://www.npmjs.com/package/dot-dom) [![size](https://badgen.net/badgesize/gzip/https://cdn.jsdelivr.net/npm/dot-dom@0.3.1/dotdom.min.js)](https://cdn.jsdelivr.net/npm/dot-dom@0.3.1/dotdom.min.js) [![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/anon/pen/OrzaXB?editors=0010) [![Gitter](https://badges.gitter.im/wavesoft/dot-dom.svg)](https://gitter.im/wavesoft/dot-dom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
2
3> A tiny (512 byte) virtual DOM template engine for embedded projects
4
5| <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/edge.png" alt="IE / Edge" width="16px" height="16px" /> IE / Edge | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/firefox.png" alt="Firefox" width="16px" height="16px" /> Firefox | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome.png" alt="Chrome" width="16px" height="16px" /> Chrome | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari.png" alt="Safari" width="16px" height="16px" /> Safari | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/opera.png" alt="Opera" width="16px" height="16px" /> Opera | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari-ios.png" alt="iOS Safari" width="16px" height="16px" /> iOS Safari | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome-android.png" alt="Chrome for Android" width="16px" height="16px" /> Chrome for Android |
6| --------- | --------- | --------- | --------- | --------- | --------- | --------- |
7| Edge 14+ | 45+ | 49+ | 10+ | 37+ | 10.2+ | 55+
8
9**.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.
10
11Why? 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!
12
13### Features
14
15* _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.
16
17* _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.
18
19* _Declarative_ : Describe your HTML DOM in a structured, natural manner, helping you create powerful yet readable user interfaces.
20
21* _Component-Oriented_ : Just like React.js, **.dom** promotes the use of functional components.
22
23* _"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.
24
25### Projects Using `.dom`
26
27* [Open Graph Image as a Service](https://github.com/styfle/og-image) - [demo](https://og-image.now.sh/)
28
29Are you using `.dom` in your project? Fork this repository and add yours on the list!
30
31
32## Installation
33
34For minimum footprint, include `dotdom.min.js.gz` (512b) to your project.
35
36```html
37<script src="dotdom.min.js.gz" />
38```
39
40Alternatively 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).
41
42## Examples
43
44If you already know React.js, the following examples can help you understand how
45the .dom primitives relate to React.
46
47#### 1. Plain DOM
48
49Rendering a very simple DOM structure.
50
51<table width="100%">
52 <tr>
53 <th>React</th>
54 <th>.dom</th>
55 </tr>
56 <tr>
57 <td valign="top">
58<pre lang="javascript">
59ReactDOM.render(
60 React.createElement('div', null, 'Hello world'),
61 document.body
62);
63</pre>
64 </td>
65 <td valign="top">
66<pre lang="javascript">
67R(
68 H('div', 'Hello world'),
69 document.body
70)
71</pre>
72 </td>
73 </tr>
74</table>
75
76#### 2. Stateless Component
77
78Creating a component on which you can pass properties.
79
80<table width="100%">
81 <tr>
82 <th>React</th>
83 <th>.dom</th>
84 </tr>
85 <tr>
86 <td valign="top">
87<pre lang="javascript">
88function Hello(props) {
89 return React.createElement(
90 'div', null, `Hello ${props.toWhat}`
91 );
92 }
93<br />
94ReactDOM.render(
95 React.createElement(
96 Hello, {toWhat: 'World'}, null
97 ),
98 document.body
99);
100</pre>
101 </td>
102 <td valign="top">
103<pre lang="javascript">
104function Hello(props) {
105 return H('div', `Hello ${props.toWhat}`);
106}
107<br />
108R(
109 H(Hello, {toWhat: 'World'}),
110 document.body
111)
112</pre>
113 </td>
114 </tr>
115</table>
116
117#### 3. Stateful Component
118
119Creating components that can maintain their own state.
120
121<table width="100%">
122 <tr>
123 <th>React</th>
124 <th>.dom</th>
125 </tr>
126 <tr>
127 <td valign="top">
128<pre lang="javascript">
129class Clickable extends React.Component {
130 constructor() {
131 super(...arguments);
132 this.state = {
133 clicks: 0
134 };
135 }
136<br />
137 render() {
138 const {clicks} = this.state;
139<br />
140 return React.createElement(
141 'button', {
142 onClick() {
143 this.setState({clicks: clicks+1})
144 }
145 }, `Clicked ${clicks} times`
146 );
147 }
148}
149<br />
150ReactDOM.render(
151 React.createElement('div', null,
152 React.createElement(Clickable, null, null),
153 React.createElement(Clickable, null, null)
154 ),
155 document.body
156);
157</pre>
158 </td>
159 <td valign="top">
160<pre lang="javascript">
161function Clickable(props, state, setState) {
162 const {clicks=0} = state;
163<br />
164 return H('button',
165 {
166 onclick() {
167 setState({clicks: clicks+1})
168 }
169 },
170 `Clicked ${clicks} times`
171 );
172}
173<br />
174R(
175 H('div',
176 H(Clickable),
177 H(Clickable)
178 ),
179 document.body
180)
181</pre>
182 </td>
183 </tr>
184</table>
185
186#### 4. Life-Cycle Component Events
187
188The component can also subscribe to life-cycle events:
189
190<table width="100%">
191 <tr>
192 <th>React</th>
193 <th>.dom</th>
194 </tr>
195 <tr>
196 <td valign="top">
197<pre lang="javascript">
198class WithLifeCycle extends React.Component {
199 constructor() {
200 super(...arguments);
201 this.state = {
202 mounted: "no"
203 };
204 }
205<br />
206 componentDidMount() {
207 this.setState({ mounted: "yes" })
208 }
209<br />
210 render() {
211 const {mounted} = this.state;
212<br />
213 return React.createElement(
214 'div', null, `mounted = ${mounted}`
215 );
216 }
217}
218<br />
219ReactDOM.render(
220 React.createElement('div', null,
221 React.createElement(WithLifeCycle, null, null),
222 ),
223 document.body
224);
225</pre>
226 </td>
227 <td valign="top">
228<pre lang="javascript">
229function WithLifeCycle(props, state, setState, hooks) {
230 const {mounted = "no"} = state;
231 hooks.m.push(() => {
232 setState({ mounted: "yes" })
233 });
234<br />
235 return H('div',
236 `mounted = ${mounted}`
237 );
238}
239<br />
240R(
241 H('div', H(WithLifeCycle)),
242 document.body
243)
244</pre>
245 </td>
246 </tr>
247</table>
248
249#### 5. Keyed Updates
250
251Keyed 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.
252
253A 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.
254
255To 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.
256
257This means that you don't need any `key` property, just make sure you return the same VDom instance as before.
258
259If you are creating dynamic elements (eg. an array of vdom elements), **.dom** might have trouble detecting the correct update order.
260
261<table width="100%">
262 <tr>
263 <th>React</th>
264 <th>.dom</th>
265 </tr>
266 <tr>
267 <td valign="top">
268<pre lang="javascript">
269class Clickable extends React.Component {
270 constructor() {
271 super(...arguments);
272 this.state = {
273 clicks: 0
274 };
275 }
276<br />
277 render() {
278 const {clicks} = this.state;
279 const {ket} = this.props;
280<br />
281 return React.createElement(
282 'button', {
283 onClick() {
284 this.setState({clicks: clicks+1})
285 }
286 }, `clicks=${clicks}, key=${key}`
287 );
288 }
289}
290<br />
291const list = ["first", "second", "third"];
292const components = list.map(key =>
293 React.createElement(Clickable, {key}, null);
294<br />
295ReactDOM.render(
296 React.createElement('div', null,
297 components
298 ),
299 document.body
300);
301</pre>
302 </td>
303 <td valign="top">
304<pre lang="javascript">
305function Clickable(props, state, setState) {
306 const {clicks=0} = state;
307 const {key} = props;
308<br />
309 return H('button',
310 {
311 onclick() {
312 setState({clicks: clicks+1})
313 }
314 },
315 `clicks=${clicks}, key=${key}`
316 );
317}
318<br />
319const list = ["first", "second", "third"];
320const components = list.map(key =>
321 H(Clickable, {key});
322<br />
323R(
324 H('div', components),
325 document.body
326)
327</pre>
328 </td>
329 </tr>
330</table>
331
332Note 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.
333
334```js
335function Container(props, state) {
336 const {components} = props;
337 // The function `K` accepts the component state and an array of components that
338 // contain the `key` property, and returns the same array of components, with their
339 // state correctly manipulated.
340 return H("div", K(state, components));
341}
342```
343
344#### 6. Raw (Unreconciled) Nodes
345
346You 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.
347
348This will disable further reconciliation to the child nodes, and therefore keep your contents intact.
349
350```js
351function Description(props, state, setState, hooks) {
352 const { html } = props;
353 hooks.r = 1; // Enable raw mode
354 return H('div', {
355 innerHTML: html
356 })
357}
358```
359
360## API Reference
361
362### Render `R( VNode, DOMElement )`
363
364```js
365R( H('div', 'Hello'), document.body )
366```
367
368Renders the given VNode tree to the given DOM element. Further updates from
369stateful components will only occur on their immediate children.
370
371### Create Element `H( tagName | function, [properties], [children ...])`
372
373```js
374H( 'tag' )
375H( 'tag', {prop: "value"})
376H( 'tag', H( 'child' ))
377H( 'tag', {prop: "value"}, H( 'child' ))
378H( Component, {prop: "value"} )
379```
380
381Creates a VNode element. If a string is passed as the first argument, it will
382create a HTML element. If a function is given, it will create a stateful
383component.
384
385Properties and children are optional and they can be omitted.
386
387#### Functional Components
388
389Instead of a tag name you can provide a function that returns a Virtual DOM
390according to some higher-level logic. Such function have the following signature:
391
392```js
393const Component = (props, state, setState, hooks) {
394
395 // Return your Virtual DOM
396 return div( ... )
397}
398```
399
400The `props` property contains the properties object as given when the component
401was created.
402
403The `state` is initialized to an empty object `{}` and it's updated by calling
404the `setState({ newState })` method. The latter will also trigger an update to
405the component and it's children.
406
407You can also assign properties to the `state` object directly if you don't want
408to cause an update.
409
410The `hooks` object can be used when you want to register handlers to the component life-cycle methods.
411
412#### Component Life-Cycle
413
414Similar to React, the **.dom** components have a life-cycle:
415
416 * They are **mounted** when their root DOM element is placed on the document.
417 * They are **unmounted** when their root DOM element is removed from the document.
418 * The yare **updated** when the state, the properties, or the rendered DOM has changed.
419
420To 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:
421
422```js
423const Component = (props, state, setState, hooks) {
424 hooks.m.push((domElement) => {
425 // '.m' is called when the component is mounted
426 });
427 hooks.u.push(() => {
428 // `.u` is called when the component is unmounted
429 });
430 hooks.d.push((domElement, previousDomElement) => {
431 // `.d` is called when the component is updated
432 });
433 ...
434}
435```
436
437### Tag Shorthand `tag( [properties], [children ...] )`
438
439```js
440const {div, span, a} = H;
441
442div( 'hello', span( 'world' ) )
443div( 'click', a({href: '#'}, 'Here'), 'to continue')
444```
445
446A shorthand function can be extracted as a property from the `H` function. Such
447shorthands behave exactly like `H`, but with the tag name already populated.
448
449It's recommended to use a deconstructuring assignment in the beginning of your
450script in order to help javascript minifiers further optimize the result:
451
452```
453const {div, span, a, button} = H;
454```
455
456### Tag + Class Shorthand `tag.class( [properties], [children ...] )`
457
458```js
459const {h1, span, p} = H;
460
461h1.short( 'short header', span.strong( 'strong text' ) )
462button.primary({onclick: handleClick}, 'Primary Action')
463p.bold.italic( twitterPost )
464```
465
466Instead of providing the `className` as a property, you can use the `.className` shorthand in combination with the shorthand tag methods.
467
468This is the same as calling `div({className: 'className'})` and the function interface is exactly the same as above.
469
470*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'})`.
471
472## Caveats
473
474Since 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:
475
476* You cannot trigger an update with a property removal. You **must** set the new property to an empty value instead. For example:
477
478 ```js
479 // Wrong
480 R(div({className: 'foo'}), document.body);
481 R(div({}), document.body);
482
483 // Correct
484 R(div({className: 'foo'}), document.body);
485 R(div({className: ''}), document.body);
486 ```
487
488* 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.
489
490 ```js
491 // *NEVER* do this!
492 R(H(MyComponent, {$: 'Foo'}), document.body)
493 ```
494
495## Plugin Reference
496
497### Keyed Update List `K(state, components)`
498
499> In `plugin-keyed.min.js`
500
501Ensures 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:
502
503```js
504function ValueRenderer(...) {
505 ...
506}
507
508function MyComponent(props, state) {
509 const { values } = props;
510 const components = values.map(value => {
511 H(ValueRenderer, {
512 key: value,
513 value: value
514 });
515 })
516
517 // Synchronize state of components, based on their key
518 return H('div', K(state, components))
519}
520```
521
522## Contribution
523
524Are you interested in contributing to **.dom**? You are more than welcome! Just be sure to follow the guidelines:
525
5261. Install a local development environment (you will need node.js **6.x** or later)
527
528 ```
529 npm install
530 ```
531
5322. **Always** run the following when you think you are ready for a pull request:
533
534 ```
535 npm test && npm run build && ls -l dotdom.min.js.gz
536 ```
537
5383. 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.
539
5404. Make sure to properly comments your code, since you will most probably have to do some extreme javascript hacking. The gudeliens are the following:
541
542 ```js
543 /**
544 * Functions are commented as JSDoc blocks
545 *
546 * @param {VNode|Array<VNode>} vnodes - The node on an array of nodes to render
547 * ...
548 */
549 global.R = render = (
550 vnodes, // Flat-code comments start on column 70 and
551 dom, // wrap after column 120.
552
553 /* Logical separations can be commented like this */
554
555 ...
556 ```
557# License
558
559Licensed under the [Apache License, Version 2.0](https://raw.githubusercontent.com/wavesoft/dot-dom/master/LICENSE)
560
\No newline at end of file