本文首发于 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. 【Beta阶段】测试与发布

    [Beta阶段]测试报告 一.Bug记录 1. 已经修复的BUG:文件查重的小组的空指针处理了 . 2.未能修复的bug: (1).在进行查重的时候必要要有10个文件,不然会报错:        (2 ...

  2. 201521123030 《Java程序设计》第7周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 public boo ...

  3. 201521145048《Java程序设计》第5周学习总结

    1. 本章学习总结 2. 书面作业 Q1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. 错误 ...

  4. 201521123081《Java程序设计》 第9周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 参考资料:XMIND 2. 书面作业 本次PTA作业题集 异常 常用异常 Q1. 题目5-1 1.1 截图你的提交结 ...

  5. page分页类

    <?php /** file: Page.class.php 完美分页类 Page */ class Page { private $total; //数据表中总记录数 private $lis ...

  6. 快速搭建一个Fabric 1.0的环境

    之前笔者写了一篇Fabric1.0 Beta的部署和Fabric 1.0的多机部署.但是很多人在部署Fabric的时候还是很容易出问题,所以我就再把Fabric 1.0的单机环境搭建讲一下.其实很多内 ...

  7. MyEclipse 快捷键问题

    解决Myeclipse提示快捷键Alt+/不可用问题 http://blog.163.com/cd-key666/blog/static/648929422011229123826/ 解决Myecli ...

  8. MySQL集群(四)之keepalived实现mysql双主高可用

    前面大家介绍了主从.主主复制以及他们的中间件mysql-proxy的使用,这一篇给大家介绍的是keepalived的搭建与使用! 一.keepalived简介 1.1.keepalived介绍 Kee ...

  9. readfile & file_get_contents异同

    记录一下:应用memcache时,准备把整个文件缓存到内存中,遇到了比较奇怪的事情,因为最初使用readfile来读取文件,结果这个函数返回一个字节数,而不是一个字符串,于是文件没办法再输出,最后使用 ...

  10. ApplicationContextAware

    1.实现了ApplicationContextAware接口,在Bean的实例化时会自动调用setApplicationContext()方法! 2.通过调用静态方法getBean即可获取 sprin ...