zoukankan      html  css  js  c++  java
  • jscodeshift 简易教程

    本文首发于 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 的结构:

    image

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

    image

    可以看到上面那串代码被转换成了这么一种树形结构,其中 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 写代码的时候,只要写了这个方法就会有入参提示:

    image

    知道了这些,再举个例子,我要把上面的 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 源码。个人觉得直接看源码比看文档简单多了。

  • 相关阅读:
    mysql系列二、mysql内部执行过程
    mysql系列一、mysql数据库规范
    Centos6.5使用yum安装mysql——快速上手必备
    linux安装tomcat
    linux安装jdk
    tar 解压缩命令
    java并发编程系列四、AQS-AbstractQueuedSynchronizer
    JS数组方法汇总 array数组元素的添加和删除
    如何提升工作效率
    Excel学习笔记
  • 原文地址:https://www.cnblogs.com/axes/p/7694041.html
Copyright © 2011-2022 走看看