hyperhtml
Version:
A Fast & Light Virtual DOM Alternative
83 lines (74 loc) • 3.05 kB
JavaScript
import WeakMap from '@ungap/weakmap';
import tta from '@ungap/template-tag-arguments';
import Wire from 'hyperhtml-wire';
import {Tagger} from '../objects/Updates.js';
// all wires used per each context
const wires = new WeakMap;
// A wire is a callback used as tag function
// to lazily relate a generic object to a template literal.
// hyper.wire(user)`<div id=user>${user.name}</div>`; => the div#user
// This provides the ability to have a unique DOM structure
// related to a unique JS object through a reusable template literal.
// A wire can specify a type, as svg or html, and also an id
// via html:id or :id convention. Such :id allows same JS objects
// to be associated to different DOM structures accordingly with
// the used template literal without losing previously rendered parts.
const wire = (obj, type) => obj == null ?
content(type || 'html') :
weakly(obj, type || 'html');
// A wire content is a virtual reference to one or more nodes.
// It's represented by either a DOM node, or an Array.
// In both cases, the wire content role is to simply update
// all nodes through the list of related callbacks.
// In few words, a wire content is like an invisible parent node
// in charge of updating its content like a bound element would do.
const content = type => {
let wire, tagger, template;
return function () {
const args = tta.apply(null, arguments);
if (template !== args[0]) {
template = args[0];
tagger = new Tagger(type);
wire = wireContent(tagger.apply(tagger, args));
} else {
tagger.apply(tagger, args);
}
return wire;
};
};
// wires are weakly created through objects.
// Each object can have multiple wires associated
// and this is thanks to the type + :id feature.
const weakly = (obj, type) => {
const i = type.indexOf(':');
let wire = wires.get(obj);
let id = type;
if (-1 < i) {
id = type.slice(i + 1);
type = type.slice(0, i) || 'html';
}
if (!wire)
wires.set(obj, wire = {});
return wire[id] || (wire[id] = content(type));
};
// A document fragment loses its nodes
// as soon as it is appended into another node.
// This has the undesired effect of losing wired content
// on a second render call, because (by then) the fragment would be empty:
// no longer providing access to those sub-nodes that ultimately need to
// stay associated with the original interpolation.
// To prevent hyperHTML from forgetting about a fragment's sub-nodes,
// fragments are instead returned as an Array of nodes or, if there's only one entry,
// as a single referenced node which, unlike fragments, will indeed persist
// wire content throughout multiple renderings.
// The initial fragment, at this point, would be used as unique reference to this
// array of nodes or to this single referenced node.
const wireContent = node => {
const childNodes = node.childNodes;
const {length} = childNodes;
return length === 1 ?
childNodes[0] :
(length ? new Wire(childNodes) : node);
};
export { content, weakly };
export default wire;