What’s a Babel?

The official website

Babel is a toolchain that primarily converts ECMAScript 2015+ version code into backwardly compatible JavaScript syntax to run in current and older browsers or other environments.

The syntax conversion

As a simple example, and this is an official example, let’s implement the conversion locally:

// Babel input: ES2015 arrow function
[1.2.3].map((n) = > n + 1);

// Babel output: the same functionality as the ES5 syntax
[1.2.3].map(function(n) {
  return n + 1;
});
Copy the code
Install the command line integration tool for the Babel core module and some preset configurations
$ npm install -D @babel/core @babel/cli @babel/preset-env
Copy the code

Write the configuration file babel.config.json (v7.8.0 or later required) or.babelrc

{
	"presets": [
        "@babel/preset-env"]}Copy the code

Then create a SRC directory and edit a test.js file:

[1.2.3].map((n) = > n + 1);
Copy the code

Run the following command to compile code from SRC to lib:

$ ./node_modules/.bin/babel src --out-dir lib
Copy the code

Output a js file with the same name:

"use strict";

[1.2.3].map(function (n) {
  return n + 1;
});
Copy the code

@babel/preset-env

@babel/preset-env is an intelligent preset environment that allows you to use the latest JavaScript regardless of which syntax the target environment is compatible with. In general, this default environment includes our usual ES2015 + syntax, allowing us to use new syntax such as let, const, arrow functions, and so on, but not stage-x plugins.

$ npm i -D @babel/preset-env
Copy the code

Configuration items (just a few common ones, the rest can be viewed in the official documentation) :

  • Targets Indicates the list of supported environments
  • useBuiltIns
  • Modules Module type
  • include
  • exclude

@babel/polyfill

By default, Babel only converts JS syntax (such as destruct assignment, arrow functions, etc.), not apis (Promise, Maps, Proxy, etc.), and Polyfill was built to support these apis, providing a shim for the current environment.

As of Babel 7.4.0, this package is no longer recommended, It is recommended to include core-js/stable (for emulating ECMAScript functionality) and regenerator-Runtime/Runtime (with translated generator functions required) directly

// Whether to import depends on the situation
import "core-js/stable";
import "regenerator-runtime/runtime";
Copy the code

The @babel/ Polyfill module contains core-JS and a custom ReGenerator Runtime to simulate a full ES2015+ environment.

This means you can use new built-in components like Promise and WeakMap, static methods like Array.from or Object.assign, and array.prototype.includes And generator functions (if you use the ReGenerator plug-in). To add these capabilities, polyfills are added to global scopes and native prototypes like Strings.

This may be too much for authors of software libraries/tools. If you don’t need instance methods like array.prototype. includes, you can use the Transform Runtime plugin instead of @babel/ Polyfill polluting the global scope.

For example, if polyfill is not added, the following situation will occur when packaging code with promises, and promises will not actually work under certain circumstances without any changes:

/ / before the conversion
const p = new Promise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(1)},1000)
})

p.then(data= > {
    console.log(data)
})

/ / after the transformation
var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
});
p.then(function (data) {
  console.log(data);
});
Copy the code

To solve this problem we use polyfill:

# old usage, no longer recommended
$ npm install -S @babel/polyfill
# new USES
$ npm install -S core-js
Copy the code

If @babel/polyfill is used, then we need to set useBuiltIns to Usage on the env. This will load the polyfill you need.

{
  "presets": [["@babel/env",
      {
        "targets": {
          "edge": "17"."firefox": "60"."chrome": "67"."safari": "11.1"."ie": "11"
        },
        "useBuiltIns": "usage"}}]]Copy the code

The repackaged file looks like this:

"use strict";

require("core-js/modules/es6.object.to-string.js");

require("core-js/modules/es6.promise.js");

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
});
p.then(function (data) {
  console.log(data);
});
Copy the code

If we remove the useBuiltIns attribute when configuring.babelrc, which is false by default, then we need to import polyfill in our code:

import '@babel/polyfill'

const p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
      resolve(1)},1000)
})

p.then(data= > {
  console.log(data)
})
Copy the code

The packaged files are as follows:

"use strict";

require("@babel/polyfill");

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
});
p.then(function (data) {
  console.log(data);
});
Copy the code

If we configure the useBuiltIns property as entry when we configure.babelrc, the imported Polyfill will be converted into the Polyfill module our environment needs, regardless of whether the code uses it or not, in conjunction with the target configuration.

The configuration file and the original file are the same as in the previous example, packaged as follows:

"use strict";

require("core-js/modules/es7.array.flat-map.js");

/ /... More than a hundred lines are omitted

require("core-js/modules/web.dom.iterable.js");

const p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(1);
  }, 1000);
});
p.then(data= > {
  console.log(data);
});
Copy the code

If core-js is used, set useBuiltIns to Entry and add “corejs”: “3”, change the original file to:

import "core-js/stable";
import "regenerator-runtime/runtime";

const p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
      resolve(1)},1000)
})

p.then(data= > {
  console.log(data)
})
Copy the code

The packaged file is:

"use strict";

require("core-js/modules/es.symbol.description.js");

/ /... Hundreds of lines are omitted

require("core-js/modules/web.url-search-params.js");

const p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(1);
  }, 1000);
});
p.then(data= > {
  console.log(data);
});
Copy the code

If useBuiltIns is set to Usage and import is removed, the packaged file looks like this:

"use strict";

require("core-js/modules/es.promise.js");

require("core-js/modules/es.object.to-string.js");

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
});
p.then(function (data) {
  console.log(data);
});
Copy the code

Plugin

Babel is a compiler (input source => output compiled code). Just like any other compiler, the compilation process is divided into three stages: parsing, conversion, and printout.

Right now, Babel doesn’t do anything out of the box. It’s basically like const Babel = code => code; , parses the code and prints the same code. If you want Babel to do any real work, you need to add plug-ins to it.

In addition to adding plug-ins one by one, preset is also available, which helps you preset a set of plug-ins.

@babel/plugin-transform-runtime

This is a popular plugin for Babel that helps reduce the size of packaged files. Here’s an example:

The original js file is as follows:

const p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
      resolve(1)},1000)}) (async function() {
  console.log(awaitp); }) ()Copy the code

The package generation file without the plug-in is as follows:

"use strict";

require("regenerator-runtime/runtime.js");

require("core-js/modules/es.promise.js");

require("core-js/modules/es.object.to-string.js");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { /* Omit a long piece of code */ }

function _asyncToGenerator(fn) { return function () { /* Omit a long piece of code */ }

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000); }) (/*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
  return regeneratorRuntime.wrap(function _callee$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.t0 = console;
          _context.next = 3;
          return p;

        case 3:
          _context.t1 = _context.sent;

          _context.t0.log.call(_context.t0, _context.t1);

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _callee);
})))();
Copy the code

_asyncToGenerator is defined and used in the current file, and is defined in this way if await is also used in other files, resulting in repetition and wasted space.

Modify the configuration file:

{
    "presets": [["@babel/env",
        {
          "targets": {
            "edge": "17"."firefox": "60"."chrome": "67"."safari": "11.1"."ie": "11"
          },
          "useBuiltIns": "usage"."corejs": "3"}]],"plugins": [
        "@babel/plugin-transform-runtime"]}Copy the code

Install plug-in:

$ npm i @babel/plugin-transform-runtime -S
Copy the code

Repackage and generate the following file:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

require("core-js/modules/es.promise.js");

require("core-js/modules/es.object.to-string.js");

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1);
  }, 1000); }) (/*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
  return _regenerator.default.wrap(function _callee$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.t0 = console;
          _context.next = 3;
          return p;

        case 3:
          _context.t1 = _context.sent;

          _context.t0.log.call(_context.t0, _context.t1);

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _callee);
})))();
Copy the code

You can see that the long section of code defining _asyncToGenerator becomes require a module.

The plugin development

The Babel plug-in modifies the code by modifying the AST (Abstract Syntax Tree). AST represents the code in a Tree structure, and the program traverses the Tree. During the traversal process, if it encounters a node matching the plug-in, the logic in the plug-in is executed to modify or replace the node. The AST Explorer allows you to view the AST of a piece of code. More details can be found in babel-handbook. Here is just a simple example of reversing variable names.

Write a simple code to convert:

const abc = '123';
const xyz = '234';
Copy the code

Then write a simple plugin that reverses the variable names and places them in index.js in the babel-plugin directory:

module.exports = function () {
  return {
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        // reverse the name: JavaScript -> tpircSavaJ
        path.node.name = name
          .split("")
          .reverse()
          .join(""); ,}}}; }Copy the code

Plugins used in the.babelrc configuration file (in a formal environment, plug-ins should be packaged separately and published to NPM for reference) :

{
	"presets": [["@babel/env",
            {
                "targets": {
                  "edge": "17"."firefox": "60"."chrome": "67"."safari": "11.1"."ie": "11"
                },
                "useBuiltIns": "usage"."corejs": 3}]],"plugins": [
        "./babel-plugin/index"]}Copy the code

Finally, run the./node_modules/. Bin/Babel Babel –out-dir lib command. The output file is as follows:

"use strict";

var cba = '123';
var zyx = '234';
Copy the code