Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

dot-dom

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dot-dom - npm Package Compare versions

Comparing version
0.2.2
to
0.3.0
+1
-2
dotdom.min.js

@@ -1,2 +0,1 @@

((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
(()=>{let e=(e,a={},...c)=>({$:e,a:a.$||a.concat||a.removeChild?{c:[].concat(a,...c)}:(a.c=[].concat(...c),a)}),a=(e=[],a,c)=>e.map(e=>e(a,c)),c=e=>new Proxy(e,{get:(e,a,d)=>c((...c)=>((d=e(...c)).a.className=(d.a.className||"")+" "+a,d))}),d=window.R=((e,c,t=c.childNodes,o=0)=>{for([].concat(e).map((n,l,m,i=t[o++],s=i&&i.a==n.$&&i.s||{},r={s,a:n.$,m:[],u:[],d:[]})=>{for(;(n.$||e).bind;)n=n.$(n.a,s,a=>Object.assign(s,a)&&d(e,c),r);l=n.removeChild?n:n.replace?document.createTextNode(n):document.createElement(n.$),l=i?(n.removeChild?i!=n:i.$!=n.$&&i.data!=n)?(c.replaceChild(l,i),l):i:c.appendChild(l),m=i?i.a==r.a?r.d:(a(i.u),d([],i),r.m):r.m,Object.assign(l,n,r),n.removeChild||n.replace?l.data=n:Object.keys(n.a).map(e=>"style"==e?Object.assign(l[e],n.a[e]):l[e]!==n.a[e]&&(l[e]=n.a[e]))&&d(n.a.c,l),a(m,l,i)});t[o];)a(t[o].u),d([],c.removeChild(t[o]))});window.H=new Proxy(e,{get:(a,d)=>a[d]||c(e.bind(a,d))})})()

@@ -189,3 +189,3 @@ Apache License

Copyright {yyyy} {name of copyright owner}
Copyright 2018 Ioannis Charalampidis

@@ -192,0 +192,0 @@ Licensed under the Apache License, Version 2.0 (the "License");

{
"name": "dot-dom",
"version": "0.2.2",
"version": "0.3.0",
"description": "A tiny (less than 512 byte) template engine that uses virtual DOM and some of react principles",
"main": "src/dotdom.js",
"files": [
"src/dotdom.js",
"dotdom.min.js"
],
"scripts": {
"test": "./node_modules/.bin/jest",
"build": "cat src/dotdom.js | perl -0pe 's/BEGIN NPM-GLUE.*END NPM-GLUE//s' | ./node_modules/.bin/babili | tee dotdom.min.js | gzip -9 > dotdom.min.js.gz"
"build": "./node_modules/.bin/gulp build",
"watch": "./node_modules/.bin/gulp watch"
},

@@ -26,10 +31,19 @@ "repository": {

"devDependencies": {
"babel-core": "^6.22.1",
"babel-preset-es2015": "^6.22.0",
"babel-register": "^6.22.0",
"babili": "0.0.10",
"jest": "^18.1.0",
"babel-preset-babili": "0.0.11",
"gulp": "3.9.1",
"gulp-babel": "^6.1.2",
"gulp-cli": "1.2.2",
"gulp-clone": "^1.0.0",
"gulp-gzip": "1.4.0",
"gulp-merge": "^0.1.1",
"gulp-rename": "^1.2.2",
"gulp-strip-code": "^0.1.4",
"gulp-uglify-es": "^1.0.4",
"gulp-util": "^3.0.8",
"jest": "^23.6.0",
"mock-browser": "^0.92.12",
"through2": "^2.0.3",
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
"watch": "^1.0.1"
}
}
+119
-23

@@ -1,4 +0,4 @@

# .dom [![Build Status](https://travis-ci.org/wavesoft/dot-dom.svg?branch=master)](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/YNdNwv?editors=0010)
# .dom [![Build Status](https://travis-ci.org/wavesoft/dot-dom.svg?branch=master)](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)
> A tiny (511 byte) virtual DOM template engine for embedded projects
> A tiny (512 byte) virtual DOM template engine for embedded projects

@@ -17,3 +17,3 @@ | <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 |

* _Built for the future_ : The library is heavily exploiting the ES6 specifications, meaning that it's **not** supported by older borwsers. Currently it's supported by the 70% of the browsers in the market, but expect this to be 90% within the next year.
* _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 70% of the browsers in the market, but expect this to be 90% within the next year.

@@ -29,3 +29,3 @@ * _Declarative_ : Describe your HTML DOM in a structured, natural manner, helping you create powerful yet readable user interfaces.

For minimum footprint, include `dotdom.min.js.gz` (511b) to your project.
For minimum footprint, include `dotdom.min.js.gz` (512b) to your project.

@@ -36,8 +36,4 @@ ```html

Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (779b):
Alternatively 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).
```js
((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
```
## Examples

@@ -77,3 +73,3 @@

#### 2. Simple component
#### 2. Stateless Component

@@ -90,10 +86,8 @@ Creating a component on which you can pass properties.

<pre lang="javascript">
class Hello extends React.Component {
render() {
function Hello(props) {
return React.createElement(
'div', null, `Hello ${this.props.toWhat}`
'div', null, `Hello ${props.toWhat}`
);
}
}
<br />
ReactDOM.render(

@@ -112,3 +106,3 @@ React.createElement(

}
<br />
R(

@@ -123,3 +117,3 @@ H(Hello, {toWhat: 'World'}),

#### 3. Stateful component
#### 3. Stateful Component

@@ -143,6 +137,6 @@ Creating components that can maintain their own state.

}
<br />
render() {
const {clicks} = this.state;
<br />
return React.createElement(

@@ -157,3 +151,3 @@ 'button', {

}
<br />
ReactDOM.render(

@@ -172,3 +166,3 @@ React.createElement('div', null,

const {clicks=0} = state;
<br />
return H('button',

@@ -183,3 +177,3 @@ {

}
<br />
R(

@@ -197,2 +191,65 @@ H('div',

#### 4. Life-Cycle Component Events
The component can also subscribe to life-cycle events:
<table width="100%">
<tr>
<th>React</th>
<th>.dom</th>
</tr>
<tr>
<td valign="top">
<pre lang="javascript">
class WithLifeCycle extends React.Component {
constructor() {
super(...arguments);
this.state = {
mounted: "no"
};
}
<br />
componentDidMount() {
this.setState({ mounted: "yes" })
}
<br />
render() {
const {mounted} = this.state;
<br />
return React.createElement(
'div', null, `mounted = ${mounted}`
);
}
}
<br />
ReactDOM.render(
React.createElement('div', null,
React.createElement(WithLifeCycle, null, null),
),
document.body
);
</pre>
</td>
<td valign="top">
<pre lang="javascript">
function WithLifeCycle(props, state, setState, hooks) {
const {mounted = "no"} = state;
hooks.m.push(() => {
setState({ mounted: "yes" })
});
<br />
return H('div',
`mounted = ${mounted}`
);
}
<br />
R(
H('div', H(WithLifeCycle)),
document.body
)
</pre>
</td>
</tr>
</table>
## API Reference

@@ -231,3 +288,3 @@

```js
const Component = (props, state, setState) {
const Component = (props, state, setState, hooks) {

@@ -249,2 +306,29 @@ // Return your Virtual DOM

The `hooks` object can be used when you want to register handlers to the component life-cycle methods.
#### Component Life-Cycle
Similar to React, the **.dom** components have a life-cycle:
* They are **mounted** when their root DOM element is placed on the document.
* They are **unmounted** when their root DOM element is removed from the document.
* The yare **updated** when the state, the properties, or the rendered DOM has changed.
To 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:
```js
const Component = (props, state, setState, hooks) {
hooks.m.push((domElement) => {
// '.m' is called when the component is mounted
});
hooks.u.push(() => {
// `.u` is called when the component is unmounted
});
hooks.d.push((domElement, previousDomElement) => {
// `.d` is called when the component is updated
});
...
}
```
### Tag Shorthand `tag( [properties], [children ...] )`

@@ -287,2 +371,4 @@

Since 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:
* You cannot trigger an update with a property removal. You **must** set the new property to an empty value instead. For example:

@@ -300,2 +386,9 @@

* 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.
```js
// *NEVER* do this!
R(H(MyComponent, {$: 'Foo'}), document.body)
```
## Contribution

@@ -336,1 +429,4 @@

```
# License
Licensed under the [Apache License, Version 2.0](https://raw.githubusercontent.com/wavesoft/dot-dom/master/LICENSE)

@@ -25,3 +25,3 @@ /**

const window = {};
var window = typeof window !== "undefined" && window || {};
module.exports = window;

@@ -31,14 +31,9 @@

((global, document, Object, vnodeFlag, globalState, createElement, render, wrapClassProxy) => {
(() => {
let
/**
* Put the `vnodeFlag` to all strings in order to be considered as virtual
* dom nodes.
*/
String.prototype[vnodeFlag] = 1;
/**
* Create a VNode element
*
* @param {String|Function} eleent - The tag name or the component to render
* @param {String|Function} element - The tag name or the component to render
* @param {Object} [props] - The object properties

@@ -48,19 +43,60 @@ * @param {Array} [children] - The child VNode elements

*/
createElement = (element, props={}, ...children) => ({
[vnodeFlag]: 1, // The vnodeFlag symbol is used by the code
// in the 'P' property to check if the `props`
// argument is not an object, but a renderable
// VNode child
createElement = (element, props={}, ...children) => (
{
E: element, // 'E' holds the name or function passed as
$: element, // '$' holds the name or function passed as
// first argument
P: props[vnodeFlag] // If the props argument is a renderable VNode,
? {C: [].concat(props, ...children)} // ... prepend it to the children
: (props.C = [].concat(...children)) && props // ... otherwise append 'C' to the property
a: (props.$ || props.concat || props.removeChild) // If the props argument is a renderable VNode,
// a string, an array (.concat exists on both
// strings and arrays), or a DOM element, then ...
? {c: [].concat(props, ...children)} // ... prepend it to the children
: ((props.c = [].concat(...children)), props) // ... otherwise append 'C' to the property
// the .concat ensures that arrays of children
// will be flattened into a single array.
})
}
)
/**
* Helper method that calls all methods in an array of functions
*
* @param {Array} methods - The array of methods to call
* @param {Object} arg1 - An arbitrary first argument
* @param {Object} arg2 - An arbitrary second argument
*/
, callLifecycleMethods = (methods = [], arg1, arg2) =>
methods.map(e => e(arg1, arg2)) // Detaching from stack is important, otherwise it
/**
* Helper function that wraps an element shorthand function with a proxy
* that can be used to append class names to the instance.
*
* The result is wrapped with the same function, creating a chainable mechanism
* for appending classes.
*
* @param {function} factoryFn - The factory function to call for creating vnode
*/
, wrapClassProxy = factoryFn =>
new Proxy( // We are creating a proxy object for every tag in
// order to be able to customize the class name
// via a shorthand call.
factoryFn,
{
get: (targetFn, className, _instance) =>
wrapClassProxy(
(...args) => (
(_instance=targetFn(...args)) // We first create the Virtual DOM instance by
// calling the wrapped factory function
.a.className = (_instance.a.className || '') // And then we assign the class name,
+ ' ' + className, // concatenating to the previous value
_instance // And finally we return the instance
)
)
}
)
/**
* Render a VNode in the DOM

@@ -71,13 +107,12 @@ *

*/
global.R = render = (
, render = window.R = (
vnodes, // 1. The vnode tree to render
dom, // 2. The DOMElement where to render into
_npath='', // a. The current state path
_children=dom.childNodes, // b. Shorthand for accessing the children
_c=0 // c. Counter for processed children
_children=dom.childNodes, // a. Shorthand for accessing the children
_c=0 // b. Counter for processed children
) => {
(vnodes.map ? vnodes : [vnodes]).map( // Cast `vnodes` to array if nor already
[].concat(vnodes).map( // Cast `vnodes` to array if nor already
// In this `map` loop we ensure that the DOM

@@ -88,18 +123,27 @@ // elements correspond to the correct virtual

vnode, // 1. We handle the vnode from the array
index, // 2. And the index
_new_dom, // 2. We ignore the index, and instead we are using
// it as the new DOM element placeholder
_callMethod, // 3. We ignore the array, and instead we are using
// it as a variable where we are keeping the
// lifecycle method to call at the end.
_child=_children[_c++], // a. Get the next DOM child + increment counter
_state=( // b. Get the current state from the DOM child
_child && // - If there is no child, bail
(_child.a == vnode.$) // - If the element has changed, bail
&& _child.s // - If the element has a state, use that
) || {}, // - Default state value
_hooks={ // c. Prepare the hooks object that will be passed
// down to the functional component
s: _state, // - The 's' property is keeping a reference to
// the current element state. (Used above)
a: vnode.$, // - The 'a' property is keeping a reference
// to the element (property '$') and is used
// for space-optimal assignment of the tag to
// the DOM element through Object.assign in the
// Update Element phase later.
m: [], // - The 'm' property contains the `mount` cb
u: [], // - The 'u' property contains the `unmount` cb
d: [] // - The 'd' property contains the `update` cb
}
_unused1, // We don't handle the array, but we need the
// placeholder for the local variables after
_path=_npath+'.'+index, // a. The state path of this vnode
_path_state=globalState[_path] || [{}, vnode.E], // b. Get the state record for this path
_state=( // c. Update and get the state record
globalState[_path] = // The record is an the following format:
_path_state[1] != vnode.E // [ {state object},
? [{}, vnode.E] // 'vnode element' ]
: _path_state // The second component is needed in order to
), // reset the state if the component has changed
_child=_children[_c++], // d. Get the next DOM child + increment counter
_new_dom // e. The new DOM element placeholder
) => {

@@ -109,75 +153,124 @@

vnode.E && vnode.E.call && // If the vnode is a functional component, expand
(vnode = vnode.E( // it and replace the current vnode variable.
for (;(vnode.$ || vnodes).bind;) // Expand recursive functional components until
// we encounter a non-callable element. (The `vnodes` is
// used as a reference to any kind of an object in order
// to be able to resolve `.bind`, even if it's undefined).
vnode = vnode.$(
vnode.P, // 1. The component properties
_state[0], // 2. The stateful component state
vnode.a, // 1. The component properties
_state, // 2. The stateful component state
(newState) => // 3. The setState function
Object.assign( // First we update the state part of the record
_state[0], // Note: When we defined the variable we kept the
newState // reference to the record array
Object.assign( // First we update the state record, that also
_state, // updates the contents of the DOM element, since
newState // the reference is perserved.
) &&
render( // We then trigger the same render cycle that will
vnodes, // update the DOM
dom,
_npath
)
dom
),
));
_hooks // 4. The lifecycle method hooks
);
/* Create new DOM element */
_new_dom =
vnode.removeChild // If this is a DOM element, pass it through ...
? vnode // Otherwise we prepare the new DOM element in advance
: vnode.replace // in order to save a few comparison bytes later.
? document.createTextNode(vnode)
: document.createElement(vnode.$);
_new_dom = // We prepare the new DOM element in advance in
vnode.trim // order to spare a few comparison bytes
? document.createTextNode(vnode)
: document.createElement(vnode.E);
/* Keep or replace the previous DOM element */
_new_dom =
_child // If we have a previous child we do some reconciliation
? (vnode.removeChild // If it's a DOM element reference, check
? _child != vnode
: (_child.$ != vnode.$ && _child.data != vnode))
? (
dom.replaceChild( // - If not, we replace the old element with the
_new_dom, // new one.
_child
),
_new_dom // ... and we make sure we return the new DOM
)
: _child // - If it's the same, we keep the old child
/* Keep or replace the previous DOM element */
: dom.appendChild( // If we did not have a previous child, just
_new_dom // append the new node
)
(_new_dom =
_child // If we have a previous child we first check if
? (_child.E != vnode.E && _child.data != vnode) // the VNode element or the text are the same
/* Prepare lifecycle methods */
? dom.replaceChild( // - If not, we replace the old element with the
_new_dom, // new one.
_child
) && _new_dom // ... and we make sure we return the new DOM
_callMethod = // We are not calling the method until we are done with
// the rendering cycle. Otherwise this could cause an
// infinite loop if `setState` is used.
: _child // - If it's the same, we keep the old child
_child // If there is a DOM reflection
? _child.a == _hooks.a // .. and the element has not changed
? _hooks.d // - [D] Just update
: (
callLifecycleMethods(_child.u), // - [U] Otherwise unmount the previous one
render( // And call the render function with empty
[], // children in order to recursively unmount
_child // the children tree.
),
_hooks.m // - [M] Mount the new
)
: dom.appendChild( // If we don't have a previous child, just append
_new_dom
)
).E = vnode.E; // We keep the vnode element to the .E property in
// order for the above comparison to work.
// If there is no DOM reflection
: _hooks.m; // - [M] Mount the new
/* Update Element */
/* Update Element State */
vnode.trim
? _new_dom.data = vnode // - String nodes update only the text
: Object.keys(vnode.P).map( // - Element nodes have properties
(
key // 1. The property name
) =>
Object.assign(_new_dom, vnode, _hooks); // Keep the following information in the DOM:
// - $ : The tag name from the vnode. We use this
// instead of the .tagName because some
// browsers convert it to capital-case
// - u : The `didUnmount` hook that is called when
// the DOM element is removed
//
// By assigning the entire _hooks and vnode
// objects we expose some unneeded properties, but
// it occupies less space than assigning $ and u
// individually.
key == 'style' ? // The 'style' property is an object and must be
/* Apply properties to the DOM element */
vnode.removeChild || // If this is a DOM element, don't do anything
vnode.replace
? _new_dom.data = vnode // - String nodes update only the text
: Object.keys(vnode.a).map( // - Element nodes have properties
(
key // 1. The property name
) =>
key == 'style' ? // The 'style' property is an object and must be
// applied recursively.
Object.assign(
_new_dom[key], // '[key]' is shorter than '.style'
vnode.P[key]
)
Object.assign(
_new_dom[key], // '[key]' is shorter than '.style'
vnode.a[key]
)
: (_new_dom[key] !== vnode.P[key] && // All properties are applied directly to DOM, as
(_new_dom[key] = vnode.P[key])) // long as they are different than ther value in the
: (_new_dom[key] !== vnode.a[key] && // All properties are applied directly to DOM, as
(_new_dom[key] = vnode.a[key])) // long as they are different than ther value in the
// instance. This includes `onXXX` event handlers.
) &&
render( // Only if we have an element (and not text node)
vnode.P.C, // we recursively continue rendering into it's
_new_dom, // child nodes.
_path
)
) &&
render( // Only if we have an element (and not text node)
vnode.a.c, // we recursively continue rendering into it's
_new_dom // child nodes.
)
/* Call life-cycle methods */
callLifecycleMethods(
_callMethod,
// Pass the following arguments:
_new_dom, // 1. The new DOM instance
_child // 2. The old DOM instance
);
}

@@ -188,35 +281,16 @@ );

while (_children[_c]) // The _c property keeps track of the number of
dom.removeChild(_children[_c]) // elements in the VDom. If there are more child
} // nodes in the DOM, we remove them.
for (;_children[_c];) { // The _c property keeps track of the number of
// elements in the VDom. If there are more child
// nodes in the DOM, we remove them.
/**
* Helper function that wraps an element shorthand function with a proxy
* that can be used to append class names to the instance.
*
* The result is wrapped with the same function, creating a chainable mechanism
* for appending classes.
*
* @param {function} factoryFn - The factory function to call for creating vnode
*/
wrapClassProxy = (factoryFn) =>
new Proxy( // We are creating a proxy object for every tag in
// order to be able to customize the class name
// via a shorthand call.
factoryFn,
{
get: (targetFn, className, _instance) =>
wrapClassProxy(
(...args) => (
(_instance=targetFn(...args)) // We first create the Virtual DOM instance by
// calling the wrapped factory function
callLifecycleMethods(_children[_c].u) // We then call the unmount lifecycle method for the
// elements that will be removed
.P.className = // And then we assign the class name,
[_instance.P.className] + ' ' + className, // concatenating to the previous value
render( // Remove child an trigger a recursive child removal
[], // in order to call the correct lifecycle methods in our
dom.removeChild(_children[_c]) // deep children too.
)
_instance // And finally we return the instance
)
)
}
)
}
}

@@ -228,12 +302,15 @@ /**

*/
global.H = new Proxy(
window.H = new Proxy(
createElement,
{
get: (targetFn, tagName) =>
targetFn[tagName] || wrapClassProxy(
createElement.bind(global, tagName)
)
}
targetFn[tagName] || // Make sure we don't override any native
// property or method from the base function
wrapClassProxy( // Otherwise, for every tag we extract a
createElement.bind(targetFn, tagName) // class-wrapped crateElement method, bound to the
) // tag named as the property requested. We are not
// using 'this', therefore we are using any reference
} // that could lead on reduced code footprint.
)
})(window, document, Object, Symbol(), {});
})()
{
"presets": ["es2015"]
}

Sorry, the diff of this file is not supported yet

language: node_js
node_js:
- "6.9.5"

Sorry, the diff of this file is not supported yet

const dd = require('../dotdom');
describe('.dom', function () {
describe('#H', function () {
describe('Factory', function () {
it('should create vnode without arguments', function () {
const vdom = dd.H('div');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({C: []});
});
it('should create vnode with props', function () {
const vdom = dd.H('div', {foo: 'bar'});
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({foo: 'bar', C: []});
});
it('should create vnode with props and children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', {foo: 'bar'}, cdom);
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
foo: 'bar',
C: [ cdom ]
});
});
it('should create vnode with props and children as array', function () {
const cdom1 = dd.H('div');
const cdom2 = dd.H('div');
const cdom3 = dd.H('div');
const vdom = dd.H('div', {foo: 'bar'}, cdom1, [cdom2, cdom3]);
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
foo: 'bar',
C: [ cdom1, cdom2, cdom3 ]
});
});
it('should create vnode with props and mixed children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', {foo: 'bar'}, 'foo', cdom);
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
foo: 'bar',
C: [ 'foo', cdom ]
});
});
it('should create vnode with props and string children', function () {
const vdom = dd.H('div', {foo: 'bar'}, 'foo');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
foo: 'bar',
C: [ 'foo' ]
});
});
it('should create vnode with only child', function () {
const vdom = dd.H('div', 'foo');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ 'foo' ]
});
});
it('should create vnode with children', function () {
const vdom = dd.H('div', 'foo', 'bar', 'baz');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ 'foo', 'bar', 'baz' ]
});
});
it('should create vnode with children in arrays', function () {
const vdom = dd.H('div', 'foo', ['bar', 'baz']);
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ 'foo', 'bar', 'baz' ]
});
});
it('should create vnode with only mixed children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', cdom, 'foo');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ cdom, 'foo' ]
});
});
});
describe('Proxy', function () {
it('H.apply should be proxied', function () {
const cdom = dd.H('div');
const vdom = dd.H.apply({}, ['div', cdom, 'foo']);
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ cdom, 'foo' ]
});
});
it('H.call should be proxied', function () {
const cdom = dd.H('div');
const vdom = dd.H.call({}, 'div', cdom, 'foo');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ cdom, 'foo' ]
});
});
it('H.tag should be a shorthand', function () {
const cdom = dd.H('div');
const vdom = dd.H.div(cdom, 'foo');
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
C: [ cdom, 'foo' ]
});
});
});
});
describe('#R', function () {
describe('DOM Manipulation', function () {
it('should render simple DOM', function () {
const dom = document.createElement('div');
const vdom = dd.H('div');
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div></div>'
);
});
it('should render 1-level nested DOM', function () {
const dom = document.createElement('div');
const vdom = dd.H('div', dd.H('a'), dd.H('b'));
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div><a></a><b></b></div>'
);
});
it('should render 2-level nested DOM', function () {
const dom = document.createElement('div');
const vdom = dd.H('div',
dd.H('a', dd.H('ul')),
dd.H('b', dd.H('ol'))
);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div><a><ul></ul></a><b><ol></ol></b></div>'
);
});
it('should render text children', function () {
const dom = document.createElement('div');
const vdom = dd.H('div', 'foo');
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div>foo</div>'
);
});
it('should render combined dom and text nodes', function () {
const dom = document.createElement('div');
const vdom = dd.H('div', dd.H('a'), 'foo', dd.H('b'));
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div><a></a>foo<b></b></div>'
);
});
it('should apply style properties', function () {
const dom = document.createElement('div');
const vdom = dd.H('div', {style: {color: 'red'}});
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div style="color: red;"></div>'
);
});
it('should apply element attributes', function () {
const dom = document.createElement('div');
const vdom = dd.H('a', {href: '/'});
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<a href="/"></a>'
);
});
it('should apply event handlers', function () {
const dom = document.createElement('div');
const callback = jest.fn();
const vdom = dd.H('a', {href: '/', onclick:callback});
dd.R(vdom, dom);
expect(dom.innerHTML).toEqual(
'<a href="/"></a>'
);
const event = new window.MouseEvent('click');
dom.firstChild.dispatchEvent(event);
expect(callback).toBeCalled();
});
it('should accept props and children', function () {
const dom = document.createElement('div');
const vdom = dd.H('a', {href: '/'}, 'test');
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<a href="/">test</a>'
);
});
});
describe('Reconciliation', function () {
it('should not replace DOM if tag & props are the same', function () {
const dom = document.createElement('div');
const vdom1 = dd.H('div');
const vdom2 = dd.H('div');
dd.R(vdom1, dom)
const c1 = dom.firstChild;
dd.R(vdom2, dom)
const c2 = dom.firstChild;
expect(c1).toBe(c2);
});
it('should replace DOM if tag has changed', function () {
const dom = document.createElement('div');
const vdom1 = dd.H('div');
const vdom2 = dd.H('span');
dd.R(vdom1, dom)
const c1 = dom.firstChild;
dd.R(vdom2, dom)
const c2 = dom.firstChild;
expect(c1).not.toBe(c2);
});
it('should not replace DOM if props have changed', function () {
const dom = document.createElement('div');
const vdom1 = dd.H('div', {foo: 1});
const vdom2 = dd.H('div', {foo: 2});
dd.R(vdom1, dom)
const c1 = dom.firstChild;
dd.R(vdom2, dom)
const c2 = dom.firstChild;
expect(c1).toBe(c2);
});
it('should not replace DOM if only children have changed', function () {
const dom = document.createElement('div');
const vdom1 = dd.H('div', dd.H('span', 'foo'));
const vdom2 = dd.H('div', dd.H('span', 'bar'));
dd.R(vdom1, dom)
const c1 = dom.firstChild;
dd.R(vdom2, dom)
const c2 = dom.firstChild;
expect(c1).toBe(c2);
});
it('should add new DOM elements keeping the old ones intact', function () {
const dom = document.createElement('div');
const vdom1 = [dd.H('div')];
const vdom2 = [dd.H('div'), dd.H('span')];
dd.R(vdom1, dom)
const c1 = dom.firstChild;
expect(dom.children.length).toEqual(1);
dd.R(vdom2, dom)
const c2 = dom.firstChild;
expect(dom.children.length).toEqual(2);
expect(c1).toBe(c2);
});
it('should remove excess DOM elements', function () {
const dom = document.createElement('div');
const vdom1 = [dd.H('div'), dd.H('span')];
const vdom2 = [dd.H('div')];
dd.R(vdom1, dom)
expect(dom.children.length).toEqual(2);
dd.R(vdom2, dom)
expect(dom.children.length).toEqual(1);
});
});
describe('Components', function () {
it('should render simple component', function () {
const dom = document.createElement('div');
const Component = function() {
return dd.H('div')
}
const vdom = dd.H(Component);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div></div>'
);
});
it('should render nested components', function () {
const dom = document.createElement('div');
const Component = function() {
return dd.H('div')
}
const HostComponent = function() {
return dd.H('div',
dd.H(Component),
dd.H(Component)
)
}
const vdom = dd.H(HostComponent);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div><div></div><div></div></div>'
);
});
it('should render component with props', function () {
const dom = document.createElement('div');
const Component = function(props) {
return dd.H('a', {href: props.href})
}
const vdom = dd.H(Component, {href: '/'});
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<a href="/"></a>'
);
});
it('should render stateful components', function () {
const dom = document.createElement('div');
const Component = function(props, {href='/'}) {
return dd.H('a', {
href: href
})
}
const vdom = dd.H(Component, {href: '/'});
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<a href="/"></a>'
);
});
it('should update stateful components', function () {
const dom = document.createElement('div');
const Component = function(props, {clicks=0}, setState) {
return dd.H('button', {
onclick() {
setState({
clicks: clicks + 1
})
}
}, `${clicks} clicks`)
}
const vdom = dd.H(Component);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<button>0 clicks</button>'
);
const event = new window.MouseEvent('click');
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button>1 clicks</button>'
);
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button>2 clicks</button>'
);
});
it('should update independently parallel stateful components', function () {
const dom = document.createElement('div');
const Component = function(props, {clicks=0}, setState) {
return dd.H('button', {
onclick() {
setState({
clicks: clicks + 1
})
}
}, `${clicks} clicks`)
}
const vdom = dd.H('div',
dd.H(Component),
dd.H(Component)
);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div><button>0 clicks</button><button>0 clicks</button></div>'
);
const event = new window.MouseEvent('click');
dom.firstChild.childNodes[0].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<div><button>1 clicks</button><button>0 clicks</button></div>'
);
dom.firstChild.childNodes[0].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<div><button>2 clicks</button><button>0 clicks</button></div>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<div><button>2 clicks</button><button>1 clicks</button></div>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<div><button>2 clicks</button><button>2 clicks</button></div>'
);
dom.firstChild.childNodes[0].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<div><button>3 clicks</button><button>2 clicks</button></div>'
);
});
it('should maintain state of child components', function () {
const dom = document.createElement('div');
const Component = function(props, {clicks=0}, setState) {
return dd.H('button', {
onclick() {
setState({
clicks: clicks + 1
})
}
}, `${clicks} clicks`)
}
const HostComponent = function(props, {clicks=0}, setState) {
return dd.H('button',
{
onclick() {
setState({
clicks: clicks + 1
})
}
},
dd.H('div', `${clicks} clicks`),
dd.H(Component),
dd.H(Component)
)
}
const vdom = dd.H(HostComponent);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<button><div>0 clicks</div><button>0 clicks</button><button>0 clicks</button></button>'
);
const event = new window.MouseEvent('click');
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button>0 clicks</button><button>0 clicks</button></button>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button>1 clicks</button><button>0 clicks</button></button>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button>2 clicks</button><button>0 clicks</button></button>'
);
dom.firstChild.childNodes[2].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button>2 clicks</button><button>1 clicks</button></button>'
);
dom.firstChild.childNodes[2].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button>2 clicks</button><button>2 clicks</button></button>'
);
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>2 clicks</div><button>2 clicks</button><button>2 clicks</button></button>'
);
});
it('should discard child state when it\'s type changes', function () {
const dom = document.createElement('div');
const ComponentA = function(props, {clicks=0}, setState) {
return dd.H('button', {
title: 'a',
onclick() {
setState({
clicks: clicks + 1
})
}
}, `${clicks} clicks`)
}
const ComponentB = function(props, {clicks=0}, setState) {
return dd.H('button', {
title: 'b',
onclick() {
setState({
clicks: clicks + 1
})
}
}, `${clicks} clicks`)
}
const HostComponent = function(props, {clicks=0}, setState) {
const children = [];
if (clicks % 2) {
children.push(dd.H(ComponentA));
children.push(dd.H(ComponentB));
} else {
children.push(dd.H(ComponentB));
children.push(dd.H(ComponentA));
}
return dd.H('button',
{
onclick() {
setState({
clicks: clicks + 1
})
}
},
dd.H('div', `${clicks} clicks`),
children[0],
children[1]
)
}
const vdom = dd.H(HostComponent);
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<button><div>0 clicks</div><button title="b">0 clicks</button><button title="a">0 clicks</button></button>'
);
const event = new window.MouseEvent('click');
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button title="a">0 clicks</button><button title="b">0 clicks</button></button>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button title="a">1 clicks</button><button title="b">0 clicks</button></button>'
);
dom.firstChild.childNodes[2].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>1 clicks</div><button title="a">1 clicks</button><button title="b">1 clicks</button></button>'
);
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>2 clicks</div><button title="b">0 clicks</button><button title="a">0 clicks</button></button>'
);
dom.firstChild.childNodes[1].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>2 clicks</div><button title="b">1 clicks</button><button title="a">0 clicks</button></button>'
);
dom.firstChild.childNodes[2].dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>2 clicks</div><button title="b">1 clicks</button><button title="a">1 clicks</button></button>'
);
dom.firstChild.dispatchEvent(event);
expect(dom.innerHTML).toEqual(
'<button><div>3 clicks</div><button title="a">0 clicks</button><button title="b">0 clicks</button></button>'
);
});
});
describe('Tag Shorthands', function () {
it(`should dynamically create tag shorthands`, function () {
const dom = document.createElement('div');
const {div} = dd.H;
const vdom = div();
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
`<div></div>`
);
});
it('should expand className shorthands', function () {
const dom = document.createElement('div');
const {div} = dd.H;
const vdom = div.class1();
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div class=" class1"></div>'
);
})
it('should expand multiple className shorthands', function () {
const dom = document.createElement('div');
const {div} = dd.H;
const vdom = div.class1.class2.class3();
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div class=" class1 class2 class3"></div>'
);
})
it('should append className shorthands on className props', function () {
const dom = document.createElement('div');
const {div} = dd.H;
const vdom = div.class1.class2.class3({className: 'foo'});
dd.R(vdom, dom)
expect(dom.innerHTML).toEqual(
'<div class="foo class1 class2 class3"></div>'
);
})
});
});
});