What is the Babel

Babel is a JS compiler

As JavaScript evolved over time, new features and syntax came along, but browser vendors didn’t fully support it. So you had to have a tool that translated new features and syntax into a standard syntax that browsers would agree on. Babel was born. ES6/ES7/ES8 => Babel => ES5.

Babel configuration

@babel/cli @babel/core

@babel/cli is the command line tool of Babel, mainly providing the Babel command.

The core functions of Babel are contained in the @babel/core module. Without it, you’re doomed to fail in Babel’s world. Babel cannot be compiled without @babel/core installed.

Command line installation

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

Now you can compile using Babel in your project

First create a new project, such as babel-config, initialize it with NPM init-y, and create SRC /index.js with the following file contents

let func = () => { }
Copy the code

Modify package.json and add it to script

// package.json
"scripts": {
    "babel": "babel src --out-dir dist"
}
Copy the code

Use NPM run Babel to do the compilation. If the compilation is successful, look at the index.js file in dist:

let func = () => {};
Copy the code

The code is exactly the same before and after compilation.

This is because Babel doesn’t do anything out of the box, and if you want Babel to do any real work, you need to add a plugin to it.

The plug-in

We need to create a configuration file babel.config.js

The Babel developers provide various forms for configuration files, and babel7 officially recommends babel.config.js. You can also use.babelrc,.babelrc.js or put it in package.json

All of Babel’s functionality is built on plugins, which are installed and then used in configuration files. For example, install @babel/plugin-transform-arrow-functions and specify the plugin in the babel.config.js configuration file

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

Then execute NPM run Babel and you can see that the arrow function has been compiled

let func = function () {};
Copy the code

But let is not transformed, because the plug-in we just introduced is specifically used to transform arrow functions, so let is not transformed. If you want to convert lets, you need to introduce a new plug-in.

So with all the es6 features, I have to introduce them one by one? Of course not. Babel provides us with preset

preset

Preset is a set of plug-ins.

There are several preset versions for our common environments:

  • @babel/preset-env
  • @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

The plugin included with @babel/preset-env will support all the latest JS features (excluding the stages) to convert them into ES5 code. Allows us to use the latest JS syntax, such as let, const, arrow functions, etc.

What is stage? Stage contains the draft of the latest specification for the year, updated annually.

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

Compile again and run the result:

"use strict";

var func = function func() {};
Copy the code

SRC /index.js looks perfect.

let func = () => { }

let arr = [1, 2, 4]
arr.includes(3)
Copy the code

The compiled result is:

"use strict";

var func = function func() {};

var arr = [1, 2, 4];
arr.includes(3);
Copy the code

This compiled code is obviously problematic for use in older browsers, where there is no includes method on array instances.

Babel only converts the new JS syntax, such as arrow functions, but not the new API. In this case, you need to use @babel/ Polyfill to load the new features of ES.

The new API classifs global objects such as Promise, Map, and Object and their own methods, such as Object.assign and promise.resolve. The other is the new instance method

@babel/polyfill

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

The @babel/ Polyfill module includes core-JS and a custom ReGenerator Runtime module that can simulate a full ES2015+ environment (not including pre-phase 4 proposals).

After Babel v7.4, @babel/polyfill is no longer recommended. Instead, install core-js and Regenerator-Runtime to replace @babel/polyfill.

Although @babel/ Polyfill is still being upgraded, the core-js package it uses is 2.x.x. The core-js package itself has been released to 3.x.x, and @babel/ Polyfill will not use 3.x.x in the future. New features are no longer added to the core-js@2 branch; they are added to core-js@3.

Install core-js and regenerator-Runtime:

npm install --save core-js regenerator-runtime
Copy the code

Note: Do not use –save-dev because this is a polyfill that needs to be run before the source.

We need to load the full polyfill before the code to modify our SRC /index.js:

//import '@babel/polyfill'; (not recommended after Babel V7.4) import "core-js/stable"; import "regenerator-runtime/runtime"; let func = () => { } let arr = [1, 2, 4] arr.includes(3)Copy the code

The compiled result is:

"use strict";

require("core-js/stable");
require("regenerator-runtime/runtime");

var func = function func() {};
var arr = [1, 2, 4];
arr.includes(3);
Copy the code

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

@babel/runtime

Modify the contents of SRC /index.js:

class Person {    
    sayname() {        
        return 'name'    
    }
}
var john = new Person()
console.log(john)
Copy the code

The code generated after compilation is as follows:

"use strict"; require("core-js/stable"); require("regenerator-runtime/runtime"); function _classCallCheck(instance, Constructor) { if (! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Person = /*#__PURE__*/function () { function Person() { _classCallCheck(this, Person); } _createClass(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person; } (); var john = new Person(); console.log(john);Copy the code

You can see that several function declarations have been added to the transformed code. This is the injected function, which we call the helper function. The @babel/preset-env injects these function declarations for use after a grammar conversion.

But there’s a problem with this. In our normal front-end engineering development time, at least dozens of JS files, more than a thousand. If the class syntax is used in every file, the same function declarations will be injected at the top of every converted file. This results in very large bags that we pack out.

So what do we do? One idea is to put these function declarations in an NPM package and import them directly from that package into our files when needed. Even thousands of files will reference these functions from the same package. When packaging with a build tool like WebPack, we only import functions from the NPM package we use once, so we can reuse and reduce the volume.

@babel/runtime is the NPM package mentioned above. @babel/runtime integrates all the helper functions used for syntax conversion.

Let’s install this package first :(@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

If you go to node_modules and look at the @babel/ Runtime /helpers file, you’ll find that the _classCallCheck, _defineProperties and _createClass helper functions are located under the @babel/ Runtime /helpers file. If we manually replace the function declaration with the auxiliary function, the previous file code should look like this:

"use strict"; require("core-js/stable"); require("regenerator-runtime/runtime"); var _classCallCheck = require("@babel/runtime/helpers/classCallCheck"); var _defineProperties = require("@babel/runtime/helpers/defineProperties"); var _createClass = require("@babel/runtime/helpers/createClass"); var Person = /*#__PURE__*/function () { function Person() { _classCallCheck(this, Person); } _createClass(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person; } (); var john = new Person(); console.log(john);Copy the code

This eliminates the problem of code reuse and resulting file size. However, so many auxiliary functions to remember one by one and manually introduced, ordinary people are not able to do, I also can not do. The @babel/ plugin-transform-Runtime plugin helps solve this problem.

@babel/plugin-transform-runtime

Inline Babel helpers at @babel/ Runtime /helpers inline Babel helpers at @babel/runtime/helpers helpers at @babel/runtime/helpers helpers at @babel/runtime/helpers helpers at @babel/runtime/helpers helpers This saves us the hassle of manual introduction.

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

Now, our Babel configuration file looks like this:

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

Compiled code:

"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); require("core-js/stable"); require("regenerator-runtime/runtime"); var Person = /*#__PURE__*/function () { function Person() { (0, _classCallCheck2["default"])(this, Person); } (0, _createClass2["default"])(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person; } (); var john = new Person(); console.log(john);Copy the code

As you can see, the generated code is much more elegant than if we had completely manually introduced the helper functions in @babel/ Runtime.

Another function of @babel/plugin-transform-runtime is to create a sandbox environment to avoid polluting the global environment

Using Promise as an example, pre-compile code:

var obj = Promise.resolve();
Copy the code

If babel-polyfill or core-js/stable and regenerator-Runtime /runtime are used for global API completion, the compiled code will still be

var obj = Promise.resolve();
Copy the code

Polyfill simply completes the browser’s Window. Promise object.

If we do not use polyfill, we turn on @babel/ plugin-transform-Runtime API conversion. So the compiled code will be

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

var obj = _promise["default"].resolve();
Copy the code

@babel/ plugin-transform-Runtime changes the Promise in our code to _promise[” default “], and _promise[” default “] has all the functionality that ES promises have. Now, our code works fine even if the browser doesn’t have promises.

So, what is the use of the API conversion mentioned above, when you can polyfill the API to make the code work in the browser?

In fact, API transformations are mainly for people who develop JS libraries or NPM packages, etc., and our front-end projects still use Polyfill to complete the API.

It can be imagined that if the developer of JS library uses Polyfill completion API and our front-end engineering also uses Polyfill completion API, but the polyfill version or content of JS library is not consistent with our front-end engineering, then the introduction of this JS library may lead to problems in our front-end engineering. So, people who develop JS libraries or NPM packages, etc., will use API conversion.

How to enable @babel/ plugin-transform-Runtime API conversion

Install @ Babel/runtime – corejs3

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

Modify the Babel. Config. Js:

{ "presets": [ "@babel/preset-env" ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 // Corejs values are false, 2, and 3. The default value is false}]]}Copy the code

@babel/ Runtime – The difference between @babel/runtime and @babel/runtime

@babel/runtime/corejs3 is an evolution of @babel/runtime/corejs3. Core-js API conversion functions are also included.

In addition to these two packages, there is another @babel/ Runtime-corejs2 package. It has the same functionality as @babel/ Runtime-corejs3, except that the functions are for the core-js2 version.

For @babel/ Runtime and its evolution @babel/ Runtime-corejs2 and @babel/ Runtime-corejs3, we only need to install one for our own needs. If you don’t need core-js API conversions, install @babel/ Runtime and set corejs to false. If you need core-js2 API conversion, install @babel/ Runtime-corejs2 and set corejs to 2. If you need core-js3 API conversion, install @babel/ Runtime-corejs3 and set corejs to 3.

Browserslist integration

@babel/preset-env will generate a list of plug-ins to compile based on the target environment of the configuration.

When compatibility with all browsers and environments is not required, you can keep compiled code to a minimum by specifying the target environment.

The official recommendation is to use the.browserslistrc file to specify the target environment.

//.browserslistrc
> 1%
not ie <= 8
Copy the code

The above configuration implies that the target environment is browsers with a market share greater than 1% and excludes IE8 and below.

Configure the contents of.browserslistrc to:

last 2 Chrome versions
Copy the code

If you run NPM run Babel, you will see that the arrow functions will not compile to ES5, because the arrow functions are supported in the last two versions of Chrome.

See more configurations of Browserslist

Refer to the article

Babel7 knowledge not to be missed

Babel tutorial