Babel是一个JavaScript编译器,不仅能将当前运行环境不支持的JavaScript语法(例如ES6、ES7等)编译成向下兼容的可用语法(例如ES3或ES5),这其中会涉及新语法的转换和缺失特性的修补;还支持语法扩展,从而能随时随地的使用JSX、TypeScript等语法。目前最新版本是7.4,自从6.0以来,Babel被分解的更加模块化,各种转译功能都以插件的形式分离出来,可按自己的需求,灵活配置。
  在7.0版本中,对Babel的包做了一次大调整,统一改成域级包,将原先以“babel-”为前缀的包迁移到@babel的命名空间,例如@babel/core、@babel/cli等。这种模块化的设计,既能区分是否是官方发布的,也能避免命名冲突。
## 一、@babel/core
  如果要以编程的方式使用Babel,那么可以通过@babel/core实现,安装命令如下所示。
~~~
npm install --save-dev @babel/core
~~~
  在引入该包后,就能在JavaScript文件中直接编译代码、文件或AST。以编译代码为例,可选择的方法有三个,如下所示。
~~~
var babel = require("@babel/core");
babel.transform(code, options, function(err, result) {
console.log(result); // => { code, map, ast }
});
babel.transformSync(code, options) // => { code, map, ast }
babel.transformAsync(code, options) // => Promise<{ code, map, ast }>
~~~
  虽然transform()用于异步编译,transformSync()用于同步编译,但是它们得到的结果相同,都是一个由转换后的代码(code)、源码映射信息(map)和抽象语法树(ast)组成的对象。而transformAsync()得到的结果与前面两个方法不同,它返回一个包含code、map和ast的Promise对象。这些方法的第二个options参数,可用于传递配置信息,具体可参考[官方文档](https://www.babeljs.cn/docs/options)。
  如果要编译文件或AST,那么也有类似的三个方法可供选择。除了能编译之外,利用这个包还能把代码解析成AST(即传入一段代码,得到一个AST)以便其它插件对其进行语法分析,可使用的方法有parse()、parseSync()和parseAsync()。
## 二、@babel/cli
  @babel/cli是一个Babel内置的命令行工具,可通过命令来编译文件,其安装如下所示。
~~~
npm install --save-dev @babel/cli
~~~
  假设有一个src.js文件,其内容如下代码所示,用到了ES6中的箭头函数。
~~~
let fn = () => true;
~~~
  在运行下面的命令后,可以看到输出和输入的代码相同,这是因为此时还未指定任何转译插件。
~~~
./node_modules/.bin/babel src.js
~~~
  如果当前的npm版本是5.2以上,那么可将./node\_modules/.bin缩短为npx,如下所示。
~~~
npx babel src.js
~~~
  在babel中,有多个可用的参数,下面只列举其中的四个,其余参数的用法可参考[官方文档](https://www.babeljs.cn/docs/babel-cli)。
~~~
npx babel src --out-dir dist
npx babel src.js --out-file dist.js
npx babel src.js --out-file dist.js --plugins=@babel/plugin-transform-classes,@babel/transform-modules-amd
npx babel src.js --out-file dist.js --presets=@babel/preset-env,@babel/flow
~~~
  (1)“--out-dir”参数可编译整个src目录下的文件并输出到dist目录中。
  (2)“--out-file”参数可编译src.js文件并输出到dist.js文件中。
  (3)“--plugins”参数可指定编译过程中要使用的插件,多个插件用逗号隔开。
  (4)“--presets”参数可指定编译过程中要使用的预设,多个预设用逗号隔开。
  在package.json文件中,可以通过scripts字段声明脚本命令。如果想要简化babel命令,那么可以将它们迁移到scripts字段中,如下所示。
~~~
{
"scripts": {
"compile": "npx babel src.js --out-file dist.js"
}
}
~~~
  现在要编译目录的话,只要执行下面的这条npm命令即可。
~~~
npm run compile
~~~
## 三、配置
  在Babel中,可以将各种命令的参数集中到一个配置文件中,而可配置的文件包括babel.config.js、.babelrc和package.json。
**1)babel.config.js**
  这是Babel 7最新引入的配置文件,存在于根目录中(即package.json文件所在的目录)。它不仅能以编程的方式声明全局生效的[配置参数](https://www.babeljs.cn/docs/config-files#project-wide-configuration),还能利用overrides字段对不同的子目录进行针对性的配置,从而就能避免为相关目录创建一个.babelrc文件了。在下面的示例中,为test目录单独配置了预设(presets)。
~~~
module.exports = {
presets: [...],
overrides: [{
test: ["./test"],
presets: [...]
}]
};
~~~
  当运行下面两条命令进行编译时,第一条读取的是最外层的预设,第二条读取的是overrides中的预设。
~~~
npx babel src.js
npx babel ./test/src.js
~~~
**2).babelrc**
  babel.config.js并不是.babelrc的替代品,.babelrc文件用于局部配置(如下代码所示),可放置于所有目录中。如果当前目录没有.babelrc文件,那么就会往上查找直至找到为止。
~~~
{
"presets": [...]
}
~~~
  注意,如果.babelrc的后缀是“.js”(即.babelrc.js),那么在文件中可以通过JavaScript配置参数。
**3)package.json**
  在package.json文件中,可以声明一个babel字段,其值就是.babelrc文件中的[配置参数](https://www.babeljs.cn/docs/config-files#file-relative-configuration),如下所示。
~~~
{
"babel": {
"presets": [...]
}
}
~~~
  注意,不能让.babelrc和声明过babel字段的package.json处在相同的目录中。
**4)配置函数或方法**
  前面三种都是用单独的文件来配置Babel的参数,其实还可以通过相关的函数或方法来达到相同地目的。在下面的示例中,引入gulp-babel包后就能通过得到的babel()函数来配置Babel的参数。
~~~
let babel = require("gulp-babel");
babel({
presets: [...]
});
~~~
## 四、插件
  Babel的编译可分为三个阶段:解析、转换和生成,而插件(Plugin)在转换过程中起到了至关重要的作用。借助Babel的插件可将解析完成的AST按照特定的要求进行处理,然后再输出生成的代码。
**1)插件类型**
  Babel中的插件分为两种类型:语法和转换。语法插件只允许Babel解析成特定类型的语法,例如JSX、Flow等。转换插件常用来编译ES6、ES7、React和Modules等,由于能自动开启相应的语法插件,因此不用显式的指定两种插件。以之前的箭头函数为例,如果要将其编译成所有浏览器都能识别的形式,那么就得安装相应的插件,如下所示。
~~~
npm install --save-dev @babel/plugin-transform-arrow-functions
~~~
  在.babelrc文件中的配置如下所示。
~~~
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
~~~
  在配置文件中,不仅能指定插件的相对或绝对路径,还可省略插件名称中的“babel-plugin-”前缀。假设有个插件名叫@org/babel-plugin-name,那么它的相对路径和简写名称如下所示。
~~~
{
"plugins": [
"../@org/babel-plugin-name",
"@org/name"
]
}
~~~
**2)执行顺序**
  插件的执行顺序会受配置时所处的位置的影响,具体规则如下所列,其中预设是指官方预先设计的一组插件集,本质上仍然是插件。
  (1)插件执行在预设之前。
  (2)插件会按顺序从前往后执行。
  (3)预设与插件相反,从后往前执行。
  以下面的配置信息为例,在插件中,先执行@babel/plugin-transform-arrow-functions,后执行@babel/plugin-transform-classes。而在预设中,先执行@babel/preset-react,后执行@babel/preset-env。
~~~
{
"plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-classes"],
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
~~~
**3)插件参数**
  如果要配置插件的参数,那么得在插件名称后增加一个参数对象,并且写成数组的形式,如下所示。
~~~
{
"plugins": [
["@babel/plugin-transform-arrow-functions", { "spec": true }],
["@babel/plugin-transform-classes", { "loose": true }]
]
}
~~~
  预设也能接收参数,其写法与之类似。
## 五、预设
  如果在配置文件中一个一个的声明插件,那么不仅会让该文件变得巨大,而且还难免会有所遗漏。官方为了避免此类问题,引入了预设(Preset)的概念。预设类似于生活中的套餐,每个套餐会集合不同的插件,从而能够一次性安装各类插件,并且还可共享配置的参数。
**1)@babel/preset-env**
  此预设不仅集合了最新的ES语法(即编译指定的ES标准),还能配置所要支持的平台(例如Node、Chrome等)和版本,以及按需加载插件,其安装命令如下所示。
~~~
npm install --save-dev @babel/preset-env
~~~
  下面是一个预设的配置示例,支持Chrome 58和IE 11,以及超过市场份额5%的浏览器。还有许多其它可供选择的配置参数,具体可翻阅[官方文档](https://www.babeljs.cn/docs/babel-preset-env)。
~~~
{
presets: [
[
"@babel/preset-env",
{
targets: {
chrome: "58",
ie: "11",
browsers: "> 5%"
}
}
]
]
}
~~~
  除了@babel/preset-env之外,官方还给出了其它实用的预设,例如@babel/preset-flow、@babel/preset-react和@babel/preset-typescript等。
  与插件类似,预设也能指定相对或绝对路径,只不过它省略的前缀是“babel-preset-”。假设有个预设名叫@org/babel-preset-name,那么它的相对路径和简写名称如下所示。
~~~
{
"presets": [
"../@org/babel-preset-name",
"@org/name"
]
}
~~~
**2)stage-x**
  这是一组实验性质的预设,囊括了处于提案阶段的标准,TC39委员会将提案分为5个阶段,如表2所示。
:-: ![](https://img.kancloud.cn/fa/83/fa83507f599e86c1231f1e88bb55cebe_1837x450.png)
表2 提案的五个阶段
  以上4个阶段的预设存在着依赖关系,阶段靠前的依赖阶段靠后的(即数字小的包含数字大的),例如@babel/preset-stage-0依赖@babel/preset-stage-1。
  由于这些提案阶段的预设不太稳定,很有可能会被TC39委员会除名或变更,并且混合使用在配置上容易出错,因此从Babel 7开始,它们都将被废弃。
## 六、@babel/polyfill
  虽然env预设(@babel/preset-env)能统一JavaScript的新语法(即高版本编译成低版本),但是无法支持内置的新方法或新对象,例如Promise、Array.of()等。为此,Babel引入了的Polyfill技术(全部打包在@babel/polyfill中),将所缺的特性添加到全局对象中或内置对象的原型上,弥补env预设的不足,从而模拟出完整的ES6+语法和特性。
  @babel/polyfill包含两个模块:regenerator-runtime和[core-js](https://github.com/zloirock/core-js),前者用于编译生成器与异步函数(async和await),后者用于处理其它兼容性问题。@babel/polyfill的安装命令如下所示,注意,使用的参数是--save而不是--save-dev,因为需要在源码之前先执行Polyfill。
~~~
npm install --save @babel/polyfill
~~~
**1)useBuiltIns**
  在env预设中,存在一个与@babel/polyfill有紧密联系的useBuiltIns参数,它有三个关键字可供选择,分别是false、entry和usage,具体说明如下所列。
  (1)false:默认值,不开启Polyfill,显式的配置如下所示。
~~~
{
presets: [
["@babel/preset-env", { useBuiltIns: false }]
]
}
~~~
  (2)entry:加载运行环境(可在targets参数中声明)所需的Polyfill,下面是一个使用Promise的例子。
~~~
require("@babel/polyfill");
new Promise();
~~~
  当useBuiltIns参数的值为entry时,这段代码在编译后,就会引入许多不相干的JavaScript文件,造成资源的浪费,如下所示。
~~~
require("core-js/modules/es6.promise");
require("core-js/modules/es6.array.fill");
require("core-js/modules/es6.math.trunc");
require("core-js/modules/es6.string.fixed");
......
new Promise();
~~~
  (3)usage:自动加载源码所需的Polyfill,仍然以Promise为例,如下代码所示,不用再显式的引入@babel/polyfill。
~~~
new Promise();
~~~
  当useBuiltIns参数的值为usage时,这段代码在编译后,就会只引入需要的JavaScript文件,如下所示。
~~~
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
new Promise();
~~~
## 七、@babel/runtime
  @babel/runtime与@babel/polyfill类似,也是用来增强env预设的,只是它以非侵入式的辅助函数来填补平台所没有的特性,其安装命令如下所示。
~~~
npm install --save @babel/runtime
~~~
  在Babel 7中还引入了一个@babel/runtime-corejs2,它比@babel/runtime多包含一个core-js模块,即能够编译Promise、Symbol等。接下来以ES6的类为例,如下所示。
~~~
class People {
name() {}
}
~~~
  在将People类编译后,得到的结果如下所示,省略了三个函数中的逻辑代码。
~~~
"use strict";
function _classCallCheck(instance, Constructor) { }
function _defineProperties(target, props) { }
function _createClass(Constructor, protoProps, staticProps) { }
var People =
/*#__PURE__*/
function () {
function People() {
_classCallCheck(this, People);
}
_createClass(People, [{
key: "name",
value: function name() {}
}]);
return People;
}();
~~~
**1)@babel/plugin-transform-runtime**
  由于编译生成的辅助函数会滞留在所使用的文件中,因此文件越多冗余的函数就越多。如果人工分离,那工作量将巨大,因此Babel提供了能自动将它们分离的@babel/plugin-transform-runtime插件,其安装命令如下所示。
~~~
npm install --save-dev @babel/plugin-transform-runtime
~~~
  仍然以ES6的People类为例,先在配置文件中将其声明,如下所示。
~~~
{
plugins: ["@babel/plugin-transform-runtime"]
}
~~~
  然后再将类编译,三个辅助函数就能通过引入的方式得到,如下所示。
~~~
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
~~~
## 八、动态编译
  在Babel中有两种方式实现动态编译,分别是@babel/register和@babel/node,它们的安装命令如下所示。
~~~
npm install --save-dev @babel/register
npm install --save-dev @babel/node
~~~
  虽然动态编译省去了手动操作,但是会损耗程序的速度和性能,因此不适合生产环境。
**1)@babel/register**
  在将其安装后,就会为Node.js的require()函数增加一个钩子。每当通过require()加载后缀为.es6、.es、.jsx、.mjs或.js的文件时,就能自动对其进行Babel编译。接下来用一个例子来演示@babel/register的用法,首先创建一个src.js的文件,其代码如下所示。
~~~
module.exports = () => true;
~~~
  然后创建一个default.js文件,引入@babel/register和之前的src.js,如下所示。
~~~
require("@babel/register");
var code = require("./src.js");
console.log(code.toString());
~~~
  最后在命令行工具中输入“node default.js”,打印出编译后的匿名函数,如下所示。
~~~
function () {
return true;
}
~~~
  在@babel/register中,如果需要使用Polyfill,那么得逐个引入。还要注意,它默认不会编译node\_modules目录中的模块,但是可以通过ignore参数修改忽略规则来覆盖其行为。
  ignore参数是一个由正则表达式和函数组成的数组(如下代码所示),如果要让文件不被编译,那么得将其路径与正则表达式匹配或函数返回true,其中函数的参数就是文件路径。
~~~
require("babel-register")({
ignore: [
/regex/,
function(filepath) {
return true;
}
]
});
~~~
  另外还有一个only参数,其值也是一个数组,同样也用来设置文件不被编译的条件。只是其规则与ignore参数正好相反,即路径与正则表达式不匹配或函数返回false时,才不编译。
  @babel/register还可以接收其它参数,例如extensions、cache等,并且会合并配置文件中的信息,作为其参数传递进来。
**2)@babel/node**
  在Babel 7之前,@babel/cli会自带babel-node命令,但之后,官方将其拆成一个单独的包:@babel/node。与@babel/register不同,@babel/node不需要调整源码,直接一个命令就能实现动态编译,如下代码所示,其中src就是之前的src.js文件。
~~~
npx babel-node src
~~~
*****
> 原文出处:
[博客园-前端利器躬行记](https://www.cnblogs.com/strick/category/1472499.html)
[知乎专栏-前端利器躬行记](https://zhuanlan.zhihu.com/pwtool)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020