DIO.js

Dio is a blazing fast, lightweight (~10kb) feature rich Virtual DOM framework.
- ~10kb minified+gzipped
- ~25kb minified

Browser Support
- Edge
- IE 9+
- Chrome
- Firefox
- Safari
Installation
direct download
<script src=dio.min.js></script>
CDN
<script src=https://cdnjs.cloudflare.com/ajax/libs/dio/3.4.0/dio.min.js></script>
<script src=https://cdn.jsdelivr.net/dio/3.4.0/dio.min.js></script>
<script src=https://unpkg.com/dio.js@3.4.0/dio.min.js></script>
bower
bower install dio.js
npm
npm install dio.js --save
You can also play with Dio on this jsbin
Getting Started
Dio is a blazing fast, lightweight (~10kb) feature rich Virtual DOM framework
built around the concept that any function/object can become a component.
Components in Dio share the same api's as react with a few additions,
this means that you can easily port both code and and your knowledge of the api
both ways between Dio and React as and when needed.
Having said that Dio can be used as just a "view" library but it does come
self containeed with everything you would need to build an application,
from routing, http requests, server-side rendering to testing, state stores, animations and more.
In that respect this getting started guide aims to show you how to go from zero to hello world in Dio.
Hello world
function HelloWorld () {
return {
render: function (props) {
return h('h1', props.text);
}
}
}
dio.render(HelloWorld)({text: 'Hello World'});
Will mount a h1 element onto the page the contents of which will be 'Hello World'.
Performance
API Reference
components schema
{
shouldComponentUpdate: (newProps, newState) => {}
componentWillReceiveProps: (newProps) => {}
componentWillUpdate: (newProps, newState) => {}
componentDidUpdate: (oldProps, oldState) => {}
componentWillMount: () => {}
componentDidMount: () => {}
componentWillUnmount: () => {}
displayName: {string}
propTypes: {Object}
statics: {Object}
render: (props, state, this) => {}
stylesheet: () => ''
this.forceUpdate: ()
this.setState: ({Object}, callback: {function=})
this.withAttr ({string|string[]}, {function|function[]})
this.autoBind (string[])
}
creating hyperscript objects
h(
type: {String},
props?|children?: {Object} | {Array|Object}...,
children?: {Array|Object}...
)
h('.wrap')
h('input[type=checkbox]')
h('input', {value: 'text', onInput: ()=>{}})
h('div', 'Text')
h('div', h('span', 'Text'))
h('div', [h('h1', '1st'), h('h1', '2nd'), ...])
h('div', {innerHTML: "<script>alert('hello')</script>"});
h(Component, {who: 'World'}, 1, 2, 3, 4)
h('div', ComponentFunction)
h('div', Component)
h('@fragment', 'Hello', 'World')
h('@foo', 'Hello', 'World')
{
nodeType: 1,
type: 'div',
props: {},
children: [
{
nodeType: 3,
type: 'text',
props: {},
children: 'Hello World'
}
]
}
h('div', 'Hello World');
dio.createClass
dio.createClass({Function|Object})
class Component extends dio.Component {
render() {
}
}
var myComponent = dio.createClass({
render: () => { return h('div') }
});
var myComponent = dio.createClass(function () {
return {
render: () => { return h('div') }
};
});
class myComponent extends dio.Component {
render() {
return h('div')
}
}
dio.render
dio.render(component: {function|Object},mount?: {string|Element})
var instance = dio.render(Component);
instance(props: {Object});
dio.render(h(Component, {...props}, ...children));
component examples.
function () {
return h('div', 'Hello World')
}
function () {
return {
render: function () {
return h('div', 'Hello World')
}
}
}
h('div', 'Hello World');
{
render: function () {
return h('div', 'Hello World')
}
}
dio.createClass({
render: function () {
return h('div', 'Hello World')
}
})
dio.createClass(function () {
return {
render: function () {
return h('div', 'Hello World')
}
}
})
class Component extends dio.Component {
constructor(props) {
super(props)
}
render() {
return h('div', 'Hello World')
}
}
Components created with .createClass/extends dio.Component
are
statefull by default. There are also other scenarios that pure functions
may become statefull, below are some examples.
function Pure () {
return {
render: function () {
return h('h1');
}
}
}
function Parent () {
return {
render: function () {
return h('div', Pure);
}
}
}
var render = dio.render(Pure);
var render = dio.render(Parent);
Note that in the getting started section 'Hello World'
we did not create a component with dio.createClass()
but rather just used a pure function that we passed
to dio.render(here)
this is because
.render
will create a component if
what is passed to it is not already a component as detailed above.
mount examples.
document
document.body
document.querySelector('.myapp')
'.myapp'
document.createElement('div')
document.createDocumentFragment();
function el () {
return document.body;
}
dio.render(__, mount);
How do i render one component within another?
Components are for the most part functions(statefull/stateless),
to render place them h('div', A)
or h('div', h(A, {}, []))
.
note: render instances(created with .render) are not components
function () {
var elementHolder = dio.stream();
return {
render: function () {
return h('div', {ref: elementHolder},
Foo({text: 'Hello'}),
Bar({text: 'World'})
)
}
}
}
dio.Component.prototype.stylesheet
Before we continue to server-side rendering to explain the stylesheet signature
that appeared in the schema. It allows use to define a component as follows
class Button extends Component {
stylesheet () {
return `
{
color: black;
border: 1px solid red;
padding: 10px;
}
`
}
render () {
return button(null, [text('Click Me')]);
}
}
The return value of the stylesheet method when it exists is expected to
be a string representing css for that component.
The returned styles will be applied to every instance of that component.
Behind the scenes when dio first encounters an element with a stylesheet method
it parses the css returned, prefixes(if applicable), namespaces and caches the output of this.
It will then add this output(string) to the document(browser) or template(server-side).
prefixes supported are appearance, transform, keyframes & animation.
@keyframes are namespaced and so are animations to match. Eveything else works
as it would with regular css with the addition of &{}
and {}
which match the component itself and @root {}
whic pushes a block to
the global namespace.
for example
`
{
color: 'red'
}
&, & h1{
color: 'blue'
}
@root {
body {
background: red;
}
}
`
dio.stylis
Is the interface used to access the internal css compiler that Dio uss to generate
stylesheets.
dio.stylis(selector: {string}, css: {string}, element: {(boolean|Node)=})
dio.stylis('#user', `h1, & { color: red; }`);
'#user h1, #user { color: red; }';
dio.renderToString
Like the name suggests this methods outputs the html
of a specific component this is normally used for server side rendering.
When used as a server-side tool adding the attribute data-hydrate
will tell dio to hydrate the present structure.
dio.renderToString(
subject: {(function|Object|VNode[])},
template: {(string|function)=}
)
dio.renderToString(h('div', 'Text'));
dio.renderToString(Component);
const http = require('http');
const dio = require('./dio.js');
const {Component, renderToString} = dio;
const {button, text, h1} = dio.DOM(['button', 'h1', 'text']);
class Button extends Component {
stylesheet () {
return `
{
color: black;
border: 1px solid red;
padding: 10px;
}
`
}
render () {
return button(null, [text('Click Me')]);
}
}
class Heading extends Component {
render () {
return h1(null, [text('Hello World')])
}
}
const body = renderToString([Heading, Button], `
<html>
<head>
<title>Example</title>
</head>
<body data-hydrate>
{{body}}
</body>
</html>
`);
const body = renderToString([Heading, Button], function (body, style) {
return `
<html>
<head>
<title>Example</title>
${style}
</head>
<body data-hydrate>
${body}
</body>
</html>
`;
});
http.createServer(function(request, response) {
response.writeHeader(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}).listen(3000, '127.0.0.1');
`
<html>
<head>
<title>Example</title>
</head>
<body data-hydrate>
<h1>Hello World</h1><button data-scope="ButtontJroa">Click Me</button>
<style id="ButtontJroa">[data-scope=ButtontJroa] {color:black;border:1px solid red;padding:10px;}</style>
</body>
</html>
`
dio.renderToStream
This method is identical to renderToString with the exception
of its async nature and does not support template functions
dio.renderToStream(
subject: {(function|Object|VNode[])},
template: {string}
)
http.createServer(function(request, response) {
var hoisted = dio.renderToStream();
var stream = new hoisted(Component);
var stream = new hoisted(Component, `<body>{{body}}</body>`);
stream.pipe(response);
}).listen(3000, '127.0.0.1');
http.createServer(function(request, response) {
renderToStream(Component).pipe(response);
}).listen(3000, '127.0.0.1');
dio.renderToCache
dio.renderToCache(subject: ({(VNode[]|VNode|Component)}))
pre-renders and caches the output by executing renderToString
on a component
and store a html cache of the output such that when the same
component is rendered again the cache will be used
instead of doing a re-render. Works with both renderToString
and renderToStream
and is meant to be used on the server-side, for example i could build the whole
html page with components.
class Head extends Component {
render () {
return h('head',
h('title', 'Hello World'),
h('link', {rel: 'stylesheet', href: 'style.css'})
)
}
}
class Button extends Component {
render () {
return h('button', 'Click Me');
}
}
class Body extends Component {
render () {
return h('body', Hello, Button, Button);
}
}
class Page extends Component {
render () {
return h('html',
Head,
Body
)
}
}
so imagine we want Head
to be static i could cache its output
dio.renderToCache(Head);
dio.renderToCache([Head, Button])
dio.renderToCache(Page);
at this point there is almost no overhead because i am essentially rendering a string when
i pass Page
to renderToString
/renderToStream
but depending on the situation you may not
want to cache the whole page but a selection of elements.
if you where wondering what the above Page
component will render
see the following minus the whitespace.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello World</h1>
<button>Click Me</button>
<button>Click Me</button>
</body>
</html>
dio.router
dio.router(
routes: {(function|Object)},
rootAddress: {string=},
onInit: {(string|function)=},
mount: {(string|Node)=}
middleware: {function}
)
dio.router({(function|Object)}, {
root: {string=},
init: {(string|function)=},
mount: {(string|Node)=}
middleware: {function}
})
example router
dio.router({
'/': function (data) {
dio.render(h(home, data))
},
'/user/:id': function (data) {
dio.render(H(user, data));
}
}, '/backend', '/user/sultan')
dio.router({
'/': ComponentA,
'/user/:id': ComponentA
}, {
mount: document.body
});
You can then assign this route to a variable and use it to navigate across views
var router = dio.router({
'/': () => {...}
'/user/:id': () => {...}
});
router.nav('/user/sultan')
router.back()
router.go(-2)
router.forward()
router.link('href')
h('h1', {onClick: router.link('href'), href: '/'}, 'Home')
h('h1', {onClick: router.link('/'), href: '/'}, 'Home')
h('h1', {onClick: router.link(el => el.getAttribute('href')), href: '/'}, 'Home')
h('a', {href: '/about', onClick: router.link(
function () {
return this.getAttribute('href');
}
)});
dio.createStore
dio.createStore(
reducer: {(function|Object)},
initalState: {Any}
enhancer: {Function}
)
Rather exactly like redux createStore
with the different of .connect
that accepts a component & mount/callback
with which to update everytime the store is updated.
Which is mostly a short hand for creating a listerner with .subscribe
that updates your component on state changes.
var store = dio.createStore(reducer: {Function})
var store = dio.createStore(object of reducers: {Object})
store.dispatch({type: '' ...})
store.getState()
store.subscribe(listener: {Function})
store.connect(callback: {function})
store.connect(callback: {(function|Object)}, element: {(string|Node)})
dio.stream
dio.stream(store: {Any|Function}, mapper);
var alwaysString = dio.stream(100, String);
alwaysString()
var foo = dio.stream('initial value')
foo('changed value')
foo('changed')('again')
foo()
var bar = foo.map(function(foo){
return foo + ' and bar';
});
bar()
foo('hello world')
bar()
var faz = dio.stream.combine(function(foo, bar, prevValueOfFaz){
return foo() + bar();
}, foo, bar);
foo(1)
bar(2)
faz()
faz.then(function(faz){
console.log(faz);
});
faz('changed')
faz.then(fn).then(fn)....
dio.stream.all([dep1, dep2]).then(fn);
var async = dio.stream(function (resolve, reject) {
setTimeout(resolve, 500, 'value');
});
var async = dio.stream(function () {
setTimeout(reject, 500, 'just because');
}).then(function (value) {
console.log(value + 'resolved')
}).catch(function (reason) {
console.log('why:' + reason)
});
var foo = dio.stream(function (resolve, reject) {
xhr.onload = resolve;
xhr.onerror = reject;
xhr.send();
});
foo.then(function (value) { return 100 })
.then(function (value) { console.log(value+20) })
.catch(function (value) { return 100 })
.catch(function (value) { console.log(value+200) })
var numbers = stream();
var sum = stream.scan(function(sum, numbers) {
return sum + numbers();
}, 0, numbers);
numbers(2)(3)(5);
sum();
dio.defer
var foo = dio.defer(
fn: {function},
args...: {any[]},
preventDefault: {boolean},
)
onClick: dio.defer(
(a) => {'look no e.preventDefault()'},
['a'],
true
)
function DoesOneThing (component, arg1, arg2) {
component.setState({...});
}
h('input', {
onInput: dio.defer(DoesOneThing, [this, 1, 2], true)
})
dio.createFactory
dio.createFactory(element);
Like it would work in react createFactory returns a function that
produces a hyperscript element of a single given type.
var div = dio.createFactory('div');
h('div', 'Hello World');
div('Hello World');
dio.findDOMNode
Given a component findDOMNode will return the dom node
attached to that component if the component is mounted.
class Component extends dio.Component {
componentDidMount(){
var node = dio.findDOMNode(this);
}
render(){
return h('div')
}
}
dio.DOM
creates common element factories
var {div, li, input} = dio.DOM();
h('div', {}, [div('input')])
div({}, [input()])
[
'h1','h2','h3','h4','h5', 'h6','audio','video','canvas',
'header','nav','main','aside','footer','section','article','div',
'form','button','fieldset','form','input','label','option','select','textarea',
'ul','ol','li','p','a','pre','code','span','img','strong','time','small','hr','br',
'table','tr','td','th','tbody','thead',
];
[
'rect','path','polygon','circle','ellipse','line','polyline','image','marker','a','symbol',
'linearGradient','radialGradient','stop','filter','use','clipPath','view','pattern','svg',
'g','defs','text','textPath','tspan','mpath','defs','g','marker','mask'
];
dio.request
a http helper that makes ajax requests.
dio.request(
url: {String},
payload?: {Object},
enctype?: {String},
withCredentials?: {Boolean},
initial?: {Any},
config?: {Function},
username?: {String},
password?: {String}
)
dio.request.post('/url', {id: 1234}, 'json')
.then((res)=>{return res})
.then((res)=>{'do something'})
.catch((err)=>{throw err});
dio.request({
method: 'GET',
url: '/url',
payload: {id: 1234},
enctype: 'json',
withCredentials: false,
config: function (xhr) {
xhr.setRequestHeader('x-auth-token',"xxxx");
}
}).then((res)=>{return res}).catch((err)=>{throw err});
dio.animateWith
dio.animateWith.flip(
className: {string},
duration: {number},
transform: {string},
transformOrigin: {string},
easing: {string}
)(
element: {(Node|string)}
)
dio.animateWith.transitions(
className: {string},
type?: {(string|number|boolean)}
)(
element: {Node},
callback: {function} => (element: {Node}, transitions: {function})
)
dio.animateWith.animations(...)
for example dio.animateWith.flip
can be used within a render as follows
render: function () {
return h('.card',
{
onclick: dio.animateWith.flip('active-state', 200)
},
''
)
}
since dio.animateWith.flip(...)
returns a function this is the same as
dio.animateWith.flip('active-state', 200)(elementNode)
another animation helper is animateWith.transitions
and animateWith.animations
the callback function
supplied after the element will execute after the
resulting animation/transition from adding/removing the class completes.
handleDelete: function (e) {
var element = e.currentTarget,
self = this
dio.animateWith.transitions('slideUp')(element, function(el, next) {
store.dispatch({type: 'DELETE', id: 1234});
next('slideLeft')(el, function (el, next) {
next('slideLeft', -1)(el);
});
})
}
dio.PropTypes
validates props passed to components insuring they are
of the the specificied type. The built in validtors work
exactly as they do in react land namely...
[
number, string, bool, array, object, func, element, node
any, shape, instanceOf, oneOf, oneOfType, arrayOf, objectOf
]
It should however be noted that propTypes/validations are only evaluated
when process.env.NODE_ENV
is set to 'development'
or
if you set dio.enviroment = 'development'
dio.createClass({
propTypes: {
id: dio.PropTypes.string.isRequired,
name: dio.PropTypes.string,
custom: function (
props,
propName,
displayName,
createInvalidPropTypeError,
createRequiredPropTypeError
) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + displayName + '`. Validation failed.'
);
}
}
}
render: function () {
}
})
createInvalidPropTypeError(
propName: {string},
propValue: {any},
displayName: {string},
expectedType: {string}
)
createRequiredPropTypeError(
propName: {string},
displayName: {string}
)
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
return createInvalidPropTypeError(
propName, props[propName], displayName, 'expected type'
)
dio.window
assigns a mock window object to be used for writting tests for
features that do not exist outside of browser enviroment,
like XMLHttpRequest
and #document
operations.
dio.window = {Object}
Utilities
{
escape: (string: {string}) => {string}
panic: (message: {(string|Error)}, silent: {boolean}) => {(Error|undefined)}
sandbox: (func: {function}, onerror: {function=}, value: {any=}) => {any}
compose: (...funcs: {...function}) => {function}
random: (length: {number}) => {string}
flatten: (array: {any[]}) => {any[]}
input: (string: {string}) => {}
isObject: (subject: {any=}) => {boolean}
isFunction: (subject: {any=}) => {boolean}
isString: (subject: {any=}) => {boolean}
isArray: (subject: {any=}) => {boolean}
isDefined: (subject: {any=}) => {boolean}
isNumber: (subject: {any=}) => {boolean}
isArrayLike: (subject: {any=}) => {boolean}
}
Performance Tips
The hyperscript helper h
for creating vnodes is very versatile,
however if you are trying to draw the most out of performance
the following way of creating vnodes are the most optimal performance wise..
{
nodeType: 1,
type: 'div',
props: {},
children: [
{
nodeType: 3,
type: 'text',
props: {},
children: 'Hello World'
}
]
}
dio.VElement('div', {}, [VText('Hello World')])
dio.VComponent(Component)
dio.VFragment([...])
dio.VSvg({}, [...])
dio.VText('Content')
dio.VBlueprint(vnode);
dio.VBlueprint(vnode);