Do you still need Polyfill after using Babel???

Time:2021-7-30

A colleague told me such an interview question two days ago. When the interviewer came up, he asked him, “do you still need Polyfill when Babel is used in the project?” At first, his heart was confused. How could he still have such a problem of not playing cards according to the routine? According to the basic principles of the interview, the answer must be needed. Otherwise, how can he ask further. So he said “yes”. When the interviewer dug deep, he finally couldn’t stand it.
In fact, most of the scaffolds used in the process of development are ready-made scaffolds. They rarely look at the configuration of Babel carefully, and they don’t dig into the usage of Babel. So today I’ll give you a good answer to this question.

First of all, the Babel we will talk about later is based on 7.10.0.

What is Babel

Throw out the definition of Chinese official documents

Babel is a tool chain, which is mainly used to convert the code of ECMAScript 2015 + version into backward compatible JavaScript syntax, so that it can run in current and old versions of browsers or other environments.
Here’s what Babel can do for you:

  • grammatical transformation
  • Add missing features in the target environment by Polyfill (via@babel/polyfillModule)
  • Source code conversion (codemods)
  • More( View thesevideoGet inspired)

After reading the official definition, do you think Babel can finish all backward compatibility things alone (I have to say it’s a little misleading), but it’s not at all.

The real situation is that Babel only provides a “platform” for more capable plugins to settle in my platform. These plugins provide the ability to convert the code of ECMAScript 2015 + version into backward compatible JavaScript syntax.

So how did he do it? This has to mention the famous ast.

How Babel works

Babel’s work process is divided into three stages:Parsing, transforming, printing

  • In the parsing phase, Babylon inside Babel is responsible for transforming ES6 code into an abstract syntax tree after syntax analysis and lexical analysis
  • Babel traverse in the transforming phase is responsible for transforming the abstract syntax tree
  • The Babel generator inside the printing phase is responsible for generating the corresponding code

The second step of transformation is the most important. Babel’s plug-in mechanism also plays a role in this step. Plugins operate here, convert it into a new ast, and then hand it over to Babel generator in the third step.
Therefore, as we said above, if these plugins are not stationed on the platform, the “platform” of Babel does not have any capabilities. It’s like this:

const babel = code => code;

So we can confidently say the answer “need”. You need not only Polyfill, but also a large number of plugins.

Let’s illustrate with examples and what plugins are needed to perfectly convert the code of ECMAScript 2015 + version into backward compatible JavaScript syntax.

Preset Env, Polyfill, plugin transform runtime differences

Now let’s create an example with NPM init – y, and then install @ Babel / CLI and @ Babel / core.
Refer to this screenshot for the code structure, through the commandbabel index.js --out-file compiled.jsCompile the index file into compiled.js with Babel
Do you still need Polyfill after using Babel???

// index.js
const fn = () => {
  console.log("wens");
};
const p = new Promise((resolve, reject) => {
  resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);

Without any plugins

To confirm the above statement, we first test the case without any plugins, and the results are as follows

//compiled.js
const fn = () => {
  console.log("wens");
};

const p = new Promise((resolve, reject) => {
  resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);

The compiled file has no change, which confirms our above statement. Next, let’s join plugins.

Before joining the plugins test, we need to know some pre knowledge. Babel divides the ECMAScript 2015 + code into two cases:

  • Syntax layer: let, const, class, arrow function, etc. these need to be translated during construction, which refers to the translation at the syntax level
  • API method layer: promise, includes, map, etc. These are new methods added on the global or prototype of object, array, etc. they can be redefined in the corresponding Es5 way

Babel’s translation of these two cases is different, and we need to give the corresponding configuration.

Join preset env

The above example is const. The arrow function belongs to the syntax level, while promise and map belong to the API method level. Now let’s add preset env to see the effect

// babel.config.js
module.exports = {
  presets: ["@babel/env"],
  plugins: []
};

The presets configuration item officially defined by Babel represents a collection of plugins. It saves us from writing plugins one by one. It directly defines presets for dealing with react, typescript, etc

//compiled.js
"use strict";
var fn = function fn() {
  console.log("wens");
};

var p = new Promise(function (resolve, reject) {
  resolve("wens");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});

Sure enough, they were downgraded from the grammatical level. So how to deal with it at the API level?
Now let’s join@bable/polyfill

Join Polyfill

For the definition of Polyfill, I believe that students who often visit MDN will not be unfamiliar. He rewrites the methods that are not supported by the current browser to obtain support.

Install @ bable / Polyfill in the project and import it into the index.js file

// index.js
import "@bable/polyfill";
const fn = () => {
  console.log("wens");
};
const p = new Promise((resolve, reject) => {
  resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);

Compile again and let’s see the results

// compiled.js
"use strict";

require("@bable/polyfill");

var fn = function fn() {
  console.log("wens");
};

var p = new Promise(function (resolve, reject) {
  resolve("wens");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});

Without other changes, there is an additional line of require (“@ bable / Polyfill”). In fact, all Polyfill in this library are introduced here, just like all lodash methods in our project. This supports promise and map methods.

Careful students will find that this is a bit “stupid”. Lodash provides on-demand loading. You have introduced it all at once, but I only need promise and map. Don’t panic. Let’s keep looking down.

Configure usebuiltins

Up there, we passimport "@bable/polyfill"To achieve “smoothing” at the API level. However, since Babel v7.4.0, officials have not recommended this approach.
Because introducing @ bable / Polyfill is equivalent to introducing the following two libraries into the code

import "core-js/stable"; 
import "regenerator-runtime/runtime";

This means that not only can’t load on demand, but also the global space is polluted. Because it is implemented by adding methods to the prototype of global objects and built-in objects.

Therefore, Babel decided to hand over the work of these two people to @ Babel / env mentioned above, which not only won’t cause global pollution, but also supports on-demand loading. Isn’t it wonderful. Now let’s look at the modified configuration

// index.js
//Removed Polyfill
const fn = () => {
  console.log("wens");
};
const p = new Promise((resolve, reject) => {
  resolve("wens");
});
const list = [1, 2, 3, 4].map(item => item * 2);
// webpack.config.js
module.exports = {
  presets: [
    [
      "@babel/env",
      {
        Usebuiltins: "usage", // load on demand
        corejs: { 
          version: 3, 
          proposals: true 
        }
      }
    ]
  ],
  plugins: []
};

By configuring @ Babel / env with usebuiltins and corejs attributes, we realized the on-demand loading of Polyfill method. For all configuration items, seeOfficial documents

// compiled.js
"use strict";

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

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

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

var fn = function fn() {
  console.log("wens");
};

var p = new Promise(function (resolve, reject) {
  resolve("wens");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});

The compiled JS file only requires the required methods, which is perfect. So do we still have room for optimization? Yes, yes, we’ll look down.

Add @ Babel / plugin transform runtime

Transform the above example

// index.js
class Person {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(this.name);
  }
}

Just convert one person class. Let’s see what the converted file looks like

// compiled.js 
"use strict";

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

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

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(name) {
    _classCallCheck(this, Person);

    this.name = name;
  }

  _createClass(Person, [{
    key: "say",
    value: function say() {
      console.log(this.name);
    }
  }]);

  return Person;
}();

In addition to the require part, there are many custom functions. Students, think about it. Now there is only one index file to be converted, but there will be a large number of files to be converted in the actual project development. If there are the same functions in each converted file, isn’t it a waste? How can we remove the duplicate functions?

The plugin transform runtime comes on stage.

What appears above_ classCallCheck,_ defineProperties,_ The three createclass functions are called auxiliary functions, which assist Babel in the compilation stage.

When the plugin transform runtime plug-in is used, the inline auxiliary functions added to the file during Babel translation can be uniformly isolated into the helper module provided by Babel runtime. During compilation, they are loaded directly from the helper module without repeatedly defining the auxiliary functions in each file, so as to reduce the size of the package. Let’s see the following effects:

// webpack.config.js
module.exports = {
  presets: [
    [
      "@babel/env",
      {
        useBuiltIns: "usage",
        corejs: { version: 3, proposals: true }
      }
    ]
  ],
  plugins: ["@babel/plugin-transform-runtime"]
};
// compiled.js
"use strict";

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

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

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var Person = /*#__PURE__*/function () {
  function Person(name) {
    (0, _classCallCheck2["default"])(this, Person);
    this.name = name;
  }

  (0, _createClass2["default"])(Person, [{
    key: "say",
    value: function say() {
      console.log(this.name);
    }
  }]);
  return Person;
}();

It perfectly solves the problem of code redundancy.
Do you think it’s over? Not yet. Students who carefully see here should notice that although it is used aboveuseBuiltInsThe configuration item implements the on-demand reference of poilyfill, but it still has global variable pollution. For example, this code rewrites the prototype method of array, causing global pollution.

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

Finally, transform Babel’s configuration file again

// webpack.config.js
module.exports = {
  presets: ["@babel/env"],
  plugins: [
    [
      "@babel/plugin-transform-runtime",
      {
        corejs: { version: 3 }
      }
    ]
  ]
};

We see that the @ Babel / env related parameters are removed and the corejs parameter is added to the plugin transform runtime. Finally, the Polyfill require method will not appear in the converted file.

// compiled.js
"use strict";

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

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

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

var Person = /*#__PURE__*/function () {
  function Person(name) {
    (0, _classCallCheck2["default"])(this, Person);
    this.name = name;
  }

  (0, _createClass2["default"])(Person, [{
    key: "say",
    value: function say() {
      console.log(this.name);
    }
  }]);
  return Person;
}();

To sum up, the plugin transform runtime plug-in realizes the following two important functions with the help of Babel runtime

  • Reuse the auxiliary functions to solve the code redundancy when translating the syntax layer
  • Solve the global variable pollution in the translation API layer

end

Finally, it took two days from reading the document to producing the article. I hope the students who see the article can gain as much as I do~~

Recommended Today

Hot! Front and rear learning routes of GitHub target 144K

Hello, Sifu’s little friend. I’m silent Wang Er. Last week, while appreciating teacher Ruan Yifeng’s science and technology weekly, I found a powerful learning route, which has been marked with 144K on GitHub. It’s very popular. It covers not only the front-end and back-end learning routes, but also the operation and maintenance learning routes. As […]