TopicsIntroduction to Programming LanguagesIntroduction to Babel

Introduction to Babel

Introduction

Babel is a JavaScript transpiler that converts not only ECMAScript 2015+ but also JS extensions like JSX, Flow, TypeScript, and other proposals into JavaScript code that can run in any browser or JavaScript environment.

(Not so) Simple Example: Finding non declared variables

Clone the repo ULL-ESIT-PL/babel-learning.

The problem

We want to write a Babel plugin that checks for non declared variables in a JavaScript program.

Babel plugins are JavaScript functions that take an Abstract Syntax Tree (AST) as input and return a modified AST. Therefore we intervene in the “Transform AST” stage of the compiler pipeline.

You have an example in the directory src/scope/non-declared

➜  non-declared git:(main) ✗ pwd -P
/Users/casianorodriguezleon/campus-virtual/2324/learning/babel-learning/src/scope/non-declared

Consider an input.js like this:

/src/scope/non-declared/input.js
let n = 4;
let f = m => m + n; // m is declared
m = 9;              // m is not declared
n = n * m;

where n is declared but the m at lines 3 and 4 is not declared.

The transpiling with our plugin should produce an output like this:

➜  non-declared git:(main) npx babel input.js 
 
Searching for variable "m"
m at 2 is declared.
m at 3 is not declared.
m at 4 is not declared.

This is achieved with the plugin nondeclared.mjs

➜ non-declared git:(main) ✗ cat nondeclared.mjs

/src/scope/non-declared/nondeclared.mjs
export default function () {
  return {
    visitor: {
      Program: {
        enter(path, state) {
          let varName = state.opts.varName;
          console.log(`Searching for variable "${varName}"`);
          state.nonDeclared = new Map();
          state.Declared = new Map();
        },
        exit(path, state) {
          state.Declared.forEach((value, key) => { console.log(key, value); });
          state.nonDeclared.forEach((value, key) => { console.log(key, value); });
          process.exit(0);
        }
      },
      Identifier(path, state) {
        let varName = state.opts.varName;
        let node = path.node;
        if (node.name !== varName) { return; }
        if (!path.scope.hasBinding(varName)) {
          state.nonDeclared.set(`${varName} at ${node.loc.start.line}`, `is not declared.`)
          return
        }
        state.Declared.set(`${varName} at ${node.loc.start.line}`, `is declared.`);
        return;
      },
    }
  };
}

State as a way to set context for the visit

Babel plugins can have functions that are run before or after plugins. They can be used for setup or cleanup/analysis purposes. See this tutorial. In this example:

  1. In the enter method of Program, we initialize some state.
  2. We have other visitors (like Identifier) that perform operations and potentially modify the state.
  3. The exit method of Program is called after all nodes have been visited and processed.

Configuring Babel and passing options to the plugin

The value of the variable state.opts.varName is set as an option to the plugin via the configuration file src/scope/non-declared/babel.config.js

➜ non-declared git:(main) ✗ cat babel.config.js

/src/scope/non-declared/babel.config.js
module.exports = function(api) {
  api.cache(true); // You are instructing Babel to cache the computed configuration and 
                   // reuse it on subsequent builds. 
  const defaultEnv = {
    plugins: [
      ["./nondeclared.mjs", { "varName": "m" }]
    ]
  };
 
  const customEnv = {
    plugins: [
      ["./nondeclared.mjs", { "varName": "n" }]
    ]
  };
 
  return {
    env: {
      development: defaultEnv,
      custom: customEnv
    }
  };
};

and specified later in the command line via the --env-name option:

➜  non-declared git:(main) ✗   non-declared git:(main) npx babel input.js  --env-name custom 
Searching for variable "n"
n at 1 is declared.
n at 2 is declared.
n at 4 is declared.

You can also use the BABEL_ENVor NODE_ENV environment variables to specify the environment:

➜  non-declared git:(main) BABEL_ENV=custom npx babel input.js
Searching for variable "n"
n at 1 is declared.
n at 2 is declared.
n at 4 is declared.

References