"工欲善其事 必先利其器",Babel是当下前端开发的一把利剑,对于构建工程化、模块化的前端开发而言,将是必需要掌握的技能了
Babel是什么
Babel是一个工具集,主要用于将ES6版本的JS代码转换为ES5等向后兼容的JS代码,从而使代码可以运行在低版本浏览器或其他环境中; 它能帮我们做以下事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js)
- 源码转换(codemods)
概览
如何将 ES2015+ 语法的 JavaScript 代码编译为能在当前浏览器上工作的代码。这将涉及到新语法的转换和缺失特性的修补,要实现这些功能将经历如下步骤
- Babel安装
npm install --save-dev @babel/core @babel/cli @babel/preset-env -D
- 创建Babel配置文件
在项目的根目录下创建一个命名为
babel.config.json的配置文件 ( 如果你使用的是 Babel 的旧版本,则文件名为babel.config.js), 配置文件内容如:
// 最基本的结构
module.exports = {
presets: ["@babel/env"],
plugins: []
}
// 相对较为完善的
module.exports = {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
plugins: []
}
- 利用Babel进行转码
// `src` 目录下的所有代码编译到 `lib` 目录
./node_modules/.bin/babel src --out-dir lib
// 可以利用 npm@5.2.0 所自带的 npm 包运行器将 `./node_modules/.bin/babel` 命令缩短为 `npx babel`
// 将main.js编译输出compiled.js
npx babel main.js -o compiled.js
一个完整的Babel转码工程通常包括如下文件:
- Babel配置文件
- Babel相关的npm包
- 需要转码的JS文件
Babel基础操作
Babel插件与预设
代码转换功能以插件的形式出现,插件是小型的 JavaScript 程序,用于指导 Babel 如何对代码进行转换。你甚至可以编写自己的插件将你所需要的任何代码转换功能应用到你的代码上
-
plugin代表插件,preset代表预设,它们被分别放在plugins和presets目录下,通常每个插件或预设都是一个npm包; 所有插件和预设都需要先安装npm包到node_modules后才可以使用
-
预设是帮我们解决[插件数量过多]的问题的。为了避免实现某个功能要安装过多的插件; 预设可以代替一堆插件;
-
Babel官方常用的preset有:
- @babel/preset-env
- @babel/preset-react
- @babel/preset-typescript
- @babel/preset-stage-0
- @babel/preset-stage-1
插件与预设的短名称
可以在配置文件里写插件的短名称。
- 若插件的npm包名称的前缀为
babel-plugin-, 则可以省略其前缀,如: - 若预设短名称规则与插件类似,预设npm包名称的前缀为
babel-preset-或作用域@xxx/babel-preset-xxx的可以省略babel-preset- - 如果npm包名称的前缀带有npm作用域@,如
@org/babel-plugin-xxx,则短名称可以写成@org/xxx
{
"babel": {
"presets": [],
"plugins": ["babel-plugin-transform-decorators-legacy"]
}
}
// 可以写成短名称
{
"babel": {
"presets": [],
"plugins": ["transform-decorators-legacy"]
}
}
- plugins插件数组和presets预设数组是有顺序要求的。如果两个插件或预设都要处理同一个代码片段,那么会根据插件和预设的顺序来执行,规则如下:
-
插件比预设先执行
-
插件执行顺序是插件数组从前往后依次执行
-
预设执行顺序是预设数组元素从后向前依次执行
预设与插件的选择
预设与插件有一百多个,我们不可能一一学习,在我们的Web前端工程开,常用的插件和预设其实就只有几个。抓住重点,学习几个,其他的举一反三
预设的选择
babel-preset-lastest,在Babel6时期,是所有年代preset的集合,在Babel6最后一个版本中,它是babel-preset-es2015、babel-preset-es2016、babel-preset-es2017的集合。 因为Babel官方不在退出babel-preset-es2017之后的年代preset了,所以babel-preset-lastest变成了TC39每年发包的进入标准的ES语法转换器预设集合。其实,这和Babel6时期 的它的内涵是一样的
-
@babel/preset-env包含了babel-preset-lastes的功能,并对其进行了增强,现在@babel/preset-env完全可以替代babel-preset-lastest; 以前用到的很多,现在只用这个即可
-
在实际开发工程中,除了使用@babel/preset-env对标准ES6语法进行转换,我们可能还需要类型检查和React等预设对特定语法进行转换;
总结起来官方预设可以使用的其实只有4-5个
-
@babel/preset-flow
-
@babel/preset-react
-
@babel/preset-typescript
-
@babel/preset-env
-
@babel/preset-vue (vue工程)
插件的选择
虽然Babel 7官方有九十多个插件,不过其中大多数已经被整合在@babel/preset-env和@babel/preset-react等预设里了,我们在开发的时候直接使用预设就可以; 目前最常用的插件只有@babel/plugin-transform-runtime
@babel/preset-env
-
在Babel6版本里,@babel/preset-env的名字是babel-preset-env,从Babel7版本开始,统一使用名字@babel/preset-env;
-
@babel/preset-env是整个Babel大家族中最重要的一个预设。如果只能配置一个插件或预设,而且要求能完成现代JS工程所需的所有转码要求,那么一定非@babel/preset-env莫属; 在使用它之前,需要先安装
npm install @babel/preset-env --save-dev -
@babel/preset-env是Babel6时期babel-preset-lastest的增强版。该预设除了包含所有稳定的转码插件,还可以更加我们设定的目标环境进行正针对性转码;
-
@babel/env 是 @babel/preset-env的简写; 对于预设,当我们不需要对其设置参数,只需要把该预设的名字放入presets数组里即可
-
如果需要对某个预设设置参数,该预设就不能以字符串形式直接放在presets数组中,而是应该再包裹一层数组,数组的第一项是该预设名称字符串,数组的第二项想该预设的参数对象。
-
如果该预设没有参数需要设置,则数组的第二项可以是空对象或者直接不写第二项 以下写法是等价的
module.exports = {
presets: ['@babel/env'],
plugins: []
}
// 第二种
module.exports = {
presets: [['@babel/env', {}]],
plugins: []
}
// 第三种
module.exports = {
presets: [['@babel/env']],
plugins: []
}
@babel/preset-env与browserslist
"browserslist": {
"> 1%",
"not ie <= 8"
}
// 以上含义是,该项目工程的目标是市场份额大于1%的浏览器并且不考虑IE 8浏览器。
//
-
browserslist叫做目标环境配置表,除了写在package.json文件里,也可可以单独写在工程目录下的.browserslistrc文件里; 我们用browserslist来指定代码最终要运行在哪些浏览器或Node.js环境里。 Autoprefixer\PostCSS等可以根据我们设置的browserslist,来自动判断是否要增加 CSS前缀(如-webkit-)
-
Babel也可以使用browserslist,如果你使用了@babel/preset-env预设,此时Babel就会读取browserslist的配置
-
我们不为@babel/preset-env设置任何参数,Babel就会完全根据browserslist的配置来做语法转换。如果没有browserslist,那么Babel就会把所有ES6的语法转换成ES5的语法
Babel工具
- @babel/core 是使用Babel进行转码的核心npm包,我们使用的babel-cli、babel-node都依赖于这个包,我们在进行前端开发的时候,通常都需要安装这个包
npm install @babel/core --save-devnpm install @babel/preset-env --save-dev无论我们是通过命令行转码,还是通过Webpack进行转码,底层都是通过Node.js来调用@babel/core相关功能的API来实现的
@babel/cli是一个npm包,安装了它之后,我们就可以再命令行里使用命令进行转码了
@babel/cli的安装方法有全局安装和项目本地安装两种
npm install @babel/cli --global
npm install @babel/cli --save-dev
在命令行里转码有两种方法:
babel a.js -o b.js
npx babel a.js -o b.js
``
@babel/node @babel/node和Node.js的功能非常接近,@babel/node的优点是在执行命令的时候可以配置Babel的编写配置项。 如果遇到Node.js不支持的ES6语法,我们可以通过@babel/node来实现
- 在Babel 7中,我们需要单独安装该工具
npm install @babel/node@7.13.10 --save-dev然后我们就可以用@babel/node的babel-node命令来允许JS文件了
npx babel-node index.js
Babel原理与Babel插件开发
Babel原理
Babel的转码过程主要由三个阶段组成: 解析(parse)、转换(transform)、和生成(generate)。这三个阶段分别由@babel/parser、@babel/core和@babel/generator来完成
1、解析阶段 该阶段由Babel读取源码并生产抽象语法树(AST),该阶段由两部分组成: 词法分析与语法分析。 词法分析会将字符串形式的代码转换成tokens流,语法分析会将tokens流转换为AST
所谓AST是指一种特殊的树状结构,由AST explorer。有多种工具可以生成AST,Babel 7之前的版本主要使用Babylon, Babel 7使用由Babylon发展而来的@babel/parser来进行解析工作
2、转换阶段 AST是一个树状的JSON结构。可以通过Babel插件对该树状结构执行修改操作,修改完成后就得到了新的AST
3、生成阶段 通过转换阶段的工作,我们得到新的AST。在生成阶段,我们对AST的树状JSON结构进行还原操作,生成新的JS代码,通常这就是我们需要的ES5代码
以上三个阶段的重点是第二个阶段(转换阶段),该阶段使用不同的Babel插件得到不同的AST,也就意味着最终生成不同的JS代码。在我们评审的开发中,主要工作也是选择合适的Babel插件或预设
Babel插件开发
// animalToDog.js
module.exports = function({types: t}){
return {
name: 'animalToDog',
visitor: {
Identifier(path, state){
if(path.node.name === 'animal'){
path.node.name = 'dog';
}
}
}
}
}
// Babel插件的代码总体上要对外输出一个函数; 上面使用module.exports = function({}){} 的方式对外输出一个函数,也就是说,Babel插件的本质上就是一个函数,这是Babel插件的固定格式
//{ types: t} 这里使用Es6的解构赋值。 Babel在调用插件的时候,是会向该函数传入参数的,这个参数其实是@babel/types这个工具库;
// 通过ES6解构赋值,我们把@babel、types对外提供的对象types赋值给变量t
// @babel/types工具库可以用来对AST的节点进行验证。例如: 可以通过t.isIdentifier方法验证一个节点是不是Indentifer类型的
// 插件的返回值是一个对象,对象的属性name是该插件的名称,属性visitor也是一个对象。
// 我们编写Babel插件的主要工作就是修改visitor对象,该对是遍历AST各个节点的方法。在上面插件里,要把变量名animal修改为dog,于是我们修改了visitor.Identifier方法。
// 我们编写的babel插件实际上是在执行转换阶段的工作,该工作需要前一个阶段解析工作先完成。在解析阶段,我们得到了转换前的代码的AST树状结构信息,该AST上会有Identifier等节点信息,我们编写的插件的时候参考该AST的信息即可。 一个简单的方法是通过开源工具AST explorer来得到AST信息
// Identifier方法,有两个参数path和state,vis 中的每个方法都接收这两个参数,path代表路径。 最后我们判断path上节点信息name是不是animal,是的话把它修改为dog即可
// letToVar.js
module.exports = function({types: t}){
return {
name: 'animalToDog',
visitor: {
Identifier(path, state){
if(path.node.name === 'animal'){
path.node.name = 'dog';
}
}
}
}
}
Babel插件传参
- Babel插件都是支持传参的。可以在Babel配置文件里配置参数,如:
module.exports = {
plugins: [
'./animalToDog.js',
[
'./letToVar.js',{
ES5: false
}
]
]
}
- 在插件内部可以通过state.opts获取Babel配置文件配置的参数
module.exports = function({types: t}){
return {
name: 'letToVar',
visitor: {
VariableDeclaration(path, stat){
if(path.node.kind === 'let' && state.opts.ES5 === true){
path.node.kind= 'var';
}
}
}
}
}
// 当配置参数ES5为false时,let没有转码为var;
// 当我们把ES5配置成true时,let就会转码为var;