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 源码。我的以为直接看源码比看文档简单多了。

  • 相关阅读:
    bzoj 3531 [Sdoi2014]旅行(树链剖分,线段树)
    bzoj 2243 [SDOI2011]染色(树链剖分,线段树)
    spoj 375 Query on a tree(树链剖分,线段树)
    bzoj 2618 2618: [Cqoi2006]凸多边形(半平面交)
    C++中int型与char型相互转换的问题
    408 二进制求和
    407 加一
    斐波那契数列几种算法及时间复杂度分析
    397 Longest Continuous Increasing Subsequence
    376 二叉树的路径和
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/14685106.html
Copyright © 2011-2022 走看看