Awesome
🐪 Kaboobie
<sup>Social Media Photo by Mariam Soliman on Unsplash</sup>
Abandoned
Beside the fact I've managed to bring JSX to µland, something you can play with online, this project cannot compete with more recent alternatives such as µbe and µbe-ssr.
A <em>µ</em>land based experiment.
import {Component, render, html, useState} from 'kaboobie';
// components are identical to µland, except
// each component receives its "props" object
const Counter = Component(({start}) => {
const [count, setCount] = useState(start);
return html`
<button onclick=${() => setCount(count + 1)}>
Count: ${count}
</button>`;
});
// "props" are like attributes passed as key => value
render(document.body, html`
<div>
A bounce of counters.<hr>
<!-- these two will be two buttons -->
<${Counter} start=${0} />
<${Counter} start=${1} />
</div>
`);
F.A.Q.
<details> <summary><strong>Who is Kaboobie?</strong></summary> <div>It's Shazzan's flying camel, and Shazzan is a cartoon I've watched when I was a kid.
Magic, illusions, and a flying camel, summarize pretty well the idea behind this project:
- the template literal you write is not the template literal µland parses
- if µland is "a unicorn" in terms of easiness, features, and lightness, a flying camel is the closest unicorn's friend I could think about
- finding an npm name that's not already taken is hard, but "fortunately" I'm old enough to know magic creatures younger developers might have never heard about
The concept is a mix of re-mapped templates literals and related values through placeholder DOM elements handled by a MutationObserver that gets upgraded in a similar way Custom Elements do, each time one new component lands on the page, and without needing Custom Elements at all.
Basically, the following template literal tag:
html`
<${Component} test=${{data: 123}} value=${456}>
<${A} any=${'thing'} />
<${B}> Hello </>
</>
`;
Would represent the following template literal and values as arguments:
html(
[
"<", " test=", " value=", ">\n <",
" any=", " />\n <",
"> Hello </>\n </>"
],
Component,
{data: 123},
456,
A,
B
)
What Kaboobie does, is re-map once both template and values to become the following:
html`
<kaboobie style="display:none"
.$=${Component} ._=${({test: {data: 123}, value: 456})}>
<kaboobie style="display:none" .$=${A} ._=${{any: 'thing'}} />
<kaboobie style="display:none" .$=${B} ._=${{}}> Hello </kaboobie>
</kaboobie>
`;
Meaning, the tag will receive instead:
html(
[
"<kaboobie style=\"display:none\" .$=", " ._=",
">\n <kaboobie style=\"display:none\" .$=", " ._=",
" />\n <kaboobie style=\"display:none\" .$=", " ._=",
"> Hello </kaboobie>\n </kaboobie>"
],
Component,
{test: {data: 123}, value: 456},
A,
{any: 'thing'},
B,
{}
)
And render it accordingly with uhtml direct properties .name=${value}
feature.
The MutationObserver at this point looks only for <kaboobie>
nodes, and replaces these with a µland component through the render(...)
utility.
const fragment = document.createDocumentFragment();
render(fragment, kaboobie.$(kaboobie._));
Props are also defined as setter, so that whenever an outer component gets rendered again, setting kaboobie._ = props
would re-trigger a render update, as the outer component would still believe it has kaboobie nodes within its content, making the concept an illusion for the underlying µhtml parser.
That's it: a template/values manipulation to trick µhtml parser while serving µland components, updated each time through hooks and render(...)
.
Kaboobie can't be faster than µland due extra one-off template parsing, followed by values updates to recreate props each time, and it's surely not faster than µhtml.
However, since µhtml is probably the fastest library of its kind, Kaboobie should be fast enough for medium to complex hooks based applications.
That being said, for now I'm playing around to make it work so performance might be even better in the future, but so far I couldn't measure any relevant bottleneck.
</div> </details> <details> <summary><strong>What about memory?</strong></summary> <div>Magic has a cost, and in Kaboobie case, each component inevitably needs to retain its own placeholder and a unique document fragment reference to work as expected.
While the fragment could probably be avoided somehow, outer rendered components still need to update their <kaboobie>
nodes to signal, and pass along, new possible props
for each nested component.
In few words, components cost whatever µland costs, or any hook based library as there's a lot of GC going on there, plus a disconnected unknown kaboobie node and a fragment per each component.
Have these ever been a real issue? I don't think so, but if you consider that no Virtual DOM is used, I think memory consumption is at par, if not lower, than most competitors.
</div> </details> <details> <summary><strong>Can I use it already?</strong></summary> <div>Please do, but be aware this is currently an experiment, so while I don't think its most basic features will ever change, and so far these work more or less as expected, it's not been used in production like my libraries alternatives do.
</div> </details> <details> <summary><strong>Any better example?</strong></summary> <div>For other basic examples, check the test folder, which is also published live.
As I'll likely keep playing around with this idea, more examples will come, but if you have some cool demo around this library, please do let me know (file an issue, contact me on twitter, any other mean) and I'll list it in this README, thank you ♥
</div> </details> <details> <summary><strong>Any caveat?</strong></summary> <div>Elements that cannot be represented standalone within an unknown element, such as <tr>
, <td>
, or an <option>
can't be part of the static layout.
Example:
const Table = Component(({children}) => {
return html`
<table>
${children}
</table>
`;
});
const Tr = Component(({children}) => {
return html`
<tr>
${children}
</tr>
`;
});
const Td = Component(({value}) => {
return html`
<td>
${value}
</td>
`;
});
render(document.body, html`
<${Table}>
<${Tr}>
<${Td} value=${'This will be visible'} />
</>
<tr>
<td>Parent TR will be swalloed</td>
</tr>
</>
`);
The reason is that there's no way to place some specific element outside their expected container, and a <template>
tag within a <template>
tag might produce undesired results.
That's it, remember that special elements are either fully static, statc withn their own definition, or simply use their components without mixing up components with native elements, and everything should be fine.
</div> </details>