Hacker News new | past | comments | ask | show | jobs | submit login
VanillaJSX.com (vanillajsx.com)
538 points by novocantico 25 days ago | hide | past | favorite | 225 comments



Returning actual DOM nodes entirely blunts the big advantage of JSX (and non-JSX libraries like Lit) - which is their immediate mode style API, and UI=f(state) model.

You want to return a description of the DOM, rather than the real DOM, because you want to be able to reevaluate your templates repeatedly with new state, and efficiently update the DOM where that template is rendered to.

All the examples here use imperative DOM APIs to do updates, like with this:

    function TodoInput(attrs: { add: (v: string) => void }) {
      const input = <input /> as HTMLInputElement;
      input.placeholder = 'Add todo item...';
      input.onkeydown = (e) => {
        if (e.key === 'Enter') {
          attrs.add(input.value);
          input.value = '';
        }
      };
      return input;
    }

    class TodoList {
      ul = <ul class='todolist' /> as HTMLUListElement;
      add(v: string) {
        const item = <li>{v}</li> as HTMLLIElement;
        item.onclick = () => item.remove();
        this.ul.append(item);
      }
    }
Avoiding those `input.onkeydown = ...` and `this.ul.append(item)` cases, and instead just iterating over items in your template, is probably the main benefit of a VDOM.

(The problem with VDOMs is that diffing is slow, a problem solved by using templates that separate static from dynamic parts, like Lit - a library I work on).


From my experience creating complex web UIs, the performance angle of using a vdom is pure fantasy if your application is complex enough.

In fact I now strongly believe it's counter productive, because most people come to it thinking "I can just trigger however many re-renders of this large piece of UI as I like, the vdom makes it ok" and it doesn't, the performance sucks, but now you have architected the app in a way that requires a rewrite to make the app perform well.

I have seen this exact sequence of events four times, by four different teams. The second, third and fourth, as a principal architect consulting for the team I tried to intervene and advocate for a vanilla architecture that is mindful about performance, citing the issues they would likely experience with react, but to no avail. There was a lot of "oh but there many ways to avoid those issues" followed by a list of things I was presumably ignorant about.

I guess most of us need to learn things the hard way.


There were two groups I was hoping vanillajsx would resonate with. The first is people who still buy into the React dream but are beginning to be disillusioned with its inability to deliver on its promises, and the second is people who already are fully disillusioned.

Specifically, I'm hoping to show that vanilla architectures can be not only performant, but easy to maintain with well designed code that uses stable and known patterns. Using JSX just so happens to clean up the code nicely and make the relationship between React and vanilla very visible, but that's really all it did here.

Although to be fair, the hack required to get JSX-as-DOM to work is really unfortunately and I'm not very happy with it, and I would prefer JSX to just render as an object tree that anyone can render however they want. But when I tried that for a few months or a year, it was not nearly as performant as rendering them as strings as soon as they're evaluated, which can then be cached via standard module caching. At least, that's how I got immaculatalibrary to entirely render all HTML files in ~700ms initially and ~70ms on most file changes.

I'll try to do some experimentation next week to see if I can get more performance back out of having <foo bar={qux}>child</foo> to render as {foo:{bar:qux, children:[child]}} again though, because that would absolutely be the ideal, and would unfork JSX in the same way Typed Annotations proposes to unfork JavaScript types.


Thank you for posting this! VanillaJSX is refreshingly different, and we desperately need new ideas in the front-end space to reduce the complexity and get closer to the browser. I also feel like the discussion in this thread is very rich and gives people on both sides of the fence a lot of stuff to think about.

There were two groups I was hoping vanillajsx would resonate with. The first is people who still buy into the React dream but are beginning to be disillusioned with its inability to deliver on its promises, and the second is people who already are fully disillusioned.

I don't know if you've seen it, but Alex Russell just did a blog series where he directly talks about this disillusion and proposes a move away from React for most web apps: https://infrequently.org/series/reckoning/

I am not as anti-React as that myself, but I do agree it is hard to scale up and have it perform well, not at all like the promise. As always, there are no silver bullets and you have to pick a stack that you can understand.

By the way, I made my own pitch for fully vanilla web development here: https://plainvanillaweb.com/


IMO that blog series misses the point. Knowledgeable motivated developers can make great experiences with any technology, and conversely there are bad experiences built with every technology. That series blames the people involved for not being better, but that’s just blaming plane crashes on human error and calling it a day.

- If the UK GSD is anything like USDS, using them for comparison is like comparing a pro sports team to your local high school’s. They are an outlier specifically created to be better than the average, so tautologically their stuff will be better. Code For America is a similarly odd comparison.

- The US has a massive gap in pay and prestige between public and private sector developer jobs. It’s not that this means “worse” people work at public jobs, but in general they start less experienced and can wind up in a non-learning cycle as they don’t get mentorship/guidance from more expert folks, and if they do get good independently they leave. It’s really hard to convince people to take a pay cut to work these jobs, and many of the few willing to do so instead go to CFA, USDS, etc because they want prestige and avoid all the other inefficiencies in public jobs.

I could go on about the structural problems leading to this, but suffice it to say that blaming React and other JS frameworks is a miss. For some services it’s lucky they are online at all, and a slow web page is still orders of magnitude faster than physical mail or god forbid going to a physical office. The sites could definitely be better but this is not fundamentally a problem of technology choice.


I'm sorry, but I really don't understand the point you're making here.

Frameworks have evolved over time, as we've identified better ways of doing things and as the browsers have implemented native solutions to problems that frameworks were invented to address.

For example, we don't use dojo anymore. Or ember. Or backbone. The list of once-popular frameworks that have faded is long.

The point that I (and and increasing number of other developers) have been making for a while now is that React has hit this stage. Many of the problems it was invented to solve are mostly not currently relevant as native solutions have become more widely adopted.

This has caused React to evolve into bloatware in an effort to maintain mind share. I think that evolution has had the opposite effect. I think it has driven many developers to seek simpler solutions.

I think it's completely valid to recognize framework's strengths and (especially) weaknesses. You can call this "blame" or just a justified critique.

I agree with your point that lower-skilled developers can make a mess out of any technology, but one of the supposed benefits to adopting a framework is to provide guard rails against this. If that's not working, it makes you question the fundamental value of using a framework at all.


You might find this project[0] interesting if you haven’t given it a look.

It was attempting to do something along the same lines as you first suggest

[0]: https://github.com/jridgewell/jsx2


And from my experience building complex web UIs, those team members were right -- there are many ways to avoid the issues and using vdom is great in general. True, there are situations where it falls short, which is why you will want to fall back to other techniques for those bits of architecture. Just like your JS, Python or Ruby server will call a bunch of functions written in C or the like. That doesn't mean you should write your entire backend in C.


Yes, there are ways to avoid the issues, and they involve abandoning the immediate mode illusion that react created in the name of simplicity.


Write it in React, and if you run into performance issues, there are a bunch of well-known performance optimizations you can make which are easy to discover. It's a well-trodden path that many engineers have walked before.

Write it in your own vanilla framework, and you will effectively re-invent all the complexity of React, but in a way that no one has ever done before. It's easy at small application scales, but once your app gets large, good luck debugging the thing that exists primarily in your principal engineer's head.


The fact that react is popular and has learning resources available doesn't change the argument: “immediate mode” that some boast as a React feature is an illusion that will end up hitting you, and you'll end up using it like any other retained state framework.

React is cool because it's widely used (hence more tooling and learning material) and because JSX is very convenient (it composes very well), not because the vdom works well to create an immediate mode API.


That's not the whole argument though - the argument is, what is the better alternative?


Svelte?


The "performance angle" isn't really an angle. It gets bandied around by junior devs new to React, but it's not the primary selling point of React - in fact, it's not a selling point at all. Don't believe me? Just go to http://react.dev and look - no where on the site does it say that React is a hyper-performant library. It's not! If you need blazing performance, you're best off using something much more minimal, or even vanilla JS.

When people say that React is fast, what they mean is that React can dom-diff faster than a naive O(n) approach. It means that updating a component with a thousand nested divs won't crash out your browser, like it might if you were to write the code by hand. It doesn't mean it's an objectively high-performing framework.

What React is good at is forcing you to write code in a clear, comprehensible way. Having every engineer on your team obey F(props) = state is a strict improvement over virtually any other paradigm. (Yes, you can still make a tangle of components if you try hard enough, but the complexity of the tangle is capped significantly lower than the complexity of a tangle of JS without any framework attached.)


> but it's not the primary selling point of React - in fact, it's not a selling point at all. Don't believe me? Just go to http://react.dev and look - no where on the site does it say that React is a hyper-performant library

Because now everybody know that it's not the case. But if you check the first video announcement from Facebook a decade ago or so, performance was definitely at the front, above everything else.


At the time, React was much faster than the competition (Angular 1).


Yes and the solution is to put on your big boy pants and to actually do your front-end application architecture properly.

Separate source of truth from derived data. Separate possibly valid user intent from validated state. Use contexts to organize the data dependency graph of your application. Ensure all your widgets use a consistent value type in and out, don't let events contaminate it. Use something like cursors or optics to simplify mutations and derive setters automatically.

I've never had an easier time building very complex UI functionality than with React. But it requires you to actively start reasoning about change in your code (what doesn't change), and this is something most people are not used to.

Personally I think React compiler is folly for this reason: they are taking the most interesting part of React, the part that lets you write apps that are incremental from top to bottom, and telling you it's too complicated for you to think about. Nonsense.

The issue is just that React makes pros feel like idiots unless they eat some humble pie and grok the principles and the reasons behind it. Which is that React is what you get when you try to come up with a UI architecture that can make entire classes of problems go away.

Without a VDOM, one way data flow, and diffing, your UI won't just be slow, it'll be full of secret O(n^2) cascades, random ifs to stop infinite cycles, random "let's update this state early so other code can immediately use it" ordering issues, and so on.


> I've never had an easier time building very complex UI functionality than with React.

How many frameworks did you have experience with?

> Without a VDOM, one way data flow, and diffing

you wanted to write "or" not "and", didn't you?


> Without a VDOM, one way data flow, and diffing, your UI won't just be slow, it'll be full of secret O(n^2) cascades, random ifs to stop infinite cycles, random "let's update this state early so other code can immediately use it" ordering issues, and so on.

you can adhere to the same principles (one way data flow) without vdom. Not saying it's easy at large scale but it's possible. I don't appreciate people invoking fud towards anyone opting out of their tech choice.


More than once I got asked on interviews why react is faster than vanilla JS and I had to tell them no, it isn't.


The clue would be in the fact that react is running in vanilla JS.

There is a persistent ‘learned helplessness’ tendency among some developers to assume that the frameworks they are using have access to magical mystical powers above and beyond those that their own code can make use of.

Framework code might well be better optimized or more tuned than the code you would write - but if you cared to employ similar techniques you could achieve those same gains; on the other hand, since by definition it’s more flexible than single-purpose code, it might not be optimal for your usecase.


You're technically correct (the best kind of correct), but it might be worth in an answer also exploring the possibility that they mean "Why is changing DOM a lot slower than changing VDOM a lot?"


I've faced the same issue with multiple teams,in different business domains, some with rathe heavy UIs (canvas rendering, sophisticated graphs etc). From my experience, modern frontend frameworks seem to not have taken into account these kinds of problems. To my recollection, in all of those cases, we ended up managing state and rerenders with vanilla, rather than react or angular.

A rewrite on those occasions, I do not consider possible. Most businesses are way to deep into frameworks.

What I personally advocate is to choose frameworks with valid escape hatches, and then make sure to staff a team with JavaScript programmers rather than some-framework Andies.

In an even larger scale, I advocate for web-component based solutions. I try to stay close to vanilla as much as I can. Whether I like it or not, it's the highest value professional decision, imo.


Wasn't the issue mostly solved with signals?

As far as I understand, signals make it much easier to keep the DOM updates to a minimum.


It sounds to me like GP got told stuff exactly like this, with the team eventually not actually doing the thing.


you shouldn't project from react to vdom in general:

https://dioxuslabs.com/blog/templates-diffing


I agree with the sibling comment that this really depends on the user. To take a different approach: JSX is just a different DSL to the createElement function call pattern (see Preact.h for example) and all of the benefits you’re describing come from the framework and runtime.

More concisely: JSX is just an alternate function call syntax with some useful applications.

For example at my last company we used JSX to make test data factories that had an XML-like look but were using a builder-pattern in the element creation that was able to make contextual decisions about what was in the final data. Nothing to do with React, DOM, or inability to express the same thing declaratively without JSX.


> For example at my last company we used JSX to make test data factories

Thats really interesting, can you elaborate more?

For example did you use a specific JSX compiler? Was that written in house or used a 3rd party library?


> You want to return a description of the DOM, rather than the real DOM, because you want to be able to reevaluate your templates repeatedly with new state, and efficiently update the DOM where that template is rendered to.

Depends who "you" are. I prefer to have my DOM nodes updated in place without all the reconciliation machinery. (no implication about what you want)


If you work on a team of suitable size I would hesitate to not leverage a VDom. I trust myself to not trigger dumb reflows, but the way I see a lot of people using React this could be a perf nightmare.


I think that hits the real problem: it’s staffing and culture, not the tool. The 90th percentile site using a vDOM is also a perf nightmare, especially for anyone who doesn’t have a recent Apple device on a fast network, and that often has big impacts on usability and accessibility as well (dynamic loading sucks on screen readers unless you put way more time into it that most people do).

I was painfully reminded of that while visiting Europe last month on a data plan which was clearly deprioritized on partner networks - the old sites with .php in the URLs loaded in a few seconds and worked perfectly, but every time something failed to load in less than 5 minutes or partially loaded but didn’t work a quick trip over to webpagetest.org showed a lot of NextJS, React, et al. scripts trickling in because clearly a form with half a dozen fields needs 25MB of JavaScript.

The root cause is obvious: you get what you measure. If businesses prioritize Jira tickets closed per day, they’re going to get this soup of things promising to be easy to use for high developer velocity and they’re never going to get around to the optimizing it. If they’re trying to be able to hire as cheaply as possible, they’re going to look for the current tool boot camps are pushing and hire based on that, not looking for deeper knowledge of web standards or experience which costs more and shrink the candidate pool. If they’re looking for a safe choice, Facebook’s marketing means all of the big consulting companies will push React and few people will pause long enough to ask whether they’re building the same kind of apps it was designed to build (long session times, tons of local state being mutated, etc.) or whether they’re willing to invest the time needed to get it to perform reliably and well.


There are more and more frameworks that avoid the VDOM but still resolve this fine. As long as DOM mutation is handled by the framework (and not done ad-hoc), and as long as the framework has a mechanism for deferring those mutations until after all reads, then there shouldn't be a problem. This is the approach taken by SolidJS, Svelte, and even the new rendering model for VueJS.

In all these frameworks, mutations happen inside effects, and effects are scheduled such that they all happen at the end of the tick, avoiding reflows and thrashing.


You don't need diffing or reconciliation to turn a description of DOM into DOM. Lit works without a VDOM.

If all JSX does is return a DocumentFragment that you then need to imperatively add event listeners to and imperatively update, how is it much better than innerHTML?


innerHTML loses all local state, such as which elements have focus or where the cursor is in a text field. Back when React first came out and people were getting used to the idea of VDOM diffing, they had demos front and center about how by using those diffs to only change what needed to change, such local state wouldn't be lost.

This in theory could do something to copy that local state over, or diff the two DOMs directly without a VDOM (though from the sound of it, it probably doesn't).


I think the answer to that is probably "as good as soy, but with modern ergonomics". E4X was basically this and I think it's a much nicer way to build DOM trees than strings since you can't create invalid markup or concat partial tags. It also lets you reuse subtrees naturally where innerHTML makes that impossible.


You can have JSX that produces DOM nodes or "light-weight element descriptions".

You can have imperative event listeners and updates.

These are two independent dimensions. I made UI framework called mutraction that produces real DOM elements from JSX expressions. It also updates any contents or attributes of those DOM nodes based on their dependencies without requiring imperative DOM interaction from application code.

https://github.com/tomtheisen/mutraction

Here's a click counter. `track()`, as you might guess creates a proxy so that reads and writes can be converted into dependencies.

    const model = track({ clicks: 0});
    const app = (
        <button onclick={() => ++model.clicks }>
            { model.clicks } clicks
        </button>
    );

    document.body.append(app);


1) Type safety for element props.

2) Autocomplete for element props.

3) IDE support such as refactors and jump to definition/jump to usages.

4) Proper syntax highlighting out of the box instead of the editor just saying "there's a string here".

5) A uniform pattern for defining custom components that work the same as primitives, rather than defining custom components as helper functions returning string fragments or something like that.

And so on. JSX has a lot going for it regardless of the semantics chosen. It's just a syntax that is very convenient for lots of kinds of tooling, and it's completely unopinated about the semantic context in which it is used.


These are definitely helpful, but what you are describing are all language tool features rather than features of JSX itself. 5 would be the exception, but that is just user preference of what kind of syntax one likes to write components with.


Well, yes. But OP was asking about what makes this better than `innerHTML`, and the obvious answer is that support for HTML programming embedded in JavaScript strings is generally bad while support for JSX is very good across all editors.


> is probably the main benefit of a VDOM

I get the "no more imperative updates" dream. I've used these frameworks for probably a decade. I've mastered them.

Me personally, I prefer imperatively updating my DOM. I get completely fine-grained control over what's happening. I can architect it to be an extremely efficient machine. I can make it extremely easy to add/change/remove/fix features in my apps without forcing myself to think according to anyone else's opinionated methodology.


If you have little state, or simple uniform state, you can actually store it in the real DOM efficiently, as values of controls, or lists of similar DOM nodes under a common known parent. If most of your DOM is static, and you only need small bits of interactivity, React is an excessively heavy tool.

The farther you get into complex GUI territory, the more you want a declarative, functional approach, because it makes things simpler. The closer you are to a handful of controls with simple logic, the more you want to just imperatively tell them what to do, and leave the rest of the page alone, because it makes things simpler. We now just have better tools than jQuery for that.


there is no reason something imperative cannot be declarative. the war is one of style, not capability, so saying you gain "fine-grained control" is kind of meaningless, imo


Returning actual DOM nodes entirely blunts the big advantage of JSX (and non-JSX libraries like Lit) - which is their immediate mode style API, and UI=f(state) model.

I feel like this is one of the leakiest abstractions in all of computing. There's a reason there's an entire cottage industry around react; how to stop things rendering multiple times, refreshing needlessly, etc.


Yeah, as much as I liked the idea of an “immediate mode API which is in fact retained under the hood which makes things both ergonomic and performant”, the reality is that React failed to deliver on that and every sufficiently big app ends up having performance problems that are then fixed by opting out of the immediate mode illusion.


JSX in SolidJS directly returns DOM elements much like in the top part of this post, yet it does not have these disadvantages. It's true that strictly put it's not immediate mode like React and Lit are, but the framework is designed such that there's few practical downsides to that.


UI is not a pure function of state[0], "UI state" is relatively stable and does not have to be recreated constantly when data input changes.

[0] https://blog.metaobject.com/2018/12/uis-are-not-pure-functio...

>you want to be able to reevaluate your templates repeatedly with new state

No you don't. It is inefficient and increases complexity. You then have to extract and keep track of state yourself where the platform/UI components could have done much of this themselves.

    Calling the same method over and over again is wasteful.

    So we don't do that.

    First, we did not start with the obviously incorrect premise that the UI is a simple "pure" function of the model. Except for games, UIs are actually very stable, more stable than the model. You have chrome, viewers, tools etc. What is a (somewhat) pure mapping from the model is the data that is displayed in the UI, but not the entire UI.

    So if we don't make the incorrect assumption that UIs are unstable (pure functions of model), then we don't have to expend additional and fragile effort to re-create that necessary stability.


This really just isn't true. If your state updates are at the component subtree level (like react) a vdom is a good choice. But, if you make your state changes more granular, you can get away with skipping VDOM entirely and work with just* regular dom nodes. Look at Solid or Svelte. No VDOM there, just pure granular updates.

*List reconciliation still has to happen, but you don't need to pull out an entire vdom. You just have to have some mapping between list items and their resulting DOM nodes.


TBF while Solid and Svelte don't use a VDOM on which they perform diffing, they still ultimately create a tree parallel to the DOM which is used to track dependencies.


Svelte and SolidJs work pretty well without VDOM


Whether you like this project or not, your comment so completely misses the point. You are confusing the JSX syntax, which is what the author wanted by extracting it away from React, for all the React candy. This is a missing the forest for the trees kind of thing.

This mind numbing reliance upon layers of abstraction nonsense around state management is why I really don't like React. State management is ridiculously simple. State management, when done correctly, is the most primitive example of MVC with no abstractions needed.


State management is not simple. You have to constantly keep in sync two different piece of states (your data model and the UI). Making sure that when you modify some parts of your model then everything depending on that is also updated is one of the hardest things to guarantee. Fundamentally this is because it is non-local: you cannot tell what will change by just looking at the definition of what you're mutating.

You might be able to handle this while you're alone and you know everything about your codebase, but the moment you're working with someone else or in a team this will no longer be the case.


Stop the madness. Its stupendously simple.

As the user, not you the author of some code, make changes to the UI it will be via events. In the respective event handlers just update a big state object with the identity of the node and how it is changed. When the page loads simply read from that state object to recreate the respective node exactly how they were when the user modified them. That's it. Nothing more.

Its how I wrote a full OS gui for the browser. It does far more than SPA framework, does it substantially faster, and requires only a tiny fraction of the code.

Yes, yes, here come all the excuses, like: I can't code or it wont work in a team, because other people cant code. Stop with the foolishness. Provide some governance, preferably fully automated, around event handling and you have complete obsoleted your giant nightmare framework.

I describe it here: https://github.com/prettydiff/wisdom/blob/master/state_manag...


If the above is what you need there's not strong reasons not to use React or similar. But for most things that will lead to an interest in "Vanilla JSX" this line of thinking is premature optimization - the advantages of vdom are extant but enormously overstated. JSX has many other advantages.

It's also not even an either-or. I've worked on a codebase that did both: React was loaded for some views & others were served with a lightweight JSX renderer.


Nice, I love lit-html(1)!

I wanted to add my two pennies to the discussion. You are of correct that with that approach you lose the declarativeness but sometimes you don't need that, if the thing is mostly static.

I went this road many years ago for a project. The nice thing of this approach is getting rid of all the ugly DOM API but enjoying it's performance over innerHTML.

(1) I absolutely love lit-html but don't like the rest of the lit components framework. Luckily you can use it independently!


I'm having fun using lit-html with vanillajs, after I saw a tweet from Marc Grabanski suggesting he didn't use the full Lit library. Admittedly I am then not taking advantage of all the reactivity goodness, but I also really dislike the decorators syntax and 95% of the time I just don't need the reactivity after the first render.

It works great! I was amazed at how you can do so much in templates, it's pretty much everything I could do in Vue templates, though a little more verbose.

I built my own `VanillaComponent` class, which has a mount() method which calls the render() function which I define on the child class.

My VanillaComponent class looks like this:

    import { html, render } from "lit-html";
    
    abstract class VanillaComponent {
      abstract render(): ReturnType<typeof html>;
    
      private _mountPoint?: HTMLElement;
    
      mount(this: VanillaComponent, target: HTMLElement) {
        target.textContent = '';
        this._mountPoint = target;
        this._update();
      }
    
      _update() {
        let templateResult = this.render();
        let rootPart = render(templateResult, this._mountPoint!);
      }
    }

So I can write something like

    class MyComponent extends VanillaComponent {
      constructor(props: { label: string }) {
        this._props = props;
      }
      
      render() {
        return html`<button>${this._props.foo}</button>`;
      }
    }
Then I can instance like so:

    let myComponent = new MyComponent({ label: "I am a button" });
    let target = document.querySelector("#demo");
    myComponent.mount(target);

The base class stores the root node (target), so later I can do

    myComponent.update()
to re-render, taking advantage of lit-html's "diffing" logic.

However something I have not been able to solve with lit-html only, is when I compose parent and child components I have to do something like :

    class MyDialog extends VanillaComponent {
      render() {
        let childComponent ...  // another VanillaComponent previously instanced
        return html`
          <div>
            ${childComponent.render()}
          </div>

So the child component I need to explicitly call render() to get the TemplateResult for the parent template.

But this means I can not do `childComponent.update()` because I don't know the root element of child component, since I did not mount it explicitly myself.

I mean technically because of the lit-html optimizations, I can do `.update()` on myDialog (the parent component) after any child component's props changes, and it will only re-render what is necessary... but let's say my child component has like 1000 cards... it seems very wasteful and it would be ideal if I could re-render only the child.

I wonder if there is a trick to get around that with just lit-html?


It's always worth checking the lit built in directives list for the one you've still missed (or at least it is for me ;).

I think in this case the ref() directive - i.e. https://lit.dev/docs/templates/directives/#ref - may be what you want. If it isn't exactly, reading how it's implemented would be my first step towards building something similar that is.


Thanks I created a custom directive, it's just ten lines of code and it works beautifully. Simply grabbed the `part.parentNode` property. I did the same for a `transition` directive that does something similar to using `appear` in Vue transition to have a nice fade in on first load.


Thanks for taking some interest in my project. It came from being frustrated with the state of SSGs over the past 10 years. I mostly just make static websites, and I wanted something that was simple and intuitive to me, and JSX seemed like a great fit. But I got very tired of the disproportionately scaled complexity of JSX frameworks like React. Long story short, I made an SSG that just renders JSX as strings. It was natural to extend that to the browser to just render JSX as DOM elements. And in a few cases (mostly layout) it lends well to shared components. Overall I'm happy with what I came up with, although some of it is admittedly a little hacky, and IDE support isn't as good as it could be.

[edit] Oh also, this solution works really well for SEO. That's another problem I didn't find solved well in other JSX frameworks.


Just curious, have you seen Astro? I feel like it's the perfect SSG, but maybe you have some reservations that VanillaJSX solves.


I'm a big fan of Astro, though I could see it being a bit more of an adjustment for JSX users than Svelte users since Astro's syntax was originally based on Svelte.

That said, JSX can be used easily with Astro as long as you get used to at least a bit of The `.astro` syntax for wrapper components and pages/layouts.


For all "have you tried ___" questions, the answer is the same. I've been trying all these new techs for the past 10-15 years, regularly, as soon as they come out, for the first few years with much with excitement, and later with disillusionment and less regularity.

Another user below said

> We've recently moved one service from next to Astro and it was just removing a ton of boilerplate and 'dance around' code.

And I get why it happens. When you first try out a new framework, you allow yourself to learn and add its inherent complexity, knowingly and intentionally. You say to yourself, "it's part of the dream, it's going to work out; there's a vision, just trust the process." This is true with literally all frameworks.

But they never deliver. The complexity is never worth it, and in the end, the intentionally added complexity is always intentionally and gladly removed when it becomes clear that it was unnecessary complexity. This is what I am glad to have learned so thoroughly that I no longer try to learn new frameworks when I initially see its complexity, imagine adopting it in view of my experience, and recognize that its almost always not worth it.

Look at the code on vanillajsx.com. Besides JSX and types, it's plain JavaScript and DOM manipulation. Translating it to document.createElement would add almost no lines of code. There's no unnecessary complexity. That's the whole point of the site. The simplicity of discovering and removing unnecessary complexity is wonderful and refreshing, and I think a lot of people agree.


I think you're misunderstanding the poster you quoted. They're indicating a positive experience with Astro.

> We've recently moved one service from next to Astro and it was just removing a ton of boilerplate and 'dance around' code.

---

> And I get why it happens. When you first try out a new framework, you allow yourself to learn and add its inherent complexity, knowingly and intentionally. You say to yourself, "it's part of the dream, it's going to work out; there's a vision, just trust the process." This is true with literally all frameworks.

I am quite picky and have strong opinions. I've used Astro for more than a year and still love it. There is complexity (especially if you use SSR), but for the use case of "I just want a static site" it is wonderful.

> Look at the code on vanillajsx.com. Besides JSX and types, it's plain JavaScript and DOM manipulation. Translating it to document.createElement would add almost no lines of code. There's no unnecessary complexity. That's the whole point of the site. The simplicity of discovering and removing unnecessary complexity is wonderful and refreshing, and I think a lot of people agree.

I don't disagree, but this doesn't replace what you might want for SSG. For one, this requires JS on the client. Astro compiles to static HTML despite using JSX-like syntax.

As an example, here's my Astro site and source code:

* https://sjer.red/

* https://github.com/shepherdjerred/sjer.red


My framework (imlib[1]) is actually more of a framework akin to Astro than what's showcased on vanillajsx.com, which itself is built with imlib.

It just runs your code in a node.js process, and translates your JSX expressions into jsx() calls. On the node.js side[2], jsx() returns a string from its tag/attrs/children. On the browser side[3], jsx() returns DOM elements.

Combined with a little bit of architecture, it becomes something extremely well suited to creating static sites. I guess SSG is an outdated term now, maybe it's a framework? Or a platform?

In any case, it seems to do something similar to Astro, but in a significantly simpler way. The only "bundle" it needs in the browser is /@imlib/jsx-browser.js [4] which in itself is just jsx-dom.ts (its impl is overridable by the "framework" user). And on the node.js side, it's implemented as a very small "vm" of sorts [5].

I'm not against Astro, I just get all the same benefit people here are saying Astro has, but with orders of magnitude more simplicity imo.

I've used imlib to make a relatively large website [6], in fact imlib was developed as this website and extracted from it over the past year. I have absolutely no difficulty breaking down my site into various reusable and encapsulated JSX components, both on the ssg-side and the browser-side. Development time is lightning fast. IDE support is essentially automatic. The site loads instantaneously in the static parts, and as quickly as reasonable in the dynamic parts.

[1] https://github.com/sdegutis/imlib

[2] https://github.com/sdegutis/imlib/blob/main/src/jsx-strings....

[3] https://github.com/sdegutis/imlib/blob/main/src/jsx-dom.ts

[4] https://vanillajsx.com/@imlib/jsx-browser.js

[5] https://github.com/sdegutis/imlib/blob/main/src/runtime.ts

[6] https://github.com/sdegutis/immaculatalibrary.com


Thanks for taking the time to clarify & not getting hostile :)

I'll look into imlib a little more.


I like your thinking. Things have gotten far more complex than they need to be. We've been piling abstractions on top of abstractions for too long and there needs to be a culling. If we get rid something that we really did need, that's better than the alternative. It's best now to ditch it all and see what we really need.


So, I take it you haven't tried Astro then?


SSG?


Static site generator, apparently


astro is nearly, if not as, "complex" as react, no?


As person who has been doing “react” since 2016 I would say that it removes so much of the “react” complex BS that I am surprised it is not x100 times more popular.

We’ve recently moved one service from next to Astro and it was just removing a ton of boilerplate and “dance around” code.


That’s how I felt going from Astro to Sveltekit, but that’s a shorter distance to travel.


Oh, but you are moving out of React. And svelto IMO is waaay friendlier and "sane" than "typical" react. Svelte reactive model (observables and computed) are very friendly and simple to use.


It doesn't really make sense to compare React to a static site generator.


Love the idea. FYI, on your demo page, the todo app is 404. And you might want to spell out static site generator instead of saying "SSG" on your docs. I didn't know what SSG was, even though I've used static site generators. I had to ask the AI.


A JSX for Django server side would be cool.


Not exactly JSX, but I came across a package django-cotton[https://github.com/wrabit/django-cotton], that is html syntax first components.


How would you envision this working?


not the hero we deserve but the villain we need


Have you looked into lit-html?

Coming from Vue I was really surprised it does a lot of what Vue templating does, including attaching events, with just vanilla JS templates. And when you use VSCode lit extension, you get syntax highlighting and full type checking inside the templates.

I learned about lit-html after a tweet from Marc Grabanski, where he said he used lit-html with vanillajs, not Lit.

After some experimenting I found it works great and it seems like you are trying to solve something very similar.

When you use the lit-html template package you can do basically evetything that is described in the Templates chapter

https://lit.dev/docs/templates/overview/

... without all the other abstraction of components that are part of lit-element.

https://lit.dev/docs/libraries/standalone-templates/#renderi...


Large WASM payload on your site could be optimized.


What I’m seeing here is not new. It’s this vanilla pattern but with enough back support to leave off the framing and get the syntax highlighting:

Var button = html(’<button>im a button</button>’);

The html() function is trivial, but it just doesn’t feel like real programming to do this, even though there’s nothing else to it in the end.


Tagged template literals for html view templating is what I LOVE about Lit and lit-html. It’s JavaScript you can run natively in browser, no server or pre processor/build step necessary.


Downvote me but tell me why. The example is using .onclick, .textContent, etc in a completely vanilla way. I'm just pointing out you can get all the way vanilla and it still works. What's the issue?


lolinder explained it well in here


He did, and he pointed out the point is just IDE support so it has typing and autocomplete and syntax highlighting. Thanks, we agree!


These "what ifs" are kinda funny because the origins of JSX can be traced back to Facebook's XHP[1], which took explicit inspiration from E4X[2], an early JS standard that looked and behaved similar to the library described here.

[1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...

[2] https://en.m.wikipedia.org/wiki/ECMAScript_for_XML


E4X had the unfortunate downside of returning actual DOM instances, which needed to be updated imperatively. That's why JSX eclipsed it, and there hasn't been a serious proposal for HTML templating in JS since then.

But maybe we can revive the general idea with a modern take: https://github.com/WICG/webcomponents/issues/1069


> had the unfortunate downside of returning actual DOM instances, which needed to be updated imperatively.

Isn't this what we have in TFA?


Yes, for elements. The project here also supports a notion of components, though, which E4X didn't contemplate.


There are separate proposals from web components that get rid of imperative updates. https://eisenbergeffect.medium.com/the-future-of-native-html...


Also E4X was only ever implemented in Firefox, never really got traction even in Firefox.

But even considering the single implementation problem, it also was just not a good language model, nor was it well specified or defined and it brought with it a pile of weird baggage and complexity.

Then because it was The Future there was no real thought into proper interop with JS (it was essentially a completely independent spec so adopted general syntax but specified in a way that meant JS could not simply adopt that syntax).


> E4X had the unfortunate downside of returning actual DOM instances, which needed to be updated imperatively

Firefox never shipped the optional E4X DOM APIs. I wrote a polyfill for them at the time.[1]

1. https://github.com/eligrey/e4x.js/blob/master/e4x.js


With "imperatively" you mean that the user of the templating system has to do it imperatively, and that is bad? Asking because imperative updates seem to be the way to go within the implementation, instead of creating new instances of elements every time.


> which needed to be updated imperatively

VanillaJSX seems to suffer from the same problem though.


Fun fact, E4X is the reason JavaScript has ‘for(of)’ instead of ‘for each’ (the reason we didn’t get ‘for (:)’ is even dumber - it would conflict with ‘:type’ annotations a few TC39 members were convinced would magically be in the language)


Like the type annotations that are now in TypeScript?


Yup, that were in typescript, pascal (and rust, etc when they came out).

But there was no real progress after years of them pushing this syntax, but failing to actually define a type system that was coherent, or a model that would allow it.

As a result I proposed `for (of)` largely to prevent sane enumeration from being blocked on the intransigence of two people.

It's also worth noting that for(:) enumeration would not even preclude their syntax - it's certainly not grammatically ambiguous - and most real world code in languages that support enumeration directly and support inference doesn't explicitly specify the types , so the ugliness of `for(let a:type:expression)` would have be rare anyway.

shrug

Given that ECMA literally killed E4X a few years later the blanket ban on "for each" or "foreach" (because it would be "confusing" in E4X) is arguably worth than for(:), but again shrug


There is a proposal to add them, though it does seem to be stalled.


There were proposals almost 2 decades ago. They've never gone anywhere because proponents of type specifiers don't want to do the necessary corollary: specifying the type system.

Typescript and similar can do it because they don't have to specify the type system, and can't change it in meaningful ways over time. Things in the language standard cannot be easily changed, if they can be changed at all.


> the necessary corollary: specifying the type system.

It's clearly not strictly necessary though. Python has shown that.

I mean I agree it is pretty mad to just say "you can write types but they mean whatever" but surprisingly in practice it seems to work ok.


It is necessary if you’re creating a standard.

The Python implementation can do whatever it wants because “Python” does not mean “The Python Language Specification”. It means the one specific implementation, and whatever that impl does is definitionally correct.

The ability for a language specification does to hand wave behaviour is very limited, and for JS is non existent (the only places where there is divergence between implementations is some squirrely edge cases of property and prototype chain mutation during for(in) enumeration).

So you can’t say “types mean whatever”, you have to specify what the implementation is required to do when it encounters those annotations. Even if they are not meant to have any semantic impact the lack of semantic impact must be specified: e.g the language specification would be required to state “here is the valid grammar for these annotations”, and specify that they are explicitly ignored and must not be evaluated or examined in any way.


> The Python implementation can do whatever it wants

No you're misunderstanding how it works in Python. This isn't like Rust where "the implementation is the specification".

The Python type checking standards explicitly don't define semantics (though I think they give guidelines). The standard Python implementation - CPython - does not include a static type checker. There is no "official" implementation.

In fact there are at least 4 Python static type checkers and they are all third party projects and they do differ in interpretation of types sometimes. The most popular ones by far are Mypy and Pyright (and Pyright is the far superior option).

So it is exactly the same as what is proposed for JavaScript. It definitely sounds mad and I do agree that it would be better if they just actually specified semantics, but not bothering isn't the complete disaster you might imagine.


No I think you’re misunderstanding my point, in your defense I was unclear: in an environment like JS you cannot leave anything as “it’s up to the environment” - the js engines must be 100% consistent which means the exact semantics of the syntax must be specified. E.g if you were to say add an optional type suffix to the language, say:

    OptionalType := (‘:’ <Expression>)?
You have to specify what that means.

Does the expression get evaluated? E.g

    let x : Foo.Bar = …;
Does this resolve Foo or subsequent property access of Bar.

Is the expression unrestricted? E.g could it be (a=>a)()

If you want something to be invoked at runtime you have to specify how and when that occurs (and you’re now going to have to specify what is being passed).

You have to specify when evaluation or calls happen, etc.

The problem for an environment like JS is you cannot add a language feature and not specify the exact behaviour.

E.g it’s not “you must define a type system” (though for the parties involved in pushing this when I was involved it would have been), it’s that even if you aren’t actually interested in defining a type system you have to do a lot of design and specification work because there cannot be ambiguity or gaps where different engines will disagree on what is valid, or will disagree on what portions result in any evaluation, or what semantic effects occur. The specification also needs to handle other things that don’t matter in the Python use cases: what happens if I do have a library that does type checking, but then my code is included in an environment that also does type checking but does it differently.

In Python it’s acceptable to say “don’t do that”, but in JS that’s not sufficient, the implementations need to agree on the result, so the result needs to be specified, and ideally the specification would need to provide semantics that simply support that.

Note that none of this is unsolvable, it’s just a lot of work, and not defining the type system doesn’t remove that specification work.


I also made a UI library based on the idea of jsx template expressions that produce real DOM nodes. It also binds model objects to attributes, eliminating some of the imperative event handler boiler-plate. I think it's a great idea, but of course I would.

https://github.com/tomtheisen/mutraction

It lets you do stuff like this.

    const model = track({ clicks: 0});
    const app = (
        <button onclick={() => ++model.clicks }>
            { model.clicks } clicks
        </button>
    );

    document.body.append(app);


VanJS deserves a mention here! https://vanjs.org/

Another interesting thing is that other JSX libraries like Solid.JS also return DOM nodes, and I love that this idea is gaining traction

The closer we get to the platform we're using, the better. Being removed by layers of abstractions CAN be useful, but in practice, I haven't found a use for abstracting away the platform. (yet.)

Maybe huge projects like Facebook benefit from this tho (which I haven't worked on)


Isn't SolidJS useless in the bundle size comparison?


That may be a point in favor of imlib.

Technically this is the only code bundled with vanilla jsx:

https://vanillajsx.com/@imlib/jsx-browser.js


These examples are cool but I think it’s important to note that none of them show components whose props can change over time, since that ability doesn’t seem to be modeled at all. Clever if you don’t need that but I’m having trouble seeing how it would scale to more complex apps.


The technique I used here and in all my browser-side code is the exact same technique used by VS Code internally, and it scales very well. The only difference in my code is it's more concise than writing 10 lines to construct and setup a DOM element the typical way.

Honestly, the real interesting part about my framework is literally everything else. Returning strings from JSX on the ssg-side; being able to import raw source directories and manipulate string|Buffer at ssg-time; the extremely efficient and lightning fast module system I wrote on top of chokidar and swc; probably more I'm forgetting, but basically the JSX-as-DOM is only the most visually interesting part. But really just a party trick.

[edit] Case in point: the source code to vanillajsx.com is extremely concise and clear and short, I literally wrote the whole thing today with zero deps (besides imlib), and the JSX-as-DOM demos are the least innovative part of it: https://github.com/sdegutis/vanillajsx.com/tree/main/site


I just added a more complex todo app to the bottom of the page. So it should give an idea of how a more complex hierarchy can respond to events elsewhere in the hierarchy and update themselves and each other accordingly.


Yup, in order to scale this approach to any real size (and still have confidence that everything is working together like you expect), a proper reactivity solution is needed.

For those that appreciate this approach of JSX returning concrete DOM elements, Solid works exactly like this, with the addition of a proper reactivity layer.


Maybe I'm missing something, but how would this prevent you from using setTimeout/setInterval? But I agree that these projects often work great in small use cases, but quickly crumble under "real world" scenarios.


I admit that the two most complex "interactive apps" I've built with this are not that complex according to many standards:

* https://www.immaculatalibrary.com/books.html (src = https://github.com/sdegutis/immaculatalibrary.com/blob/main/...)

* https://www.immaculatalibrary.com/prayers/ (src = https://github.com/sdegutis/immaculatalibrary.com/blob/main/...)


I'd be hesitant to run something like a 30fps render loop in a web app. Its been years since I last saw or tried that in a real world app but it didn't end well for performance.

Your best bet would be to queue up specific UI changes that need to be made as diff's rather than checking the entire UI state. At that point, though, you might as well run them immediately as the change is needed.

If that was still a perf problem you would end up chasing a very complex solution like react fiber to partially update the UI on a loop while periodically pausing for user events.


Sure, if you blow away the entire app on every state change. But that would lose not only state defined in components (like `i` in ClickMe) but also all state implicitly stored in DOM elements (selection, focus, scroll position, input value, media playback).


I would almost certainly never implement a UI as a render loop, but if you wanted to go down that path requestAnimationFrame is a much more idiomatic way to do it if you want to match the user's display refresh rate.


I'm by no means an advocate of this library, and never plan to use it, but to support component props that trigger rerenders, a'la React/Vue, I would use JS Proxies here. Wouldn't be that hard to implement.


How would you suggest using Proxy?


This is very similar to Vanilla TSX: https://github.com/wisercoder/uibuilder

Here’s an app written using Vanilla TSX: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...


Reminds me of Action Script 3 which had XML at the core of the language. It was a fun language to work with, but famously failed to become ES4. Oh well, took us 10+ years to arrive close to that with Typescript and JSX.


https://en.wikipedia.org/wiki/ECMAScript_for_XML - Firefox had it too, but people at large just didn't want it, so it got removed. It got disabled for web pages with the release of Firefox 17, 6 months prior to the first release of React.


Personally I never heard about it. So it might not be that people didn't want it, but that it wasn't promoted much.

Also, it sounds like the only browser to ever support it was Firefox? That was probably much more of a limiting factor for adoption.


If you weren't coding for the flash platform you would have easily missed it.

Its a shame, E4X was really nice


People didn't want it because browsers didn't support it (except FF, as you noted). Some of us had our fingers crossed that other browsers would pick it up.


I don’t recall being able to construct XML inline like this unless maybe that was a Flex server thing?


I don't recall being able to do the XML construction inline either, but that just might be my memory.

However, the XML selector syntax was a godsend. Recursively parsing an XML tree is really a pain. E4X would allow you to do things like:

    var foo = someXml..childNodes.@attribute;
I'm not even sure if that would work actually. There were a bunch of operators for doing things like getting a collection of children that all had the same tag so you could work with XML like:

    <someXml>
       <intermediateNodeYouWantToSkip>
           <childNode attribute="1" />
           <childNode attribute="2" />
           <childNode attribute="3" />
           <unrelatedNode />
       </intermediateNodeYouWantToSkip>
    </someXml>
Another post here said people didn't want it, but I don't think that was the real reason it was dropped. There was a lot of drama at the time about Flash in general and a massive debacle about EcmaScript 4 (which ActionScript more or less adopted). There was also the whole XHTML thing happening.

Basically JSON as a format won out over XML and ES4/XHTML were ditched. Frankly, a world that revolved around XML/SOAP would have been a nightmare, so I guess killing off the easy processing of XML in JavaScript helped to stave off that potential future. XSS, XSLT and E4X were all casualties.


It was an extension to ES4 called E4X - it allowed inline xml along with a new xml data type. More info here: https://evertpot.com/ecmascript-4-the-missing-version/


I think parent must be referring to Flex components. AS3 itself had an XML library which I recall being absolute hell to work with. The better way to send things over the wire with AS3 was with AMF.


Nope. I worked with Flex and it's MXML files extensively. But the parent is talking about E4X, which was an extension to ECMAScript that allowed you to use XML elements inline with JavaScript in a manner VERY similar to how JSX is used today. It also included the ability to much more easily query and otherwise work with those XML document trees in native JavaScript.


No, writing XML was the declarative part of Flex (like HTML), but AS3 had it’s own XML type so you could do things like this:

var data:XML = <foo><bar>hello</bar></foo>

and then data was an object instance like you’d expect


Does the final example not work in Firefox for anyone else? It worked in Edge, but not Firefox for me

    Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function


Object.groupBy doesn’t seem to be available to all browsers before march 2024: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


I never understand the appeal of JSX over something like

  h("div", {}, [
    h("p", {}, "this is easy"),
    ...list.map((l) => h("li", {}, l),
  ])
With this you automatically get loops, variable interpolation etc without having to invent a compiler and new syntax. Can someone help me understand?


I would assume that lot of people just find the JSX equivalent a lot more readable and familiar (a matter of opinion, of course.)

  <div>
    <p>this is easy</p>
    {list.map((l) => <li>{l}</li>)}
  </div>
> you automatically get loops, variable interpolation etc without having to invent a compiler and new syntax

To be fair to JSX, you use regular loops, interpolation, etc without any different syntax (`{}` accepts a vanilla JS expression), you just obviously need the compiler step to de-sugar the element tags to `createElement` calls.


Yeah the syntax is almost identical to vanilla js, but requiring a compiler is quite cumbersome compared to the advantage it provides imo.


That said if anything pretty much all of the new school frameworks and many of the tools in their ecosystems are already dependent on compilers for optimization anyway, react itself is introducing a compiler in the latest versions.

Anyway I prefer the html looking syntax if anything because it looks like the output on the page. That’s dependent on a project coding style that doesn’t unnecessarily wrap things in components, which for my company’s product I’ve set as a standard.


Requiring a compiler also allows to catch mistakes at compile type, which is much more efficient in terms of development.


Here's my perspective. I never understand how some people could look at the code you pasted and think that's just as good. But different people's brains process information differently. Your example has a lot of punctuation that's very difficult for me to parse quickly. I don't see the DOM structure that's being created unless I manually pick the syntax apart in my mind, but understanding the DOM structure at a glance is far more important to me than whether I need a compiler. For the record, I'm neurodivergent. I hope that helps.


Yes I can understand it helps if it looks like DOM on first sight, I'm thinking more about the functional aspect where it can achieve the same thing without requiring a compiler and a new syntax (altho you can argue it's not new syntax just js + html)


It doesn't achieve the same thing, though. Functionally, for the computer, it's the same. But code has two audiences: the computer and the coder who has to read and write it. And from my perspective, the human is more important than the computer. And JSX is more than a convenience at first glance. You read a lot more code than you write. If all of my code was written like this, it would add significant cognitive load, make the code more difficult to reason about and slow me down.


Keep going down that logical rabbit hole. You end up with Common Lisp!


I've wondered the same thing. I think one benefit is that it looks like HTML, which means it looks similar to what you see in the browser's DevTools, which makes it easier to compare and debug.


It also makes it easier to see what it's not: at a glance, the "p" could really be anything until you scan the context. The <p> isn't a string (that on further examination turns out to get used for marking up a paragraph), it is a paragraph demarkation (in vdom, but still).


For a single file or simple app, sure, your style works fine. But when you get a few hundred components in a complex app, importing them into each other and writing them as JSX tags feels like a simple, easy to understand syntax, as least to me.

And that is what a lot of the pushback against React seems to come down to as well -- it is overkill for simple things. And it is. But different people doing different apps with different levels of complexity... all have different needs.

So it is not as simple as one having appeal over the other. They are different tools for different problems.


You might be confusing JSX for something else. In JSX you also don’t need new syntax for loops. JSX Is JavaScript, as people like to say.

But to your point, JSX doesn’t really do much. Your h function is basically what React.creatElement does. Google “React without JSX” and you’ll see how it looks.

JSX is just syntactic sugar over React.creatElement. And that is what makes it so nice… there _are_ no special constructs for loops, or variables, or components. They are actual JavaScript loops, JavaScript variables, and JavaScript function.

It makes JSX easier to reason about than most templating languages.


Elm works a lot like this and it's quite nice.


Because that code is very hard to read, especially with a complex HTML structure.


People forget what problem the virtual dom and react is supposed to solve.

No better article than this: https://blog.vjeux.com/2013/javascript/react-performance.htm...


And then Svelte showed that you could avoid all that with a compilation step and live update the dom efficiently.

https://svelte.dev/blog/virtual-dom-is-pure-overhead

React is also at the point where re-rendering the whole app is a fiction the library maintains for you while being smarter and doing less, why not go the whole way?


And then SolidJS showed that you could do the same thing even without a compilation step.


Agree, react is way too bloated right now. The original idea and first versions were great.


The original idea and first versions were extremely inefficient and unscalable


(honest question, not trying to be snarky) Do you have one (many would be great) use cases where the practical gain of the virtual DOM solutions have a genuine impact?

I'm asking because, many of React (or friends) introductory material naturally focus on building things like TODO lists or Tic Tac Toe; while those offer insights into how to work with React (& cie), they're not showcasing cases where the performance gains are perceptible, and IMO not even cases where the "organizational" benefits of such libraries are salient.


This question is crucial to understanding the true value of React and virtual DOM technologies.

While there's no doubt that React and virtual DOM offer advantages, it's essential to clearly demonstrate where and how these benefits manifest in real-world applications.

> they're not showcasing cases where the performance gains are perceptible

According to this commenter, it's not even about the performance gains:

https://news.ycombinator.com/item?id=41271272

> and IMO not even cases where the "organizational" benefits of such libraries are salient

Apparently, that is what it ultimately boils down to:

https://news.ycombinator.com/item?id=41271367


> While there's no doubt that React and virtual DOM offer advantages, it's essential to clearly demonstrate where and how these benefits manifest in real-world applications.

Definitely; it's a struggle to find precise, concrete arguments in this direction. And there are many good reasons to be conservative: e.g. inheritance-based OO was sold with "VW inherits Car"; looks great on paper, but not as much in front of real-world issues.

> Apparently, that is what it ultimately boils down to:

If so, I'd be left wondering how much of this is actually caused by a lack of discipline, as seems to be for example indicated by the "dumb reflows" issues.


Obviously this always depends on what code you compare it to. I don't think there can be much doubt that a well written performance oriented framework-free implementation is in practice always going to be faster than anything using virtual DOM, as one can update simply the parts that need updating without creating the virtual DOM in the first place.

If you assume programmers who don't know what they are doing it is a very different question. Some people will manage to make a train wreck both with and without a framework. But if we assume that there is a skill level where people will manage to make something useful with a framework, but not without it, or vice versa, then I really do not know which way it swings.


Yes:

One project I worked on was a BI tool with a fair amount of complex state. Before we introduced react we were using backbone, event listeners, and managing dom state ourselves. With react we made it declarative, which simplified things.

Another project was an email client with a lot of complex state and caching. That started with and continued to use react so I don’t have a direct performance comparison. But again, managing the state manual would have been a nightmare.


Thanks in particular for the second example: it's a precise idea which can be studied (the former still is too abstract).

> managing the state manually would have been a nightmare

Now, of course, the annoying follow-up question would be "why?". For sure, there are poor ways to handle complex states without React, but that doesn't mean it's impossible to do it fairly well either.

For example, a divide and conquer strategy might make the situation less miserable already: associate "components" (DOM nodes) to relevant portions of the state only. But it's still too difficult to get a clear idea without actual code (hence my original question: most of the discussions about this are either objectively too hypothetical because they deal with complex/private codebases, or unconvincing toys)


For me is because is hard to remember that problem while dealing the the ones react brings.


There are plenty of cases where optimizing for performance isn't necessary. This is where React is not worth the extra headache and complexity.


React is set to become much less complex as a user once the react compiler is in place and if you use server components/actions; in my product we’ve already basically eliminated 95% of useEffect calls, almost all data fetching, client side state management with the current gen tools, and once the compiler is in then all memoization will be gone too.

You still end up with the bloated bundle size but with one of the more modern react alternatives you can eliminate that too. So at least for me, I don’t mind the build complexity for the power I get; especially now that node itself is supporting typescript, the build side is getting simpler to set up as well.


Anyone else used Hono with SSR JSX? [1]

Was super productive and easy to create a Cloudflare Worker Web App that’s free to host thanks to Cloudflare’s generous 100k daily worker request limit.

Generally don’t believe in serverless for larger Apps, but for small websites that you just want to create, deploy and ignore - it’s great!

https://hono.dev/docs/guides/jsx


What is the benefit of mixing js and html?

    el = <button>Click me</button> as HTMLButtonElement;
What would be the downside of

    el = html.button('<button>Click me</button>');
?

That way no compilation step would be needed and debugging would be easier as the code executed in the browser is the same code the developer writes.


The benefit is that it makes people puke from looking at it so you have more job security I guess. Putting xml onto the same line with a scripting language is like mixing toothpaste and orange juice.

I don't understand why people take such offense to calling document.createElement() or document.getElementById() or kind of document. or window. function. It's consistent and native.


Compare

    class Item extends EventTarget {

        #checkbox = <input type='checkbox' /> as HTMLInputElement;
        li;

        constructor(private list: List, text: string) {
            super();
            this.li = (
              <li class='item'>
                {this.#checkbox}
                <span onclick={() => this.toggle()}>{text}</span>
                <button class='close' onclick={() => this.remove()}></button>
              </li> as HTMLLIElement
            );
            this.#checkbox.onclick = () => this.toggle();
        }
    }
    
with

    class Item extends EventTarget {

        #checkbox;
        li;
        
        constructor(private list: List, text: string) {
            this.#checkbox = createElement('input');
            this.#checkbox.setAttribute('type', 'checkbox');
            
            this.li = document.createElement('li');
            this.li.className = 'item';
            this.li.appendChild(this.#checkbox);

            const span = document.createElement('span');
            span.onclick = (() => this.toggle());
            span.textContent = text;
            this.li.appendChild(span);

            const button = document.createElement('button');
            button.className = 'close';
            button.onclick = (() => this.remove());
            this.li.appendChild(button);
        }
    }
    
Of course you can create helper functions to avoid all the `createElement`s followed by `setAttribute`s. As mentioned elsewhere you can even used tagged strings. But doing things "manually" is painful.


With the first example you have syntax highlighting and compile-time check.

With the second of you have stringa.


If you use a html`` tagged template literal combined with the html-in-template-string vs code extension you get syntax highlighting. A simple html identity literal function is a one-liner: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Why wouldn't one be able to tell syntax highlighters and code checkers that the string that goes into the html.something() functions is html?


As often happens with minimalistic approaches, it only looks interesting on very small and very simple examples.

After “How would they handle large data?” it turns into an unreadable mess.

Communication between elements is not covered, global deps, DOM updates scheduling, content projection, and so on - you “just don't need it” in small demo examples, but you do need it in the real apps.


Nice project! I do wonder though if jsx is the best way to represent elements in code?

Clojure datastructures makes this so much more enjoyable. Everything is just basic lists and maps which makes it very flexible and powerful.

[:ul [:li "task 1"] [:li "task 2"]]

It's weird that it's not more common for making web apps.


There are a lot of DOM util libraries that look like

  h("ul", h("li", "task 1"), h("li", "task 2"))
This is called "hyperscript-style" after an early library that used it. This is basically what JSX compiles to too. There used to be a lot of JSX vs hyperscript debates.

There's also variants like h.ul(h.li("task 1"), h.li("task 2")) using Proxies now too.


There is a library for Python called htpy that does this.

Trouble is if you're used to HTML it can take a while to get used to it. It's like a learned helplessness or something.


I don't see why the type casting (as HTMLButtonElement) is needed. Because document.createElement("button") returns HTMLButtonElement in TypeScript.


Imba is what anyone interested in this sort of thing should look at. I have no idea why it is not more popular. Maybe because JS devs falls for Faang marketing easily.

https://imba.io/


Many programmers seem to be scared of anything that doesn’t have semicolons and braces.


Sometimes you need to lay things out differently to help comprehensibility.

Braces and semicolons exist so you can do that.

Sometimes it's just easier to read an array that's split over multiple lines.

Sometimes it's just easier to read a statement that's split over multiple lines.

Sometimes it's just easier to read two statements sharing the same line.

If your language's restrictions is making code harder to read, then it's making your job harder than it needs to be.


It's very strange that when I land on the page for the very first time, I land halfway down the page and I'm staring at a block of random code.

Not what you'd expect to see.


#real-todolist has an autofocus element and I'm using Firefox


Oops. Fixing now.


\o/


A side question. The advantage of JSX I see is the ability to connect, declaratively, components. I find this very helpful in terms of understanding programs I write. I wonder if I use React not because of the virtual dom, but simply because of JSX.

So I would like to explore the ability to use JSX in non-DOM environments. react-three-fiber does this with Threejs, but then it is still React oriented. I found this article about parsing JSX https://blog.bitsrc.io/demystifying-jsx-building-your-own-js.... And I know babel has something that parses JSX.

Does anyone have recommendations for doing this. Threejs to me a good candidate - a non React version, since it is a hierarchical system (scene, meshes, materials etc), but I suspect there are other applications.

I made an attempt to implement a Javascript version of Hickey's transducers - a sort of conveyor belt of functions and that is another instance of a series of processing steps that might be best represented in JSX


I see what you’re getting at, but technically the virtual DOM is what makes JSX declarative.

JSX doesn’t actually write anything, it’s just a templating language over React.createElement.

It’s the virtual DOM that actually syncs those structures created by createElement to the real DOM. So it’s the virtual DOM that allows you to write your code declaratively.

That’s evidenced by OP’s project, which is JSX without the declarative piece. You just get an Element and then you have to update it imperatively if you want to change anything.


Just out of interest I wanted to see something a little bit similar in Web Components:

    <html lang="en">
    <body>
      <h1>Web Components Examples</h1>
      <h2>Counter Component</h2>
      <counter-component></counter-component>
      <h2>Clickable Button Component</h2>
      <clickable-button></clickable-button>
      <h2>Toggler Component</h2>
      <toggler-component></toggler-component>
      <script>
        class CounterComponent extends HTMLElement {
          constructor() {
            super();
            this.count = 0;
            this.button = document.createElement('button');
            this.button.textContent = this.count;
            this.button.addEventListener('click', () => {
              this.count++;
              this.button.textContent = this.count;
            });
            this.attachShadow({ mode: 'open' }).appendChild(this.button);
          }
        }
    
        class ClickableButton extends HTMLElement {
          constructor() {
            super();
            this.clicked = false;
            this.button = document.createElement('button');
            this.button.textContent = "Click me!";
            this.button.addEventListener('click', () => {
              this.clicked = !this.clicked;
              this.button.textContent = this.clicked ? "Clicked!" : "Click me!";
            });
            this.attachShadow({ mode: 'open' }).appendChild(this.button);
          }
        }
    
        class TogglerComponent extends HTMLElement {
          constructor() {
            super();
            this.on = false;
            this.button = document.createElement('button');
            this.button.textContent = "OFF";
            this.button.addEventListener('click', () => {
              this.on = !this.on;
              this.button.textContent = this.on ? "ON" : "OFF";
            });
            this.attachShadow({ mode: 'open' }).appendChild(this.button);
          }
        }
        customElements.define('counter-component', CounterComponent);
        customElements.define('clickable-button', ClickableButton);
        customElements.define('toggler-component', TogglerComponent);
      </script>
    </body>
    </html>


For anyone who can live without <> syntax I made DOM Maker, no compilation step, no injection vulnerability footguns, just make a bunch of function calls in a tree structure, and you get DOM with the same tree structure, complete with non-string event handlers.

Mostly I just do Vanilla.js, but the vanilla DOM creation functions turn really verbose, I got tired of that and created this to cut back on code size and increase readability.

There are other libraries that do something similar, but in my own very biased opinion this is one of the better.

https://github.com/NoHatCoder/DOM_Maker


I frown at JSX. Just a layer of abstraction that is so "leaky" that you have to know what actually goes on in the layers below or you are fucked.

It looks simpler at first glance/ to a untrained eye; but it's just adding complexity without really solving any problems.

I like approaches like Kotlinx.html, scalatags, Elm's HTML package or HtmlFlow. They are also abstractions, but they add typesafety that html-as-a-string does not offer. On top of that you get breakpoints, code completion, and you can keep working in one language.


I used to explore similar stuff and prototyped something I call “Vanilla Components” but then in the end I fell in love with Web Components and quit React (and all other frameworks).


I was bummed when they removed E4X from the browser implementations.


What benefit does the virtual DOM add?


If you couldn’t efficiently batch updates, a vDOM could avoid repetitive updates in close succession, especially on IE6 (the browser React was designed for).

If you can control your app’s structure, it primarily adds significant increases in the RAM and CPU required for your app and slows load time because you are using a huge amount of JavaScript to emulate the carefully tuned C++ code built in to the browser. If you notice, most of the benchmarks from when React launched claiming performance wins were compared to heavyweight frameworks or complex jQuery plug-in combinations where a single user interaction might trigger cascading updates forcing the browser to rerender things which didn’t change along or to reflow multiple times in cascading update-measure-update chains. Pure DOM implementations were always faster, often by multiple orders of magnitude and once you could drop IE6, and then IE11, the DOM APIs and CSS were rich enough that much of the library code is now a net negative as well (e.g. people used to use complex code trying to build layouts which CSS grids solved).


It enabled a style of view library where you write immediate-mode type code that always recreates a whole component from scratch, versus having to write finicky code that both creates and then updates pieces of the page as state changes (dirty tracking, etc). Behind the scenes, you're creating the vDOM from scratch, which is diffed against the actual retained-mode DOM, and then only the pieces that are different are updated.


DOM interactions (read, writes) are synchronous, they're very slow, and it must happen on the main thread. This can cause the browser tab to freezing if access and updates aren't carefully "curated" (ie you don't want to read-check-then-write in a tight loop; or even write too often, even if it's the same value).

It can also simplify some stuff surrounding event handling (but that's not it's main goal I think)

So people wrote various ways to defer/batch/denounce updates.

Virtual DOM is a general solution/implementation. It's not the only one, but I think you always need at least a tiny runtime to avoid too much DOM access (ie Svelte, Solid JS are fairly minimal)


> but I think you always need at least a tiny runtime to avoid too much DOM access

Unless you use lit-html, which has a very efficient diffing algorithm that only updates the nodes that have changed.


How is that done without a vdom?


Lit-html uses template literals for that

https://lit.dev/docs/libraries/standalone-templates/

"lit-html lets you write HTML templates in JavaScript using template literals with embedded JavaScript expressions. lit-html identifies the static and dynamic parts of your templates so it can efficiently update just the changed portions."


A a high level there's not much difference between template literals and JSX, they are both syntax-sugary ways to represent trees of essentially function calls.

> efficiently update just the changed portions

Since actually applying each change to the real DOM is too slow, the only way to efficiently update is to batch changes and then apply the delta to the actual DOM.

That means we need to keep track of some state, namely the previously applied state and the current goal state, which you then compare.

Now, you may have noticed that we've just independently invented the concept of diffing. And the extra state that needed to be tracked can be given a spiffy name, like "virtual DOM", since it's like the DOM, but not the real thing.

So, I'm quite unconvinced by Lit-html's claim that they are able to efficiently mutate the DOM without using a vDOM anywhere.

Either their method is not efficient (for example it falls over for rapid updates), or there is a data structure under the hood that is analogous to a vDOM, even if they prefer to give that data structure a different name.


Oh well, we gotta thanks the great developers of Lit-html for making it transparent then. And a lot faster than React.

https://krausest.github.io/js-framework-benchmark/current.ht...


Yes, looking it into it more, Lit-html is doing a few things to make it all work:

1. It leans on support for the <template> element from browsers to hold fragments of HTML

2. For most use cases, it has a more restricted programming model compared to React or other vdom libraries, because templates are not allowed to change the shape (the tree structure of nodes) of the templated DOM.

3. For some cases where you want it to act more like React (for example, dynamically picking an HTML tag) you must use other mechanisms such as unsafeStatic. The docs explicitly tell you this immediately triggers a re-render = slow.

So I guess that answered my own curiosity: the vDOM is mostly replaced by a collection of static templates that don't need to be diffed, because the onus is on the dev to write DOM fragments where the tree does not change.

This is a more restrictive model that what React gives you, where you can generate any fragment tree, including ones with different shapes, entirely programatically.

If you do want the tree's shape to change, then lit-html isn't promising you good performance. You'll need to use methods like unsafeStatic which are slow. All in all, this is pushing more work off onto the developer.

Is this a good tradeoff? I think for most websites you can probably work within Lit's programming model. But the benchmarks you yourself linked points to many, many vDOM libraries that are about as performant as Lit (including React, whose main downside somewhat more memory usage) and has a more convenient React-like programming model.


Thanks for the comprehensive analysis, quite interesting.

I disagree about the trade-off in convenience tho, React programming model (especially related to managing state) is quite confusing, verbose and error-prone.

Multiple useless re-renders are the norm.

useEffects inside the body of functions a very poor way to manage the lifecycle of a component and this is way simpler with Lit.

All in all, just for it to be more like React pre-15 I would choose Lit.


The virtual dom makes implementing a declarative templating system easer, and declarative templates are easer for a developer to reason about, and less error prone, than having to mutate the dom directly.

People often mistakingly describe the vdom as faster than the dom, this is incorrect. It would be faster than throwing away the whole components dom and rebuilding, so the same templating code building a new dom, rather than a vdom that's then diffed. Hand crafter mutations will be faster than a vdom diff, simply because the computer is doing les work, however much more error prone.


Virtual DOM is to classical JS what garbage collection is to malloc and free.

Garbage collection is less efficient, but it is sometimes very difficult to figure out exactly when a piece of memory stops being used, which leads to use-after-free, double-free and memory leak bugs.

Same goes for classical UI approaches. In classical UI, most pieces of state are kept in at least two places, once in code and at least once in the DOM.

For example, in a shopping cart, the total might appear three times, implicitly in the code (as a function that sums the prices of all the items), once as the label of the "open cart" button in the navbar, and once as text in the "your cart" modal, which that button shows or hides. THe cart may be modifiable from different places, the cart modal itself, product pages, product collection pages, order history (when re-ordering recently purchased items) etc.

In the classical approach, you need to make sure that all modifications to the cart accurately change the state in all three places. You also need to ensure that if you remove a product from the cart using the modal and you're currently on a page that lets you order the product in any way, the "remove from cart" button on that page needs to turn back into "add to cart", and there may be hundreds of different such buttons, which the cart modal needs to handle somehow. It is very easy to make mistakes here and have the state in the code (array of products) fall out of sync with what the user sees on the page.

In React, there's just one array of products, one function to calculate the total, and a lot of places in the code that use this array. WHenever the array changes, the pieces of the page that rely on it automatically re-render, while everything else stays the same. There's no way for the UI and the array to fall out of sync, and there's no need to track where the array is being used and where it's being modified.


I don't quite get how this reactivity is only possible with a VDom, for example Svelte https://learn.svelte.dev/tutorial/updating-arrays-and-object... also allows you to update the UI on changes to the array.


To extend the malloc versus GC metaphor, Svelte here is like Rust, it has really good developer experience while still giving you most benefits of Virtual DOM.


> People often mistakingly describe the vdom as faster than the dom, this is incorrect.

You'll get better performance with _carefully crafted_ DOM access, but that's easier said than done, especially on a larger applications.

vDOM takes care of the "carefully crafted" part with some trade offs, especially if it also defers rendering and doesn't wccess the DOM on every update.

So yes, it's easier to write declarative UIs with it, but it's also there to address common performance issues with unchecked/eager DOM access. Even if you don't throw away the whole tree and insert a new one, it can be very slow. Just _reading_ from the DOM is slow _and_ everything stops while that's being done too.


Slows down your app too, sometimes. Depends how well you can work with and mutate a DOM, but if all things equal no VDOM is always faster cause no diffing.


A lot of people can benefit from offsetting mutations with rAF and dbl rAF and batching reads/writes (FastDOM), before needing or considering a VDOM. VDOM came to prominence because of REACT and then started becoming used even when it wasn't needed. It does serve a purpose and scenario when needed, tho


With vDOM I could say `x = JSX` then cache that in state, inserting it in multiple places. Switching to Solid you have to make sure to use `x = () => JSX` & there's some mental model adjustments since logic outside JSX isn't reactive


No matter how complex your app is but still React will not break, performance on web is not a big issue as benchmarks say, even a junior developer can achieve 90%+ lighthouse score, but any senior developer may fail to ship it successfully.

ultimately go to react.dev because: "Maturing is realizing React is best"


Those interested in this space may find my fairly unknown project interesting: https://nakedjsx.org/

It started as a static site generator but added a bunch of support for client JavaScript too.


I’m having fun using vanilla js with lit-html. Using string templates instead of jsx. VSCode extensions for lit make it almost identical to editing vue templates with type checking etc


This plays very nicely with the locality of behavior model of htmx


For me is like old PHP where HTML and controlling and data access was all around. We use to call it spaghetti code.


I just don't understand how people can configure their brains to parse html inside JavaScript


There's a trick to it. Kind of like one of those 'magic eye' stereograms that were popular in the nineties. You sort of unfocus and boom, there it is.

It also reminds me of that Douglas Adams line about flying: it's the trick of falling and completely missing the ground, so in order to do it, you can't think about it too hard.


You're not alone. Someone suggested that the W3C should convene a Community Group to discuss JSX, but the grown guys involved in writing standards immediately scrapped the idea.


If there can be JS inside HTML, why not HTML inside JS?


One of the reasons for JSX originally was to reduce usage of the DOM APIs, as they are slower than direct JS object manipulation. The JSX diff of prev/next allows you to minimize DOM API calls.

I would guess there is more overhead in creating a dom element than a JS object (which JSX elements compile to).


React came out in 2013. At that time object manipulation would likely have been, at best, only marginally faster than writing to the DOM.

First, you have to understand that at that time Firefox was about 500x faster at accessing the DOM than Chrome and about 250,000x faster accessing the DOM via the API methods than via querySelectors. Firefox and Chrome performed about equally in use of querySelectors with Chrome being a tiny bit faster. So, the DOM was already fast, but occupied a different memory space than JS.

At any rate the original motivation had nothing to do with performance. The goal was to introduce a template system that fit with React’s state/component system. JS modules weren’t a thing yet, so code organization was very different at that time and centered around concepts like AMD and Common.js, though it was mostly some form of AMD typically require.js.

The design of the template system in Vue was created to solve for the exact same conditions according to the internal organization of Vue.


Respectfully, my experience says otherwise:

https://jsben.ch/cSWJa

- JS object appears to be at least 2x faster than document.createElement() (Chrome)

- Note: JS object only loosely represents JSX element so it is a bit unfair. But with actual JSX objects I would assume it is still somewhat faster than the DOM API.

https://youtu.be/DgVS-zXgMTk&t=1532

- Pete Hunt, one of the React devs, says "JSX is faster than the DOM because it is JS memory."


Your current benchmark does not represent browsers in 2013.

It also presents a wildly different use case. It’s common to rapidly create and destroy nodes only because that is what convenient for framework logic. Outside of frameworks DOM nodes are modified more frequently than created anew.

> - Pete Hunt, one of the React devs, says "JSX is faster than the DOM because it is JS memory."

That doesn’t make sense. Your only choices to display content in the browser using JS is via DOM modification or canvas. If not using canvas you are touching the DOM no matter what. It is true that DOM memory is different than JS memory, but the DOM API is fast enough that it doesn’t matter, especially since you are touching the DOM no matter what. If your approach to DOM interaction is too slow it’s because of string parsing on things like innerHTML and querySelectors. So if performance is important then don’t do things that parse strings and don’t abstract away the DOM interaction because you are touching it no matter what.


Great job, is there something similar but for SwiftUI?


I wonder what The examples would look like in ECMAscript 5.


How does this stack up aginst using lit-html?


Any comparisons on performance?


It’s already solved. It works well. Just walk away.


I genuinely don't understand why anyone would be interested in using frameworks on top of JS. None of them can do anything that pure JS can't do (+libraries), they just make it less readable and less intuitive compared to the original C-like syntax of JS. JS libraries make sense, of course, but why keep messing with the syntax?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: