Dig Deeper into JSX

Dig Deeper into JSX

·

4 min read

What is JSX

JSX is an XML-like syntax extension to ECMAScript. With JSX, we can write html and JavaScript together inside js file by concise syntax.

We are pretty familiar with JSX because of React. There are some good documents in React documentation website, such as Introducing JSX and JSX In Depth. And the complete JSX document is in jsx/#sec-intro.

So for example, with JSX, we can write html and JavaScript together like below.

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

And we can compile this code into below valid JavaScript code.

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

Compiling JSX

So we know JSX is simply a special syntax, now let's see how to do the compiling. We can use Babel and a Babel plugin @babel/plugin-transform-react-jsx. Now let's walk through the steps.

First, let init a new node project.

npm init -y

Then we install babel.

npm install --save-dev @babel/core @babel/cli

Then we write a babel config file babel.config.js. We don't have anything yet, just leave them empty.

const presets = [];
const plugins = [];

module.exports = { presets, plugins };

Now write a simple js source code src/index.js.

const hello = () => {
    return "hello";
}

Now we can compile the source code with babel cli command.

./node_modules/.bin/babel src --out-dir lib

This command should compile our source code and store result into the lib folder. The result should be the same as the source because we haven't ask babel to do anything.

Now let install the @babel/plugin-transform-react-jsx plugin.

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

And add it into babel config.

const presets = [];
const plugins = [
  [
    "@babel/plugin-transform-react-jsx",
  ]
];

module.exports = { presets, plugins };

Write a component in JSX.

<MyButton color="blue" shadowSize={2}>
    Click Me
</MyButton>

Now compile again.

/node_modules/.bin/babel src --out-dir lib

Now we should see the compiled result.

/*#__PURE__*/React.createElement(MyButton, {
  color: "blue",
  shadowSize: 2
}, "Click Me");

Beyond React

Beside using in React, with the power of embedding JavaScript and XML like syntax, JSX actually can be used in other cases. Recently I read an article An Overview of JSX With 3 Non-React Examples and it is very inspiring for me. There is an example about using JSX to in express router I found very interesting. Let's walk it through in here.

We know that in express.js we can register routes into global and we can also use Router function to create nested routes. Below is a simple nested example.

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

/**
 * Build a router like below
 *
 * app
 *   dog
 *     bulldog
 *     poodle
 *   cat
 *     persian
 */

const dogRouter = express.Router();
const catRouter = express.Router();
app.use("/dog", dogRouter);
app.use("/cat", catRouter);

const bulldogRouter = express.Router();
const poodleRouter = express.Router();
dogRouter.use("/bulldog", bulldogRouter);
dogRouter.use("/poodle", poodleRouter);

bulldogRouter.get("/about", getBulldog);
function getBulldog(req, res) {
    res.send("bulldog");
}
poodleRouter.get("/about", getPoodle);
function getPoodle(req, res) {
    res.send("poodle");
}

const persianRouter = express.Router();
catRouter.use("/persian", persianRouter);

persianRouter.get("/about", getPersian);
function getPersian(req, res) {
    res.send("persian");
}

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

// http://localhost:3000/dog/bulldog/about

As you can see, its only 3 level but hard to see the structure.

We can rewrite the routes structure in JSX and write a function like React.createElement to create the routes.

const express = require("express");
const app = express();
const port = 3000;

/**
 * Build a router like below
 *
 * app
 *   dog
 *     bulldog
 *     poodle
 *   cat
 *     persian
 */

const App = 0;
const Router = 1;
const Get = 2;

/** @jsx createRoute */

<App>
  <Router path="/dog">
    <Router path="/bulldog">
      <Get path="/about" handler={getBulldog} />
    </Router>
    <Router path="/poodle">
      <Get path="/about" handler={getPoodle} />
    </Router>
  </Router>
  <Router path="/cat">
    <Router path="/persian">
      <Get path="/about" handler={getPersian} />
    </Router>
  </Router>
</App>;

function createRoute(element, props, ...children) {
  switch (element) {
    case App:
      for (const child of children) {
        app[child.method](child.path, child.handler);
      }
      break;
    case Get:
      return { path: props.path, handler: props.handler, method: "get" };
    case Router:
      const router = express.Router();
      for (const child of children) {
        router[child.method](child.path, child.handler);
      }
      return { path: props.path, handler: router, method: "use" };
    default:
      throw new Error("not supported element");
  }
}

function getBulldog(req, res) {
  res.send("bulldog");
}
function getPoodle(req, res) {
  res.send("poodle");
}

function getPersian(req, res) {
  res.send("persian");
}

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

// http://localhost:3000/dog/bulldog/about

As you can see, this is a lot clearer.

So that's it. Thanks.