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 的一个标准模块化库
- 它包含了
ECMAScript
2021 年最新更新的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
里的配置会从后往前执行。
参考文献: