As an aside, I really like the class name and text content ergonomics (e.g div.someclass, span:some content). Reminiscent of pug/jade
I 'invented' the concept for this back in 2011, and it was used (as a proprietary lib) in various startups. Even though many similar open source libs have been released since, and boy have I tried a lot of them, none have been able to capture the elegance and DX of what we had back then. I might be biased though. :-)
So I started creating a cleaned-up, modern, TypeScript, open source implementation for the concept about five years ago. After many iterations, working on the project on and off, I'm finally happy with its API and the developer experience it offers. I'm calling it 1.0!
The concept: It uses many small, anonymous functions for emitting DOM elements, and automatically reruns them when their underlying proxied data changes. This proxied data can be anything from simple values to complex, typed, and deeply nested data structures.
As I'm currently free to spend my time on labors of love like this, I'm planning to expand the ecosystem around this to include synchronizing data with a remote server/database, and to make CRUD apps very rapid and perhaps even pleasurable to implement.
I've celebrated 1.0 by creating a tutorial with editable interactive examples! https://aberdeenjs.org/Tutorial/
I would love to hear your feedback. The first few people to actually give Aberdeen a shot can expect fanatical support from me! :-)
As an aside, I really like the class name and text content ergonomics (e.g div.someclass, span:some content). Reminiscent of pug/jade
Also, in order to transform JSX into individual rerunnable functions, we'd need a whole different transpiler. I like being able to code browser-runnable JavaScript directly.
To each their own. :-)
I have a POC syntax extension (babel parser fork) I named JSXG where I introduced "generator elements" which treats the body of the element as a JS generator function that yields JSX elements.
The simple/naive implementation of just running the generator was okay, but I (perhaps prematurely) worried that it would be not ideal to have the resulting list of child elements be actually dynamic-- as opposed to being fixed size but have false/null in place of "empty" slots and also using arrays for lists made by loops.
So, I also had a transform that followed conditional branching and loops etc. and made a template of "slots" and that resulted in a stable count of children, and that improved things a whole lot.
It's been a while since I revisited that, I should try and find it!
Comparisons below.
Aberdeen:
$('div', () => {
if (user.loggedIn) {
$('button.outline:Logout', {
click: () => user.loggedIn = false
});
} else {
$('button:Login', {
click: () => user.loggedIn = true
});
}
});
$('div.row.wide', {$marginTop: '1em'}, () => {
$('div.box:By key', () => {
onEach(pairs, (value, key) => {
$(`li:${key}: ${value}`)
});
})
$('div.box:By desc value', () => {
onEach(pairs, (value, key) => {
$(`li:${key}: ${value}`)
}, value => invertString(value));
})
})
JSX: <div>
{user.loggedIn ? (
<button
className="outline"
onClick={() => user.loggedIn = false}
>
Logout
</button>
) : (
<button onClick={() => user.loggedIn = true}>
Login
</button>
)}
</div>
<div
className="row wide"
style={{ marginTop: '1em' }}
>
<div className="box">
By key
<ul>
{Object.entries(pairs).map(([key, value]) => (
<li key={key}>
{key}: {value}
</li>
))}
</ul>
</div>
<div className="box">
By desc value
<ul>
{Object.entries(pairs).map(([key, value]) => (
<li key={key}>
{key}: {invertString(value)}
</li>
))}
</ul>
</div>
</div>
JSXG: <*div>
if (user.loggedIn) {
yield <button
className="outline"
onClick={() => user.loggedIn = false}
>
Logout
</button>
} else {
yield <button onClick={() => user.loggedIn = true}>
Login
</button>
}
</*div>
<div
className="row wide"
style={{ marginTop: '1em' }}
>
<div className="box">
By key
<*ul>
for (const [key, value] of Object.entries(pairs)) {
yield <li key={key}>
{key}: {value}
</li>
}
</*ul>
</div>
<div className="box">
By desc value
<*ul>
for (const [key, value] of Object.entries(pairs)) {
yield <li key={key}>
{key}: {invertString(value)}
</li>
}
</*ul>
</div>
</div>
Edit:Come to think of it, I think it may have been <div*>...</div*> or even (:O gasp) <div*>...</div>.
The first was using </* as the marker for the end tag of a JSXG element. It worked, but it seemed like it wouldn't be too well received as parsers today treat /* as a comment in that spot iirc.
Edit: The other premature concern/feature was the ability to have a for loop NOT render to an array and rather be inline.
Normaly
for (...) { yield <>...</> }
// becomes
[[<>...</>, <>...</>, ...]]
But I added a mechanism using string directives that would inline it: "use jsxg:inline"
for (...) { yield <>...</> }
// becomes
[<>...</>, <>...</>, ...]