preface

It was hard to study Babel’s official documentation, probably because English was so poor that year, but fortunately a lot of great people translated the documentation. Also saw a lot of predecessors’ articles, benefit a lot. But the paper came to light, eyes always deceiving me. When I thought I understood, I closed my eyes and asked myself, “What did I know?” That’s when I realized everything was what I thought. So I reorganized my ideas and wrote them down for easy review.

What’s a Babel?

Babel is a JavaScript compiler

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.

What can Babel do?

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

In layman’s terms, Babel simply transfers syntaxes introduced by the new standard, such as arrow functions in ES6, deconstruction, and so on. Methods and functions added to the new standard need to be solved by Polyfill adding missing features (methods and functions added to the new standard, etc.) to the target environment.

The Babel compilation process is divided into three stages:

  • Parsing: Parsing code strings into abstract syntax trees.
  • Transform: Transforms the abstract syntax tree.
  • Output: Regenerates code strings from the transformed abstract syntax tree.

This article focuses on the three phases of transformation, most of which is taken from the Babel website.

Babel works out of the box, but if nothing is configured, it parses the code and outputs the same code. So we need to configure Plugins and Preset to transform our code.

To prepare

To get a better idea of how Babel “works,” here’s how to prepare.

  • Create a demo

    Create folder babel-test and go to this directory. Const fn = () => 1; const fn = () => 1;

  • Install the necessary dependencies

    @babel/core is the core of Babel and contains all the core apis. Babel /cli is a command line tool that provides us with the Babel command to compile files.

    # Current version of the article installed:
    # @ Babel/core: ^ 7.9.0
    # @ Babel/cli: ^ 7.8.4
    npm install --save-dev @babel/core @babel/cli
    Copy the code
  • Adding a compile command

    Add an entry under the script field in package.json:

    {..."scripts": {
        "trans": "babel src --out-dir lib"}... }Copy the code

    The command line converts every JavaScript file in the babel-test/ SRC directory to the babel-test/lib directory.

The current package.json file content is as follows:

npm run trans
lib/index.js
src/index.js
Babel

The plug-in

Plug-ins are used to transform our code and come in two types: syntax plug-ins and transformation plug-ins.

  • Grammar plug-in

    Babel is only allowed to parse (not transform) certain types of syntax.

  • The transformation plug-in

    The transformation plug-in will enable the corresponding syntax plug-in, so we do not have to specify both.

    When we enable the transformation plug-in, the corresponding syntax plug-in is automatically enabled for parsing, and then the transformation is performed through the transformation plug-in. To learn more about the conversion plug-ins available, see here: Plug-ins.

  • Use of plug-ins

    If the plug-in is installed in the project root directory via NPM, we can enter the name of the plug-in and Babel will automatically check to see if it is already installed in node_modules.

    To convert SRC /index.js arrow functions to normal functions, we can use @babel/plugin-transform-arrow-functions.

    npm isntall --save-dev @babel/plugin-transform-arrow-functions
    Copy the code

    Create a new bebal-test/.babelrc file and add the plugin installed above.

    {
      "plugins": ["@babel/plugin-transform-arrow-functions"]}Copy the code

    Once the configuration is complete, run the NPM run trans command again and we can see that the lib/index.js file is what we want.

    Of course, in real development, it would be troublesome if we configured the transformation plug-ins one by one. Fortunately, Babel provides us with Preset.

The preset

Preset can be used as a combination of the Babel plug-in.

There are several Preset versions for our common environments:

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

Create a Preset

To create preset, export a configuration.

module.exports = function() {
  return {
    plugins: [
      "pluginA"."pluginB"."pluginC"]}; }Copy the code

Preset can contain other preset, as well as plug-ins with parameters.

module.exports = () => ({
  presets: [
    require("@babel/preset-env"),
  ],
  plugins: [
    [require("@babel/plugin-proposal-class-properties"), { loose: true }],
    require("@babel/plugin-proposal-object-rest-spread")]});Copy the code

@ Babel/preset – env is introduced

@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller! (website)

@babel/preset-env is a flexible preset that allows you to use the latest JavaScript without having to manage the syntactic conversions required by the target environment or the browser polyfills, and also makes JavaScript packed files smaller.

So what’s the use of @babel/preset-env?

  • Convert the new syntax introduced by JavaScript to ES5 syntax.
  • Load browser polyfills.

Note that @babel/preset-env does not support stage-x.

The installation

# @ Babel/preset - env: ^ 7.9.0
npm install --save-dev @babel/preset-env
Copy the code

Modify. Babelrc file contents:

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

Chestnut 🌰

Modify the contents of SRC /index.js as follows:

Array.from('foo'); / / /'f'.'o'.'o'] Array.from([1, 2, 3], x => x + x); / / (2, 4, 6]let promsie = new Promise();
Copy the code

NPM run trans, lib/index.js:

"use strict";

Array.from('foo'); / / /'f'.'o'.'o']

Array.from([1, 2, 3], function (x) {
  returnx + x; }); / / (2, 4, 6]let promsie = new Promise();
Copy the code

By comparing the above two pieces of code, we can see that only the original arrow function has been transformed. The array. from method and the Promise constructor are not converted.

Summary:

Because @babel/preset-env is a syntax conversion and doesn’t contain new global variables, methods, etc., it needs to load browser polyfills to perfect the code conversion. (Polyfill will improve the. Babelrc file after the introduction.)

Browserslist integration

@babel/preset-env takes our specified target environment, checks these mapping tables to compile a set of plug-ins and passes them to Babel. For browser-based or electron-based projects, the official recommendation is to use the.browserslistrc file to specify the target environment. If targets or ignoreBrowserslistConfig is not set, @babel/preset-env is used by default. Browserslistrc.

For example, when we only want to include polyfills and transcoding for browsers with a market share greater than 0.25% :

  • Options (more options configuration for @babel/preset-env)

    {
      "presets": [["@babel/preset-env",
          {
            "targets": "> 0.25%, not dead." "}}]]Copy the code
  • browserslist

    > 0.25%
    not dead
    Copy the code
  • package.json

    {
      "browserslist": "> 0.25%, not dead." "
    }
    Copy the code

Polyfill

Polyfill, which means shim in Chinese, is used to smooth out differences between target environments, making new built-in functions, instance methods, and so on available in older browsers.

Since Babel 7.4.0, @babel/ Polyfill has been deprecated and is not recommended. Supports direct import of Core-js /stable (polyfill ECMAScript) and regenerator-Runtime/Runtime (with converted generator functions required).

Here I will introduce the usage of Polyfill after Babel 7.4.0 according to my own understanding. If there is any misunderstanding, please kindly help me to point out.

Core-js and Regenerator-Runtime will emulate the full ES2015 + environment (excluding the phase 4 proposal) and be introduced before the JavaScript code executes.

This means you can use new built-ins like Promise or WeakMap, static methods like Array.from or Object.assign, instance methods like Array.prototype.includes, and generator functions (provided you use the regenerator plugin). The polyfill adds to the global scope as well as native prototypes like String in order to do this.

This means we can use: new built-in functions such as Promise and WeakMap; New static methods such as array. from and Object.assign; New instance methods such as array.prototype. includes and generator functions (provided the @babel/plugin-transform-regenerator plugin is used). Polyfill is added to the global scope as well as native stereotypes such as String to perform this operation.

Install core-js and Regenerator-Runtime.

# core - js: ^ 3.6.4 radar echoes captured. Provides new es features.
# regenerator: ^ 0.14.4; Support for generator and async functions in application code.
npm install --save core-js regenerator
Copy the code

Modify the SRC /index.js file after the installation is complete.

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

Array.from('foo'); / / /'f'.'o'.'o'] Array.from([1, 2, 3], x => x + x); / / (2, 4, 6]let promsie = new Promise();
Copy the code

After running the NPM run trans command, view the contents in lib/index.js.

"use strict";

require("core-js/stable");

require("regenerator-runtime/runtime");

Array.from('foo'); / / /'f'.'o'.'o']

Array.from([1, 2, 3], function (x) {
  return x + x;
}); // [2, 4, 6]

var promsie = new Promise();
Copy the code

At this point, our code works fine in earlier browsers.

After packaging with Webpack, the size of the package is 127KB. This is because we introduced all of the polyfills that made the compressed packages bulky, so we preferred to introduce them on demand, and fortunately Babel has provided us with a solution (webPack configuration files are posted at the end of this article).

@babel/preset-env provides a useBuiltIns parameter, and when set to Usage, only polyfills needed by the code will be included. When setting this parameter, corejs must be set at the same time. (no new features will be added to core-js@2, new features will be added to core-js@3)

To optimize the

Modify the configuration file. Babelrc.

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}]]}Copy the code

Modify the SRC/index. Js.

Array.from('foo'); / / /'f'.'o'.'o'] Array.from([1, 2, 3], x => x + x); / / (2, 4, 6]let promsie = new Promise();

async function fn() {
  return1}Copy the code

After running the NPM run trans command, view the contents in lib/index.js.

"use strict";

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

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

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

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

require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else{ Promise.resolve(value).then(_next, _throw); }}function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

Array.from('foo'); / / /'f'.'o'.'o']

Array.from([1, 2, 3], function (x) {
  return x + x;
}); // [2, 4, 6]

var promsie = new Promise();

function fn() {
  return _fn.apply(this, arguments);
}

function _fn() {
  _fn = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          caseZero:return _context.abrupt("return", 1);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _fn.apply(this, arguments);
}
Copy the code

From the output results, we can see that we have implemented the introduction of Polyfill on demand. After packaging again, we found that the size of the package has become 30KB, which is a significant effect, but this way of using the problem of global environment pollution.

To solve this problem, Babel gives us @babel-Runtime. It extracts the global built-in objects that developers rely on into separate modules and introduces them through module import to avoid pollution to the global environment.

@babel/runtime

Babel/Runtime is a library containing Babel Modular Runtime helpers and Regenerator-Runtime.

withPolyfillThe difference between:

  • Polyfill modifies (overrides) new built-in functions, static methods, and instance methods.

  • @babel/ Runtime doesn’t, it just introduces some helper functions and creates corresponding methods.

Install (@babel-Runtime is a dependency that the code needs to run, so it needs to be installed as a production dependency)

npm install --save @babel/runtime
Copy the code

Sometimes Babel might inject some of the same code across files into the output, so it might be reused.

Chestnut 🌰

Modify the contents of SRC /index.js:

class Parent  {}
Copy the code

NPM run trans, lib/index.js:

"use strict";

function _classCallCheck(instance, Constructor) { if(! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

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

This means that every file containing a class will introduce _classCallCheck, and repeated code injection will inevitably lead to larger packages. This is where the @babel/ plugin-transform-Runtime plugin is needed.

@babel/ plugin-transform-Runtime is used for code conversion during the build process, while @babel/ Runtime is a module that provides helper methods so that duplicate code injection can be avoided.

@babel/plugin-transform-runtime

@babel/ plugin-transform-Runtime is a plug-in that can reuse Babel injected helper to save code size.

Babel uses very small helpers to perform functions such as Class. By default, it will be added to every file that needs it. Sometimes duplication is unnecessary, especially when our application is distributed across multiple files. This is where the @babel/ plugin-transform-Runtime plug-in comes from: all helper programs reference this module, @babel/ Runtime to avoid duplication in compiled output. The runtime will be compiled into our build. Another purpose of this converter is to create a sandbox environment for our code. If core-js or @babel/ Polyfill is introduced directly, it provides built-in plug-ins like Promise, Set, and Map that pollute the global environment. While this may be fine for applications or command-line tools, it can be a problem if our code is a library to be distributed for others to use, or if we don’t have complete control over the environment in which our code runs. @babel/ plugin-transform-Runtime will use these built-in aliases as core-js aliases, so we can use them seamlessly without polyfills. (website)

The installation

npm install --save-dev @babel/plugin-transform-runtime
Copy the code

Avoid reinjection

Modify. Babelrc file contents:

{
  "presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3}]],"plugins": [
    "@babel/plugin-transform-runtime"]}Copy the code

Run NPM run trans:

"use strict";

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

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

var Parent = function Parent() {
  (0, _classCallCheck2["default"])(this, Parent);
};
Copy the code

As you can see from the output in lib/index.js, classCallCheck is not injected directly into the code, but is imported from @babel/ Runtime, which avoids repeated injection of the same code.

Avoid global pollution

You can add configurations to prevent global environment contamination.

Install @ Babel/runtime – corejs3

npm install --save @babel/runtime-corejs3
Copy the code

Modify the SRC/index. Js:

Array.from('foo'); / / /'f'.'o'.'o'] Array.from([1, 2, 3], x => x + x); / / (2, 4, 6]let promsie = new Promise();

async function fn() {
  return1}Copy the code

Modified. Babelrc:

{
  "presets": [
    "@babel/preset-env"]."plugins": [["@babel/plugin-transform-runtime",
      {
        "corejs": 3}]]}Copy the code

NPM run trans, lib/index.js:

"use strict";

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

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

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

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from"));

(0, _from["default"]) ('foo'); / / /'f'.'o'.'o']

(0, _from["default"]] ([1, 2, 3].function (x) {
  return x + x;
}); // [2, 4, 6]

var promsie = new _promise["default"] ();function fn() {
  return _fn.apply(this, arguments);
}

function _fn() {
  _fn = (0, _asyncToGenerator2["default"(/ *)#__PURE__*/_regenerator["default"].mark(function _callee() {
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          caseZero:return _context.abrupt("return", 1);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _fn.apply(this, arguments);
}
Copy the code

As you can see from the output file, @babel/ plugin-transform-Runtime imports the required functional code by module import, avoiding contamination of the global environment.

supplement

Execution order of Plugin and Preset

  • Plugin runs before Presets.
  • Plugins are executed from front to back.
  • Preset is run from back to front.

Webpack configuration file

  • Install the necessary dependencies
# WebPack related version used in this article
# [email protected]
# [email protected]
# [email protected]
# [email protected]
npm install --save-dev webpack-cli webpack babel-loader clean-webpack-plugin
Copy the code
  • newbabel-test/webpack.config.jsfile
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  mode: 'production',
  entry: './lib/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[hash].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}

Copy the code
  • Add a command

    Add an entry under the script field in package.json:

    {..."scripts": {
        "build": "webpack --mode=production"}... }Copy the code
  • Execute the command

    npm run build
    Copy the code

conclusion

The paper come zhongjue shallow, and must know this to practice.

In addition, thank you for your big article, let me benefit a lot, the following link can go to see oh. 7

If this article is also helpful to you, please leave me a like, thanks ~




Babel7: The Babel Community overview