JSX Unchained: Make a template engine without React
Tags: webdev, frontend, javascript, react
Published: 2023-10-18 14:20:36

**Hello, developer.**

Today, let's talk about JSX, the love-hate relationship of all React developers. Many hate it, many love it, but have you ever wondered how you could make the most of the power of JSX outside the usual React context?

**Well, you're in the right place!**

In the vast world of web development, the use of JSX in combination with React has changed how we create dynamic and responsive user interfaces. It must be admitted that the developer experience (DX) offered by React dominates over other frameworks, and JSX is one of the factors that has contributed to the success of this endeavor.

However, at times, you might feel the need to free JSX from this tight connection with React, thus opening the doors to new creative possibilities.

## Let's peek behind the scenes.

Here's where the questions arise: how can we use JSX independently, without the weight of React? This article is here to guide you through the construction of a template engine that will allow you to do just that.

Imagine being able to leverage the powerful syntax of JSX in scenarios outside the traditional framework, customizing how JSX elements are evaluated and rendered (pretty cool, huh?).

Before diving into the creation of our template engine, let's take a quick look at what's behind the scenes between JSX and React:

JSX, which stands for JavaScript XML, is a kind of markup language that looks very similar to HTML. In reality, behind a JSX tag, there's the React.createElement function that handles the transformation of components. Yes, JSX tags are actually JavaScript functions (when you've recovered from the shock, keep reading).

Initially, JSX was designed to be used exclusively with React, but over time, it has evolved more and more to be considered a project on its own.

Our goal is, therefore, to create a template engine that adapts to our needs using JSX. Whether you're building a lightweight and fast app or exploring unusual scenarios, this template engine will be the key to opening new doors to our creativity.

## Template Engine Design

Let's start with this:

> I want to maintain the DX of React and write a core capable of translating my JavaScript functions, enriched with JSX, into a representation of pure HTML.

Imagine being able to define your user interface declaratively and then, with a magical touch, make it ready for action.

**But how do we achieve all this?**

Simple, we need a bundler! In particular, in this article, I'll use Webpack, but you can reconstruct everything with Vite, EsBuild, etc.

I'll try to guide you step by step.

Let's initialize the project.

Create a new folder and type:

`npm init -y`

Done? Great.

Now we need two things in particular:

The first thing is to install Webpack, and the second is to use a transpiler to "transform" our JSX into JavaScript objects.

As for the second point, we'll use Babel, but let's focus on Webpack for now.

`npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin`

Great! Now let's install Babel.

`npm i -D @babel/cli @babel/core @babel/preset-env babel-loader`

Last piece: we need a Babel plugin that allows us to transpile JSX. We'll delve into its usage later.

`npm i -D @babel/plugin-transform-react-jsx`

Now that we've installed all the necessary dependencies, let's add the following scripts to our package.json.

```json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open",
    "dev": "webpack-dev-server --open"
}
```
Plainly put, these scripts are for running our project and building it if we want to. Now, we need to create our "magic." Thanks to the babel/plugin-transform-react-jsx plugin, we can tell Babel to handle a custom runtime for transpiling JSX.

Essentially, we will now customize how Babel should "evaluate" JSX expressions. So, in the root of our project, let's create the file _jsx-runtime.js_.

```js
const add = (parent, child) => {
  parent.appendChild(child?.nodeType ? child : document.createTextNode(child));
};

const appendChild = (parent, child) => {
  if (Array.isArray(child)) {
    child.forEach((nestedChild) => appendChild(parent, nestedChild));
  } else {
    add(parent, child);
  }
};

export const jsx = (tag, props) => {
  const { children, ...rest } = props;
  if (typeof tag === 'function') return tag(props, children);
  const element = document.createElement(tag);

  for (const p in rest) {
    if (p.startsWith('on') && p.toLowerCase() in window) {
      element.addEventListener(p.toLowerCase().substring(2), props[p]);
    }
  }

  appendChild(element, children);
  return element;
};

export const jsxs = jsx;
```

This very minimal runtime allows us to transform our "components" with props into pure HTML, bidding farewell to the Virtual DOM. Consider that we could extend the runtime to handle custom components, directives, or anything else that comes to mind. In short, see it as the foundation of our template engine or a future framework.

Let's add the last piece of the puzzle: the webpack configuration file. Again, in the root of the project, let's create the _webpack.config.js_ file.

```js
const HtmlWebpackPlugin = require('html-webpack-plugin');

const path = require('path');
module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    path: `${__dirname}/dist`,
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(?:js|jsx|mjs|cjs)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['@babel/preset-env']],
            plugins: [
              [
                '@babel/plugin-transform-react-jsx',
                {
                  runtime: 'automatic',
                  importSource: path.resolve(__dirname + '/./'),
                },
              ],
            ],
          },
        },
      },
    ],
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};
```

If we look at the module attribute, we find the configuration for Babel and the plugin plugin-transform-react-jsx, which refers to the root of the project to find our custom runtime file.

Aaaaaand that's it, we're done.

If you want a working example of what I've described so far, I've prepared a functional project for you on [StackBlitz](https://stackblitz.com/edit/jsx-as-template-engine?file=src%2Findex.js).

What's missing? A reactivity system. But that's another story...

## Conclusion

And so, our journey into the discovery of an independent template engine based on JSX comes to an end. We've explored the reasons behind the need to free JSX from React, delved into the architecture of the template engine, and tackled the challenge of implementing a flexible and powerful templating language.

With this tool at your disposal, you're now armed with a new perspective in web development. Take a look at the benefits that this freedom can bring to your daily workflow.

Your feedback is valuable. If you've experimented with the proposed template engine, have questions, or suggestions on how to improve it, feel free to share them in the comments. If the article has been helpful, share it with your developer friends!