Vanilla JSX
Tags: javascript, typescript, vite, webdev
Published: 2022-04-14 13:37:00

Hi everyone! In this article I will tell how you can use JSX in a project with TypeScript and Vite bundler. This material is for you, if you:

* ⚛️ Have experience with React, but no idea about how it handles JSX
* 🕵️‍♂️ Curious about front-end fundamentals
* 🤓 A geek who loves vanilla TypeScript and all around it

Why? For fun, of course! The idea of vanilla JSX is not suitable for a real project without over-engineering. Most likely, scaling the JSX support would cause the creation of the new frontend framework. So, open the [GitHub repo](https://github.com/VanishMax/vanilla-jsx) in a new browser tab and make yourself comfortable. We have a deep dive into the JSX ahead!

## What is JSX?

**JSX** is a syntactic extension over JS. It is not in the ECMAScript standards, so the tools like [Babel and React](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/) deal with JSX transpiling into the plain JavaScript. Let's look at the classic JSX example:

```js
const profile = (
  <div>
    <img src="avatar.png" className="profile" />
    <h3>{[user.firstName, user.lastName].join(" ")}</h3>
  </div>
);
```

After the `@babel/plugin-transform-react-jsx` run, the code will become understandable by browsers:

```js
const profile = React.createElement(
  "div",
  null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
```

As you can see, Babel successfully transformed JSX into a neat `React.createElement` function. It comprises the wrapper tag, its properties (or attributes, in the above case - `null`) and child elements, which, in their turn, are created with the same function.

React, Vue, and Solid frameworks deal with JSX by themselves, but they do it differently. This happens because they have different implementations of the `createElement` function. By the way, it is called **JSX Pragma**. When I learned about it, I immediately decided to create my own Pragma.

## JSX Parsing

Before jumping into the Pragma creation, we need to learn how to parse JSX. For small modern project without old browsers support, we wouldn't need Babel, but Vite or TypeScript is enough. We will use both.

![JSX Code transformation: from code to typescript parser, vite bundler, into the Vanilla JS code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kr9mn13salshabreoiep.png)

[Vite](https://vitejs.dev/) is a modern frontend app bundler. Compared to Webpack, it serves the source code over [native ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). To bootstrap the project, we just need to run the command:

```bash
npm create vite@latest
```

Vite and TypeScript, by default, parse JSX in `.jsx` or `.tsx` files. It substitues the result of parsing into the `React.createElement` function. Though, if we want a custom function to be substituted, we need to change the `tsconfig.json`.

```js
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}
```

If you are writing the app without TypeScript, change the `vite.config.js`.

```js
import { defineConfig } from 'vite';
export default defineConfig({
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment'
  }
});
```

These settings tell the parser to use the `h` (meaning _hyperscript, hypertext + javascript_) function for JSX with only one root element and `Fragment` for multiple-root JSX.

## JSX Pragma

After configuring the parser to handle the `h` function, we can start implementing it in `src/pragma.ts`.

```ts
// Tag can be string or a function if we parse the functional component 
type Tag = string | ((props: any, children: any[]) => JSX.Element);

// Attributes of the element – object or null
type Props = Record<string, string> | null;
  
// Element children – return value from the h()
type Children = (Node | string)[];
  
export const h = (tag: Tag, props: Props, ...children: Children) => {
  // If tag is a component, call it
  if (typeof tag === 'function') {
    return tag({ ... props }, children);
  }
  // Create HTML-element with given attributes
  const el = document.createElement(tag);
  if (props) {
    Object.entries(props).forEach(([key, val]) => {
      if (key === 'className') {
        el.classList.add(...(val as string || '').trim().split(' '));
        return;
      }
      el.setAttribute(key, val);
    });
  }
  
  // Append child elements into the parent
  children.forEach((child) => {
    el.append(child);
  });
  
  return el;
};
```

Just like `createElement`, the `h` function accepts the tag name (or a functional component), properties and results of the `h` function over the child elements.

All `.jsx` files must import the `h` function, so it is in the code scope after the transpilation. For example, this is a simple JSX usage.

```js
import { h } from '../pragma';
import { LikeComponent } from './like';
export const App = (
  <main className="hello">
    <h1>
      Hello JSX!
    </h1>
    <LikeComponent big />
  </main>
);
```

All we have to do now is add the transpiled code into the HTML:

```js
import { App } from './components/app';
const app = document.querySelector<HTMLDivElement>('#app')!
app.append(App);
```

That's it! We've created the app in with TypeScript that parses the JSX, and Pragma that creates the correct DOM for displaying it on a website!

## Practical use

As I said in the beginning, this idea is not meant for use in real projects. It only shows how easy it is to parse JSX without the use of runtime libraries, literally working in vanilla JS.

The concept of JSX Pragma is difficult to scale. If you wish to add logic to functional components, you'd have to handle the cases with variables and event listeners, thus re-implementing [the concept of reactivity](https://hackernoon.com/how-to-build-your-own-reactivity-system-fc48863a1b7c).

## Conclusion

It turned out that such a non-standard concept as JSX Pragma can easily be handled with no frameworks, with vanilla JS only! 

I encourage you to experiment with all the technologies that you can get your hands on, go as deep as possible into them. Good luck!