前言
babel只能转义ES6语法,比如箭头函数,但是遇到ES6新增的api就无能为力了,比如Promise和includes。对于这些新增的api,需要polyfill去做兼容。babel提供了两个plugin来处理这些api的polyfill。
@babel/preset-env
如果使用preset-env来处理polyfill,需要安装@babel/polyfill。
@babel/preset-env通过配置项,能够智能的帮你处理ES6的语法。它通过读取项目中的browserslist配置,来决定需要对哪些语法进行处理。一个配置的例子
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "58",
"ie": "11"
}
"useBuiltIns": "entry",
"modules": false,
}
]
]
}
复制代码
options.targets
用来配置需要支持的的环境,不仅支持浏览器,还支持node。
如果没有配置targets选项,就会读取项目中的browserslist配置项。
options.loose
默认值是false,如果preset-env中包含的plugin支持loose的设置,那么可以通过这个字段来做统一的设置。
options.modules
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
,默认值是auto
用来转换ES6的模块语法。如果使用false,将不会对文件的模块语法进行转化。
如果要使用webpack中的一些新特性,比如tree shaking 和 sideEffects,就需要设置为false,对ES6的模块文件不做转化,因为这些特性只对ES6的模块有效。
options.useBuiltIns
"usage" | "entry" | false
,默认值是false
这个配置项主要是用来处理@babel/polyfill。
- 设置为false时,会把@babel/polyfill这个包引进来,忽略targets配置项和项目里的browserslist配置
- 设置为entry时,在整个项目里,只需要引入一次@babel/polyfill,它会根据targets和browserslist,然后只加载目标环境不支持的api文件
- 设置为usage时,babel会在目标环境的基础上,只加载项目里用的那些不支持的api的文件,做到按需加载
其他的一些配置项可以看 官方文档
@babel/plugin-transform-runtime
这个插件可以通过一些helper函数的注入来减少语法转换函数的开销。
const c = {...b};
复制代码
通过babel编译以后,对象的解构语法会被编译为下面的文件
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 c = _extends({}, b);
复制代码
如果很多文件都用到了解构语法,那么每个文件都会生成一个相同的_extends方法,@babel/plugin-transform-runtime会使用helper函数来处理对应的语法,避免这种重复定义方法的问题,使用这个插件后,会生成下面的文件
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
var c = (0, _objectSpread2.default)({}, b);
复制代码
对插件进行配置
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
复制代码
babel7相比于babel6,删除了polyfill和useBuiltIns这两个配置。
options.corejs
boolean or number
,默认值是false
e.g. ['@babel/plugin-transform-runtime', { corejs: 2 }]
当设置为false时,只对语法进行转换,不对api进行处理
当设置为2的时候,需要安装@babel/runtime-corejs2,这时会对api进行处理。这里需要注意,不能polyfill Array.includes这种需要重写property的api,这种会污染全局变量,需要在项目里使用@babel/polyfill来处理。
options.helpers
默认值是true,用来开启是否使用helper函数来重写语法转换的函数。
options.useESModules
默认值是false,是否对文件使用ES的模块语法,使用ES的模块语法可以减少文件的大小。
// useESModules:false
exports.__esModule = true;
exports.default = function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
复制代码
// useESModules:true
export default function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
复制代码
@babel/plugin-transform-runtime默认情况下安装@babel/runtime这个库,即corejs为false时使用;当corejs设置为2时,需要安装使用@babel/runtime-corejs2。runtime和runtime-corejs2这两个库唯一的区别是,corejs2这个库增加了对core-js这个库的依赖,而core-js是用来对ES6各个语法polyfill的库,所以在corejs为false的情况下,只能做语法的转换,并不能polyfill任何api。
@babel/polyfill && @babbel/runtime
polyfill和runtime都可以用来对api进行垫片处理,但是两者还有一定的不同。使用polyfill的时候,会污染全局变量;而runtime的时候,会使用局部变量来处理,不会污染全局变量。这也是为什么runtime无法处理原型上的api的原因,因为要模拟这些api,必须要污染全局变量。
core-js
@babel/polyfill和@babel/runtime-corejs2都使用了core-js(v2)这个库来进行api的处理。
这个库有两个核心的文件夹,分别是library和modules。runtime使用library这个文件夹,polyfill使用modules这个文件夹。
- library使用helper的方式,局部实现某个api,不会污染全局变量
- modules以污染全局变量的方法来实现api
library和modules包含的文件基本相同,最大的不同是_export.js这个文件。
core-js/modules/_exports.js
文件如下
var global = require('./_global');
var core = require('./_core');
var hide = require('./_hide');
var redefine = require('./_redefine');
var ctx = require('./_ctx');
var PROTOTYPE = 'prototype';
var $export = function (type, name, source) {
var IS_FORCED = type & $export.F;
var IS_GLOBAL = type & $export.G;
var IS_STATIC = type & $export.S;
var IS_PROTO = type & $export.P;
var IS_BIND = type & $export.B;
var target = IS_GLOBAL ? global : IS_STATIC ? global[name] || (global[name] = {}) : (global[name] || {})[PROTOTYPE];
var exports = IS_GLOBAL ? core : core[name] || (core[name] = {});
var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});
var key, own, out, exp;
if (IS_GLOBAL) source = name;
for (key in source) {
// contains in native
own = !IS_FORCED && target && target[key] !== undefined;
// export native or passed
out = (own ? target : source)[key];
// bind timers to global for call from export context
exp = IS_BIND && own ? ctx(out, global) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
// extend global
if (target) redefine(target, key, out, type & $export.U);
// export
if (exports[key] != out) hide(exports, key, exp);
if (IS_PROTO && expProto[key] != out) expProto[key] = out;
}
};
global.core = core;
// type bitmap
$export.F = 1; // forced
$export.G = 2; // global
$export.S = 4; // static
$export.P = 8; // proto
$export.B = 16; // bind
$export.W = 32; // wrap
$export.U = 64; // safe
$export.R = 128; // real proto method for `library`
module.exports = $export;
复制代码
core-js/library/_exports.js
文件如下
var global = require('./_global');
var core = require('./_core');
var ctx = require('./_ctx');
var hide = require('./_hide');
var has = require('./_has');
var PROTOTYPE = 'prototype';
var $export = function (type, name, source) {
var IS_FORCED = type & $export.F;
var IS_GLOBAL = type & $export.G;
var IS_STATIC = type & $export.S;
var IS_PROTO = type & $export.P;
var IS_BIND = type & $export.B;
var IS_WRAP = type & $export.W;
var exports = IS_GLOBAL ? core : core[name] || (core[name] = {});
var expProto = exports[PROTOTYPE];
var target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE];
var key, own, out;
if (IS_GLOBAL) source = name;
for (key in source) {
// contains in native
own = !IS_FORCED && target && target[key] !== undefined;
if (own && has(exports, key)) continue;
// export native or passed
out = own ? target[key] : source[key];
// prevent global pollution for namespaces
exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key]
// bind timers to global for call from export context
: IS_BIND && own ? ctx(out, global)
// wrap global constructors for prevent change them in library
: IS_WRAP && target[key] == out ? (function (C) {
var F = function (a, b, c) {
if (this instanceof C) {
switch (arguments.length) {
case 0: return new C();
case 1: return new C(a);
case 2: return new C(a, b);
} return new C(a, b, c);
} return C.apply(this, arguments);
};
F[PROTOTYPE] = C[PROTOTYPE];
return F;
// make static versions for prototype methods
})(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
// export proto methods to core.%CONSTRUCTOR%.methods.%NAME%
if (IS_PROTO) {
(exports.virtual || (exports.virtual = {}))[key] = out;
// export proto methods to core.%CONSTRUCTOR%.prototype.%NAME%
if (type & $export.R && expProto && !expProto[key]) hide(expProto, key, out);
}
}
};
// type bitmap
$export.F = 1; // forced
$export.G = 2; // global
$export.S = 4; // static
$export.P = 8; // proto
$export.B = 16; // bind
$export.W = 32; // wrap
$export.U = 64; // safe
$export.R = 128; // real proto method for `library`
module.exports = $export;
复制代码
可以看出,library下的这个$export方法,会实现一个wrapper函数,防止污染全局变量。
var p = new Promise();
// @babel/polyfill
require("core-js/modules/es6.promise");
var p = new Promise();
// @babel/runtime-corejs2
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var a = new _promise.default();
复制代码
从上面这个例子可以看出,对于Promise这个api,@babel/polyfill引用了core-js/modules中的es6.promise.js文件,因为是对全局变量进行处理,所以赋值语句不用做处理;@babel/runtime-corejs2会生成一个局部变量_promise,然后把Promise都替换成_promise,这样就不会污染全局变量了。
项目和库开发
在项目开发的时候,使用@babel/polyfill;在开发库的时候,使用runtime。
如果在库开发的时候,用到了一些新的api,可以选择不要处理,把选择权留给开发者,让开发者在项目中使用@babel/polyfill去处理。
因为在项目中都会排除node_modules里面的js文件,加快项目的编译速度。出于这个考虑,不建议使用把env的useBuiltIns设置为usage,这样可能会导致node_modules里面的库使用了某些需要polyfill的api,但是我们又没有引入对应的polyfill文件,在某些环境会出现bug。
在项目里,建议使用entry这个配置,并配合browserslist,对于某些已经被目标环境支持的api不用引入对应的polyfill文件,减少项目的文件体积。