Transpile JSX using your own custom built babel plugin
Tags: react, javascript, babel
Published: 2020-08-16 15:47:20

Ever wondered how does react jsx code (`<div>Hello World</div>`) gets compiled to `React.createElement("div", null, "Hello World")`. This blog is all about this compilation process by taking help from the source code of [babel-preset-react](https://github.com/babel/babel/tree/master/packages/babel-preset-react) and trying to build our own custom plugin.

Just to make things clear, I will not use Webpack at all because its significance lies in just bundling process. It has nothing to do with transpilation part. I will just use babel and 3 files. Thats it. No HTML nothing. The goal of this blog is to actually convert this jsx code to js code that browsers can really understand.

Github Link -> https://github.com/pulkitnagpal/custom-jsx-plugin

Before moving straight to the code, lets revise some basics

##Basics 

I tried this `<div>Hello world</div>` code in a normal script tag and got "Unexpected token <". I thought create-react-app does everything under the hood and does some magic to convert it into React.createElement syntax. 
Everyone must know that this `div` in jsx is not an actual HTML Element. The compilation process just converts it into a function call. Not into HTML Element. That part is done by react.

I dug further and gradually realised that there is some power(apologies for using it :P) that converts this jsx into a  function call like syntax. This power is harnessed by BABEL.

create-react-app and many other tools uses babel under the hood. 

How does Babel works ?

1. **Parses your code** => Babel converts your code to 
   AST(Abstract Syntax Tree). Heavy term right ? No problem 
   try this tool (https://astexplorer.net/). Try writing 
   something on the left and a tree like structure will be 
   generated on the right. This is done by a parser built 
   inside babel.
2. **Traverse & Transform** => This is where babel plugins and 
   presets comes into play. A visitor pattern is provided by 
   babel that let us traverse through all the tree nodes of 
   AST and transform/manipulate those nodes into something we 
   desire.
3. **Generate** => This is the stage where babel converts the 
   transformed tree back into human readable code.

Before moving to our own custom plugin, let's try to use already built react preset and transpile our index file using babel cli. 
1. Steps to install babel-cli are mentioned [here] 
   (https://babeljs.io/docs/en/babel-cli)
2. Install React and ReactDOM and react [preset] 
   (https://babeljs.io/docs/en/6.26.3/babel-preset-react) 
3. Create an index.js file and .babelrc file

Add this to **index** file

```javascript
ReactDOM.render(<div><p>Hello World</p></div>, document.getElementById("root"))
``` 

and this to **.babelrc**

```javascript
{
  "presets": ["react"]
}
```

Run this command on the terminal
`node ./node_modules/babel-cli/bin/babel index.js`

and we can see the **transpiled** code on the terminal screen. We can also create separate output file. But I wanted to make things simple. As we can see how this jsx code got transpiled to React createElement syntax. We will try to build our own plugin that will do the same thing. 

**NOTE**: I will ignore the props and attributes part of the jsx in the custom plugin. 

##Custom-jsx-plugin

Clear the .babelrc file.

Create a new file custom-jsx-plugin.js

Try below code in (https://astexplorer.net/) to get an overview on how jsx code looks like in AST

```javascript
function anything() {
  return <div><p>Hello World</p></div>
}
```
and as we can see on the right side. The jsx part has a node type of `JSXElement`. This is what we need to manipulate and replace it with a `CallExpression` as `React.createElement` is actually a javascript function.

When you try to parse this jsx using your local babel cli, you will get a syntax error. Because the parser does not know anything about the jsx syntax. 
Thats why we need to add a file which manipulates the parser, name it as jsx-syntax-parser.js

jsx-syntax-parser.js

```javascript
module.exports = function () {
  return {
    manipulateOptions: function manipulateOptions(opts, parserOpts) {
      parserOpts.plugins.push("jsx");
    }
  };
};
```
and now our new .babelrc file will look like

```javascript
{
  "plugins": ["./custom-jsx-plugin", "./jsx-syntax-parser"]
}
```

The order of plugins matter and its actually in the reverse order. Right to Left. First our syntax parser will be executed which tells babel that it needs to parse jsx syntax also and then it will execute our custom plugin file which is for now empty.

As we still have not written anything inside our `custom-jsx-plugin` file. The output of babel transpilation will be same as the index file. Nothing should have been changed. 

Add this to `custom-jsx-plugin` file

```javascript
module.exports = function (babel) {
  var t = babel.types;
  return {
    name: "custom-jsx-plugin",
    visitor: {
      JSXElement(path) {
        //get the opening element from jsxElement node
        var openingElement = path.node.openingElement;  
         //tagname is name of tag like div, p etc
        var tagName = openingElement.name.name;
        // arguments for React.createElement function
        var args = []; 
        //adds "div" or any tag as a string as one of the argument
        args.push(t.stringLiteral(tagName)); 
        // as we are considering props as null for now
        var attribs = t.nullLiteral(); 
        //push props or other attributes which is null for now
        args.push(attribs); 
        // order in AST Top to bottom -> (CallExpression => MemberExpression => Identifiers)
        // below are the steps to create a callExpression
        var reactIdentifier = t.identifier("React"); //object
        var createElementIdentifier = t.identifier("createElement"); //property of object
        var callee = t.memberExpression(reactIdentifier, createElementIdentifier)
        var callExpression = t.callExpression(callee, args);
         //now add children as a third argument
        callExpression.arguments = callExpression.arguments.concat(path.node.children);
        // replace jsxElement node with the call expression node made above
        path.replaceWith(callExpression, path.node); 
      },
    },
  };
};
```

And thats it. These 12 lines of code can easily transpile our jsx code.
Run this command again on the terminal
`node ./node_modules/babel-cli/bin/babel index.js`

and notice that the result is the same as created by react-preset

like this 
`ReactDOM.render(React.createElement("div", null, React.createElement("p", null, Hello World)), document.getElementById("root"));`

###Explanation of the code

1. In the visitor pattern of babel, during traversal of 
   the AST, for every `JSXElement` node, this callback 
   function as defined above will be executed.
2. This node has two parts opening and closing elements. The 
   name of opening element (eg "div") is extracted to be used 
   as first argument of the function (React.createElement)
3. The second argument (props or attributes) is considered as 
   null for this example. Ignoring props just for simplicity.
4. Now to create a function call, we will need to create 3 
   things CallExpression => MemberExpression => Identifiers. 
   The 2 identifiers used here are obviously `React` as an 
   object and `createElement` as the property.
5. Then we need to concat the rest arguments which are the 
   child nodes of current node. 
6. At last we need to replace(used inbuilt function of path) 
   the current `JSXElement` node with the `callExpression` 
   node you have created. This modifies the AST.


##Conclusion

This is obviously not a production ready code. I have taken help from the source code of [babel-preset-react](https://github.com/babel/babel/tree/master/packages/babel-preset-react) and just to make things simpler I made the code shorter for better understanding. It is just the basic overview of how does this plugin works under the hood.