众所周知某些浏览器对ES6+的支持程度堪忧,然而迫于产品的压力我们不得不去像其妥协。所以前端的代码中不可避免的会有一些polyfill代码,polyfill的直译是填充材料。我们正是靠polyfill来实现在旧的浏览器上支持一些新的ES6+的JS特性,例如(Promise、async\await、spread语法等等)。实际上这些polyfill的本质就是通过ES5甚至ES3的代码来实现新的特性,以实现在旧的浏览器上能跑新版本的JS。
babel上常用的polyfill莫过于@babel/polyfill
了,其底层是通过core-js来实现polyfill,本文就来小谈一下其使用和效果。
首先安装babel全家桶
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/preset-env": "^7.1.0",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2"
}
然后新增babel配置文件babel.config.js
module.exports = (api) => {
const presets = [
[
"@babel/preset-env", {
"targets": {
"chrome": "70",
"ie": "11"
},
"useBuiltIns": "usage"
}
]
];
api.cache(false);
return { presets };
}
注意两个地方,一个是 targets,这里我写了云泥之别的两个浏览器,一个是当前市面上支持JS新特性最多的Chrome 70,一个是IE 11。后续会拿两者打包后的代码作对比。
还有一个选项是useBuiltIns,下文会说。
先看测试文件 index.js
import util from './util';
function a(args) {
console.log(...args);
console.log([...arguments])
}
里面使用了ES6的展开运算符,以及利用[...arguments]来将类数组的arguments对象转成数组。
util.js文件内容如下
export default async function() {
console.log('hello');
}
单纯的输出了一个async方法。
然后我们开始打包,此时useBuiltIns的取值是usage,targets开启了ie 11: dist/index.js
"use strict";
require("core-js/modules/es6.string.iterator");
require("core-js/modules/es6.array.from");
require("core-js/modules/es6.regexp.to-string");
require("core-js/modules/es7.symbol.async-iterator");
require("core-js/modules/es6.symbol");
require("core-js/modules/web.dom.iterable");
var _util = _interopRequireDefault(require("./util"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
function a(args) {
var _console;
(_console = console).log.apply(_console, _toConsumableArray(args));
console.log(Array.prototype.slice.call(arguments));
}
dist/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
require("regenerator-runtime/runtime");
require("core-js/modules/es6.promise");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function _default() {
return _ref.apply(this, arguments);
}
function _ref() {
_ref = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('hello');
case 1:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
return _ref.apply(this, arguments);
}
可以看到两个文件头部都被自动的引入了core-js的一些modules,值得注意的是,并不是引入完整的core-js,而是根据配置的targets环境和当前JS文件使用了哪些特性来决定需要引入哪些modules。为了验证这一点,我们把targets中的ie 11注释掉,现在只打包chrome 70版本,在chrome中,上述两个文件中用到的语法都是支持的,我们来看一下打包结果: dist/index.js
"use strict";
var _util = _interopRequireDefault(require("./util"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function a(args) {
console.log(...args);
console.log([...arguments]);
}
dist/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
async function _default() {
console.log('hello');
}
果然和预想的一致,两个文件中,除了代码被转成es5以外,新的语法特性完全没有任何变化,直接原样输出。 所以,上面这种场景是我们经常会用到的polyfill场景。
useBuiltIns为usage虽然好,但是我们也得看一下另外一个配置值entry。这里就直接叙述结果了,使用entry的话,我们必须在入口文件处加import 'polyfill';
,否则不会自动引入。 然后babel会在打包时,将当前环境需要的所有的polyfill module全部在入口文件引入,这里相对于前面的usage来说,就可能引入多余的polyfill module。
useBuiltIns 还有一个默认值 false,这个就很好理解,完全手动polyfill,哪个文件需要polyfill,你就自己在那个文件的顶部引入polyfill。