Skip to content

Commit

Permalink
Merge pull request #20 from wavesoft/devel/0.2.2
Browse files Browse the repository at this point in the history
v0.2.2 Release
  • Loading branch information
wavesoft authored Feb 18, 2017
2 parents 5e2376b + 962e78e commit e21d91a
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 76 deletions.
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# .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)

> A tiny (510 byte) virtual DOM template engine for embedded projects
> A tiny (511 byte) virtual DOM template engine for embedded projects
| <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 |
| --------- | --------- | --------- | --------- | --------- | --------- | --------- |
Expand All @@ -12,7 +12,7 @@ Why? Because with such library you can create powerful GUIs in tight space envir

### Features

* _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.
* _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.

* _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.

Expand All @@ -25,16 +25,16 @@ Why? Because with such library you can create powerful GUIs in tight space envir

## Installation

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

```html
<script src="dotdom.min.js.gz" />
```
Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (755b):
Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (779b):
```js
((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]&&k.unshift(j)&&{C:k}||(j.C=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,w,x,y=n.P[v])=>'style'==v?c.assign(u[v],y):'C'!=v&&(u[v]=y))&&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)=>h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
((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
Expand Down Expand Up @@ -210,6 +210,29 @@ component.
Properties and children are optional and they can be omitted.
#### Functional Components
Instead of a tag name you can provide a function that returns a Virtual DOM
according to some higher-level logic. Such function have the following signature:
```js
const Component = (props, state, setState) {
// Return your Virtual DOM
return div( ... )
}
```
The `props` property contains the properties object as given when the component
was created.
The `state` is initialized to an empty object `{}` and it's updated by calling
the `setState({ newState })` method. The latter will also trigger an update to
the component and it's children.
You can also assign properties to the `state` object directly if you don't want
to cause an update.
### Tag Shorthand `tag( [properties], [children ...] )`
```js
Expand Down Expand Up @@ -289,8 +312,8 @@ Are you interested in contributing to **.dom**? You are more than welcome! Just
global.R = render = (
vnodes, // Flat-code comments start on column 70 and
dom, // wrap after column 120.
/* Logical separations can be commented like this */
...
```
2 changes: 1 addition & 1 deletion dotdom.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified dotdom.min.js.gz
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "dot-dom",
"version": "0.2.1",
"version": "0.2.2",
"description": "A tiny (less than 512 byte) template engine that uses virtual DOM and some of react principles",
"main": "dotdom.min.js",
"main": "src/dotdom.js",
"scripts": {
"test": "./node_modules/.bin/jest",
"build": "./node_modules/.bin/babili src/dotdom.js | tee dotdom.min.js | gzip -9 > dotdom.min.js.gz"
"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"
},
"repository": {
"type": "git",
Expand Down
169 changes: 119 additions & 50 deletions src/__tests__/dotdom-test.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,141 @@
require('../dotdom');
const dd = window;
const dd = require('../dotdom');

describe('.dom', function () {

describe('#H', function () {
it('should create vnode without arguments', function () {
const vdom = dd.H('div');

expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({C: []});
});
describe('Factory', function () {

it('should create vnode with props', function () {
const vdom = dd.H('div', {foo: 'bar'});
it('should create vnode without arguments', function () {
const vdom = dd.H('div');

expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({foo: 'bar', C: []});
});
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({C: []});
});

it('should create vnode with props and children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', {foo: 'bar'}, cdom);
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: [ cdom ]
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({foo: 'bar', C: []});
});
});

it('should create vnode with props and mixed children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', {foo: 'bar'}, 'foo', cdom);
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: [ 'foo', 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 string children', function () {
const vdom = dd.H('div', {foo: 'bar'}, 'foo');
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' ]
expect(vdom.E).toEqual('div');
expect(vdom.P).toEqual({
foo: 'bar',
C: [ 'foo', cdom ]
});
});
});

it('should create vnode with only child', function () {
const vdom = dd.H('div', 'foo');
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({
C: [ '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' ]
});
});

});

it('should create vnode with only mixed children', function () {
const cdom = dd.H('div');
const vdom = dd.H('div', 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' ]
});
});

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' ]
});
});

});

});
Expand Down Expand Up @@ -292,8 +361,8 @@ describe('.dom', function () {
}
const HostComponent = function() {
return dd.H('div',
H(Component),
H(Component)
dd.H(Component),
dd.H(Component)
)
}
const vdom = dd.H(HostComponent);
Expand Down Expand Up @@ -379,8 +448,8 @@ describe('.dom', function () {
}, `${clicks} clicks`)
}
const vdom = dd.H('div',
H(Component),
H(Component)
dd.H(Component),
dd.H(Component)
);

dd.R(vdom, dom)
Expand Down
37 changes: 22 additions & 15 deletions src/dotdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* BEGIN NPM-GLUE */

// This code block will be striped when building the stand-alone version.
// When using `npm` this exports the correct functions in order to be easily
// imported in the correct scope, without leaking to the global scope.

const window = {};
module.exports = window;

/* END NPM-GLUE */

((global, document, Object, vnodeFlag, globalState, createElement, render, wrapClassProxy) => {

/**
Expand All @@ -41,8 +53,10 @@
// first argument

P: props[vnodeFlag] // If the props argument is a renderable VNode,
&& children.unshift(props) && {C: children} // ... prepend it to the children
|| (props.C = children) && props // ... otherwise append 'C' to the property
? {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.
})

/**
Expand Down Expand Up @@ -140,25 +154,18 @@
? _new_dom.data = vnode // - String nodes update only the text
: Object.keys(vnode.P).map( // - Element nodes have properties
(
key, // 1. The property name

_unused2, // 2. Index is unused
_unused3, // 3. Array is unused

_value=vnode.P[key] // a. We cache the property value

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'
_value
vnode.P[key]
)

: (key != 'C' && // 'C' is the children, so we skip it

(_new_dom[key] = _value)) // All properties are applied directly to DOM
: (_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
// instance. This includes `onXXX` event handlers.

) &&
Expand Down Expand Up @@ -214,8 +221,8 @@
global.H = new Proxy(
createElement,
{
get: (_unused4, tagName) =>
wrapClassProxy(
get: (targetFn, tagName) =>
targetFn[tagName] || wrapClassProxy(
createElement.bind(global, tagName)
)
}
Expand Down

0 comments on commit e21d91a

Please sign in to comment.