Clickbait lol ~~~

The original address

I believe that many people, like me before, copy and paste Babel configuration from the Internet or use off-the-shelf scaffolding, although this works, but I want you to know why, so this article will do a more complete overview of Babel (babel@7) configuration.

Grammar and API

It’s important to keep in mind that the new features in ES6 can be broken down into syntax and API sections. New syntax like arrow functions, deconstruction, etc. :

const fn = (a)= > {}

const arr2 = [...arr1]
Copy the code

New apis like Map, Promise, and more:

const m = new Map(a)const p = new Promise((a)= > {})
Copy the code

@babel/core

@babel/core, this is the core of Babel, so first install this package

npm install @babel/core
Copy the code

What it does is convert the code based on our configuration file, which is usually.babelrc (static file) or babel.config.js (programmable). In this case, using.babelrc as an example, create an empty file in the root directory of the project named.babelrc, Then create a js file (test.js) to test with:

/* test.js */
const fn = (a)= > {}
Copy the code

Here we install @babel/cli so that we can use Babel at the command line

npm install @babel/cli
Copy the code

After installation, execute Babel compilation, command line input

npx babel test.js --watch --out-file test-compiled.js
Copy the code

As it turns out, the contents of test-compiled.js are still the arrow functions of ES6, so don’t worry, our.babelrc is not configured yet

The Plugins and Presets

Now, out of the box Babel doesn’t do anything. It basically acts like const babel = code => code; by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.

Babel is based on a plug-in architecture. If you don’t provide any plug-ins, Babel doesn’t do anything. So now we want to convert the clipper function to an ES5 function by providing a plug-in for the arrow function:

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

After the transformation, test-compile.js is:

/* test.js */
const fn = (a)= > {}

/* test-compiled.js */
const fn = function () {}
Copy the code

What if I want to use es6’s deconstruction syntax? It’s as simple as adding a destruct plugin:

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

Fortunately, Babel provides presets, which can be understood as a collection of plug-ins, saving us the trouble of introducing plug-ins one by one. Many presets are officially provided. For example, preset-env (a set of plug-ins to deal with ES6 + standard syntax), preset-stage (a set of plug-ins to deal with the proposal syntax), and preset-react (a set of plug-ins to deal with react syntax), etc. Here we mainly introduce preset-env:

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

preset-env

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

Babel preset-env lets you write code using ES6 syntax and preset only the code that needs to be preset. By default, nothing needs to be configured for preset-env, which converts all ES6 + code, however we can provide a targets configuration item to specify the running environment:

/* .babelrc */
{
  "presets": [["@babel/preset-env", {
      "targets": "ie >= 8"}}]]Copy the code

At this point only syntax not supported by ie8 or older will be converted. Looking at our test-compile.js file, we can see that everything is fine:

/* test.js */
const fn = (a)= > {}
const arr1 = [1.2.3]
const arr2 = [...arr1]


/* test-compiled.js */
var fn = function fn() {};
var arr1 = [1.2.3];
var arr2 = [].concat(arr1);
Copy the code

@babel/polyfill

Now let’s change test.js slightly:

/* test.js */
const fn = (a)= > {}
new Promise((a)= > {})


/* test-compiled.js */
var fn = function fn() {};
new Promise(function () {});
Copy the code

We discovered that the Promise had not been converted. What! Ie8 also supports Promise? That’s impossible… . The new syntax can be used to transform using Babel. However, the new API can only be polyfill, so we need to install @babel/polyfill and simply modify test.js as follows:

/* test.js */
import '@babel/polyfill'

const fn = (a)= > {}
new Promise((a)= > {})


/* test-compiled.js */
import '@babel/polyfill';

var fn = function fn() {};
new Promise(function () {});
Copy the code

The code now runs perfectly in IE8, but there is one problem: the @babel/ Polyfill package is too big, we just need Promise, if we can polyfill on demand. Luckily, preset-env provides just this feature:

/* .babelrc */
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "useBuiltIns": "entry",
      "targets": "ie >= 8"
    }]
  ]    
}
Copy the code

We just need to add a useBuiltIns configuration item to preset-env. The value can be Entry and Usage. If entry is used, all polyfills that don’t support apis in IE8 + will be introduced at the entry, as follows:

/* test.js */
import '@babel/polyfill'

const fn = (a)= > {}
new Promise((a)= > {})


/* test-compiled.js */
import "core-js/modules/es6.array.copy-within";
import "core-js/modules/es6.array.every";
import "core-js/modules/es6.array.fill"; .// omit several introductions
import "core-js/modules/web.immediate";
import "core-js/modules/web.dom.iterable";
import "regenerator-runtime/runtime";

var fn = function fn() {};
new Promise(function () {});
Copy the code

The import ‘@babel/polyfill’ will disappear after the transform, and the import ‘core-js/… In fact, the @babel/ Polyfill package itself has no content. It relies on core-js and Regenerator-Runtime, which provide the es6+ specification runtime environment. So when we don’t need to polyfill on demand we just import @babel-Polyfill, it imports both core-JS and Regenerator-Runtime, and when we need to polyfill on demand we just configure useBuiltIns, It automatically introduces core-JS and Regenerator-Runtime on demand, depending on the target environment.

UseBuiltIns can also be usage, which is more powerful. It scans your code and introduces polyfills only when your code uses a new API:

/* .babelrc */
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "useBuiltIns": "usage",
      "targets": "ie >= 8"
    }]
  ]    
}
Copy the code

Transform-compiled. Js will be much simpler:

/* test.js */
const fn = (a)= > {}
new Promise((a)= > {})

/* test-compiled.js */
import "core-js/modules/es6.promise";
import "core-js/modules/es6.object.to-string";

var fn = function fn() {};
new Promise(function () {});
Copy the code

Unfortunately, this feature is still experimental, so use it with caution.

In fact, if you are writing an app, the Babel configuration above is almost enough, you may need to add a Plugin and Preset for specific purposes. For example, in the React project, you need to add @babel/ Preset – React in the presets, If you want to use dynamic import, you need to add @babel/plugin-syntax-dynamic-import to your plugins. If you are writing a public library or framework, the following points may require your attention.

@babel/runtime

In some cases, syntax conversions are complex and may require some helper functions, such as converting es6 classes:

/* test.js */
class Test {}


/* test-compiled.js */
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 the example, the es6 class requires a _classCallCheck helper function. If we use the ES6 class in multiple files, then we need to define _classCallCheck for each file, which would be a waste of money. If these helper functions are separated into one package and referenced by all files, you can reduce the amount of code you need. And @babel/ Runtime does exactly that. It provides a variety of helper functions, but how do we know which one to introduce? You can’t do it yourself. In fact, Babel provides a @babel/ plugin-transform-Runtime plugin to automatically introduce helpers. Install @babel/runtime and @babel/plugin-transform-runtime

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

Then modify the Babel configuration as follows:

/* .babelrc */
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "useBuiltIns": "usage",
      "targets": "ie >= 8"
    }]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]  
}
Copy the code

Now look at the test-compiled. Js file, where the _classCallCheck helper is already imported from @babel/runtime:

/* test.js */
class Test {}


/* test-compiled.js */
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";

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

You might look at this and say, this is bullshit! A couple of helper functions would save me so much volume that I wouldn’t bother installing plug-ins. In fact, @babel/ plugin-transform-Runtime has an even more important function, which is to create a sandboxed environment for your code, especially if you are writing common code such as class libraries.

As we mentioned earlier, for es6+ spec apis like Promise, Map, etc., we provide polyfill compatibility with lower version browsers. One side effect of this is that it contaminates global variables, which is fine if you’re writing an app, but can cause problems if you’re writing a common library. Your library may overwrite some global apis. Fortunately, @babel/ plugin-transform-Runtime provides us with a configuration item, corejs, which isolates these variables in local scope:

/* .babelrc */

{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "targets": "ie >= 8"
    }]
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "corejs": 2
    }]
  ]  
}
Copy the code

Note: Corejs must be configured and @babel/runtime-corejs2 installed. If this is not configured, @babel/plugin-transform-runtime does not introduce polyfill helpers by default. Corejs is currently specified as 2, which can be approximated as the @babel/ Runtime version. Now let’s look at what test-compiled.js is converted to:

/* test.js */
class Test {}
new Promise((a)= > {})


/* test-compiled.js */
import _Promise from "@babel/runtime-corejs2/core-js/promise";
import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck";

var Test = function Test() {
  _classCallCheck(this, Test);
};

new _Promise(function () {});
Copy the code

As we hoped, we have created a sandbox environment for Promise’s Polyfill.

Finally, we’ll add a little more to test.js:

/* test.js */
class Test {}
new Promise((a)= > {})

const b = [1.2.3].includes(1)


/* test-compiled.js */
import _Promise from "@babel/runtime-corejs2/core-js/promise";
import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck";

var Test = function Test() {
  _classCallCheck(this, Test);
};

new _Promise(function () {});
var b = [1.2.3].includes(1);
Copy the code

As you can see, the includes method does not include any helper functions, but it is also an API in ES6. This is because includes is an instance method of an Array, and to polyfill you have to modify the prototype of the Array, which would pollute the global environment, so @babel/ plugin-transform-Runtime cannot handle instance methods of the ES6 + specification.

tips

The above is basically all the content of this article. Finally, there is a summary and some points to pay attention to:

  1. This article does not mentionpreset-stageIn fact babel@7 is no longer recommended. If you need to use the syntax that is still being proposed, just add the plugin.
  2. For normal projects, it can be used directlypreset-envConfiguration polyfill
  3. For class library projects, this is recommended@babel/runtimeNote the use of some instance methods
  4. The content of this article is based on babel@7. If you have any problems in the project, you can try to update itbabel-loaderThe version of the… To be added

The full text after