Setting up the Test Environment

In the root directory NPM init-y, generate the basic package.json, create SRC, and create the index.js file in SRC for balel to compile later. Then create a. Babelrc file in the root directory as the configuration file for Babel.

Install dependencies

Install some dependency modules of Babel according to the process of the official website

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
Copy the code

Configuration. Babelrc

Babel compilation automatically reads configuration from the.babelrc file in the directory

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "chrome": "43"
        },
        "useBuiltIns": "usage",
        "corejs": {
          "version": 2
        }
      }
    ]
  ],
  "plugins": []
}
Copy the code

There may be two preset fields in the configuration file. This is a brief introduction:

  • Plugins: used to convert code from high version syntax to a collection of low version plug-ins such as @babel/plugin-transform-arrow-functions to convert arrow functions to ES5.

  • Preset: Since many Bable plug-ins may be used in a project, it would be inconvenient to add them one by one each time. To solve this problem, Babel provides a combination of plug-ins, known as perset.

Test es6 syntax

let a = 1;
const foo = () = > {
    console.log("Arrow function")}Copy the code

Run the following command

// compile SRC files and print them to lib
npx babel src --out-dir lib

// Compile the result
"use strict";

var a = 1;

var foo = function foo() {
  console.log("Arrow function");
};
Copy the code

You can see that it has been packaged into es5 syntax.

Test the higher version API

Includes is a new API of ES7. What will it be packaged into during the test?

let arr = [1.2.3];
console.log(arr.includes(1))
Copy the code

After the package

"use strict";

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

var arr = [1.2.3];
console.log(arr.includes(1));
Copy the code

This file overwrites the includes method in Array and implements the new includes method in ES7 using the lower version syntax.

babel-polyfill

In fact, the syntax above is not transformed by Babel, but by babel-polyfill, which is worth talking about. Put the official website first.

A quick explanation: Babel only escapes JS syntax by default, but does not convert some new APIS, such as include, array. from, etc. What Babel-Polyfill does is help you accommodate these higher version syntaxes.

Composition: Babel-polyfill contains two packages core-js and Regenerator-Runtime.

Core-js: This library is used to convert some of the higher-version syntax to lower-version syntax.

Regenerator-runtime: library that provides transformations to async await.

But strangely 🤔, we didn’t introduce Babel-Polyfill anywhere in the project, how did it work 😳😳. This is actually thanks to the @babel/env default that we configured in.babelrc, so what exactly does that do?

@babel/env

Env is the one we use most often in Babel, and the core purpose of env is to learn the characteristics of the target environment through configuration and then make only the necessary transformations. For example, THE target browser supports ES2015, es2015 preset is not needed, so the code can be smaller (converted code is always longer) and the build time can be shorter.

If no configuration items are written, env is equivalent to Latest and is equivalent to the sum of ES2015 + ES2016 + ES2017 (excluding stage-x plug-ins). The list of plug-ins included with env is maintained here.

So let’s see now, why didn’t we introduce polyfill and it just went into effect? It is no surprise that the env default is introduced. We can look at useBuiltIns in Babel /env

"usage" | "entry" | false, defaults to false.

This option configures how @babel/preset-env handles polyfills.

When either the usage or entry options are used, @babel-preset-env will add direct references to core-js modules as bare imports (or requires). This means core-js will be resolved relative to the file itself and needs to be accessible.

When useBuiltIns = entry, polyfill needs to be referenced at the top of the code

/ / input
import "@babel/polyfill"

let arr = [1.2.3];
console.log(arr.includes(1))

// Compile the result
"use strict"; .require("core-js/modules/es6.array.copy-within.js");

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

require("core-js/modules/es7.array.includes.js"); .var arr = [1.2.3];
console.log(arr.includes(1));
Copy the code

As you can see, in this mode Babel brings in all the content not supported by Chrome 43, resulting in a very large package size, and we only need an includes method here.

When useBuiltIns = Usage, it will help us load on demand and do not need to be imported manually

/ / input
let arr = [1.2.3];
console.log(arr.includes(1))

// Compile the result
"use strict";

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

var arr = [1.2.3];
console.log(arr.includes(1));
Copy the code

If array.includs is called, then reference it. But is that really the case?

/ / input
const Foo = function () {};
Foo.prototype.includes = function () {};new Foo().includes();

// Compile the result
"use strict";
require("core-js/modules/es7.array.includes.js");

var Foo = function Foo() {};
Foo.prototype.includes = function () {};
new Foo().includes();
Copy the code

As you can see, I didn’t call array.includes, but it was introduced anyway, so it should use the method name to determine if it needs to be introduced.

When useBuiltIns = false, the API is not processed.

disadvantages

But there are two drawbacks to the above two approaches,

  1. They polyfill by directly modifying methods on the prototype object of the global constructor, which can cause global contamination and may cause unexpected bugs.

  2. When translating, Babel sometimes uses auxiliary functions to help, such as:

    / / input
    class Test {}
    
    // Compile the result
    "use strict";
    
    function _classCallCheck(instance, Constructor) { if(! (instanceinstanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}var Test = function Test() {
      _classCallCheck(this, Test);
    };
    Copy the code

In class syntax, Babel defines the _classCallCheck function to help. If a project has multiple files and each file has a class, the final package of the project will have 100 _classCallCheck functions. This is obviously unreasonable.

To avoid both, we can use the @babel/ plugin-transform-Runtime plugin.

@babel/plugin-transform-runtime

The plugin relies on the @babel/ Runtime-corejs2 or @babel/ Runtime-corejs3 packages, which you can think of as implementations of polyfill

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

Assume the following code:

class Test {}
const set = new Set(a);console.log([].includes)
Copy the code

. Babelrc configuration

{
  "presets": [["@babel/env",
      {
        "targets": {
          "chrome": "43"
        },
        "useBuiltIns": "usage"."corejs": {
          "version": 2}}]],"plugins": []}Copy the code

Packing results:

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _set = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/set"));
class Test {}
const set = new _set.default();
console.log([].includes);
Copy the code

Core-js2 only handles Set, but not includes, because core-js2 handles class/static methods used in code, not methods in the prototype chain.

Now change corejs of. Babelrc to 3 and compile

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _set = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var Test = function Test() {(0, _classCallCheck2.default)(this, Test);
};
var set = new _set.default();
console.log((0, _includes.default)([]));
Copy the code

Core-js3 handles Set and includes, so you can see that core-js3 also handles methods on the prototype chain.

From the above, we can see that the plugin-transform-Runtime plugin solves the above two problems

  1. Instead of directly modifying methods on the object prototype object, it is imported from a unified module, avoiding contamination of global variables.
  2. Auxiliary functions are introduced from a unified module to avoid the existence of multiple auxiliary function codes in the code.