Native/Vanilla JSX support

Please be patient with me, this is my first post :)

Who would like to see native/vanilla JSX support in JavaScript? I feel like this would be very beneficial to the JS community. First, here are some examples.

element.appendChild(
  <button onClick={()=> {
    console.log('lasers on!');
  }}>
    Activate Lasers
  </button>
);

Also imagine the possibilities with SSR simply in express:

app.get('/version', (req, res) => {
  res.send(
      <div>
        {process.version.node}
      </div>
    );
});

The way this would work is that a JSX fragment is it's own type which can be imported/exported from different files and represented as functions with a return value like (but not) react components.

In the first example, the JSX would be serialized into a HTMLElement or string (for .innerHTML), and when using document.appendChild, all the event listeners like onClick would be added to the corresponding element.

In the second example (Node.js runtime), the jsx would be converted to a string. Since this is SSR (server-side rendering) there obviously would be no event listeners or dom element. Instead, the JSX would be converted to a string that is then simply passed to the res.send() function. Obviously, things like <!doctype HTML> would also be passed as a string. This would require no change from the express.js framework.

I was thinking about cross-compatibility between Node.js and the browser or other runtimes, and I thought to make this a vanilla JS feature instead of browser or Node api so it could be used everywhere.

I did not mention the main reason I think this is a good idea, so I will note them out below:

1) Setting/changing HTML

If you have used React, Vue, Svelte, Ember, Qwik, Angular, Preact, Solid, or something similar, you may have noticed that the development process is way easier for complex applications. But this comes with a downside, like JavaScript support and large components that are downloaded in the browser. It's the norm currently to load a third party software from NPM to write any type of app these days. Almost every web app these days uses some type of framework. While React is good for building complex apps, I feel like native JSX would set a middle ground between simple apps with forms or basic interactivity and advanced apps like Discord or Asana.

The big issues these days with building interactive vanilla JS apps and SPA's is making interactive UIs. Here are some approaches:

element.innerHTML = `<button id="magic">Make some magic!</button>`
document.getElementById('magic').addEventListener('click', () => {
  console.log('magic');
});

First of all, using innerHTML could be unsafe and code gets really messy when put inside template literals. Also, linters cannot format the HTML inside of this. The separate event listener is also not very declarative nor easy to read.

function magic(){
   console.log('magic');
}
element.innerHTML = `<button onclick="magic()" id="magic">Make some magic!</button>`

Sure, this is more declarative but can also get very messy.

const element = document.createElement('div');
const button = document.createElement('button');
const buttonText = document.createTextNode('Make some magic!');
button.appendChild(buttonText);
button.setAttribute('id', 'magic');
element.appendChild(button);
document.body.appendChild(element);

button.addEventListener('click', () => {
  console.log('magic');
});

Another way of doing it is using document.createElement which is a lot cleaner but is also very tedious and not declarative at all. Imagine a whole codebase with these types of nested elements. Now, look at the example above and ask yourself which - which one is the best?

Nested components

It's no lie that nested components are way easier to use and are one of the most popular reasons to switch to React over vanilla JS for a good reason. It is clearly way simpler to create functions like this instead of writing all your code inside html or strings within the JavaScript:

const Greeting = (props) => {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
    </div>
  );
};

const MainComponent = (props) => {
  return (
    <div>
      <h1>Main Component</h1>
      <Greeting name={props.name} />
    </div>
  );
};

document.body.appendChild(<MainComponent />)

This example has no react, and does not need any types of external libraries. Making changes would also be very simple.

document.addEventListener('keypress', () => {
  console.log('Key pressed:', event.key);
  document.querySelector("name").replaceChild(<Greeting id="" />)
});

3) Security

This is very simple. XSS can be prevented by either using .innerText but this does not work for innerHTML. You could sanitize the HTML string using regex or .replace(), but it adds extra steps that should instead be baked into JS.

const Greeting = (props) => {
  return (
    <div>
      <h1>Hello, {untrustedAjaxResponse.name}!</h1>
    </div>
  );
};

4) Server-side rendering

We got tools like Astro and Next.js which are obviously more advanced, but what if you could just eliminate all those steps and render JSX in Node.js or deno, simply using JavaScript? This would eliminate SSR frameworks to some extent and also static site builders

ejs, moustache, and handlebars were also made to solve this, but it was very messy and not built in to the JS ecosystem. They are not type-safe and hard to debug and format.

Deno fresh is actually a great implementation of this, but would obviously require seperate .jsx files.

Another benefit is that if it was built into JavaScript it would most likely be way faster than the above alternatives.

2 Likes

You may also be interested in looking at a (rather lengthy) thread for a similar proposal: Proposal: ESX as core JS feature.

1 Like

I would like to remind you that the DOM is an API. Anything that has to do with HTML is separate from native JS and ECMAScript. I probably sound pessimistic, but no matter how strong the argument is, proposals that mention HTML in any way are often dismissed as "irrelevant."

To @DiriectorDoc's point - for JSX to become native syntax, I think we'd need to see use-cases for non HTML/XML-related things. e.g. if I'm buliding a command-line program in node, why is JSX syntax useful for me?

Syntax generally has a high bar it needs to pass to be accepted, and I think for a syntax construct as large as JSX to make it in, we'd need to show that it's useful for general-purpose programming, not just HTML/XML specifically.

1 Like

It's useful for any kind of tree structure. Off the top of my head there is GitHub - syntax-tree/hastscript: utility to create hast trees. I'm fairly sure someone has built a JSX-based configuration system as well.

Users are already doing this. Ink is a React renderer for node CLIs. React Native is for iOS and Android.

At this point I think JSX's use cases outside of HTML are both obvious and well supported. Facebook/Meta has been really lagging behind on improvements to the JSX spec, and I'd trust it more in the hands of an organization like TC39, WHATWG, or W3C.

Additionally, from a historical standpoint, I think ES4 (which was cancelled) was underappreciated. I understand it was controversial at its time, but TypeScript and JSX dominate the JavaScript community now, and I think this applies to E4X/JSX as much as it does types.

1 Like

I'll reiterate here my point of view on this: the cost of JSX is simply too high to be a syntactic addition to JS. However I still believe it may be possible to get most of the ergonomics that JSX affords by leveraging tagged template literals, and we could even make the tag itself part of the standard library if properly abstracted away from HTML.

One usual counter argument that comes up is poor syntax highlighting for tagged template literals. However I don't see a reason why developer tools can't recognize and highlight syntax in tagged literals.

I agree that some things like string tag names not being able to reference context would be an ergonomic regression, however I've always found that feature ambiguous in JSX. An explicit registry might be able to alleviate the need for tag name interpolation.

2 Likes

the cost of JSX is simply too high to be a syntactic addition to JS.

What's the cost? Also, pretty much every modern tool has built in JSX support now, even the performance focused ones like Vite and esbuild, which I think is proof enough that it's worth it for many users.

I agree that some things like string tag names not being able to reference context would be an ergonomic regression, however I've always found that feature ambiguous in JSX. An explicit registry might be able to alleviate the need for tag name interpolation.

What is ambiguous? Ultimately JSX is converted to function calls with references to components and objects of props, which is a very similar model to custom element lookup, except I think it's better to not have to maintain a global table manually. JSX also fits better with modern JS features like let and const which are block scoped to prevent clashes in global variable naming.

2 Likes

Of note is that what JSX gets converted into can vary based on not only framework but also debug and production.

For example a dev build of React JSX also adds extra metadata about the source location for React devtools and hot reloading to make use of.

I don't think the dev metadata would be necessary without a build step. There's also pragma syntax for changing what JSX can do without changing the syntax.

1 Like

JSX is a whole different language, with its own parsing rules. And from what I recall it requires balanced tags.

And that's besides the cognitive overhead for authors to understand when an expression is switching into the JSX realm, or when something is interpolated. Tagged template literals were designed to use a single sigil for start/finish, with explicit interpolation.

Knowing when tag names are taken from context or when they're "built-in". Yes I know about the capitalization rules of tag names for JSX, but that's the kind of ambiguity I don't want to see in JavaScript.

Back to my original point. Any language addition needs to be evaluated between what it enables and what complexity it adds. Any syntax changes is intrinsically a more complex addition to begin with. JSX is not a simple language, and I don't think anyone can make an argument that as a language addition, it's a simple one. The question then become whether the feature pays for its fairly high cost. Given the alternative of leveraging the existing tagged template literals feature, I don't see how that cost can ever be justified.

1 Like

the ESX proposal has onthing to do with HTML or the DOM so this comment after mentioning the ESX thread feels very naive (read: you didn't spend a minute in that thread).

The ESX proposal indeed has nothing to do with JSX limitations or constraints around onClick or similar events, it has zero constraints around class VS classList fields, it's just syntax to express a tree as in XML or E4X did at some point in time, without coupling by any mean anything to WHATWG or the HTML standard.

ESX would allow JSX out of the box but with better primitives and guards so that indeed the only library delivering it currently vaporizes React, Preact, and others, in the most famous benchmark, but it still would work out of the box for NodeJS SSR purpose, being that SVG, XML, HTML, or MathML ... or you name it.

I don't think that @DiriectorDoc was talking about ESX - they were probably talking about the original description in this thread, and reminding us not to overly focus on the HTML use-case if we're trying to get new native syntax into JavaScript.

It's true that both ESX, and further down in this thread, many non-HTML use-cases have been brought up.

1 Like

My bad then, timing (around answers) was imho a bit bad.

ESX exists already as a whole defined proposal, so as much as I'd like to refine it, it would solve any need/claim/usage around any alternative, most likely JSX, as I have a transpilation layer already fully working for it, so most of the job is virtually done too from an implementation details perspective.

1 Like