本文首发于 https://github.com/whxaxes/blog/issues/10

背景

jscodeshift 是 fb 出的一个 codemod toolkit,基于 recast 这个 js 解析器封装了很多方便使用的工具方法。但是由于官网对使用方式的描述有点谜,刚用起来会有点蛋疼,所以写篇教程说一下。

简单先说明一下 jscodeshift 能用来干嘛,其实就是能够解析 js ,将 js 内容解析成 AST 语法树,然后提供一些便利的操作接口,方便我们对各个节点进行更改,比如更改所有的属性名之类的。比如这个官方提供的最简单的 demo:

const j = require('jscodeshift');

j(jsContent)
.find(j.Identifier)
.replaceWith(
p => j.identifier(p.node.name.split('').reverse().join(''))
);

可以实现的效果就是:

console.log('123')

会被转换为

elosnoc.gol('123')

更复杂一些的话,我们甚至可以基于 jscodeshift 来做类似于 babel 的功能,将 es6 转换为 es5,当然已经有 babel 的情况下就没必要去再实现了,那还可以做啥?就是 codemod,也就是代码自动升级工具,比如框架进行了一个大的升级,业务代码要升级框架要进行大量更改,而这些更改操作就可以通过 jscodeshift 来实现了。

使用

配套工具

在具体说 jscodeshift 如何使用之前,有个网站是必须得配合使用的,就是 jscodeshift 提供的一个配套的 ast 可视化工具 AST explorer

基本上使用 jscodeshift 都要配合这个站点上可视化的 ast tree 来实现。

比如我有一串 js 内容为下面这段

app.say = function(test) {
console.log(test);
} app.get('/api/config/save', checkConfigHighRiskPermission, function() {
console.log('cool')
}); app.say('123')

你们可以自己把代码贴到 ast explorer 中用鼠标移到各个节点看看,在这里不好截那么大的图,就只截了 ast tree 的结构:

可以看到有三个 ExpressionStatement 结构,如果我们点开中间那个,其实也就是 app.get 那串代码,结果就如下:

可以看到上面那串代码被转换成了这么一种树形结构,其中 ExpressionStatement 代表的是表达式模块,也就是 app.get 整个串代码,而其中的 MemberExpression 代表的是 app.get,arguments 代表的是后面的方法参数那串,然后按顺序,Literal 就是 '/api/config/save',Identifier 就是 checkConfigHighRiskPermission,然后 FunctionExpression 就是最后的那个方法。

那么,如果我需要把上面代码中的 app.get... 的那段代码,把里面的 app.get 换成 app.post,并且把 app.get 中的那个回调方法,换成一个 generator 该怎么换?下面就介绍如何增删改查。

jscodeshift 提供了方便的 find 方法供我们快速查找到我们需要处理的节点,而查找方式就是按照 ast explorer 中的结构来查找

const ast = j(jsContent).find(j.CallExpression, {
callee: {
object: {
name: 'app'
},
property: {
name: 'get'
}
}
});

通过 find 方法,查找所有的 CallExpression,然后传入查询条件,查询条件其实就是 CallExpression 中的 json 结构,所以传入 callee.object.name 为 app,然后传入 callee.property.name 为 get,找到的 path 就是我们要的 path 了。

找到我们需要的 CallExpression 之后,先替换 app.get 为 app.post,直接接着上面的代码写:

// 找到名称为 get 的 Identifier ,然后替换成一个新的 identifier
ast.find(j.Identifier, { name: 'get' })
.forEach(path => {
j(path).replaceWith(j.identifier('post'));
});

然后是替换 function 为 generator:

// 找到 app.get 表达式中的 function,替换成 generator function
ast.find(j.FunctionExpression)
.forEach(path => {
j(path).replaceWith(
j.functionExpression(
path.value.id, // identify 方法名
path.value.params, // 方法参数
path.value.body, // 方法体
true, // 是否为 generator
false // expression
)
)
})

然后再调用:

ast.toSource();

就可以看到代码已经被改成:

app.say = function(test) {
console.log(test);
} app.post('/api/config/save', checkConfigHighRiskPermission, function*() {
console.log('cool')
}); app.say('123')

简单来说,在 ast explorer 出现了的 type,在 jscodeshift 中都可以用来查找,比如我要找 MemberExpression 就 j.MemberExpression,我要找 Identifier 就 j.Identifier。所以需要什么类型的节点,就 j.类型名称 就能查到所有这个类型的节点。

如果想了解所有的类型:可以戳这个链接 https://github.com/benjamn/ast-types/tree/master/def

说完类型,如果我们要创建一个某种类型的节点,就像上面的通过 replaceWith 成新的 generator 节点,也是跟类型一样的,只是首字母小写了,比如我要创建一个 MemberExpression 就调用 j.memberExpression(...args),我要创建一个 FunctionExpression 就调用 j.functionExpression(...args),而至于入参要传什么,在 ast explorer 写代码的时候,只要写了这个方法就会有入参提示:

知道了这些,再举个例子,我要把上面的 function 不替换成 generator 了,而是替换成箭头函数也是一样,就只需要改成使用 arrowFunctionExpression 方法即可:

ast.find(j.FunctionExpression)
.forEach(path => {
j(path).replaceWith(
j.arrowFunctionExpression(
path.value.params, // 方法参数
path.value.body, // 方法体
false // expression
)
)
})

如果要增加节点的话 jscodeshift 也提供了两个方法,分别是 insertAfter 和 insertBefore,看方法名就可以知道,这两个方法分别是用于插前面,还是插后面。比如也是上面的 app.get 中,我想在后面的回调中再插入一个回调。就可以直接用 insertAfter:

ast.find(j.FunctionExpression)
.forEach(path => {
j(path).insertAfter(
j.arrowFunctionExpression(
path.value.params, // 方法参数
path.value.body, // 方法体
false // expression
)
)
})

如果想删掉某个节点,则只需要 replaceWith 传入空值即可。

// 删除
j(path).replaceWith();

小技巧

再说个小技巧,如果我们需要插入一大段代码,如果按照上面的写法,就得使用 jscodeshift 的 type 方法生成一个又一个节点对象。相当繁琐。那如何来偷懒呢?比如我要在某个 path 后面加一段 console 的代码:

j(path).insertAfter(
j(`console.log('123123')`).find(j.ExpressionStatement).__paths[0].value
)

也就是将代码转换成 ast 对象,然后再找到根节点插入到 path 后面。就可以了。

最后

上面说的 findforEachreplaceWithinsertAfterinsertBefore 方法都是比较常用,除此之外还有 filterget 等方法,具体有哪些方法可以直接看 jscodeshift 的 collection 源码。个人觉得直接看源码比看文档简单多了。

jscodeshift 简易教程的更多相关文章

  1. 生活科技两相宜:(一)Win7使用微软SkyDrive网盘简易教程

    今天得写一个Win7使用微软SkyDrive网盘的简易教程,主要是给我老婆看,顺便贴出来给大家共享一下:)    使用微软SkyDrive网盘有两个层次.一个是使用网页版,这个跟使用163或者QQ网盘 ...

  2. JavaScript简易教程(转)

    原文:http://www.cnblogs.com/yanhaijing/p/3685304.html 这是我所知道的最完整最简洁的JavaScript基础教程. 这篇文章带你尽快走进JavaScri ...

  3. Emacs简易教程

    Emacs简易教程阅读: 命令: $emacs 进入之后,输入: C-h t 这里,C-h表示按住[Ctrl]键的同时按h ####### 20090620 *退出: 输入“C-x C-c” *撤销: ...

  4. 文件上传利器SWFUpload入门简易教程

    凡做过网站开发的都应该知道表单file的确鸡肋. Ajax解决了不刷新页面提交表单,但是却没有解决文件上传不刷新页面,当然也有其它技术让不刷新页面而提交文件,该技术主要是利用隐藏的iFrame, 较A ...

  5. 【转】Delphi内嵌ASM简易教程

    Delphi内嵌ASM简易教程 作者:heiying2006-03-19 18:33分类:默认分类标签: 前言 Delphi作为一个快速高效的开发平台,使用的人越来越多,但熟悉在Delphi代码中嵌入 ...

  6. Ant 简易教程

    转载:http://www.cnblogs.com/jingmoxukong/p/4433945.html Ant 简易教程 Apache Ant,是一个将软件编译.测试.部署等步骤联系在一起加以自动 ...

  7. Intellj IDEA 简易教程

    Intellj IDEA 简易教程 目录 JDK 安装测试 IDEA 安装测试 调试 单元测试 重构 Git Android 其他 参考资料 Java开发IDE(Integrated Developm ...

  8. MetaProducts Offline Explorer使用简易教程

    MetaProducts Offline Explorer使用简易教程 by windtrace  20170419 最近想下载一个网站上的内容打包成chm文件,以便离线浏览,webzip太长时间不更 ...

  9. Zabbix实战-简易教程系列

    一.基础篇(安装和接入) Zabbix实战-简易教程--总流程  Zabbix实战-简易教程--整体架构图 Zabbix实战-简易教程--DB安装和表分区 Zabbix实战-简易教程--Server端 ...

随机推荐

  1. 201521123036 《Java程序设计》第8周学习总结

    本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 HashMap自定义排序 List<Map.Entry<Stri ...

  2. 201521123061 《Java程序设计》第五周学习总结

    201521123061 <Java程序设计>第五周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 1.代 ...

  3. 201521123026《JAVA程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  4. Java:验证在类继承过程中equals()、 hashcode()、toString()方法的使用

    以下通过实际例子对类创建过程汇中常用的equals().hashcode().toString()方法进行展示,三个方法的创建过程具有通用性,在项目中可直接改写. //通过超类Employee和其子类 ...

  5. mysql:视图,触发器,事务,存储过程,函数

    一.视图 视图是一个虚拟表并不是(正实存在的) 创建老师表 create table teacher( id int primary key auto_increment, tname varchar ...

  6. 解决Maven管理的项目下"Missing artifact xxx bundle"问题

    例如使用maven编译使用了mina的包的工程,出现如下提示:  [INFO] Scanning for projects... [INFO]                             ...

  7. Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/***]]

    问题描述:Tomcat容器和Eclipse启动运行时候报错 Failed to start component [StandardEngine[Catalina].StandardHost[local ...

  8. [python学习笔记] 开篇

    今天开始学习markdown,立贴为证. 平均一天一小时.两个月先学完基础. 所有的帖子只为了学习记录,有不对的请提出,轻吐槽

  9. 判断字符串中是否包含指定的内容&&字符串截取方法比较说明

    1.使用indexOf()方法 方法说明: 作用:indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置(从前向后查找). 语法:stringObject.indexOf(searc ...

  10. ASP.NET Core中如何调整HTTP请求大小的几种方式

    一.前言 一般的情况下,我们都无需调用HTTP请求的大小,只有在上传一些大文件,或者使用HTTP协议写入较大的值时(如调用WebService)才可能会调用HTTP最大请求值. 在ASP.NET Co ...