导读
babel是我们能在众多浏览器各种版本中一直使用ECMAscript新特性的工具,我们可以不需要考虑浏览器兼容性的情况下使用新的特性/规范,babel可以在打包的时候将代码转化为我们需要兼容的版本,但由于配置比较复杂,且大部分脚手架都已经帮我们处理了,所以很少人会去了解 babel。
但如果出现由于 babel 配置导致的问题,就很难找到原因。
本文主要是介绍如何使用babel,以及babel的主体结构;
注: 本文使用的babel版本是babel@7.16.12;
应用
其实babel的应用比较复杂,复杂在于需要理解babel的整体架构和调用顺序。e.g.
1、presets/plugin的区别,用哪些presets,用哪些plugin,怎么配置;
2、例如一般我们开发会用的框架react/vue,或者是typescript,在调用babel之前,我们需要先通过对应的编译工具(e.g. vue-loader, ts-loader等)编译为符合ECMAscript规范的js代码,再通过babel编译到我们预期的ECMAscript版本;
在成型项目打包中运用前,我们可以先尝试单独使用babel:
1、创建一个简单的项目,安装依赖;
// 编译的主要逻辑在core中,cli是命令行集成,preset-env是我们最常用的编译规则集合
yarn add -D @babel/cli @babel/core @babel/preset-env
2、创建一个需要编译的js文件,e.g.
// index.js, 需要编译的内容有async..await, const, 箭头函数, array.includes, promise, 字符串模板
(async() => {
const fn = () => {
return new Promise((resolve, reject) => {
const arr = [1,2,3]
setTimeout(() => {
if(arr.includes(1)) {
console.log(`wellcome to bable!`)
}
})
})
}
const wellcome = await fn();
})()
3、执行babel cli;
npx babel index.js --presets=@babel/preset-env
4、stdout
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); }); }; }
_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var fn, wellcome;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
fn = function fn() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("wellcome to bable!");
});
});
};
_context.next = 3;
return fn();
case 3:
wellcome = _context.sent;
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}))();
可以看到除了 promise, array.includes 之外都被translate了。
因为 babel 将 ECMAScript 分两部分,语法和api;
语法部分由 @babel/core 处理,而 api 部分由 polyfill 处理
polyfill
由于引入方式麻烦,或可能导致更大的包体积,babel在 7.4.0版本已废除了 polyfill 这个包。
废除之后,改为在@babel/preset-env引入这两个包(core-js, regenerator),不再需要手动引入 polyfill,而是通过@babel/preset-env的 useBuiltIns 选项进行配置;
"presets": [
["@babel/preset-env",
{
"useBuiltIns": "usage", // "usage" | "entry" | false, default表示不引入 polyfill , usage表示按需加载 polyfill , entry表示需要手动引入 polyfill
"corejs": "3.21", // 指定 corejs版本,默认2.0
}
]
]
不建议使用 'entry' 选项,因为在手动引入, e.g.
import "core-js/stable";
会将所有 api 的引用全部导入,导致包体积过大
// 引入后结果(过多,省略部分包展示)
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.async-iterator.js");
require("core-js/modules/es.symbol.has-instance.js");
require("core-js/modules/es.symbol.is-concat-spreadable.js");
...
core-js && regenerator
core-js 包含了大部分新特性的polyfill, e.g. promises, symbols, collections, iterators, typed arrays...
regenerator 主要是处理 generators/yield, Asynchronous Iteration proposal , spits out efficient JS-of-today.
babel plugin && preset
preset是一系列babel plugin的集合,如果不用preset的话,我们需要这样去配置:
"plugins": [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-async-to-generator",
"@babel/plugin-transform-block-scoped-functions",
...
]
babel的preset如下:
1、@babel/preset-env; // es6,7,8,9...
2、@babel/preset-react; // jsx
3、@babel/preset-typescript; // typescript 语法
4、@babel/preset-flow; // flow,facebook的一个类型编程语言
Babel Preset视为Babel Plugin的集合,preset和plugin的执行顺序如下:
1、先执行完所有Plugin,再执行Preset。
2、多个Plugin,按照声明次序顺序执行。
3、多个Preset,按照声明次序逆序执行。
@babel/preset-env
@babel/preset-env 除了上面提到 useBuiltIns/corejs 配置项外,还有一个重要且复杂的配置项 targets;
这个是 babel 优化打包体积的一个重要配置项。
browserslist
targets 主要是通过 browserslist 这个插件来查询需要兼容的浏览器版本,e.g.
// 全局安装 browserslist 后键入命令,即可查看默认项的浏览器版本列表,babel默认项配置 '> 0.5%, last 2 versions, Firefox ESR, not dead'
browserslist
// 输出
and_chr 98
and_ff 96
and_qq 10.4
and_uc 12.12
android 98
baidu 7.12
chrome 98
chrome 97
chrome 96
...
也可以直接在根目录下生成配置 .browserslistrc, e.g.
// .browserslistrc
Chrome 98
常用配置项
1、 > 5% // 全球超过 5% 的人使用的浏览器,也可以在后面指定[国家代码](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements);
2、last 2 version // 所有浏览器兼容最后两个版本,也可以指定浏览器,e.g. last 1 chrome version
3、dead // 超过24个月没有更新的浏览器,现在特指 IE 10, IE_Mob 11
@babel/plugin-transform-runtime
当 @babel/preset-env 配置了 useBuildIns 后,
1、对于 polyfill 会通过 require 引入:
require("regenerator-runtime/runtime.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
引入的文件会修改原型的方式,污染全局变量
2、对于 @babel/core 编译的语法,会有部分直接通过插入别名函数的方式直接插入到代码中,当打包的时候容易出现重复。
transform-runtime 通过模块引入的方式解决了这两个问题,e.g.
// 需要编译的代码
var p = new Promise()
// 编译后的代码
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
var p = new _Promise()
但同时也有新的问题, transform-runtime 打包出来的文件会比 useBuildIns 更大,因为 transform-runtime 不会处理你配置的 targets ,会将所有的 polyfill 都打包进来;
那我们在什么场景下使用 useBuildIns ,什么场景使用 transform-runtime 呢?
1、当你在开发业务应用,非提供给其他人使用的库时,建议使用 useBuildIns;
2、当你在开发第三方库时,建议使用 transform-runtime ,避免污染;
问题记录
1、是否可以 userBuildIns 和 transform-runtime 公用,e.g.
["@babel/preset-env",
{
"useBuiltIns": "usage", // "usage" | "entry" | false, default to false ()
"corejs": 3,
}
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false
}
]
]
不可以,这样会将corejs 2/3两个版本都打包进去;
参考文章:
1 官网: babeljs.io/docs
2、What is @babel/preset-env and why do I need it?: blog.jakoblind.no/babel-prese…
3、Understanding and properly configuring @babel/env and @babel/transform-runtime: www.jmarkoski.com/understandi…