1link// Step 1: Element Creation
2linkconst x = <div class={y}>z {w}</div>; // @see [explanation](#element-creation)
3link
4link// Step 2: Rendering & Life-Cycle
5linkrenderer.render(x).on(document.body); // @see [explanation](#rendering--life-cycle)
6linkrenderer.remove(x); // @see [explanation](#rendering--life-cycle)
To see what callbag-jsx
does under the hood, we will go through this code step by step.
1linkconst x = <div class={y}>z {w}</div>;
As detailed here, this code is transpiled to this:
1linkconst x = renderer.create('div', { 'class': y }, 'z', w); // --> notice how tag is a string here
The renderer
will now:
'div'
)1linkdocument.createElement('div')
The renderer
will then set the properties of this newly created element, again looking
for volunteering plugins first and falling back to default if no plugin accepts given property on given node.
For example, if y
is a callbag, then CallbagPropPlugin
will be utilized, who will in turn add a life-cycle hook to the element
that subscribes to given callbag and updates the corresponding property when it emits:
1linkrenderer.hook(node, makeHook(target, value => renderer.setProp(node, prop, value)));
The same happens to children that are appended to the element. Plugins are consulted first, and fallback default
is used if none volunteers. In our example, if w
is a callbag, then CallbagAppendPlugin
will create a leaf node (a text node) with a life-cycle hook for
subscribing into given callbag and updating the leaf's content accordingly, and appends given leaf node to target node
instead:
1linkconst leaf = renderer.leaf();
2link
3linkrenderer.hook(leaf, makeHook(target, value => {
4link renderer.setContent(leaf, value);
5link}));
6linkrenderer.append(leaf, host);
1linkrenderer.render(x).on(document.body);
☝️ This will append x
to body and bind all of its life-cycle hooks. Each life-cycle hook
looks like this:
1linkinterface LifeCycleHook {
2link bind?(): void;
3link clear?(): void;
4link}
👉 bind()
will be called when the element is added to main document (appears on screen).
👉 clear()
will be called when the element is removed from main document.
A commonly used life-cycle hook is one that subscribes to a given callbag, which looks like this:
1linkfunction makeHook(callbag, op) {
2link let dispose;
3link
4link return {
5link bind() { dispose = pipe(subscribe(callbag, op)) }
6link clear() { dispose() }
7link }
8link}
This ensures that given callbag is subscribed to while the element is on the screen, and the subscription (and its associated resources) are cleared up when it is taken off-screen.
1linkrenderer.remove(x);
☝️ This will remove x
from main document, and clear its life-cycle hooks.
1linkconst x = <MyComponent property={y}/>;
☝️ This is transpiled to the following:
1linkconst x = renderer.create(MyComponent, {property: y}); // --> notice how tag is not a string anymore
The same element creation steps are taken for this component. If MyComponent
is a function,
then FunctionalComponentPlugin
will take over the element creation process:
1linkclass FunctionalComponentPlugin extends ComponentPlugin {
2link match(component) {
3link return typeof component.tag === 'function'; // --> only works for functions
4link }
5link
6link create(component, provision) {
7link return component.tag.apply(
8link provision, // --> will become the `this` argument
9link [
10link component.props, // --> the props dictionary, first argument
11link this.renderer(), // --> the renderer object
12link component.children // --> the children array
13link ]);
14link }
15link}
👉 There is a special type of renderer plugin named ComponentProcessor
. Processors can provide specific provisions for each component (accessible via this
, in
functional components), or run some side-effects on the elements returned by the component.
For example, tracking is enabled by CallbagTrackPlugin
, who roughly looks like this:
1linkclass CallbagTrackPlugin extends ComponentProcessor {
2link process(provide, post) {
3link const tracked = [];
4link
5link provide({
6link track: trackable => tracked.push(trackable) // --> this becomes `this.track()`
7link });
8link
9link post(node => tracked.forEach(
10link t => this.renderer().hook(node, makeHook(t))
11link ));
12link }
13link}
callbag-jsx
is an extension of Render JSX. It basically
provides a set of rendering plugins that allow
embedding callbags in DOM elements, and some helper components
for dynamic lists, conditional DOM, etc.
It is highly recommended to checkout the docs for Render JSX itself
for learning more about how callbag-jsx
operates under the hood.