How to create the app using JSX without React

 

What is JSX and How Does it Work?

JSX is an XML-like syntax extension to JavaScript. It is similar to a template language, but it has the full power of JavaScript. JSX gets compiled to React.createElement() calls which return plain JavaScript objects called “React elements”. To get a basic introduction to JSX see the docs here.

In this article, we will take a look at how JSX works and how to create custom components using JSX without using any frameworks.

Let's get started

Create a new Project

Create a directory for your project by running the following command in your terminal:

mkdir jsx-demo
cd jsx-demo
npm init -y

Install Babel

Babel is a JavaScript compiler. It converts ECMAScript code into a backward-compatible version of JavaScript in current and older browsers or environments, Babel is used to compiling JSX to JavaScript.

npm install --save-dev @babel/core @babel/cli @babel/plugin-transform-react-jsx

Create a babel.config.js file

const presets = [];
const plugins = [
  [
    "@babel/plugin-transform-react-jsx",
    { runtime: "automatic", importSource: "./core" },
  ],
];

module.exports = {
  presets,
  plugins,
};

NOTE: The importSource the option is used to specify the path to the module that will be used to create the JSX elements. In this case, we are using the core module. We will create this module in the next step.

Create a src directory in your project, This is where we will write our code.

mkdir src
cd src
touch core/jsx-runtime.js

Let's create an index.html file in the root directory of our project

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My App</title>
  </head>
  <body>
    <h1>Hello Friends</h1>
    <div id="root"></div>
  </body>
</html>

NOTE: The id of the root element is important. It is used to mount the application. We will see how to do this in the next step.

Let's create App.jsx and Button.jsx

We will create a App.jsx and Button.jsx files in the src directory. App.jsx will be the entry point for our project.

Button.jsx

const Button = ({ children, onClick }) => (
  <button onClick={onClick}>{children}</button>
);
export default Button;

App.jsx

import Button from "./Button";

const App = () => (
  <div>
    <Button onClick={() => alert(1)}>Click 11</Button>
    <Button onClick={() => alert(2)}>Click 12</Button>
    <Button onClick={() => alert(3)}>Click 13</Button>
  </div>
);

const rootElement = document.getElementById("root");
rootElement.appendChild(<App />);

export default App;

Let's add the babel script in package.json

This script will compile our JSX code to JavaScript code. The output will be in the dist directory.

"scripts": {
    "build-babel": "babel src -d dist",
},

Let's compile our code

npm run build-babel

Let's see how it looks like.

import Button from "./Button";
import { jsx as _jsx } from "./core/jsx-runtime";
import { jsxs as _jsxs } from "./core/jsx-runtime";

const App = () =>
  _jsxs("div", {
    children: [
      _jsx(Button, {
        onClick: () => alert(1),
        children: "Click 11",
      }),
      _jsx(Button, {
        onClick: () => alert(2),
        children: "Click 12",
      }),
      _jsx(Button, {
        onClick: () => alert(3),
        children: "Click 13",
      }),
    ],
  });

const rootElement = document.getElementById("root");
rootElement.appendChild(_jsx(App, {}));
export default App;

NOTE:

  1. The jsx the function is imported from the core module. We added the babel.config.js file, but we have not created jsx-runtime yet. We will create it in the next step.
  2. JSX code is converted to a JavaScript function, Which will call the jsx function.

The most important part of this article is creating the jsx-runtime module. This is where the magic happens.

All the components import the jsx function from the jsx-runtime module. The jsx function is responsible for creating the elements.

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 } = props;
  if (typeof tag === "function") return tag(props, children);
  const element = document.createElement(tag);
  Object.entries(props || {}).forEach(([name, value]) => {
    if (name.startsWith("on") && name.toLowerCase() in window) {
      element.addEventListener(name.toLowerCase().substr(2), value);
    } else {
      element.setAttribute(name, value);
    }
  });
  appendChild(element, children);
  return element;
};

export const jsxs = jsx;

Let's understand how the jsx function works.

As you can see, the jsx function takes two arguments, the first argument is the tag name and the second argument is the props. The jsx function returns a plain JavaScript object. If the tag is a function, it will call the function and return the result. If the tag is a string, it will create a DOM element, set all the props to the element, and return the element.

The last step creates a bundle file using webpack And injects the bundle file into the index.html file.

Install webpack, webpack-cli, and html-webpack-plugin using the following command:

npm install --save-dev webpack webpack-cli html-webpack-plugin

NOTE: We can also configure babel in the webpack config, But I'm using babel separately to get a better understanding of how JSX code is compiled.

Create a webpack.config.js file

Add the following code to the webpack.config.js file.

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./dist/App.js",
  mode: "production",
  output: {
    path: `${__dirname}/build`,
    filename: "bundle.js",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
  ],
};

NOTE: The entry is the path of the compiled file. The output is the path where the bundle file will be created. The template is the path of the index.html file.

Update package.json scripts

"scripts": {
  "start": "webpack serve --open",
  "build-babel": "babel src -d dist",
  "build-webpack": "webpack --config webpack.config.js",
  "build": "npm run build-babel && npm run build-webpack"
},

NOTE: The start script will start the webpack dev server. The build script will compile the code using babel and create the bundle file using webpack.

Time to build our project

npm run build

After running the above command, you will see a build directory in your project. This is where the compiled code will be saved. Also, the dist directory will have the compiled code. We can delete the dist directory if we want, I would recommend keeping it for reference.

Run the project

npm run start

Quick Tip: You can use serve package to start the HTTP server from any directory. This is very useful when you are working on a static website.

cd build
npx serve

Live Demo: Here

In the next article, We will also add a state to this app.

Thank you for reading 😊

Got any additional questions? please leave a comment.

Next Post Previous Post
No Comment
Add Comment
comment url