刚接触bable的同学可能会认为使用了Babel,配置了preset预设后就可以愉快的使用 es6+做开发了,事实上,在默认情况下Babel只会做语法转换(let、const、class、箭头函数等),而不做新api的转换,新的api总结起来分为两类:

  • 全局对象和全局对象相关的方法,例如Promise、Map、Set、Object.assign......
  • 实例的新方法,例如数组的find、flat等等......

想让es6+的api在不支持的浏览器上运行,就需要借助polyfill技术,它使用一系列低版本的js代码模拟了浏览器原生api的实现,俗称打补丁。

要使用babel,需要安装@babel/core、@babel/preset-env,如果是直接使用babel编译,还需要安装@babel/cli。

 

1. 手动引入方式补丁包

在babel 7.4之前,只需要安装@babel/polyfill这个包就可以了;从7.4版本开始,虽然@babel/polyfill还会更新,但它内部的core-js包版将一直使用2.x,无法使用core-js 3.x中新增的补丁代码,例如数组的includes方法等,因此官方建议直接安装core-js和regenerator-runtime这两个包。

接下来在在项目的入口文件中引入补丁包:

//index.js
import “core-js”
import “regenerator-runtime/runtime”

也可以在webpack配置文件的entry节点引入,例:“entry”:['core-js', 'regenerator-runtime/runtime', './index.js']。

这种方法会引入所有的补丁代码,导致打包出来的代码体积巨大,因此还需要使用@babel/preset根据目标浏览器版本引入浏览器未实现的api补丁。

改进:

babel使用browserslist库的语法格式来声明浏览器版本,声明浏览器版本既可以在package.json中,也可以在babel配置中,建议在package.json中进行声明,这样其他的工具也可以共享该配置。package.json中的声明方法是增加一个“browserslist”节点,值是数组格式,声明好后可以通过运行npx browserslist命令检查声明是否有效。

接下来在babel的配置文件中为@babel/preset-env增加如下的配置:

module.exports = {
	presets: [["@babel/preset-env", {
	useBuiltIns: "entry",
	"corejs": 3,
	}]],
}

注意:如果引入的是@babel/polyfill库,则corejs版本需要指定为2

 

2. 自动引入方式

开发者无需在入口处引入core-js和regenerator-runtime, babel会根据目标浏览器版本+项目中实际用到的api来按需引入,打包出来的代码体积更小。

要实现自动引入,首先需要在package.json文件中声明目标浏览器及版本,然后可以使用下面两种方法中的一种:

  • 将@babel/preset-env中的useBuiltIns的值改为usage。
  • 使用@babel/plugin-transform-runtime插件。此种方式不需要安装core-js、regenerator-runtime,只需要安装@babel/runtime-corejs3和@babel/plugin-transform-runtime,并将babel配置修改为:
module.exports = {
  presets:['@babel/preset-env'],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 3
    }]
  ]
}

}

 

这两种方式有何差别?以目标浏览器版本为firefox 60、使用数组flat方法为例,两种方法引入的补丁代码如下:

方法1:

"use strict";
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
var arr = [[1, 2, [3, 4]]];
var b = arr.flat();

方法2:

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat"));
var arr = [[1, 2, [3, 4]]];
var b = (0, _flat.default)(arr).call(arr);

 

从上面的代码对比中可以看到,方法1通过修改Array对象的原型链方式实现了api;方法2通过一个局部函数来实现api,避免了对全局对象的污染。既然两种方法都能实现打补丁,该如何选择?若是开发项目,应采用方法1,可节省代码、缩减代码体积;若是开发组件或库等具备复用的代码,应采用方法2,原因是当库和项目使用了同一api,但库的兼容目标需要使用补丁代码,而项目的兼容目标中该api已由浏览器原生实现时,若采用方法1,则库和项目调用该api时,使用的都是js模拟代码,模拟代码的执行效率肯定没有浏览器原生api实现代码执行效率高。