Awesome
TUSK (UNMAINTAINED) - See Set-DOM
A lightweight viritual-dom with a friendly interface.
Why
Many virtual-dom implementations are bulky and are not optimized for immutable data or server side rendering. Currently this is experimental and should not be used in production.
Features
- ~3kb min/gzip.
- Minimal API.
- Designed for immutable data.
- No extra "data-react-id" attributes.
- No random span's inserted into DOM.
- Allows any custom attribute (react only allows data-).
- Renders onto an element, instead of into.
- Render nested arrays.
Supports
- Event delegation.
- Full page rendering.
- Server side rendering (With ability to bootstrap a page).
- SVG and MathML namespaces in a clean way.
- Keyed nodes - same as react although optional.
- Contexts - same as react (but easier to use).
- Node constants. (Good for memoization).
- JSX (Or non JSX, just call tusk directly).
Installation
Npm
npm install tusk
Bower
bower install tusk
Example
/** @jsx tusk */
let tusk = require('tusk');
// Using immstruct for example, feel free to replace with immutable.js or others.
let immstruct = require('immstruct');
// Define some initial state for the app.
let struct = immstruct({ i : 0 });
function MyCounter (props, children) {
let { message, cursor } = props;
// Define handlers.
let handleClick = (e)=> cursor.update((state)=> state.set("i", state.get("i") + 1));
let setup = (e)=> ...;
let teardown = (e)=> ...;
// Render the component.
return (
<body>
<button onClick={ handleClick } onMount={ setup } onDismount={ teardown }>
{ message } : { cursor.get('i') }
</button>
</body>
);
}
// Initial render
render();
// We can use the render function to re-render when the state changes.
struct.on("next-animation-frame", function render () {
tusk.render(document.body,
<MyCounter message="Times clicked" cursor={ struct.cursor() }/>
);
});
// We can also render into a string (Usually for the server).
let HTML = String(<MyCounter type="Times clicked" cursor={ struct.cursor() }/>);
// -> "<body><button>Times clicked : 0</button></body>"
API
- render(HTMLEntity, node) : Bootstrap or update a virtual
node
inside of anHTML Entity
.
tusk.render(document.body,
<body>
<div>Hello World</div>
</body>
);
// -> document.body.innerHTML === "<div>Hello World</div>"
- isElement(element) : Tests if given
element
is a tusk virtual element.
tusk.isElement(<div/>); // true
- with(context, renderer) : Gives all components inside a render function some external
context
.
// renderer must be a function that returns a virtual node.
function MyComponent (props, children, context) {
return (
<div>External data: { context }</div>
);
}
String(tusk.with(1, ()=> <MyComponent/>));
//-> "<div>External Data: 1</div>"
- createElement(type, props, children...) : Create a virtual node/component.
// Automatically called when using JSX.
let vNode = tusk.createElement("div", { editable: true }, "Hello World");
// Or call tusk directly
let vNode = tusk("div", { editable: true }, "Hello World");
// Render to string on the server.
vNode.toString(); // '<div editable="true">Hello World</div>';
/**
* @params type can also be a function (shown in example above).
*/
Advanced Performance
Memoization
In React and many other virtual doms "shouldUpdate" is a common theme for performance. Tusk does not feature shouldUpdate and opts for a more performant, simpler, and well known approach: memoization.
Basically Tusk will never re-render when given the same node twice, meaning the following will only render once.
let _ = require("lodash");
let MyDiv = _.memoize(function () {
return (
<div>Hello World</div>
);
});
// creates and renders myDiv.
tusk.render(HTMLEntity, <MyDiv/>);
// noop.
tusk.render(HTMLEntity, <MyDiv/>);
// render something entirely different.
tusk.render(HTMLEntity, <MyOtherDiv/>);
// switch back - reuses existing "MyDiv" dom. (Extremely fast).
tusk.render(HTMLEntity, <MyDiv/>);
Stateful Nodes
Sometimes it makes sense to have full control over the DOM for certain types of components. In Tusk the framework won't fight you and instead provides an attribute for these types of nodes; "ignore". An ignored node will skip updates if the new node also has an "ignore" attribute.
function MyMap (props, children) {
return (
<div>
Map: { props.title }
The div below will only be rendered by tusk once, all updates are noops.
<div ignore class="google-map" onMount={ confirgureGoogleMap }/>
</div>
);
}
Contributions
- Use gulp to run tests.
Please feel free to create a PR!