Star

Created With

Under the Hood


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.




linkElement Creation

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:

  1. Ask all plugins to see if anyone wants to create the element for given tag ('div')
  2. Will pick the first plugin that accepts or use its own fallback mechanism:
    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);




linkRendering & Life-Cycle

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.




linkComponents

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}

Learn More



linkFurther Reading

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.

Learn More





Element CreationRendering & Life-CycleComponentsFurther Reading

Home Getting Started


Introductionchevron_right

Basicschevron_right

Reactivitychevron_right

Componentschevron_right

In-Depthchevron_right

Metachevron_right