Finally started learning Babel

Babel official Chinese document

Babel official English document

What is Babel?

Babel is a toolchain for converting ECMAScript 2015+ version code into backwardly compatible JavaScript syntax so it can run in current and older versions of browsers or other environments. Here’s a list of things Babel can do for you:

  1. The syntax conversion
  2. Polyfill missing features in the target environment (via @babel/ Polyfill module)
  3. Source code conversion (Codemods)

Still not sure what a compiler is?

It is highly recommended to find out through the the-super-tiny-Compiler project.

The working process of a compiler

Parse (lexical + grammar) => transform => generate

In the case of the -super-tin-Compiler project, the (Add 2 (Subtract 4 2)) code needs to be processed to create Add (2, subtract(4, 2)); The code.

  1. Lexical Analysis

Parsing is generally divided into two stages: Lexical Analysis and Syntactic Analysis.

Lexical analysis takes raw code and splits it into things called Tokens. This is done in a Tokenizer or Lexer

The Token is an array made up of fragments of code statements. They can be numbers, labels, punctuation marks, operators, or anything else. Subtract 4 2) After a lexical analyzer, the Token is found as follows:

[{"type": "paren"."value": "(" },
  { "type": "name"."value": "add"},
  { "type": "number"."value": "2"},
  { "type": "paren"."value": "("},
  { "type": "name"."value": "subtract"},
  { "type": "number"."value": "4"},
  { "type": "number"."value": "2"},
  { "type": "paren"."value": ")"},
  { "type": "paren"."value": ")"}]Copy the code
  1. Syntactic Analysis

Parsing takes previously generated tokens and converts them into an abstract representation that describes each fragment of a code statement and the relationship between them. This is called intermediate representation or Abstract Syntax Tree (AST for short)

Websites to assist in development:

astexplorer

ast visualizer

An abstract syntax tree is a deeply nested object that represents the code itself in a more manageable way and gives us more information. The Token after lexical analysis is processed by the parser and the AST is as follows:

{
  "type": "Program"."body": [{"type": "CallExpression"."name": "add"."params": [{"type": "NumberLiteral"."value": "2"
        },
        {
          "type": "CallExpression"."name": "subtract"."params": [{"type": "NumberLiteral"."value": "4"
            },
            {
              "type": "NumberLiteral"."value": "2"}]."_context": [{"type": "NumberLiteral"."value": "4"
            },
            {
              "type": "NumberLiteral"."value": "2"}}]]."_context": [{"type": "NumberLiteral"."value": "2"
        },
        {
          "type": "CallExpression"."callee": {
            "type": "Identifier"."name": "subtract"
          },
          "arguments": [{"type": "NumberLiteral"."value": "4"
            },
            {
              "type": "NumberLiteral"."value": "2"}]}],"_context": [{"type": "ExpressionStatement"."expression": {
        "type": "CallExpression"."callee": {
          "type": "Identifier"."name": "add"
        },
        "arguments": [{"type": "NumberLiteral"."value": "2"
          },
          {
            "type": "CallExpression"."callee": {
              "type": "Identifier"."name": "subtract"
            },
            "arguments": [{"type": "NumberLiteral"."value": "4"
              },
              {
                "type": "NumberLiteral"."value": "2"}]}]}}Copy the code
  1. Transformation

These nodes can be added, moved, or replaced when transforming an AST, or a new AST can be generated from an existing AST

Since we need to convert the input code into a new language in our example, we will focus on generating a brand new AST for the new language, see the-super-tiny-Compiler.

{
  "type": "Program"."body": [{"type": "ExpressionStatement"."expression": {
        "type": "CallExpression"."callee": {
          "type": "Identifier"."name": "add"
        },
        "arguments": [{"type": "NumberLiteral"."value": "2"
          },
          {
            "type": "CallExpression"."callee": {
              "type": "Identifier"."name": "subtract"
            },
            "arguments": [{"type": "NumberLiteral"."value": "4"
              },
              {
                "type": "NumberLiteral"."value": "2"}]}]}}Copy the code
  1. Code generation

The last stage of the compiler is code generation, which sometimes overlaps with transformation, but the main part of code generation is to output code according to the AST.

add(2, subtract(4.2));
Copy the code

Babel in the package

We’ve shown you how a compiler works. Babel works in much the same way

@babel/babel-parser is responsible for parsing into AST from Babel.

var result = babel.transformSync("code();", options);
result.code;
result.map;
result.ast;
Copy the code

@babel/babel-traverse are the packets that are responsible for the transitions in Babel

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"; }}});Copy the code

@babel/ Generator is the package in Babel that is responsible for generating new code from the AST

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

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

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

Configure the Babel

The official documentation

In fact, just using the package above, so far by running Babel itself, you haven’t “translated” the code, you’ve just copied it from one place to another.

That’s because we haven’t told Babel what to do yet.

Because Babel is a general-purpose compiler that can be used in a variety of ways, it does nothing by default. You have to tell Babel exactly what to do. If you want Babel to do any real work, you need to add plug-ins to it.

There are several ways to configure Babel

  1. Create.babelrc in the root directory of the project
{
  "presets": [...]. ."plugins": [...]. }Copy the code
  1. Create babel.config.json in the root directory of the project
{
  "presets": [...]. ."plugins": [...]. }Copy the code
  1. You can also write babel.config.json and.babelrc.json files in JavaScript:
constpresets = [ ... ] ;constplugins = [ ... ] ;module.exports = { presets, plugins };
Copy the code

You can also call any of Node.js’s apis, such as dynamic configuration based on the process environment:

constpresets = [ ... ] ;constplugins = [ ... ] ;if (process.env["ENV"= = ="prod") { plugins.push(...) ; }module.exports = { presets, plugins };
Copy the code

Babel plug-in

There are many official plug-ins, which can be divided into the following categories

  1. The transformation of ES new features
  2. Modular transformation
  3. Function in the experiment
  4. Plug-ins that reduce the size of your code
  5. React plugin
  6. other

To get a feel for Babel, take the @babel/plugin-transform-arrow-functions plug-in as an example. The plugin-transform-arrow-functions plugin converts our ES syntax arrow functions to normal function form.

Using the CLI

  1. Initialize the project and create a new SRC directory
npm init -y
Copy the code
  1. Install @babel/core, @babel/cli

Since we now need to experience Babel programmatically and want to be able to compile our code by typing Babel on the command line, we need to install the @babel/core and @babel/ CLI packages

cnpm install -D @babel/core @babel/cli
Copy the code

The @babel/core package actually provides parsing > transform > generation. As can be seen from the package.json package, it contains @babel/ Parser, @babel/traverse and @babel/ Generator

"dependencies": {
    "@babel/code-frame": "The workspace: ^ 7.12.13"."@babel/generator": "The workspace: ^ 7.13.0"./ / generated
    "@babel/helper-compilation-targets": "The workspace: ^ 7.13.0"."@babel/helper-module-transforms": "The workspace: ^ 7.13.0"."@babel/helpers": "The workspace: ^ 7.13.0"."@babel/parser": "The workspace: ^ 7.13.0"./ / parsing
    "@babel/template": "The workspace: ^ 7.12.13"."@babel/traverse": "The workspace: ^ 7.13.0"./ / conversion
    "@babel/types": "The workspace: ^ 7.13.0"."convert-source-map": "^ 1.7.0"."debug": "^ 4.1.0." "."escape-string-regexp": "condition:BABEL_8_BREAKING ? ^ 4.0.0:"."gensync": 2 "" ^ 1.0.0 - beta.."json5": "^ 2.1.2"."lodash": "^ 4.17.19"."semver": "condition:BABEL_8_BREAKING ? ^ sections 7.3.4:7.0.0."."source-map": "^ 0.5.0"
  },
Copy the code
  1. Install @babel/plugin-transform-arrow-functions plugin
cnpm install --save-dev @babel/plugin-transform-arrow-functions
Copy the code
  1. Add Babel compiled command to pakage.json file (you can also add command line input)
"scripts": {
    "build:babel": "babel src/arrow-functions.js -d dist --plugins @babel/plugin-transform-arrow-functions"."test": "echo \"Error: no test specified\" && exit 1"
  },
Copy the code
  1. Create a new arrow-functions.js file in the SRC directory and write the code
const func = () = > {
   console.log('Hello Babel'); 
}
Copy the code
  1. Run the NPM run build: Babel command on the terminal to view the compiled result in the dsit directory
const func = function () {
  console.log('Hello Babel');
};
Copy the code

As you can see, our arrow function syntax is translated by Babel

Using babel.config.json

In addition to entering the plug-in configuration from the command line, you can also configure Babel by writing configuration files as follows

  1. Create the babel.config.json file in the project root directory

This time, we added a plugin to babel.config.json :plugin-transform-function-name

{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"."@babel/plugin-transform-function-name"]}Copy the code
  1. Installing a plug-in
cnpm install --save-dev @babel/plugin-transform-function-name
Copy the code
  1. Add the build:babel-config command to package.json
"scripts": {
    "build:babel": "babel src/arrow-functions.js -d dist --plugins @babel/plugin-transform-arrow-functions"."build:babel-config": "babel src/arrow-functions.js -d dist"."test": "echo \"Error: no test specified\" && exit 1"
},
Copy the code
  1. Run NPM run build:babel-config and view the results
const func = function func() {
  console.log('Hello Babel');
};
Copy the code

We can see that not only has the arrow function been converted, but the transform-function-name plug-in has also been converted to the function name.

Babel preset (Presets)

As you can see from the plugin example, Babel’s design idea is that we need to explicitly tell him that he needs to do A, B, and C before he does it. If preset is a hassle for you, preset can be used as a combination of Babel plugins and even as a shared options configuration.

The official Preset

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

Besides, you can write a preset yourself

There is a difference in the running order between plugin and preset. Preset is run in reverse order, plugin is run in sequence, and the priority of plugin is higher than preset.

babel-polyfill

The Babel plug-in compiles almost all new JavaScript syntax, but not for APIs. For example, the following code to compile contains the arrow function:

function addAll() {
  return Array.from(arguments).reduce((a, b) = > a + b);
}
Copy the code

When compiled, it looks like this:

function addAll() {
  return Array.from(arguments).reduce(function(a, b) {
    return a + b;
  });
}
Copy the code

Although the syntax of the arrow function is translated by Babel, the array. from API is not translated and the reality is that not all browsers support the array. from API

Uncaught TypeError: Array.from is not a function
Copy the code

To solve this problem, we use a technique called polyfill(code supplement, also translated as shim). Polyfill in Babel is essentially a combination of core-JS and Facebook’s Regenerator. Its purpose is to simulate a complete ES6 runtime environment, so it polyfills promises, maps, and other types in the form of global variables. Prototype objects are also contaminated with array.prototype.includes ().

It should be noted that since Babel 7.4.0 @babel-polyfill has been deprecated and removed in later versions, It is recommended that core-js/stable and regenerator-Runtime/Runtime be introduced directly and used in tandem with @babel/preset-env

Compiled, we found that the automatic introduces the core – js/modules/es. Array. From the js core – js will help us to low version of the browser compatibility

. require("core-js/modules/es.array.from.js"); . function addAll() { return Array.from(arguments).reduce(function (a, b) { return a + b; }); }Copy the code

Be aware of the difference between plugins and polyfills. Plugins can convert advanced JavaScript syntax into backward compatible syntax by reinforcing the conversion steps in the compiler. Polyfill is a syntactic supplement to APIs like arra. from.

Babel – the runtime and plugin – transform – runtime

Babel-runtime is a function called regenerator-Runtime that contains helpers for modular Babel operations. .

Babel-runtime is usually used with plugin-transform-Runtime.

Suppose you have code like this:

class Circle {}
Copy the code

Be converted into

function _classCallCheck(instance, Constructor) {
  / /...
}

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};
Copy the code

When we have multiple files that use this syntax, it means that we repeatedly declare the _classCallCheck function in each file.

Using @babel/ plugin-transform-Runtime will import _classCallCheck from @babel/ Runtime to avoid re-importing (similar to extracting public methods).

var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};
Copy the code

polyfill vs babel-runtime?

If you are careful, you may notice that both Ployfill and babel-Runtime use corejs and regenerator-Runtime. What’s the difference?

If you import core-js or @babel/ Polyfill directly and the built-in components it provides, such as Promise, Set, and Map, these will pollute the global scope. While this is fine for applications or command-line tools, it can be a problem if your code is a library that you plan to release for others to use, or if you don’t have complete control over the environment in which your code runs.

Therefore, babel-Polyfill is officially recommended to be used in independent business development, even if global and prototype contamination is not a big impact; Babel-runtime is suitable for third-party library development without global contamination.

reference

Babel’s official website

A suggested compiler

Babel User manual

core-js@3, babel and a look into the future

Babel went from starter to runner