为啥套娃?聊聊 Babel、Jscodeshift 和阿里妈妈的 Gogocode

开发 前端
path 是节点之间的关系,每个 path关联父节点和当前节点,path 对象构成一条从当前节点到根结点的路径。state 是遍历过程中的共享数据的机制。

[[407446]]

首先,我是《babel 插件通关秘籍》 掘金小册的作者,对 babel 有源码级的掌握,算是有资格讨论这个话题。

本来会探讨以下话题:

  • babel 是怎么转换代码的
  • jscodeshift 是怎么转换代码的
  • babel 和 jscodeshift 的区别
  • 为什么不推荐 gogocode

babel 是怎么转换代码的

babel 编译流程分为 3 步:parse、transform、generate

  • parse:把源码转成 AST,babel parser(babylon) 支持 esnext 语法,可通过插件支持 typescript、jsx、flow 等语法
  • transform:对 AST 进行转换,通过 visitor 对不同的 AST 进行处理
  • generate:打印转换后的 AST 为目标代码,并生成 sourcemap

转换插件这样写(小册中的一个 linter 的案例):

  1. const { declare } = require('@babel/helper-plugin-utils'); 
  2.  
  3. const noFuncAssignLint = declare((api, options, dirname) => { 
  4.     api.assertVersion(7); 
  5.  
  6.     return { 
  7.         pre(file) { 
  8.             file.set('errors', []); 
  9.         }, 
  10.         visitor: { 
  11.             AssignmentExpression(path, state) { 
  12.                 const errors = state.file.get('errors'); 
  13.                 const assignTarget = path.get('left').toString() 
  14.                 const binding = path.scope.getBinding(assignTarget); 
  15.                 if (binding) { 
  16.                     if (binding.path.isFunctionDeclaration() || binding.path.isFunctionExpression()) { 
  17.                         const tmp = Error.stackTraceLimit; 
  18.                         Error.stackTraceLimit = 0; 
  19.                         errors.push(path.buildCodeFrameError('can not reassign to function', Error)); 
  20.                         Error.stackTraceLimit = tmp; 
  21.                     } 
  22.                 } 
  23.             } 
  24.         }, 
  25.         post(file) { 
  26.             console.log(file.get('errors')); 
  27.         } 
  28.     } 
  29. }); 
  30.  
  31. module.exports = noFuncAssignLint; 

声明 visitor 函数,然后在遍历的过程中会被调用,其中可以拿到 path 和 state 的 api:

path 是节点之间的关系,每个 path关联父节点和当前节点,path 对象构成一条从当前节点到根结点的路径。state 是遍历过程中的共享数据的机制。

通过 path 的一系列增删改查 AST 的 api 来完成 transform。

比如下列 api:

  1. getSibling(key)  
  2. getNextSibling() 
  3. getPrevSibling() 
  4. getAllPrevSiblings() 
  5. getAllNextSiblings() 
  6. isXxx(opts) 
  7. assertXxx(opts) 
  8.  
  9. insertBefore(nodes) 
  10. insertAfter(nodes) 
  11. replaceWith(replacement) 
  12. replaceWithMultiple(nodes) 
  13. replaceWithSourceString(replacement) 
  14. remove() 

jscodeshift 是怎么转换代码的

jscodeshift 也是代码转换的工具,但是 api 风格不同,是主动查找 AST,然后修改成新的 AST,最后生成代码的形式:

  1. module.exports = function(fileInfo, api) { 
  2.   return api.jscodeshift(fileInfo.source) 
  3.     .findVariableDeclarators('foo'
  4.     .renameTo('bar'
  5.     .toSource(); 

jscodeshift 的优势是更简洁。

但是 jscodeshift 能代替 babel 么?可以看下大牛给出的答案:

babel 和 jscodeshift 的不同

jscodeshift 的 parser 是 recast,曾经有 babel 的维护者想结合这两者:

https://github.com/facebook/jscodeshift/issues/168

利用 recast 做 parse,然后基于 babel parser 做转换。

下面有一个很精彩的回复,明确了 babel 和 jscodeshift 的不同:

我来梳理一下:

babel 的 transform api 是visitor 风格,也就是声明对什么 ast 做什么处理,然后在遍历过程中被调用,这种不和具体遍历方式耦合的写法是一种设计模式(访问者模式),处理再复杂的场景也能应对。就是处理简单场景显得稍微啰嗦点。

jscodeshift 是 collection 风格,类似 jquery,主动查找 ast,放到集合中操作,适合处理简单场景,要知道每种 ast 是怎么查找到的,然后做转换,要处理很多很多 case,万一查找路径不对,那可能就漏掉了一些情况,比起 babel 来,很难在复杂场景下没有 bug。

就像 jquery 和 mvvm 的区别一样,复杂场景还是 mvvm 的方式(babel)靠谱,不会漏掉一些 dom 没处理。(只是一个类比)

所以,简单场景可以用 jscodeshift,而所有场景都可以用 babel。

babel 的 visitor 的优点就是设计模式中访问者模式的优点,不和具体遍历方式耦合易于复用。

为什么不推荐 gogocode

gogocode 是这两天阿里妈妈出的 ast 修改工具,基于 babel 做了一层封装,说是简化了 ast 操作。

api 类似这样:

  1. $(code) 
  2.   .find('var a = 1'
  3.   .attr('declarations.0.id.name''c'
  4.   .root() 
  5.   .generate(); 

没错,基于 babel 的 visitor 风格的 api 封装出了 jscodeshift 的 collection 风格的 api。

本来 babel 的 visitor 虽然写起来麻烦一些,但是所有路径都能够处理到,而改成 collection 风格之后,一旦落掉了某条路径没错里,就会 bug。处理的 case 特别多,不适合复杂场景。

babel 本来的 visitor 模式是一种优点,结果又在上层封装出了 collection api。。如果想这么封装,为啥不直接基于 jscodeshift 呢。。。我没看懂这波操作。

我不看好这个 babel 套娃,我没有自信保证复杂场景下能够处理所有路径而不遗漏 case,复杂场景我选择直接用 babel 的 api。

总结

babel 是访问者设计模式的实现,分离了遍历方式和对 AST 的操作,使得操作可以复用,jscodeshift 是 collection 风格,类似 jquery,复杂场景容易落掉 case。

gogocode 基于babel 实现了 collection 风格,不看好,容易落下 case。

一句话总结:简单场景可以用 jscodeshift,所有场景都可以用 babel,不怎么推荐 gogocode。

责任编辑:武晓燕 来源: 神光的编程秘籍
相关推荐

2021-08-31 06:51:18

Babel前端开发

2023-04-24 20:47:08

2023-08-29 22:41:02

2023-10-25 14:16:00

训练模型

2012-05-11 10:54:16

Qcon淘宝Fourinone

2015-05-13 19:30:21

2022-10-19 09:05:45

编译程序员后端

2024-06-17 09:08:55

Linuxloop设备

2020-07-27 11:33:33

操作系统Windows游戏

2023-08-01 09:10:46

阿里妈妈

2021-09-02 16:15:29

开发技能代码

2020-09-07 06:38:54

HA高可用协议

2022-03-30 09:43:19

jscodeshif自动化重构开发

2019-11-26 17:41:59

AI 数据人工智能

2021-09-17 06:28:20

JOIN阿里Java

2021-09-11 19:46:14

配置

2021-09-02 13:38:48

Eslint Babel 插件

2015-10-23 17:35:23

LG

2017-06-16 15:16:32

点赞
收藏

51CTO技术栈公众号