Intro to Babel and how to use it

Published on

Babel introduction

Babel is a transcompiler that takes your JS and converts it to work with different versions of JS.

Table of Contents

(At least, that is what its core feature is - you can set up Babel to do many things)

For example, you might want to write with arrow functions:

arrow-fn.js
const double = a => a * 2

And babel would conver that to something like:

output.js
"use strict";

var double = function double(a) {
  return a * 2;
};

That might not be a great example - I had to fudge the config to get it to output that, as arrow functions and const are so well supported. But you get the idea. It takes your nice clean JS and outputs something that other brwosers can use.

How it works

There are three main parts to understanding how babel works, the parser, transformer and generator.

Parser

It first converts your JS into a new data structure called an abstract syntax tree.

This is handled by babel-parser.

Taking the original arrow function code above, that would look like this when represented as a AST by babel. I've cut out a lot of it, but you can see it on https://astexplorer.net/ (select the babel-parser option)

arrow-fn-ast.json
{
  "type": "File",
  "start": 0,
  "end": 26,
  "loc": { ... },
  "errors": [],
  "program": {
    "type": "Program",
    "start": 0,
    "end": 26,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 2,
        "column": 0
      }
    },
    "sourceType": "module",
    "body": [...],
  },
}

It basically parses the JS, and converts it to a structure that is easier to use by other scripts. (You need to just look at the start and end properties to see where parts of the code start and end).

Transform

Then Babel passes the AST over to transformers.

The transformers will often use babel-traverse to traverse and update nodes.

example.js
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";

const code = `function square(n) {
  return n * n;
}`;

const ast = parser.parse(code);

traverse(ast, {
  enter(path) {
    if (path.isIdentifier({ name: "n" })) {
      path.node.name = "x";
    }
  },
});

Code generation

After all your transforms have run (there can be multiple transformers that go through the AST and update it multple times) it is time to put the code back together and output some JS.

Take a look at @babel/generator.

import { parse } from "@babel/parser";
import generate from "@babel/generator";

const code = "class Example {}";
const ast = parse(code);

const output = generate(
  ast,
  {
    /* options */
  },
  code
);

JSX

A good example of where Babel is very useful is when dealing with JSX.

React apps often have components that look like this:

component.jsx

export function Comp() {
    return (<h1>A heading</h1>);
}

Of course that mix of HTML inside javscript is not valid javascript.

But you can use Babel to convert to AST, then update the AST to regular JS, and it can output something like:

component.js
export function Comp() {
   React.createElement('hq', null, 'A heading');
}

If you are running a React app, you will probably use @babel/preset-react. But if you just want the JSX transformation then you can use @babel/plugin-transform-react-jsx (link)

Configuring Babel

You can configure Babel with either babel.config.json or .babelrc.json (or babel.config.js or .babelrc.js)

babel.config.json
{
  "presets": [...],
  "plugins": [...]
}
babel.config.js
module.exports = function (api) {
  api.cache(true);

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

  return {
    presets,
    plugins
  };
}

You can also configure babel in your package.json:

package.json
{
  "name": "my-package",
  "version": "1.0.0",
  "babel": {
    "presets": [ ... ],
    "plugins": [ ... ],
  }
}

Plugins

Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.

You tell babel to use a plugin with this syntax:

babel.config.json
{
  "plugins": ["@babel/plugin-transform-runtime"]
}

You can also use relative paths:

babel.config.json
{
  "plugins": ["./node_modules/asdf/plugin"]
}

Types of plugins

  • Transform plugins will apply transformations to your code.
  • Syntax plugins will tell babel how to parse the code. E.g. if you need to work with JSX, you will want to use a jsx syntax plugin so babel can parse the jsx syntax (and then pass it off to a transformer which converts jsx into valid js)

Ordering of plugins

  • Plugins run before Presets.
  • Plugin ordering is first to last.
  • Preset ordering is reversed (last to first).

So the following:

babel.config.json
{
  "plugins": ["transform-decorators-legacy", "transform-class-properties"]
}

will run transform-decorators-legacy then transform-class-properties.

Plugin options

You can pass options to each plugin, for example:

babel.config.json
{
  "plugins": [
    [
      "transform-async-to-module-method",
      {
        "module": "bluebird",
        "method": "coroutine"
      }
    ]
  ]
}

Creating your own babel plugin

Creating your own plugin is quite straightforward. I won't dig into it here, but this is an example of a simple plugin that should show some of the basic ideas.

babel-reverse-name.js
export default function() {
  return {
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        // reverse the name: JavaScript -> tpircSavaJ
        path.node.name = name
          .split("")
          .reverse()
          .join("");
      },
    },
  };
}

Presets

Babel presets can act as sharable set of Babel plugins and/or config options.

Common presets include:

  • @babel/preset-env for compiling ES2015+ syntax
  • @babel/preset-typescript for TypeScript
  • @babel/preset-react for React

You use a preset by passing it in to the presets config.

For example:

babel.config.json
{
  "presets": ["babel-preset-myPreset", "@babel/preset-env"]
}

You can also use relative paths:

babel.config.json
{
  "presets": ["./myProject/myPreset"]
}

Ordering of presets

Presets run in reverse.

In the following example, it will run @babel/preset-react first, then @babel/preset-env.

babel.config.json
{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Adding options to presets

babel.config.json
{
  "presets": [
    [
      "env",
      {
        "loose": true,
        "modules": false
      }
    ]
  ]
}