IT序号网

jscodeshift 简易教程知识解答

leader 2021年05月25日 编程语言 147 0

本文首发于 IT虾米网

背景

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.类型名称 就能查到全部这个类型的节点。

若是想了解全部的类型:能够戳这个连接 IT虾米网

说完类型,若是咱们要建立一个某种类型的节点,就像上面的经过 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 源码。我的以为直接看源码比看文档简单多了。


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

前端动画lottie-web知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。