利用 Babel 玩转你的代码
我在团队帮助开发 Node 工具时,遇到了需要对多份相似的代码进行一定的处理,但又不能改变原本仓库的代码,这个非常像我们的编译工具做的事情。在一开始的时候,参考了类似 FIS 的功能,简单参照使用代码的 HOOK 配合正则来进行代码的操作。后面觉得实在难以拓展且无法确保修正后的代码可用性,还是觉得必须使用 Babel 来实现源码的操作。
什么是 Babel
babel 可以说是前端工程化用的最多的工具之一。在 ES6 仅仅只有标准时,我们就开始利用 Babel 的 ES6 转换到 ES5 的功能来使用新的语法特性。包括后续大热的React 制定的 JSX 的语法,“凭空”创建出来的语法,为什么能这么快且自然的就融入到前端体系里,这都离不开 Babel 这个编译器的功劳(compiler)。除了编译器功能外,还可以对代码进行各类的静态分析。
原理
babel 利用 babylon 解析器进行语法解析。解析成 AST(抽象语法树)后,经过一定的规则对这个树进行处理后再生成新的语法树。最终新的语法树在生成对应的合法的 Javascript 语法。
AST
AST,就是编译时常说的抽象语法树。树状结构可以帮助我们更好的索引和操作数据结构。例如我们前端三板斧 HTML/CSS/Javascript,而我们平常认知最深的 HTML DOM 树,通过一棵树就可以表达整个页面结构,而 CSS 和 Javascript 都可以通过编译器将语法解析成 AST 抽象语法树。
代码生成 AST 需要进行词法分析和语法分析。
我们可以看一个最简单的程序语法来管中窥豹一下,我们利用 AST Expoler 看看下面这段语法的 AST 到底是长什么样的。
if(3 > 5) {
console.log('3 大于 5')
} else {
console.log('5 大于等于 3')
}
简单画个示意图,部分属性也精简了一下。
我们可以看到,所有的 AST 树的根结点都是 Program 节点。但即使一个非常简单的 if 判断语法对应的都是一支比较复杂的树结构。而 if 语句对应的最重要的三个属性就是 consequent/test/alternate。判断分支内是一个 BinaryExpression(二元表达式),而在 ture/false 分支内则是 CallExpression(函数调用表达式),而调用的函数来自 MemberExpression(成员表达式)。
下面这个表上面这段代码用到的
属性 | 描述 |
---|---|
BlockStatement | 块级表达式 |
BinaryExpression | 二元表达式 |
CallExpression | 函数调用表达式 |
MemberExpression | 成员表达式 |
Literal | 文字 |
更多详细的 AST 类型都可以查看 Babylon 提供的 Spec 文档。
babel 插件
在我们简单了解 AST 树后,就可以具体看看如何利用 Babel 插件来玩转我们的代码。Babel 提供了几个核心的 Lib,我们可以关心以下几个,babel-core
、babel-types
、babel-template
。
- babel-core。Babel 的核心库,提供了将代码编译转化的能力。
- babel-types。提供 AST 树节点的类型。
- babel-template。可以将普通字符串转化成 AST,提供更便捷的使用。
Visitors
Visitors 访问者模式,是我们访问 AST 树上的节点时所用到的方法。
const visitors = {
BinaryExpression(path) {
console.log('访问二元表达式的节点')
},
Literal(path) {
console.log('访问文本')
}
}
例如上图就可以定义不同节点类型的访问者,对节点路径进行访问并对节点进行操作并转化。
Paths
我们在 我们定义成树状结构,除了可以完整描述整个代码的结构,也方便让节点和节点之间进行索引和操作。我们通过 visitor 访问节点时,其实访问的是节点的 path,并不是实际上的节点。path 上有该节点信息 path.node
,也有类似 DOM 树上一样的可以访问到父节点的信息 path.parent
,方便后续的操作。
// 我们通过 visitor 访问到的 AST 节点后,回调参数 path 示例
{
"parent": {
"type": "BinaryExpression",
"operator": ">"
....
},
"node": {
"type": "Literal",
"name": "3",
....
}
}
// in vistors
const visitors = {
Literal(path) {
console.log(path.node.name)
console.log(path.parent.operator)
}
}
示例
我们在了解 visitors 和 paths 后,就可以正式看看如何编写自己的 babel 插件了。
替换操作符
我们最开头的例子的判断条件是一个二元表达式,我们希望将大于号(>)都转换成小于等于(<=)号。
const babel = require('babel-core')
const t = require('babel-types')
const code = `
if(3>5) {}
`
const visitor = {
BinaryExpression(path) {
if(path.node.operator === '>') {
path.replaceWith(
t.binaryExpression(
'<=',
path.node.left,
path.node.right
)
);
}
}
}
const result = babel.transform(code, {
plugins: [
{ visitor }
]
})
console.log(result.code)
import 拆分
在网上很多有关 babel 插件的例子,都会提到这个 import 语句拆分的案例,我也觉得这个案例非常实用且有意义。
我们在使用 lodash 时,如果是用普通的方式引入时,会引入非常大的包,有的时候你只需要使用其中一两个小函数,所以 lodash 提供了分包按需引入的方式。但两者的写法不一样,所以我们需要使用 babel 插件来实现这个功能。
// 转化前,bad
import { uniq, sort as _sort } from 'lodash'
// 转化后,better
import uniq from 'lodash/uniq'
import _sort from 'lodash/sort'
我们可以先去 AST explore 先看看具体 AST 的树结构。然后再来写出我们的插件。
const babel = require('babel-core')
const t = require('babel-types')
const code = `import { unqi, sort as _sort } from 'lodash'`
const visitor = {
ImportDeclaration(path) {
const specifiers = path.node.specifiers
const source = path.node.source
if(!t.isImportDefaultSpecifier(specifiers[0])) {
const myImportDeclaration = specifiers.map(specifier => {
return t.importDeclaration(
[t.importDefaultSpecifier(specifier.local)],
t.stringLiteral(`${source.value}/${specifier.imported.name}`)
)
})
path.replaceWithMultiple(myImportDeclaration)
}
}
}
const result = babel.transform(code, {
plugins: [
{ visitor }
]
})
console.log(result.code)
为你的函数插入一行代码
我们如何对一个函数内部新增 AST 节点,“凭空”插入一行正确无误的代码呢。
const babel = require('babel-core')
const t = require('babel-types')
const code = `
class Page {
onLoad() {
console.log('Hello')
}
}
`
const visitor = {
BlockStatement(path) {
if(path.parent.key.name === 'onLoad' && path.node.body.length === 1) {
const body = path.node.body
const newBlockStatement = t.blockStatement(body.concat(
t.expressionStatement(
t.callExpression(
t.memberExpression(
t.identifier('console'),
t.identifier('log')
),
[t.stringLiteral('World!')]
)
)
))
path.replaceWith(newBlockStatement)
}
}
}
const result = babel.transform(code, {
plugins: [
{ visitor }
]
})
console.log(result.code)
其实思路是一样的,只要我们构建出一个 AST 节点,再把这个节点再插到原本的 AST 树上,那么这个 AST 树就可以转化成我们想要的代码了。
最后
这里我们了解了 AST 树,理解了我们学习工作中常用的 Babel 到底背后做了哪些事,我们就可以更好的融入到前端开发的社区,去做更多的事情。更多更复杂更详细的用法,都可以参照 Babel 官网,或 Babel 插件手册。当然有兴趣的同学也可以把玩一下 CSS 的相关 AST 编译,原理都是一样的。
利用 Babel 玩转你的代码的更多相关文章
- 利用FFmpeg玩转Android视频录制与压缩(二)<转>
转载出处:http://blog.csdn.net/mabeijianxi/article/details/72983362 预热 时光荏苒,光阴如梭,离上一次吹牛逼已经过去了两三个月,身边很多人的女 ...
- XSS漏洞的渗透利用另类玩法
XSS漏洞的渗透利用另类玩法 2017-08-08 18:20程序设计/微软/手机 作者:色豹 i春秋社区 今天就来讲一下大家都熟悉的 xss漏洞的渗透利用.相信大家对xss已经很熟悉了,但是很多安全 ...
- 逆向进阶,利用 AST 技术还原 JavaScript 混淆代码
什么是 AST AST(Abstract Syntax Tree),中文抽象语法树,简称语法树(Syntax Tree),是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构. ...
- 利用Babel来转化你的ES2015脚本初步
我们在前面已经安装和学习过babel 安装babel-cli 这是babel解释器的客户端主程序 npm install -g babel-cli 安装”编译”插件(babel的JSX语法转换器) n ...
- 【更新WordPress 4.6漏洞利用PoC】PHPMailer曝远程代码执行高危漏洞(CVE-2016-10033)
[2017.5.4更新] 昨天曝出了两个比较热门的漏洞,一个是CVE-2016-10033,另一个则为CVE-2017-8295.从描述来看,前者是WordPress Core 4.6一个未经授权的R ...
- 理解Babel是如何编译JS代码的及理解抽象语法树(AST)
Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是? 很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都 ...
- git 利用hook 实现服务器自动更新代码
如何利用git的hook实现提交代码后自动更新? 因为个人开发经常需要提交代码,每次都需要连接服务器去pull代码,重启服务器就显得十分繁琐,因此github提供了一个时间钩子,用户push代码后可以 ...
- 利用Python进行异常值分析实例代码
利用Python进行异常值分析实例代码 异常值是指样本中的个别值,也称为离群点,其数值明显偏离其余的观测值.常用检测方法3σ原则和箱型图.其中,3σ原则只适用服从正态分布的数据.在3σ原则下,异常值被 ...
- 利用封装、继承对Java代码进行优化
注:本文实例分别可以在oldcastle(未优化的代码)和newcastle(优化后的代码)中查看,网址见文末 城堡游戏: 城堡中有多个房间,用户通过输入north, south, east, wes ...
随机推荐
- python学习笔记(locust性能测试模块)
locust是基于python的性能测试工具.支持python2.7及其以上的版本.相对于主流的LR与Jmeter工具使用的方式不一样.locust是通过编写python代码来完成性能测试的. 通过L ...
- Android 之低版本高版本实现沉浸式状态栏
沉浸式状态栏确切的说应该叫做透明状态栏.一般情况下,状态栏的底色都为黑色,而沉浸式状态栏则是把状态栏设置为透明或者半透明. 沉浸式状态栏是从android Kitkat(Android 4.4)开始出 ...
- Mybatis之SSM配置
applicationContext-dao.xml <?xml version="1.0" encoding="UTF-8"?> <bean ...
- ASP.NET ValidationSummary 控件
ASP.NET ValidationSummary 控件 Validation 服务器控件 定义和用法 ValidationSummary 控件用于在网页.消息框或在这两者中内联显示所有验证错误的摘要 ...
- HDU 5186 zhx's submissions 模拟,细节 难度:1
http://acm.hdu.edu.cn/showproblem.php?pid=5186 题意是分别对每一位做b进制加法,但是不要进位 模拟,注意:1 去掉前置0 2 当结果为0时输出0,而不是全 ...
- [转载]java正则表达式
转载自:http://butter.iteye.com/blog/1189600 1.正则表达式的知识要点1.正则表达式是什么?正则表达式是一种可以用于模式匹配和替换的强有力的工具.2.正则表达式的优 ...
- Can't create session svn: Unable to connect to a repository at URL “...”的解决方案
Can't create sessionsvn: Unable to connect to a repository at URL '...' Cannot negotiate authenticat ...
- 面筋: 奇虎360 c++ 后台开发 实习生 面试
投的是360上海的商业化部门,岗位是C++服务端开发实习生,记录一下面试历程: 视频面试,但是是有代码框让你写代码的. 一面: Q:先说一下个人信息,做过的项目 A:.......... Q:先写个翻 ...
- Http协议——Header说明
下图是我用IE的开发人员工具截取的一个Http Request请求的Header. 下图是我用IE的开发人员工具截取的一个Http Response的Header. header常用指令 header ...
- HTML页面每次打开的时候都清除页面缓存
解决办法为: (1) 用HTML标签设置HTTP头信息 <HEAD> <META HTTP-EQUIV="Pragma" CONTENT="no- ...