本文首发于 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. 【1414软工助教】博客链接和coding链接

    某些同学提供的coding.net用户名无法访问.请同学们自己点击自己的两个链接,如果发现有错,请在本博客的评论区给出正确的链接. 格式为: 学号后3位 链接 例如:***502 https://co ...

  2. 团队作业4——第一次项目冲刺(Alpha版本)7th day

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 在计时模式下能够记录用户的用户名和成绩,没有弄登录功能, 将程序定义为单机的 未完成的卡片为登录功能和使用QQ登录. 四.困难 ...

  3. 201521123069 《Java程序设计》 第2周学习总结

    1. 本章学习总结 (1)String类.StringBuilder类(频繁进行字符串的修改应选用StringBuilder,不会生成大量的字符串对象).Math类的用法.字符串池的概念 (2)Sca ...

  4. 201521123112《Java程序设计》第1周学习总结

    1.本周学习总结 本周通过面授课和上机课,以及在课后通过对<Java学习笔记>前一二章的阅读,初步了解了Java在计算机领域中的重要性,以及Java为什么能够这么广泛的运用在编程中.通过上 ...

  5. locale命令设置语言环境

    locale命令设置语言环境 在Linux中通过locale来设置程序运行的不同语言环境,locale由 ANSI C提供支持.locale的命名规则为_.,如zh_CN.GBK,zh代表中文, CN ...

  6. Java并发之synchronized关键字

         上篇文章我们主要介绍了并发的基本思想以及线程的基本知识,通过多线程我们可以实现对计算机资源的充分利用,但是在最后我们也说明了多线程给程序带来的两种典型的问题,针对它们,synchronize ...

  7. java类成员变量与代码块初始化

    首先根据下面的这个一段代码:引入关于java初始化顺序的问题public class InitationTest extends Person { public InitationTest() { S ...

  8. java 使用redis 数据库

    [TOC] java 使用redis 数据库 连接redis package com.wsc.redis.Test1; import java.util.List; import java.util. ...

  9. JVM菜鸟进阶高手之路十一(eden survivor分配问题)

    转载请注明原创出处,谢谢! 问题 这个Xmn设置为1G,,我用jmap -heap 看,这个Eden From To怎么不是一个整8:1:1的关系呢? 我看内存分配还是没变,我Xmn1g,感觉From ...

  10. NameError: name 'messagebox' is not defined 错误处理

    写了段代码,想在按下button的时候跳个提示框出来,调试的时候提示了messagebox未定义 from tkinter import * def test_show(): messagebox.s ...