点击上方蓝字,关注我们哦!!
韩钰佳,2013年加入Qunar,目前在大住宿事业部,曾参与QTA,Q+及去哪儿商旅项目等,对用户端交互优化和前端自动化有兴趣。
引言
Babel 是目前最流行的 JS 编译器,它可以将新一代的 JS(ES2015,ES2016 , ES2017...)代码转为 ES5 代码。这意味着,在 Babel 出现前,你需要依据用户的浏览器版本去确定自己的代码书写方式,而使用 Babel 之后,它允许你写你想写的 JavaScript 版本(这是不是很cool~),因为我们知道 Babel 会帮助我们的代码在所有的浏览器上正确的运行。下面是一个例子:
// babel转码前
input.map(item => item + 1);
// babel转码后
input.map(function (item) {
return item + 1;
});
这个是 Babel 目前最被广泛使用的功能,但其实 Babel 的能力也许并不限于此。
从Babel诞生说起
2014年9月,ES6 的很多特性已经定稿,在 JSer 圈里早已讨论地火热,但当时还没有一个浏览器能够支持 ES6 的语法,这时候 6to5(Babel的前身)出现了。短短一个月之内,被下载超过50万次,并且由于 ES7 的到来,改名为一个霸气的名字:Babel(通天塔)。 这个词来自《旧约全书》中的一个故事,说的是人类产生不同语言的起源。在这个故事中,一群只说一种语言的人在“大洪水”之后从东方来到了示拿(希伯来语:שנער)地区,并决定在这修建一座城市和一座“能够通天的”高塔;上帝见此情形就把他们的语言打乱,让他们再也不能明白对方的意思,并把他们分散到了世界各地。——维基百科。Babel 也许想做这样的创始之举,帮助 JS 开发者统一语言建造通天塔改变世界。 发展到今天,Babel项目维护着@babel/core(Babel的转译API),@babel/generator(根据 AST 生成代码)等核心包,以及 babel-plugin-xxx,babel-preset-xxx 等语法解析插件,在 Github 收获 star 3w+(截止2018年11月5日,Github 上超过3w的只有116个),俨然成为了前端开发者必备的 JS 工具。 另一方面,Babel 作为一个语法转义工具可被用于提交语法(stage-2 ,stage-3)的实现给 TC39(ESMAScript标准制定委员会)。之前的 decorator 语法,Babel 结合其社区的力量对其成为标准语法起到很大的推动作用,因此 Babel 从某种程度已经能够影响 JavaScript 语言自身的未来。
怎么使用Babel
第一步:安装Babel核心包;
npm install--save-dev@babel/core@babel/cli
第二步:创建配置文件;
在工程根目录添加配置文件 babel.config.js(babel7 新提供的一种可支持动态配置的方式,也可使用之前的静态配置.babelrc)。
const presets = [];
const plugins = [];
module.exports = { presets, plugins };
随意新建个需转义代码 test.js,来看一下效果:
let log = () => {
alert('just log');
}
在控制台运行 /node_modules/.bin/babel test.js -o new.js,生成的 new.js 与 test.js 内容完全一致,并未对 test.js 做任何转义,这是因为未配置插件的 Babel 什么也没有做。
第三步:配置plugins或presets;
安装 npm install--save-dev @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping,随后修改配置文件中加入例子中使用到的转义插件至plug参数(@babel/plugin-transform-arrow-functions 对箭头函数进行转义,@babel/plugin-transform-block-scoping 对 let 进行转义)。
const presets = [];
const plugins = ['@babel/plugin-transform-arrow-functions', '@babel/plugin-transform-block-scoping']; //新增的插件配置
module.exports = { presets, plugins };
再运行 /node_modules/.bin/babel test.js -o new.js ,new.js 内容被像期望那样转义为以下格式:
var log = function () {
alert('just log');
}
PS:上述配置只用于说明基础配置方式,实际工程化中需要注意以下几点: 1、除了上述命令行的方式执行,也可结合 webpack 下的 babel-loader 使用,配置内容与其基本相同。 2、前端项目中可配置 preset-env 插件集,完成对 ES 新版语法的解析,可不用示例中手动填入具体插件的方式,具体 preset-env 配置方式可参照官网。(env 是一个非常强大的插件集合,可以帮助我们依据浏览器环境的代码最小解析。)
“=>” 经历Babel发生了什么
为什么箭头函数经过@babel/plugin-transform-arrow-functions插件的处理转为了正确的形式?这需要先大致讲一下 Babel 的运行原理。 执行步骤分为三步:
解析
Babel 第一步会使用 @babel/parser 解析代码,输入的 javascript 代码字符串根据 ESTree 规范生成 AST(抽象语法树)。Babel 使用的解析器是 babylon(fork 自 acorn 项目)。为了更直观的理解解析过程,可以使用 AST Explorer 这个工具对解析过程进行在线预览与编辑。比如 var log =( )=>{};这个代码片段通过解析函数解析的结果如下:
该JSON中代码所对应的AST信息,包含节点类型(图中的ArrowFunctionExpression,Identifier 等,定义可参照 babylon 文档),位置,以及嵌套关系等。
转换
根据一定的规则转换、修改AST。Babel提供了@babel/traverse方法维护这AST树的整体状态,并且可完成对其的替换,删除或者增加节点,这个方法的参数为原始AST和自定义的转换规则,返回结果为转换后的AST。而这部分是插件开发的核心部分。插件@babel/plugin-transform-arrow-functions的index.js 文件中可以看到就是针对Babel解析第一步所识别出的ArrowFuncionExpress节点进行处理,完成转换。
生成
使用 @babel/generator 将修改后的 AST 转换成代码,生成过程可以对是否压缩以及是否删除注释等进行配置,并且支持 sourceMap。 简单来说,Babel对代码的处理分为三步: 1、code -> AST(解析 ) 2、AST -> new AST(转换) 3、new AST -> new Code(生成)
自定义Babel开发
Babel 提倡用户开发自己的插件集合(preset)和插件(plugin)。插件集开发并不复杂,可以查询官方资源或参考 babel-preset-airbnb 完成自己所需要的预设插件集合,在此不再赘述。在自定义开发插件功能前需要了解两个简单的概念: 1、visitor: Babel 使用 babel-traverse 进行树状的遍历,对于 AST 树上的每一个分支我们都会先向下遍历走到尽头,然后向上遍历退出遍历过的节点寻找下一个分支。Babel 提供我们一个 visitor 对象供我们获取 AST 里所需的具体节点来进行访问。比如我们创建一个visitor:
const MyVisitor = {
Identifier(path) {
console.log("Called!");
}
};
上面代码代表遍历时,每当在树中遇见一个 Identifier(标识符类型的节点)的时候会调用 Identifier() 方法。同理我们还可以配置其他针对别的节点类型的 visitor,比如针对 if 语句的 IfStatement ( ){ } 等等。 2、path: 它是访问 visitor 时被传入的参数,其中包含父元素,节点类型,上下文等信息等,我们在 visitor 方法中可以直接对 path 中的参数取值或者进行修改和其他复杂操作,完成语法的转换。
下面编写一个简单的插件:
第一步:
新建 npm 包,命名为 babel-plugin-reverse-identifier(官方建议以 babel-plugin- 开头) 包内新建 index.js 文件, 代码内容只需将刚才讲到的 visitor 对象导出。
module.exports = function() {
return {
visitor: {
Identifier(path) {
path.node.name = path.node.name.split('').reverse().join(''); //节点名称逆序
}
}
};
};
第二步:
上传至 npm 服务器。
第三步:
然后在需要使用此插件的工程中 npm install 这个包之后,增加配置 plugins: ['reverse-identifier']。 通过 Babel 命令解析原代码后,运行结果如下图,log 方法所有的标识符被逆序排列:
var gol = () => {};
这个示例只是一个最简单插件的开发,复杂的操作可以学习babel插件手册进行深度的学习和开发。 了解了 Babel 的自定义开发大致方式,其实 Babel 可以做或有趣或实用的拓展。在官方的 https://github.com/babel/awesome-babel 下可以看到很多有趣的插件开发示例,比如可以对代码进行优化,语法糖,或者对于报错信息增加更详细的信息等等,等着大家的进一步探索。