Why not JSX? There’s no real cost to making the API JSX compatible, from what I can tell, and tsc has builtin support for transpiling JSX. It would also make porting code a lot easier. I’m only saying this because the type signature of $ is so similar to createElement.
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 don't particularly like how control logic needs to be embedded within JSX using ?ternary : operators and .map(() => stuff) within the HTML.
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.
> Also, in order to transform JSX into individual rerunnable functions, we'd need a whole different transpiler.
I don't think you would. `<Component prop="example" />` gets converted by current transpilers into `jsx(Component, { prop: "example" })`. The `Component` itself is passed as is. In the case of Components that are just functions, that passes the function as-is, as a function you can just call as needed.
JSX was built for "rerunnable functions". It's a lot of how React works under the hood.
The problem is that JSX transpilers will put child nodes in an array, instead of in an anonymous function, meaning there is no easy way (without transpiler magic) to rerender just a part of the component.
In React you can, with some effort, limit virtual DOM rerenders to a single component. Aberdeen rerenders fractions of components by default, if that's all that needs to happen. That's why it works well operating directly on the actual DOM, without a virtual DOM inbetween.
Your `_jsx` function can auto-wrap children in a function. Also, you can just pass functions as children, too if you really want to make the JSX author work for it:
That doesn't even look half bad to me. It looks really close to many of your other examples.
> In React you can, with some effort, limit virtual DOM rerenders to a single component. Aberdeen rerenders fractions of components by default, if that's all that needs to happen. That's why it works well operating directly on the actual DOM, without a virtual DOM inbetween.
A lot of that depends on what your model of a "component" is. React will rerender fractions of a component in advanced areas such as a Error Boundaries and Suspense.
Also, for what little it is worth, my JSX-based library isn't a virtual DOM, operates directly on the actual DOM, and generally renders components once and only once in their lifetime, because it binds all changes even more directly to specific DOM properties.
> Your `_jsx` function can auto-wrap children in a function.
It unfortunately can't, because by the time the jsx function gets a chance to run, the expression has already been evaluated, whereas the whole point of a framework like this is to be able to run that evaluation repeatedly.
So what you'd need to write to get that to work would be something like
<div>{() => user.name}</div>
for every single case where you're interpolating a value, which starts looking kind of ugly. It also has its own pitfalls. For example, your example with a ternary in it would unnecessarily rerender (i.e. completely unmount and recreate) the child elements if the input changes but the conditional still evaluates to the same thing. (E.g. if data.enabled was 1 and we set it to 2, what we want is to have no change in the DOM, but what we'd get the input element being unmounted and remounted.)
There's also just the general issue that JSX requires some sort of transform. This isn't necessarily bad - I use SolidJS a lot which explicitly makes use of the chance to optimise the JSX rendering at compile time - but there are situations where you want to avoid that, and having a syntax that's focused on the "no compile step" case is useful.
I might be missing your point, can you elaborate? If you want to write an if statement you just do it at the end of a component, after the hooks. It's a common pattern.
You don't have to do that in the html if you don't want to. You can easily assign those operations to a variable then insert the variable into the html.
re: syntax, I agree, it's stopped me from ever trying React/JSX-based frameworks, which I am sure is an over-reaction.
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!
I think it would be pretty easy to transpile this (or something like it) to code Aberdeen can consume. In fact, it would probably be a lot easier than transpiling for React, as Aberdeen uses immediate mode the `for` problem in your comment below wouldn't be a problem at all, so no return values to worry about.
I'd use something like this myself for larger projects, I think. But asking other developers to use a different programming language just to use your library usually doesn't fly well. :-) Coming to think of it, I find it actually kind of surprising that JSX succeeded.
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.
JSX is a better data structure than strings for expressing views, but this doesn’t use strings or any other data structure.
The beauty of the OP’s approach is the immediate mode render approach. JSX is a data structure, immediate mode implies a structure from the order of execution of computation.
You need JSX to pass around fragments of a view, unless your view is made entirely of computation, in which case you can just pass around functions.
JSX does not have any inherent data structure, it immediately converts to function calls. React is well known for taking the JSX calls and converting them to a reusable data structure, but that's React's thing and React's data structure is somewhat unique to React, other virtual DOM's have different data structures.
You can do "immediate mode" JSX. There's nothing technical stopping you, and there are at least a few libraries out there that do.
No matter how one turns it, JSX is still something different from plain JS. It is a kind of format, that needs to be parsed and interpreted. Writing only plain JS from that perspective is a purer approach than having JSX anywhere.
There's no embedded interpretation in JSX, it's a direct "sugar" for a function call pattern. If you are already using Typescript, it's a "free" syntax sugar that Typescript supports well.
It's less pure in that it doesn't run directly in the browser today, but it is also way more pure than the template compilers in Angular and Vue and others.
The signature looked compatible with JSX to me. You could probably easily create JSX functions that just use $ internally, and make your web build tool's react-auto-import setting point to it.
As an aside, I really like the class name and text content ergonomics (e.g div.someclass, span:some content). Reminiscent of pug/jade