In this paper, starting from vivo Internet technology WeChat public links: mp.weixin.qq.com/s/plJewhUd0… Author: Morrain

One, foreword

In the previous section CommonJS: In ES6, we talked about ES6 Module, which is a specification for modules in ES6. ES6 is short for ECMAScript 6.0, the next generation standard for the JavaScript language. Its first version, ES2015, was officially released in June 2015, and the ES6 mentioned in this article includes ES2015, ES2016, ES2017, and more. As mentioned in the first section of “The Web: Going All the way, Forgetting all the Way”, ES2015 took more than a decade from the development to the release, and introduced many new features and new mechanisms. The progress of browser support for ES6 was far from enough to catch up with the enthusiasm of front-end development brothers to use ES6, so contradictions became increasingly obvious…

What is a Babel

Here’s the definition on the website:

Babel is a JavaScript compiler

Yes, Babel is a compiler for JavaScript. As to what a compiler is, see the project the-super-tiny-compiler for a good answer.

This article is based on the Babel 7.9.0 version for the demonstration and explanation. In addition, learners are advised to read the English official website. The Chinese official website is one version slower than the original website, and much of it is still in English.

Babel is a solution for translating ES6 code into code supported by browsers or other environments. Note that I didn’t mean convert to ES5, because different types and versions of browsers support the new features of ES6 to a different degree. Babel doesn’t convert what the browser already supports, so it depends on the browser version, which I’ll cover later. You can refer to the BrowerSlist project first.

The history of Babel

Before learning any subject, I am used to understanding its history, so that I can deeply understand its significance.

Babel was written by Sebastian McKenzie, an engineer at FaceBook. He released a JavaScript compiler, 6to5, in 2014. As the name suggests, its main function is to convert ES6 into ES5.

ES6 here refers to ES2015, as it was not officially released at the time and ES2015’s name had not yet been officially decided.

Therefore, many people evaluate that 6TO5 is just a transitional plan before ES6 is supported. Its authors strongly disagree with this view, and believe that 6TO5 will not only improve gradually according to the standard, but still have a great potential to influence and promote the formulation of the standard. Because of this, the 6to5 team felt that the name ‘6to5’ did not accurately convey the goals of the project. Plus, when ES6 was officially released, it was named ES2015, which was even more of a departure for 6to5. On February 15, 2015, 6to5 officially changed its name to Babel.

(Image from the Internet)

Babel is the Babylonian name for the tower of Babel, which is a fitting name for 6to5. Envy these people, not only good code writing, but also so literate, unlike us, have to hold a variable name for a long time, eat the loss of no culture. That’s why I called this post “Babel: The Tower of Babel that Sent ES6 to Heaven.”

How to use Babel

Now that we know what Babel is, it’s obvious to start thinking about how to use Babel to convert ES6 code. In addition to the cli tools that Babel provides, it also supports other packaging tools such as WebPack, Rollup, etc. For details, see the configuration descriptions provided on the official website.

In order to get a feel for the original use of Babel, this article uses the CLI of Babel without any other tools.

1. Build the Babel demo project

Build an NPM package with the following command and create a new SRC directory and an index.js file.

npm init -yCopy the code

Package. json has the following content:

{
  "name": "demo"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": []."author": ""."license": "ISC"
}Copy the code

2. Install dependency packages

npm install --save-dev @babel/core @babel/cli @babel/preset-envCopy the code

We’ll see what these packages do later, but first look at usage

Add the Babel command to compile files from SRC to dist:

{
  "name": "demo"."version": "1.0.0"."description": ""."main": "src/index.js"."scripts": {
    "babel": "babel src --out-dir dist"."test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": []."author": ""."license": "ISC"."devDependencies": {
    "@babel/cli": "^ 7.8.4"."@babel/core": "^ 7.9.0"."@babel/preset-env": "^ 7.9.0"}}Copy the code

3. Add the Babel configuration file

Add the babel.config.js file to the project root directory and add the configuration that is compiled by Babel. No configuration is compiled.

const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = []
 
module.exports = { presets, plugins }Copy the code

In the example above, the debug configuration is used to print the log when Babel is working, so it is convenient to see what syntax Babel translates.

  1. Presets are presets that are configured for compilation, and plugins are plug-ins that are configured for compilation. More on this later
  2. It is recommended to write configuration files in Javascript files rather than JSON files, so that you can dynamically configure the presets and plugins you need to use depending on your environment
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = []
 
if (process.env["ENV"= = ="prod") {
  plugins.push(...)
}
 
module.exports = { presets, plugins }Copy the code

4. The result of compilation

After the configuration, we run the NPM run Babel command, and you can see that the file index.js is generated under the dist folder, as follows:

// src/index.js
const add = (a, b) => a + b
 
// dist/index.js
"use strict";
 
var add = function add(a, b) {
  return a + b;
};Copy the code

As you can see, the const of ES6 is converted to var and the arrow function is converted to a normal function. The following logs are also displayed:

> babel src --out-dir dist
 
@babel/preset-env: `DEBUG` option
 
Using targets:
{}
 
Using modules transform: auto
 
Using plugins:
  proposal-nullish-coalescing-operator {}
  proposal-optional-chaining {}
  proposal-json-strings {}
  proposal-optional-catch-binding {}
  transform-parameters {}
  proposal-async-generator-functions {}
  proposal-object-rest-spread {}
  transform-dotall-regex {}
  proposal-unicode-property-regex {}
  transform-named-capturing-groups-regex {}
  transform-async-to-generator {}
  transform-exponentiation-operator {}
  transform-template-literals {}
  transform-literals {}
  transform-function-name {}
  transform-arrow-functions {}
  transform-block-scoped-functions {}
  transform-classes {}
  transform-object-super {}
  transform-shorthand-properties {}
  transform-duplicate-keys {}
  transform-computed-properties {}
  transform-for-of {}
  transform-sticky-regex {}
  transform-unicode-regex {}
  transform-spread {}
  transform-destructuring {}
  transform-block-scoping {}
  transform-typeof-symbol {}
  transform-new-target {}
  transform-regenerator {}
  transform-member-expression-literals {}
  transform-property-literals {}
  transform-reserved-words {}
  transform-modules-commonjs {}
  proposal-dynamic-import {}
 
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
Successfully compiled 1 file with Babel.Copy the code

How Babel works

Now that you’ve seen how to use it, let’s explore what goes on behind the scenes while familiarizing ourselves with the composition and advanced usage of Babel.

1. Babel workflow

As mentioned earlier, Babel is a pure JavaScript compiler. Any compiler workflow can be roughly divided into three steps:

  • Parser Parses the source file

  • Transfrom conversion

  • The Generator generates new files

Babel is no exception, as shown below:

(Image from the Internet)

Because Babel uses the Acorn engine for parsing, the library converts the source code into an abstract syntax tree (AST), converts the AST, and outputs the converted AST, resulting in a babel-compiled file.

So how did Babel know how to turn? The answer is through plug-ins. Babel provides a plug-in for every new syntax, and the configuration of the plug-in translates the corresponding syntax to the one configured in Babel. Plug-ins are named in the format @babel/plugin-xxx.

2. Babel

(1) @ Babel/preset – env

As mentioned above, @babel/preset-* is actually a collection of preset plug-ins, the most commonly used one is @babel/preset-env, which contains most of the syntax of ES6, the specific plug-in can be seen in the Babel log. If the source code uses a syntax not in @babel/preset-env, an error will be reported, just add it to the plugins manually.

For example, ES6 explicitly states that there are only static methods inside a Class and no static attributes. However, there is a proposal to provide static properties for classes, which are written by adding the static keyword before the instance properties.

// src/index.js
const add = (a, b) => a + b
 
class Person {
  static a = 'a';
  static b;
  name = 'morrain';
  age = 18
}Copy the code

The following error is reported at compile time:

Add @babel/plugin-proposal-class-properties as prompted.

npm install --save-dev @babel/plugin-proposal-class-propertiesCopy the code

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }Copy the code

Targets is a very important parameter in @babel/preset-env. As we mentioned earlier, Babel translation is on-demand, and there is no conversion for the syntax supported by the environment. By configuring
targetsProperty to let Babel know about the target environment and therefore only translate syntax that is not supported by the environment.
If not configured, all ES6 syntax will be translated by default.

// SRC /index.js const add = (a, b) => a + b // dist/index.js Targets are not configured"use strict";
 
var add = function add(a, b) {
  return a + b;
};Copy the code

Configure as follows
targets

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      targets: {
        chrome: '58'
      }
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }Copy the code

The compiled results are as follows:

// SRC /index.js const add = (a, b) => a + b // dist/index.js Targets Chrome 58"use strict";
 
const add = (a, b) => a + b;Copy the code

You can see that const and arrow functions are not translated because these features are already supported in this version of Chrome. The target environment can be flexibly configured according to requirements.

To facilitate further explanation, remove the targets configuration and let Babel translate all syntax by default.

(2) @ Babel/polyfill

A polyfill is an important concept in Babel. Let’s start with the following lines of code:

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()Copy the code

After running NPM Run Babel as before, we were surprised to find that array.prototype.includes and promises were not translated!

// dist/index.js
"use strict";
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code

Babel divides ES6 standards into syntax and built-in. Syntax is the syntax type, such as const and =>, which is translated by default by Babel. The syntax that can be overridden by rewriting is considered built-in, such as includes and promises. Babel only translates syntax by default, but @babel/polyfill is required for built-in. The @babel/ Polyfill implementation is also very simple in that it overrides the built-in additions to ES6. The schematic is as follows:

Object.defineProperty(Array.prototype, 'includes'.function(){
  ...
})Copy the code

Since Babel announced in version 7.4.0 that @babel/polyfill was deprecated and replaced with core-js, this article uses core-JS directly to illustrate the use of polyfill.

  • Install the core – js

npm install --save core-jsCopy the code

  • Note that core-JS is installed using the –save method because it needs to be injected into the source code to provide the execution environment for the built-in injection before executing the code
  • Configuration useBuiltIns

    The useBuiltIns parameter is used to control the injection of the built-in in @babel/preset-env. It can be set to ‘entry’, ‘usage’ and false. The default value is false and no gaskets are injected.

    When set to ‘entry’, you simply need to import core-JS at the entry point of the entire project.

// src/index.js
import 'core-js'
 
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
 
// dist/index.js
"use strict";
 
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.promise"); / / / /... There are many more // require("regenerator-runtime/runtime");
var add = function add(a, b) {
  return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code

  • When compiled, Babel will inject all the built-ins that are not supported by the target environment, whether they are used or not. The problem with this is that it is unnecessary to add code and waste package size for projects that use very little.

When set to ‘Usage’, instead of importing core-JS at the entry point of the project, Babel will select the implementation to inject based on the built-in usage during source compilation.

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
 
// dist/index.js
"use strict";
 
require("core-js/modules/es6.promise");
 
require("core-js/modules/es6.object.to-string");
 
require("core-js/modules/es7.array.includes");
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();Copy the code

  • The version of Corejs is configured

When useBuiltIns is set to ‘Usage’ or ‘entry’, the corejs parameter of @babel/preset-env needs to be set to specify the version of Corejs used when the built-in implementation is injected. Otherwise, the Babel log output will have a warning.

The final Babel configuration is as follows:

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      useBuiltIns: 'usage',
      corejs: 3,
      targets: {}
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }Copy the code

(3) @ Babel/plugin – transform – runtime

Before introducing the use of @babel/ plugin-transform-Runtime, here’s an example:

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise(resolve=>resolve(10))
 
class Person {
  static a = 1;
  static b;
  name = 'morrain';
  age = 18
}
 
// dist/index.js
"use strict";
 
require("core-js/modules/es.array.includes");
 
require("core-js/modules/es.object.define-property");
 
require("core-js/modules/es.object.to-string");
 
require("core-js/modules/es.promise");
 
function _classCallCheck(instance, Constructor) { if(! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  _classCallCheck(this, Person);
 
  _defineProperty(this, "name".'morrain');
 
  _defineProperty(this, "age", 18);
};
 
_defineProperty(Person, "a", 1);
 
_defineProperty(Person, "b", void 0);Copy the code

During compilation, the built-in syntax is compatible with require(“core-js/modules/ XXXX “) polyfill, For syntax types during translation, helper functions such as _classCallCheck and _defineProperty are injected into the current module for compatibility. For one module, that might be fine, but for many modules in a project, injecting these helper functions into each module is bound to create a lot of code.

@babel/ plugin-transform-Runtime is designed to reuse these helper functions and reduce the size of the code. In addition, of course, it provides a sandbox environment for compiled code to avoid global contamination.

Using the @ Babel/plugin – transform – runtime

  • (1) the installation

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

Where @babel/ plugin-transform-Runtime is used at compile time and installed as a development dependency, @babel/ Runtime is actually a collection of helper functions that need to be introduced into the compiled code, so it is installed as a production dependency

  • ② Modify the Babel plugins configuration and add @babel/plugin-transform-runtime

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      useBuiltIns: 'usage',
      corejs: 3,
      targets: {}
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties'['@babel/plugin-transform-runtime'
  ]
]
 
module.exports = { presets, plugins }Copy the code

  • The previous example, compiled once again, you can see, before the helper function, all become similar to the require (” @ Babel/runtime/helpers/classCallCheck “) implementation.
// dist/index.js
"use strict";
 
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
 
require("core-js/modules/es.array.includes");
 
require("core-js/modules/es.object.to-string");
 
require("core-js/modules/es.promise");
 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
 
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  (0, _classCallCheck2["default"])(this, Person);
  (0, _defineProperty2["default"])(this, "name".'morrain');
  (0, _defineProperty2["default"])(this, "age", 18);
};
 
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);Copy the code

  • Configuration @ Babel/plugin – transform – runtime

So far, the syntax for built-in types has been implemented using require(“core-js/modules/ XXXX “) polyfill, for example to support array.prototype.includes, Need the require

(” core – js/modules/es. Array. Includes “) in the array. The prototype to add includes ways to implement, but this could lead to a problem, it is directly modify the prototype, will cause the global pollution. This is not a problem if you are developing a standalone application, but if you are developing a library that is referenced by other projects that happen to implement array.prototype.includes, you are in trouble! The @babel/ plugin-transform-Runtime parameter corejs can be configured to resolve this problem. This parameter is false by default and can be set to 2 or 3 for @babel/runtime-corejs2 or @babel/runtime-corejs3, respectively.

Set the value of corejs for @babel/plugin-transform-runtime to 3 and replace @babel/ Runtime with @babel/runtime-corejs3.

Remove the useBuiltIns and corejs configurations of @babel/preset-env, and remove core-js. Because @babel/ Run-time Corejs3 is used for compatibility with the built-in syntax, useBuiltIns is not used.

npm uninstall @babel/runtime
npm install --save @babel/runtime-corejs3
npm uninstall core-jsCopy the code

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      targets: {}
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties'['@babel/plugin-transform-runtime',
    {
      corejs: 3
    }
  ]
]
 
module.exports = { presets, plugins }
 
 
// dist/index.js
"use strict";
 
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
 
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));
 
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
 
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = (0, _includes["default"])(arr).call(arr, 3);
new _promise["default"] (function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  (0, _classCallCheck2["default"])(this, Person);
  (0, _defineProperty2["default"])(this, "name".'morrain');
  (0, _defineProperty2["default"])(this, "age", 18);
};
 
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);Copy the code

You can see that the implementations of Promise and arr.includes have become local variables without changing the global implementation.

3. Differences in implementation of Babel Polyfill

So far, there have been three ways to polyfill built-in-type syntax:

  • Use @babel/preset-env, useBuiltIns set to ‘entry’

  • Use @babel/preset-env, useBuiltIns set to ‘usage’

  • Using the @ Babel/plugin – transform – runtime

Targets can be configured based on the target environment. Setting useBuiltIns to ‘Entry’ injects all the built-in syntax that is not supported by the target environment. Setting useBuiltIns to ‘Usage’ injects all the built-in syntax that is not supported by the target environment. The syntax of the injected built-in type is global pollution.

The third method does not support setting targets, so whether the target environment is supported or not is not considered. It implements all the built-in syntax in the form of local variables and does not pollute the global environment.

Babel is considering to solve the problem that the third method does not support setting targets. Currently, the intended solution is to unify the implementation of Polyfill through the Polyfill Provider:

  • The useBuiltIns and corejs parameters in @babel/preset-env are deprecated, and the @babel/preset-env is no longer used to implement polyfill.

  • Deprecate corejs in @babel/plugin-transform-runtime and no longer use @babel/plugin-transform-runtime to implement polyfill.

  • Add the polyfills parameter, similar to today’s presets and plugins, in place of the current polyfill scheme.

  • Push targets in @babel/preset-env up one level to the same level as presets, plugins, and polyfills and share them.

When implemented, Babel will look like this:

// babel.config.js
const targets = [
  '> 1%'
]
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties'
]
const polyfills = [
  [
    'corejs3',
    {
      method: 'usage-pure'
    }
  ]
]
 
module.exports = { targets, presets, plugins, polyfills }Copy the code

There are three methods: ‘entry-global’, ‘usage-global’, and ‘usage-pure’.

  • ‘entry-global’ is equivalent to useBuiltIns in @babel/preset-env: ‘entry’

  • ‘usage-global’ is equivalent to useBuiltIns in @babel/preset-env: ‘usage’

  • ‘usage-pure’ is equivalent to Corejs in @babel/ plugin-transform-Runtime

In this article, we use the native @babel/ CLI to compile the files. In practice, we use more third-party tools such as Webpack and Rollup.

So in the next video, we’ll talk about the packaging tool WebPack.

5. References

  1. 6to5 JavaScript Transpiler Changes Name to Babel

  2. Babel Learning Series 2-Babel Design, Composition

  3. Learn how Babel works

  4. RFC: Rethink polyfilling story

For more content, please pay attention to vivo Internet Technology wechat official account

Note: To reprint the article, please contact wechat: Labs2020.