Does not having a good understanding of what Babel’s configuration items do affect day-to-day development? To be honest, most of the time it doesn’t have a huge impact (there are search engines, after all).

However, or want to further understand, so recently read the document of Babel, plus compiling verification, the output of this article, in order to better reading experience, repaired change, personally, I prefer the way to the final is to promote the each knowledge point (the introduction of each configuration is a reason for that), Hopefully this will help you get a clearer picture of the various configurations of Babel.

Babel is a JS 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.

Let’s start with what Babel can do:

  • The syntax conversion
  • throughPolyfillAdding missing features to the target environment (@ Babel/polyfill modules)
  • Source code conversion (Codemods)

The purpose of this article is to understand the use and configuration of @babel/runtime, @babel/polyfill, @babel/plugin-transform-runtime. Why do we need to configure them, rather than how to do AST conversions? If you are interested in AST conversions, please read the source code of our RN-convert applet engine Alita, which applies a lot of AST conversions.

More articles can poke (such as Star, thank you) : https://github.com/YvetteLau/Blog

To get a clearer picture of each step, first create a new project, such as babelTemp(whatever you like), initialize it with NPM init -y, and create SRC /index.js with the following contents (you can write whatever you like) :

const fn = (a)= > {

    console.log('a');

};

Copy the code

OK, let’s put aside the created project for a bit of theoretical knowledge:

The core library @ Babel/core

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

CLI CLI tool @babel/ CLI

Babel provides the command line tool, mainly provides the Babel command, suitable for installation in the project.

@babel/node provides the babel-node command, but @babel/node is more suitable for a global installation than for a project.

npm install --save-dev @babel/core @babel/cli

Copy the code

Now you can compile with @babel/core in your project.

Configure the commands in the scripts field of the package.json file:

/ /...

"scripts": {

    "compiler""babel src --out-dir lib --watch"

}

Copy the code

Using the NPM Run Compiler to perform the compilation, we now have no plugins configured and the code is exactly the same before and after the compilation.

Because Babel does nothing out of the box, if you want Babel to do any real work, you need to add a plugin to it.

The plug-in

Babel is built on top of plug-ins, using existing or self-written plug-ins to form a transformation channel. There are two types of plug-ins for Babel: syntax plug-ins and transformation plug-ins.

Grammar plug-in

These plug-ins only allow Babel to parse (parse) certain types of grammars (not transformations) and can be used in AST transformations to support parsing new grammars, for example:

import * as babel from "@babel/core";

const code = babel.transformFromAstSync(ast, {

    // Support optional chains

    plugins: ["@babel/plugin-proposal-optional-chaining"].

    babelrcfalse

}).code;

Copy the code

The transformation plug-in

It’s easy to understand that the transformation plug-in will enable the corresponding syntax plug-in (so you don’t need to specify both), but if you don’t enable the corresponding syntax plug-in, it means you can’t parse, and if you can’t parse, what’s the point of conversion?

Use of plug-ins

If the plugin is published on NPM, you can directly fill in the name of the plugin, and Babel will automatically check if it is already installed in node_modules. In the project directory, create a new.babelrc file (more on the configuration file below) and configure it as follows:

//.babelrc

{

    "plugins": ["@babel/plugin-transform-arrow-functions"]

}

Copy the code

You can also specify a relative/absolute path for the plug-in

{

    "plugins": ["./node_modules/@babel/plugin-transform-arrow-functions"]

}

Copy the code

If NPM run compiler is executed, you can see that the arrow function is compiled and OK, lib/index.js will look like this:

const fn = function ({

    console.log('a');

};

Copy the code

For now, we only support conversion arrow functions. If you want to convert other new JS features to lower versions, you need to use other corresponding plugins. If we had to configure them individually, it would have been tedious because you might have to configure dozens of plug-ins, which is obviously very inconvenient, but is there a way to simplify this configuration?

There are! The default! (Thanks to mighty Babel)

The preset

A set of plug-ins is easy to use by using or creating a Preset.

The official Preset

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

Note: As of Babel V7, all stage presets written for features of the standard proposal stage have been deprecated, and are officially removed @babel/preset-stage-x.

@babel/preset-env

@babel/preset-env is mainly used to transcode and load polyfill for features we used and are missing in the target browser, without any configuration, The plugin included with @babel/preset-env will support all the latest JS features (ES2015,ES2016, etc., excluding the stage stage) to convert them into ES5 code. For example, if you use optional chains in your code (which is still in the stage stage), just configure @babel/preset-env, the conversion will throw an error and a separate plugin will be installed.

//.babelrc

{

    "presets": ["@babel/preset-env"]

}

Copy the code

Note that @babel/preset-env will generate a list of plug-ins to compile based on the target environment you are configuring. For browser-based or Electron projects, the official recommendation is to use the.browserslistrc file to specify the target environment. By default, @babel/preset-env will use browserslist to configure the source if you do not set targets or ignoreBrowserslistConfig in the Babel configuration file (e.g..babelrc).

If you do not want to be compatible with all browsers and environments, it is recommended that you specify the target environment so that your compiled code is kept to a minimum.

For example, only polyfill and transcoding required by users with more than 0.25% of the browser market share is included (ignoring browsers without security updates such as IE10 and BlackBerry) :

//.browserslistrc

0.25%

not dead

Copy the code

See more configurations of Browserslist

For example, you configure the contents of.browserslistrc to:

last 2 Chrome versions

Copy the code

If you run the NPM run Compiler, you will find that the arrow functions are not compiled into ES5, as they are supported in both the last two versions of Chrome. For now, we’ll change.browserslistrc to the same configuration as before.

As far as our current code is concerned, the current configuration seems to be OK.

Let’s modify SRC /index.js.

const isHas = [1.2.3].includes(2);



const p = new Promise((resolve, reject) = > {

    resolve(100);

});

Copy the code

The compiled result is:

"use strict";



var isHas = [1.2.3].includes(2);

var p = new Promise(function (resolve, reject{

  resolve(100);

});

Copy the code

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

Why is that? Syntax conversions only convert a higher version of the syntax to a lower version, but the new built-in functions, instance methods, cannot be converted. In this case, you need to use Polyfill, which means “shim” in Chinese, to smooth out the differences between different browsers and different environments, so that new built-in functions, instance methods and so on can be used in older browsers.

Polyfill

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).

This means you can use new built-in components like Promise and WeakMap, static methods like Array.from or Object.assign, and array.prototype.includes And generator functions (provided you use the @babel/plugin-transform-regenerator plug-in). To add these capabilities, Polyfill is added to global scopes and built-in archetypes like String (polluting the global environment, which we’ll describe later).

Note (2020/01/07) : Since V7.4.0, @babel/ Polyfill has been deprecated (front-end development is changing rapidly), core-JS and Regenerator-Runtime modules need to be installed separately.

First, install the @babel/polyfill dependency:

npm install --save @babel/polyfill

Copy the code

Note: do not use –save-dev because this is a shim 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';



const isHas = [1.2.3].includes(2);



const p = new Promise((resolve, reject) = > {

    resolve(100);

});

Copy the code

@babel/polyfill needs to be introduced before any other code and can be configured in Webpack.

Such as:

entry: [

    require.resolve('./polyfills'),

    path.resolve('./index')

]

Copy the code

The polyfills. Js file reads as follows:

// Of course, there may be other polyfills, such as the polyfills before Stage 4

import '@babel/polyfill';

Copy the code

Our code now works in both older and older browsers (or node environments). However, most of the time, we don’t necessarily need the full @babel/ Polyfill, which leads to the package size we end up building, which is 89K for @babel/ Polyfill (the current @babel/ Polyfill version is 7.7.0).

More importantly, if I use a new feature, I introduce the corresponding Polyfill to avoid introducing useless code.

Thankfully, Babel has taken this into account.

@babel/preset-env provides a useBuiltIns parameter, and when set to Usage, only polyfills needed by the code will be included. Note that to set this parameter to usage, corejs must be set at the same time (warning will be given if not set, default is “corejs”: 2). Note: Here you still need to install @babel/polyfill(the current @babel/polyfill version installs “corejs”: 2 by default):

First of all, the reason for using core-js@3 is that no new features are added to the core-js@2 branch. New features are added to core-js@3. For example, if you’re using array.prototype.flat (), if you’re using core-js@2, it doesn’t include this new feature. In order to use more of the new features, we recommend that you use core-js@3.

Install dependencies dependencies:

npm install --save core-js@3

Copy the code

Core-js (click to learn more) : A modular standard library for JavaScript that contains Promise, Symbol, Iterator, and many other features and allows you to load only required functionality.

Now, modify the Babel configuration file as follows:

//.babelrc

const presets = [

    [

        "@babel/env".

        {   

            "useBuiltIns""usage".

            "corejs"3

        }

    ]

]

Copy the code

Babel examines all the code to find missing functionality in the target environment, and then includes only the polyfills needed.

For example, the SRC /index.js code stays the same:

const isHas = [1.2.3].includes(2);



const p = new Promise((resolve, reject) = > {

    resolve(100);

});

Copy the code

Let’s look at the compiled file (lib/index):

"use strict";



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



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



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



var isHas = [1.2.3].includes(2);

var p = new Promise(function (resolve, reject{

    resolve(100);

});

Copy the code

Build the same code with Webpack (Production mode) and see that the final code size is only 20KB. If we add the whole @babel/polyfill, the package size is 89KB

As mentioned earlier, @babel/polyfill still needs to be installed when useBuiltIns is usage, although it does not appear to be used in our code conversion above. However, if async/await is used in our source code, So the compiled code needs to require(“regenerator-runtime/runtime”), in the dependency of @babel/polyfill, of course, You can also install regenerator-Runtime/Runtime instead of @babel/polyfill.

At this point, it’s pretty good. Do you want to jump up and spin around?

You may or may not know what I’m about to tell you, but it doesn’t matter: Babel uses very small helper functions to implement public methods like _createClass. By default, it will be injected into every file that needs it.

Suppose our SRC /index.js looks like this:

class Point {

    constructor(x, y) {

        this.x = x;

        this.y = y;

    };

    getX() {

        return this.x;

    }

}



let cp = new ColorPoint(25.8);

Copy the code

The compiled lib/index.js looks like this:

"use strict";



require("core-js/modules/es.object.define-property");



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



function _defineProperties(target, propsfor (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = trueif ("value" in descriptor) descriptor.writable = trueObject.defineProperty(target, descriptor.key, descriptor); }}



function _createClass(Constructor, protoProps, staticPropsif (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }



var Point =

    /*#__PURE__*/

    function ({

        function Point(x, y{

            _classCallCheck(this, Point);



            this.x = x;

            this.y = y;

        }



        _createClass(Point, [{

            key"getX".

            valuefunction getX({

                return this.x;

            }

        }]);



        return Point;

} ();



var cp = new ColorPoint(25.8);

Copy the code

This doesn’t seem to be a problem, but if you have 10 files that use this class, does that mean that methods like _classCallCheck, _defineProperties, and _createClass are injected 10 times? This obviously results in an increase in package size, and crucially, we don’t need to inject it multiple times.

This is where the @babel/plugin-transform-runtime plugin comes in. With the @babel/ plugin-transform-Runtime plugin, all helpers will reference the @babel/runtime module. This avoids duplicate helpers in compiled code and reduces package size.

@babel/plugin-transform-runtime

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

Note: Instance methods such as array.prototype.flat () will not work as this requires modifying existing built-in functions (@babel/polyfill can be used to solve this problem) — > Note that if you are configuring Corejs3, core-js@3 now supports the prototype approach without contaminating the prototype.

In addition, @babel/plugin-transform-runtime needs to be used with @babel/ Runtime.

@babel/plugin-transform-runtime is usually only used for development purposes, but the final code needs to rely on @babel/runtime, so @babel/runtime must be installed as a production dependency, as follows:

npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime

Copy the code

In addition to reducing the size of the compiled code, @babel/ plugin-transform-Runtime has the added benefit of creating a sandbox environment for the code, If you use @babel/ Polyfill and the built-in programs it provides (such as Promise, Set, and Map), they will pollute the global scope. While this may be fine for applications or command-line tools, it can be a problem if your code is a library to distribute for others to use, or if you don’t have full control over the environment in which your code runs.

@babel/ plugin-transform-Runtime uses these built-in aliases as core-js aliases, so you can use them seamlessly without polyfilling.

Modify the configuration of.babelrc as follows:

//.babelrc

{

    "presets": [

        [

            "@babel/preset-env".

            {

                "useBuiltIns""usage".

                "corejs"3

            }

        ]

].

    "plugins": [

        [

            "@babel/plugin-transform-runtime"

        ]

    ]

}

Copy the code

Recompiling the NPM run compiler now yields (lib/index.js):

"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"));



var Point =

    /*#__PURE__*/

    function ({

        function Point(x, y{

            (0, _classCallCheck2.default)(this, Point);

            this.x = x;

            this.y = y;

        }



        (0, _createClass2.default)(Point, [{

            key"getX".

            valuefunction getX({

                return this.x;

            }

        }]);

        return Point;

} ();



var cp = new ColorPoint(25.8);

Copy the code

As you can see, the help function is now not directly injected into the code, but imported from the @babel/ Runtime. Using @babel/ plugin-transform-Runtime can avoid global contamination, so let’s look at how to avoid contamination.

Modify SRC /index.js as follows:

let isHas = [1.2.3].includes(2);



new Promise((resolve, reject) = > {

    resolve(100);

});

Copy the code

The compiled code looks like this (lib/index.js):

"use strict";



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



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



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



var isHas = [1.2.3].includes(2);

new Promise(function (resolve, reject{

    resolve(100);

});

Copy the code

Array.prototype added an includes method and added a global Promise method to pollute the global environment, just like @babel/plugin-transform-runtime.

If we want @babel/ plugin-transform-Runtime to not only handle helper functions but also load polyfills, we need to add configuration information to @babel/ plugin-transform-Runtime.

Add dependency @babel/runtime-corejs3

npm install @babel/runtime-corejs3 --save

Copy the code

Update the configuration file as follows (remove @babel/preset-env useBuiltIns, otherwise it will repeat, try compiling with async/await):

{

    "presets": [

        [

            "@babel/preset-env"

        ]

].

    "plugins": [

        [

            "@babel/plugin-transform-runtime", {

                "corejs"3

            }

        ]

    ]

}

Copy the code

Lib /index.js: lib/index.js:

"use strict";



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



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



var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));



var _context;



var isHas = (0, _includes.default)(_context = [1.2.3]).call(_context, 2);

new _promise.default(function (resolve, reject{

  resolve(100);

});

Copy the code

As you can see, there is no direct modification of Array.prototype or new Promise methods to avoid global contamination. If the core-js of the @babel/plugin-transform-runtime configuration above is “2”, polyfill that does not contain instances needs to be introduced separately.

Important: If we configure Corejs as version 3, neither the instance method nor the global method will pollute the global environment.

See here, do not know if you have such a question? The corejs configuration for @babel/ plugin-transform-Runtime is so perfect that it can both turn helper functions into references and dynamically introduce polyfills without contaminating the global environment. Why give useBuiltIns to @babel/preset-env, it seems unnecessary.

With that in mind, I created a few new files (simple and basically consistent, with some new features) and built them with WebPack. Here’s what I compared:

The serial number . Babelrc configuration webpack mode production
0 Do not use@babel/plugin-transform-runtime 36KB
1 use@babel/plugin-transform-runtimeAnd set parameterscorejs: 3. It will not pollute the overall environment 37KB
2 use@babel/plugin-transform-runtime, no configurationcorejs 22KB

My guess is that the @babel/runtime-corejs3/XXX package itself is a bit bigger than core-js/modules/XXX

Add-ins/presets supplement knowledge

Order of plugins is important!!

If both conversion plug-ins will work on a snippet of “Program”, they are run in sequence based on the sequence of conversion plug-ins or preset.

  • The plug-in runs before the Presets.
  • The plugins are ordered from front to back.
  • Preset is reversed, (from back to front).

Such as:

{

    "plugins": ["@babel/plugin-proposal-class-properties"."@babel/plugin-syntax-dynamic-import"]

}

Copy the code

Run @babel/plugin-proposal-class-properties and @babel/plugin-syntax-dynamic-import

{

  "presets": ["@babel/preset-env"."@babel/preset-react"]

}

Copy the code

The running order of preset is reversed. @babel/preset-react and @babel/preset-env are run.

The plug-in parameters

Both plug-ins and Preset accept parameters, which consist of an array of plug-in names and parameter objects. The preset setting parameter is also in this format.

Such as:

{

    "plugins": [

        [

            "@babel/plugin-proposal-class-properties".

            { "loose"true }

        ]

    ]

}

Copy the code
The short name of the plug-in

If the plugin name is @babel/ plugin-xxx, the short name @babel/XXX can be used:

{

    "plugins": [

        "@babel/transform-arrow-functions" With "@ Babel / / / the plugin - transform - arrow - functions provides"

    ]

}

Copy the code

If the plug-in name is babel-plugin-xxx, the short name XXX can be used. This rule also applies to plug-ins with scope:

{

    "plugins": [

        "newPlugin"./ / with "Babel - plugin - newPlugin"

        "@scp/myPlugin" / / with "@ SCP/Babel - plugin - myPlugin"

    ]

}

Copy the code

Create a Preset

You can simply return an array of plug-ins

module.exports = function({

    return {

        plugins: [

            "A".

            "B".

            "C"

        ]

    }

}

Copy the code

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

module.exports = function({

    return {

        presets: [

            require("@babel/preset-env")

].

        plugins: [

            [require("@babel/plugin-proposal-class-properties"), { loosetrue}].

            require("@babel/plugin-proposal-object-rest-spread")

        ]

    }

}

Copy the code

The configuration file

Babel supports configuration files in a variety of formats. It doesn’t matter which configuration file you use, as long as your configuration is OK

All Babel API parameters can be configured, but if the parameter requires the JS code to be used, then the JS version of the configuration file may be required.

You can select different configuration files based on application scenarios:

If you want to programmatically create configuration files or compile modules in the node_modules directory: babel.config.js will do the job.

If you just need a simple configuration for a single package:.babelrc will suffice.

babel.config.js

Create a file named babel.config.js in the project root directory.

module.exports = function(api{

    api.cache(true);



    constpresets = [...] ;

    constplugins = [...] ;



    return {

        presets,

        plugins

    };



Copy the code

For details, see the babel.config.js document

.babelrc

Create a file named.babelrc in the project root directory:

{

    "presets": [].

    "plugins": []

}

Copy the code

Refer to the.babelrc documentation for detailed configuration

package.json

You can add the configuration information from.babelrc to the package.json file as the Babel key:

{

    "name""my-package".

    "babel": {

        "presets": [].

        "plugins": []

    }

}

Copy the code

.babelrc.js

Same configuration as.babelrc, but can be written in JS.

// The node.js API can be called from there

const presets = [];

const plugins = [];



module.exports = { presets, plugins };

Copy the code

I don’t know if it is comprehensive, but I really can’t write it. (If there is incomplete, I will add it later.)

Refer to the link

  1. Babel document

  2. Personal understanding of Babel 7 use

  3. One (very long) breath understands Babel

  4. core-js@3 is a surprise


Follow the public account of “little sister”