Gonna do it here instead of mail because formatting code and stuff :).
Nice to see this coming up! I'll definitely enjoy participating on this, I like goofing around with implementing things half way, but I'm still not sure if I'd roll it out as is.
Performance
Benchmark numbers on my hardware:
Benching REDOM <div> with multiple child nodes
10692ns per iteration (93529 ops/sec)
This is a regression from my implementation (it was 6500ns btw, not 5200ns, turns out I had a bug that made all tags into <div>
s which simplified all the bindings). Passing a function as an argument is powerful, but unless you do it as a reference to a function it also can be costly when you are using curry or other function builders.
I made a PR with my benchmarking tool thing, poor as it may be. It might be a good idea to test things for performance regressions when deciding whether not a particular feature should make it in.
My benchmark was fast because I only used the function call once (to set the class), and it was delegated as the last variant on the if
/else
branch as to not slow down text and element nodes, which should make up for the bulk of the markup. The trio of String
/ Node
/ Function
worked pretty well, which brings me to the next point...
Verbosity
div(
h1(setClass('doom'), 'Hello ', b('DOOM'), '!'),
p('Bacon ipsum dolor amet meatloaf meatball shank porchetta \
picanha bresaola short loin short ribs capicola fatback beef \
ribs corned beef ham hock.')
);
Became:
div(
children([
h1(className('redom'), text('Hello '), children([
b(children([
text('RE:DOM')
]))
]), text('!')),
p(
text('Bacon ipsum dolor amet meatloaf meatball shank porchetta \
picanha bresaola short loin short ribs capicola fatback beef \
ribs corned beef ham hock.')
)
])
)
That's way more verbose. While I typically prefer things explicit over implicit (I do code in Rust after all), I don't particularly like verbosity that doesn't add much, those things belong in the realms of XML and Java 😅.
- The fact that
text()
is a curry while functions such as b()
produce an element got me confused since they both are abstractions of the same type of an object - a Node
. It also means I can't use element functions directly as arguments, and I can't use text()
calls as children.
- I had to break things into more lines because the sheer number of brackets and parens made it difficult to keep track of things.
Components
I've been goofing around with how to do components with functions for dbmon, my last attempt was this:
function cell() {
var elapsed = span();
var popover = div(setClass('popover-content'));
var el = td(
elapsed,
div(setClass('popover left'), popover, div(setClass('arrow')))
);
function mount(parent) {
return el;
}
mount.update = function (data) {
el.className = data.elapsedClassName;
elapsed.textContent = data.formatElapsed;
popover.textContent = data.query;
};
return mount;
}
Which I'm not very happy with. Having a this
to bind elements you create like you'd do in FRZR components is very handy, and the functional alternative isn't very clean since variable declarations are statements and not expressions in JS.
I like your example more:
const login = form(
children(el => [
el.email = input(props({ type: 'email' })),
el.pass = input(props({ type: 'pass' })),
el.submit = button(text('Sign in'))
]),
events({
onsubmit (el, e) {
e.preventDefault();
console.log(el.email.value, el.pass.value);
}
})
);
But it has some problems. I'm not a fan of sticking random members on elements - you might get name conflicts if you are unaware, and the concept is not future proof as the standard can expand and something you've been using as a custom member can become a thing in the future, thus introducing breakage.
Actually using something akin to FRZR components would be an option here, with the views being created a function curry: div(attach(Component))
or div(mount(Component))
.
Other things.
-
I've figured that the function passed in could return a child. This made the hasChildren
for textContent
specialization simpler, and can be used to implement the component mounting.
-
One idea I had for creating a references would involve a curry such as:
var el = div('Hello ', ref('name', text('Bar')), '!');
el.refs.name.textContent = 'World!';
This would not be necessary should we use more FRZR-like components, but it's an idea. It also scopes are custom props to a single object (refs
) instead of polluting the member space.
I can roll out a PR with roughly the same implementation I've had before to see how you like it.
enhancement