Babel是什么?
在Babel官方文档中,是这样介绍Babel的
Babel是一个JavaScript编译器,用于将ECMAScript 2015+ 版本的代码转换为向后兼容的JavaScript语法,以便能够运行到当前版本和旧版本的浏览器或者其他环境中。简单来说,Babel的工作就是:
- 语法转换
- JS 源码转换
- 通过 Polyfill 的方式在目标环境中添加缺失的特性
Babel基本原理
Babel的基本原理就是将源码转换为抽象语法树,然后对抽象语法树进行处理生成新的语法树,最后将新语法树生成新的JS代码。整个编译过程可分为三个阶段:
parsing(解析)-->transforming(转换)-->generating(生成)
注意:Babel只负责编译新标准下引入的新语法,比如Class,Array Function,ES Moudle等,而不会编译原生对象新引入的方法和API,比如Array.includes,Map,Set等,这些需要通过ployfill解决。
Babel基本使用
运行Babel所需的基本环境
1.babel/cli或babel/node
npm install i -S @babel/cli
@babel/cli 是 Babel 提供的内建命令行工具。提到 @babel/cli 这里就不得不提一下 @babel/node ,这哥俩虽然都是命令行工具,但是使用场景不同,babel/cli 是安装在项目中,而 @babel/node 是全局安装。
2.@babel/core
npm install i -S @babel/core
安装完 @babel/cli 后就在项目目录下执行babel test.js会报找不到 @babel/core 的错误,因为 @babel/cli 在执行的时候会依赖 @babel/core 提供的生成 AST 相关的方法,所以安装完 @babel/cli 后还需要安装 @babel/core。
安装完这两个插件后,如果在 Mac 环境下执行会出现 command not found: babel,这是因为 @babel/cli是安装在项目下,而不是全局安装,所以无法直接使用 Babel 命令,需要在 package.json 文件中加上下面这个配置项:
"scripts": {
"babel":"babel"
}
此时代码依然无法被编码,因为还编译文件还需要安装对应的插件或者预设。
配置文件 .babelrc
Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。
该文件用来设置转码规则和插件,基本格式如下。
{
"presets": [],
"plugins": []
}
presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。
# ES2015转码规则
$ npm install --save-dev babel-preset-es2015
# react转码规则
$ npm install --save-dev babel-preset-react
# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3
然后,将这些规则加入.babelrc。
{
"presets": [
"es2015",
"react",
"stage-2"
],
"plugins": []
}
插件
插件是用来定义如何转换你的代码的。在 Babel 的配置项中填写需要使用的插件名称,Babel 在编译的时候就会去加载 node_modules 中对应的 npm 包,然后编译插件对应的语法。
.babelrc
{
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
插件执行顺序
插件在预设前执行
插件的执行顺序是从左往右执行。也就是说在上面的示例中,Babel 在进行 AST 遍历的时候会先调用 transform-decorators-legacy 插件中定义的转换方法,然后再调用 transform-class-properties 中的方法。
插件传参
参数是由插件名称和参数对象组成的一个数组。
{
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{ "loose": true }
]
]
}
预设
预设就是一堆插件(Plugin)的组合,从而达到某种转译的能力,就比如 react 中使用到的 @babel/preset-react ,它就是下面几种插件的组合。
@babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/plugin-transform-react-display-name
当然我们也可以手动的在 plugins 中配置一系列的 plugin 来达到目的,就像这样:
{
"plugins":["@babel/plugin-syntax-jsx","@babel/plugin-transform-react-jsx","@babel/plugin-transform-react-display-name"]
}
但是这样一方面显得不那么优雅,另一方面增加了使用者的使用难度。如果直接使用预设就清新脱俗多了~
{
"presets":["@babel/preset-react"]
}
预设的执行顺序
前面提到插件的执行顺序是从左往右,而预设的执行顺序恰好反其道行之,它是从右往左
前面提到插件的执行顺序是从左往右,而预设的执行顺序恰好反其道行之,它是从右往左
{
"presets": [
"a",
"b",
"c"
]
}
它的执行顺序是 c、b、a,是不是有点奇怪,这主要是为了确保向后兼容,因为大多数用户将 "es2015" 放在 "stage-0" 之前。
Polyfill
polyfill 的翻译过来就是垫片,垫片就是垫平不同浏览器环境的差异,让大家都一样。
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。
举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。
安装命令如下。
$ npm install --save babel-polyfill
注意 @babel/polyfill 不是在 Babel 配置文件中配置,而是在我们的代码中引入。
import '@babel/polyfill';
const arrFun = ()=>{}
const arr = [1,2,3]
console.log(arr.includes(1))
Promise.resolve(true)
编译后:
require("@babel/polyfill");
var arrFun = function arrFun() {};
var arr = [1, 2, 3];
console.log(arr.includes(1));
Promise.resolve(true);
这样在低版本的浏览器中也能正常运行了。
不知道大家有没有发现一个问题,这里是require("@babel/polyfill")将整个 @babel/polyfill 加载进来了,但是在这里我们需要处理 Array.includes 和 Promise 就好了,如果这样就会导致我们最终打出来的包体积变大,显然不是一个最优解。要是能按需加载就好了。其实 Babel 早就为我们想好了
按需加载useBuiltIns
回过头来再说 @babel/preset-env,他出现的目的就是实现民族大统一,连 stage-x 都干掉了,又怎么会漏掉 Polyfill 这一功能,在 @babel/preset-env 的配置项中提供了 useBuiltIns 这一参数,只要在使用 @babel/preset-env 的时候带上他,Babel 在编译的时候就会自动进行 Polyfill ,不再需要手动的在代码中引入@babel/polyfill 了,同时还能做到按需加载
注意,这里需要配置一下 corejs 的版本号,不配置编译的时候会报警告。
useBuiltIns 的机构参数:
false:此时不对Polyfill 做操作,如果引入 @babel/polyfill 则不会按需加载,会将所有代码引入
usage:会根据配置的浏览器兼容性,以及你代码中使用到的 API 来进行 Polyfill ,实现按需加载
entry:会根据配置的浏览器兼容性,以及你代码中使用到的 API 来进行 Polyfill ,实现按需加载,不过需要在入口文件中手动加上import ' @babel/polyfill'
辅助函数的代码复用
@babel/plugin-transform-runtime 可以让 Babel 在编译中复用辅助函数,从而减小打包文件体积.
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
配置 Babel:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}