babael 是什么
官网上说,babel 是一个工具链,主要用来对 es5+ 的语法做向后兼容,以便我们能在编码的过程中愉快的使用 js 的新特性,而不用担心坑爹的老旧版浏览器执行出错。
官方大大还列了一下 babel 干的具体的事儿:
-
语法转换
这个语法转换实际来讲,就是语法糖的逆向工程反糖化,被举得最多的就是 es6 真香系列的箭头函数:
// 转换前 () => { console.log('babel is a der') } // 转换后 (function () { console.log('babel is a der') }) -
API 兼容
官方说的是,通过
Polyfill方式在目标环境中添加缺失的特性,我更倾向于理解成就是对一些API的补充兼容。这里的Polyfill也是一个插件包,这个包是专门针对API的。有的同学可能听的就有点迷,我一直在强调
API,那我嘴里讲的API兼容到底是个啥呢?比如说,在
es6之前,Array原始对象和Object原始对象上,是没有像Array.form和Object.assign这些静态方法的,什么是没有?就是Array.form会提示.form是undefined,即当前js环境下不存在这个东西。那么
Polyfill干了啥呢,Polyfill可以在代码中给你塞个垫片进去:// 转换前 Array.from('babel is a der') // 转换后 var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from")); (0, _from.default)('babel is a der')看到没有,它相当于是给你手动引入了一个实现,就是你没有就给你塞一个进去,然后再让你去执行,这就是垫片。
-
源码转换
babel代码转换的核心,其实就是通过AST语法树来实现的,至于AST语法树的相关知识,我们在这里不做过多展开。
Babel 到底要怎么配置 ?
这是一个最初令我异常头疼的问题,它里面由于历史原因夹杂着各种错综复杂的线,很难分清楚什么是什么。今天,我们就来把它有条有理的归个类。
在讲这个之前,我们还得补充一个知识点,那就是,babel 所有的转换工作,都是依赖与插件包的,它甚至将自己的核心库 babel-core 都解耦出去变成了一个插件包。
presets 式配置
闲话少说,咱们先上代码:
// 包安装
~ yarn add @babel/core -D // 核心转换插件
~ yarn add @babel/preset-env -D // 语法转化插件集成库
~ yarn add core-js@3 // API 转化插件集成库(presets 模式下)
~ yarn add @babel/plugin-transform-runtime -D // 插件模块化工具,优化垫片 inject,全局变量污染
/*
* 注:`core-js@2` 分支中已经不会再添加新特性,新特性都会添加到 `core-js@3`
*/
// .babelrc.json 文件
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
],
plugins: ["@babel/plugin-transform-runtime"]
}
// .browserslistrc 文件
> 0.25%
not dead
安装包
首先,我们先来看一下我们需要的安装包
@babel/core:
core-js 其实就是 @babel/polyfill 的后代产物,core-js npm 上的定义:
- 它是 js 的一个标准模块化库
- 它包含了
ECMAScript2021 年最新更新的Promise、Symbol、Collection、Iterator等一系列的最新特性,也就是API。 - 另外还包含
WHATWG/W3C关于跨平台的一些提案。
所以,我们不难看出,core-js 这个包,干的就是以前 @babel/polyfill 干的活儿
core-js 是以预设配置项的形式配置在 env 包的配置项里的,它是以 env 包的工具形式存在的,也就是说,在进行语法转换的时候,env 包自己会转换新语法糖,API 的转换则是通过控制调用 core-js 去实现的。
@babel/preset-env
预设选项,它是一个插件包,它里面集成了一系列的语法转换依赖插件,用来转换像 () => {},2 ** 3, async { await } 等新语法糖。
targets
首先,这个 targets 是干什么的呢 ?
babel 官网对它的定义是,指定目标环境。也就是说,这个参数是让开发者告诉 babel ,我们的代码最后是要放到哪些浏览器环境下去执行的,这样 babel 才能根据目标环境去判断出当前需要兼容的特性,然后按需去引入单个插件。
这个参数在上面的配置里没有出现,但是并不是说没有用到它,我们配置文件里单独的 .browserslistrc 文件,就是对它的一个配置使用。它其实是可以以参数的形式存在:
"presets": [
["@babel/preset-env", {
...
"targets": "> 0.25%, not dead"
}]
]
官方推荐是使用 .browserslistrc 文件去指明编译的 target,因为这个配置文件还可以和autoprefixer、stylelint等工具一起共享配置。
presets
注释:
@babel/preset-env后文简称env
presets 在配置里叫做插件预设配置,它的可选参数目前就只有一个,那就是 env。env 是一个插件包,它里面集成了一系列的语法转换依赖插件,用来转换像 () => {},2 ** 3, async { await } 等新语法糖。
[
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
]
上面的这种写法,其实是对 env 做的一个单独的配置,这两个配置的作用是告诉 env 怎么去处理 polyfills 的。
在讲 useBuiltIns 之前,我们还得去补一个知识点,那就是 env 的另一个参数,
经过多方取证,我发现,@babel/preset-env 这个包,其实还是偏语法转换的,API 兼容它其实还是做不了,证据是,在 presets 式配置里,必须要安装core-js 这个依赖,我用 babel-cli 单独打包出来的文件里,兼容插件也都是从 core-js 这个包里导出来的。
好,那么 core-js 这个包,到底是个啥呢 ?
useBuiltIns
useBuiltIns 一共有 3 个参数:false、entry、usage, 默认为 false
首先,useBuiltIns 正在有实际作用的前提是,我们引入过 core-js 。
-
当参数为
false时不做任何处理; -
当参数为
entry时,我们需要在入口文件开头处手动引入core-js和regenerator-runtime,然后,env会根据我们配置的目标浏览器环境把core-js进行拆包,再将当前环境下需要的垫片在入口文件处全部引入;这个配置会有两个问题,1 是包冗余会比较严重,很多包引入进来后其实没有发挥真正的作用;2 是垫片会直接加进全局变量或对象原型,造成比较严重的变量污染。
~ yarn add regenerator-runtime // index.js 入口文件 import "core-js/stable" import "regenerator-runtime/runtime" -
当参数为
usage时,不需要手动引入core-js,env会在解析过程中会对单个文件进行分析,然后按需导入当前文件需要的包。这个配置解决了包冗余的问题,但是变量污染依然存在。
plugin-transform-runtime
plugin-transform-runtime 是 babel 基于 useBuiltIns 中所出现的问题,给出的解决方案,首先,plugin-transform-runtime 是一个插件,需要配置在配置在配置文件的 plugins 选项里,然后,plugin-transform-runtime 具体干了以下两件事:
-
将垫片的直接注入改为了引用模式,也就是说,当多个文件同时用到一个垫片时,就不会有多次代码注入,而是将该垫片模块化后保存在
@babel/runtime/helpers/下的空间里,然后在需要的文件里引入。 -
模块化解决了全局变量污染问题,并且提供了不污染原型的解决方案。
plugins 式配置
同样,我们先上代码
// 包安装
~ yarn add @babel/core -D
~ yarn add @babel/preset-env -D
~ yarn add @babel/runtime-corejs3 // 作用同 presets 模式下的 core-js@3
~ yarn add @babel/plugin-transform-runtime -D
// .babelrc.json 文件
{
"presets": ["@babel/preset-env"]
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs" 3
}]
]
}
plugins 式配置的 .browserslistrc 文件不用修改,它与 presets 最大的差别是,将 core-js 的处理权交给了 plugin-transform-runtime 插件。
语法部分的转换当然还是交给了 env 下面我来着重看看 plugins。
plugins
plugins 接收一个数组作为参数,数组里装的是单个插件,也就是说,plugins 是更细粒度化的插件配置,plugins 不同于 presets 的是:
plugins的路子更广,可以配置一些构建层的工具;plugins执行的顺序是优先于presets的。
plugin-transform-runtime
在 presets 式配置方案里,我们聊过这个插件,但是它的其实还能通过参数配置,去做一些扩展,即,它也能处理 core-js。扩展的方法也很简单,只要给 plugin-transform-runtime 配置上
corejs 即可。
runtime-corejs3
在 plugin-transform-runtime 配置 core-js 时,不能使用 presets 配置里的 core-js@3 这个包了,它依赖的是 @babel/runtime-corejs3 这个包。
几点补充
给参数添加配置项
presets 或 plugins 里,如果要给参数单独添加配置,首先要把参数占位变成一个数组,然后再将原有参数放在该数组的第一位,在第二位上补一个对象,然后再将配置写在对象里:
// 配置前
{
"presets": ["@babel/preset-env"]
}
// 配置后
{
"presets": ["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
}
配置的执行顺序
plugins 的配置会在 presets 前运行;plugins 里的配置会从前往后执行,presets 里的配置会从后往前执行。
参考文献: