It’s been years since ES6 was officially released in 2015. The latest browsers are closing in on 100% support, but for the minority user experience, we will most likely need IE9 compatibility. Babel defaults to transcoding ES6’s new syntax and does not convert new apis, such as Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise, etc. And some methods defined on global objects (object. assign, array. from) do not transcode, so we need to provide polyfill.

Babel and polyfill

If you are new to Babel, you may think that you can use ES6 painlessly after using Babel. In a word, Babel compilation does not polyfill. So what does polyfill mean? A filling material used for clothing, bedding, etc

const foo = (a, b) => {
    return Object.assign(a, b);
};
Copy the code

When we write code like the one above and give it to Babel to compile, we get:

"use strict";
 var foo = function foo(a, b) {
    return Object.assign(a, b);
 };
Copy the code

The arrow function is compiled as a normal function, but the Object. Assign method has not been changed yet, and as a new method in ES6, it is not available in browsers such as IE9. Why don’t the Object. The assign compiled into (Object) assign | | function () {/ *… */})? Good question! To ensure correct semantics, compilation only transforms the syntax rather than adding or modifying existing properties and methods. So instead of handling object. assign, Babel is the right way to do it. A solution that handles these methods is called a polyfill.

babel-plugin-transform-xxx

To solve this problem, Babel provides a series of transform plugins. For example, for object. assign, we can use babel-plugin-transform-object-assign:

npm i babel-plugin-transform-object-assign

# in .babelrc
{
  "presets": ["latest"]."plugins": ["transform-object-assign"]}Copy the code

Here’s some test code for you to try. Before compiling the code, we get:

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; }}}return target; };

var foo = exports.foo = function foo(a, b) {
  return _extends(a, b);
};
Copy the code

Babel-plugin-transform-object-assign was replaced with ES5 or earlier before we used object. assign. It looks good, but if you look at it a little more closely, you’ll find this:

// another.js
export const bar = (a, b) => Object.assign(a, b);

// index.js
import { bar } from './another';

export const foo = (a, b) => Object.assign(a, b);
Copy the code

Compiled to:

/***/ index.js:
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.foo = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; }}}return target; };

var _another = __webpack_require__(212);

var foo = exports.foo = function foo(a, b) {
  return _extends(a, b);
};

/***/ }),

/***/ another.js:
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; }}}return target; };

var bar = exports.bar = function bar(a, b) {
  return_extends(a, b); }; / * * * /})Copy the code

Plugin-transform references are module-level, which means repeated references are used by multiple modules, which can be disastrous in multi-file projects. We don’t want to add plugins one by one. It would be nice if they could be introduced automatically.

babel-runtime & babel-plugin-transform-runtime

The main problem mentioned above is that methods are introduced inline, directly inserting a line of code that cannot be optimized. With this in mind, Babel provides babel-plugin-transform-Runtime, which automatically imports corresponding methods from core-JS from a unified place.

npm i -D babel-plugin-transform-runtime
npm i babel-runtime

# .babelrc
{
  "presets": ["latest"]."plugins": ["transform-runtime"]}Copy the code
  • Install dev dependency babel-plugin-transform-runtime.
  • Install production dependency babel-Runtime (whether to rely on it in production depends on how you publish code, it’s always a good idea to put it directly in dependency)

When everything is ready, it will automatically introduce the methods you are using at compile time. But automatic means not necessarily accurate

export const foo = (a, b) => Object.assign(a, b);

export const bar = (a, b) => {
    const o = Object;
    const c = [1, 2, 3].includes(3);
    return c && o.assign(a, b);
};
Copy the code

Will compile to:

var _assign = __webpack_require__(214);
var _assign2 = _interopRequireDefault(_assign);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var foo = exports.foo = function foo(a, b) {
    return (0, _assign2.default)(a, b);
};

var bar = exports.bar = function bar(a, b) {
    var o = Object;
    var c = [1, 2, 3].includes(3);
    return c && o.assign(a, b);
};
Copy the code

Foo’s assign method will be replaced with require’s, whereas bar’s indirect call is useless. At the same time, since babel-plugin-transform-runtime is still not global, instantiated object methods cannot be polyfilled. Calls such as [1,2,3].includes that rely on global array.prototype. includes are still unavailable.

babel-polyfill

The flaw common to both polyfill schemes is scope. So Babel directly provides babel-polyfill that is globally compatible with all es2015 methods. After installing babel-polyfill, you simply import ‘babel-polyfill’ in main.js to introduce it. If you use Webpack, you can also add the entry babel-Polyfill directly to the entry.

import 'babel-polyfill';

export const foo = (a, b) => Object.assign(a, b);
Copy the code

When babel-polyfill was added, the packaged pollyfill. Js was expanded to 251KB (uncompressed). You can also see the packing results in all of the following ways.) Search polyfill.js and you’ll find global changes like this:

//polyfill
`$export($export.S + $export.F, 'Object', {assign: __webpack_require__(79)});
Copy the code

Babel-polyfill inserts all polyfill code in front of the project code to create a perfect ES2015 environment for your application. Babel recommends using Babel-Polyfill in web applications, as long as you don’t mind its slightly larger size (86KB after min), it’s definitely safest to just use it. It is worth noting that since babel-Polyfill makes changes globally, it does not need to be referenced multiple times and conflicts may result, so it is best to extract it as a common Module and place it in the project vendor, or simply extract it as a file and place it in the CDN.

If you’re developing a library or framework, babel-Polyfill is a bit bulky, especially if you’re actually using only one Object.assign. To make matters worse, it is impossible for a library to change its global environment. No one wants to use your library with an old polyfill that changes the global object. The babel-plugin-transform-Runtime, which does not pollute the global environment, is the most appropriate.

babel-preset-env

Back to application development. Using babel-Runtime auto-recognition code to introduce polyfill to optimize is not a good idea. Is it impossible to optimize? And it isn’t. Remember Babel recommended using babel-preset-env? It can determine what compilation needs to be done based on the specified target environment. Babel-polyfill can also be used to choose the desired polyfill for a given target environment, just introduce babel-Polyfill and declare useBuiltIns in babelrc, Babel automatically replaces the introduced babel-Polyfill with the required polyfill.

  1. Targets Specifies the browser type and version to be compatible with.
  2. If you’re developing with Node.js, you can also specify the Node version, or you can write “Node “: “current”, which will automatically adopt the node.js version you’re currently using to run Babel
  3. Modules Specifies the modularity mode. Supports AMD, UMD, SystemJS, CommonJS, and so on. Of course, back in the Webpack 2/3 era, it was recommended to set Modules to False, leaving modularity to Webpack and reducing the size of packaged JS files with its TreeShaking feature
# .babelrc
{
  "presets": [["env", {
      "modules": false."targets": {
        "browsers": ["last 2 versions"."safari >= 7"."IE >= 9"]."node": "current", // Automatically adopt the version of Node.js you are currently using to run Babel"modules": false 
      },
      "useBuiltIns": "entry", // entry usage false
      include: []
    }],
    "stage-2"]."plugins": ["transform-vue-jsx"."transform-runtime"]."env": {
    "test": {
      "presets": ["env"."stage-2"]."plugins": ["transform-vue-jsx"."transform-es2015-modules-commonjs"."dynamic-import-node"]}}}Copy the code

Compare the compiled file size with “IE >= 9” and “Chrome >= 59” :

An Asset Size Chunks polyfill 0 [emitted] [big] ie9.js 16 kB 1 [emitted] chrom.js 16 kB 2 [emitted] Transform-runtimes 17.3 kB 3 [emitted] transform-plugins.js 3.48 kB 4 [emitted]Copy the code

That’s down to nearly 30% on current IE9 requirements, but chrome also needs a 30KB Polyfill, presumably to fix some minor specification issues with V8.

polyfill.io

That should be enough, but it’s essentially a sacrifice for good users who want to use the latest browsers. If you’re smart enough, you might have figured out a way to optimize for a browser by choosing Polyfill. That’s right! A service offered by Polyfill. IO.

You can try to request https://cdn.polyfill.io/v2/polyfill.js this file under a different browser, the server will determine the browser UA return different polyfill file, you have to do is just on the page into the file, Polyfill is automatically resolved in the most elegant way. To our delight, Polyfill. IO not only provides CDN services, but also open source its own implementation scheme, Polyfill-Service. With a simple configuration, you can have your own Polyfill Service.

Everything looks great, but please think twice before using it. Can Polyfill. IO accurately calculate UA in the face of the strange domestic browser environment? If polyfill is missing, do you have any remedy plan? However, it’s a great idea and solution, and maybe more sites will adopt polyfill. IO in the future. Take theguardian and redux author Dan’s proposal for create-react-app (though it wasn’t accepted).