阿里妈妈出的新工具,给批量修改项目代码减轻了痛苦

作为一个程序员,当然总是期望自己的代码能「一次编写,四处运行」,但真实经验往往是「一处修改,百处填坑」,依赖落后了好几个版本了想要升级、老代码已经看着很不爽了打算重构,都需要下坚决的决心,毕竟哪里漏掉了或者改错了都可能酿成大祸,我们一般都怎么搞呢?

放弃不搞

顿时就减轻了痛苦有木有…但如果你坚持要搞,请往下看!

土办法的真香和局限

对于一些简单的需求,比如最近在掘金上看到了一个例子,去掉项目中 console.log(xxx) 代码,我相信大家平时遇到这种需求第一个想法都是直接选择编辑器批量文本替换成空字符串:

利用正则表达式,我们还是可以搞定很多需求的,但这样真的能包含所有情况么?有的同事是真的喜欢回车。

console
    .log('aaa')
复制代码

这种情况下,如果面对更复杂的需求或者严谨的场景,要么我们编写更复杂的正则表达式,要么我们就不得不去硬肝 AST 操作了。

AST有点复杂

上网搜索了一下,还真找到了利用 jscodeshift 操作 AST 去掉 console.log 的示例:

export default (fileInfo, api) => {
  const j = api.jscodeshift;

  const root = j(fileInfo.source)

  const callExpressions = root.find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: { type: 'Identifier', name: 'console' },
      },
    }
  );

  callExpressions.remove();

  return root.toSource();
};
复制代码

这需要大家对 AST 结构比较熟悉,在编写的时候需要对着解析好的节点结构才能缓缓写出,过一段时间再一看,也不会比弯弯绕绕的正则更好理解——大家平时太少接触 AST 了。

试着结合一下

有一次正当我为一个项目 API 大重构发愁,准备人肉爆肝的时候,我旁边的小姐姐实在看不下去了——她的项目比我更早地做了重构,人家不仅没爆肝,还顺手做了个工具 GoGoCode,这个工具借鉴了 jQuery 的两大思想:

选择器链式调用

用这工具去掉 console.log(xxx) ,其实就是一句话的事:

const $ = require('gogocode')

/** 刻意凌乱的代码 **/
const input = `
    console
.log(\`a, b,c\`);
`

// 关键代码
const output = $(input).replace('console.log()', '').generate()

console.log(output)
复制代码

它的创新之处就在于,把你输入的 console.log() 解析成一段 AST 节点去源代码里做节点树的匹配,这样自然就没有代码格式的问题了。你输入的代码就相当于 jQuery 里面的选择器,只不过这一次选择的是代码节点。

更多例子

清理 console.log 这个操作还是太简单了,我再举一个栗子!

我们经常使用这样的枚举列表:

const list = [
  {
    text: "A策略",
    value: 1,
    tips: "Atip",
  },
  {
    text: "B策略",
    value: 2,
    tips: "Btip",
  },
  {
    text: "C策略",
    value: 3,
    tips: "Ctip",
  },
];
复制代码

突然有一天,为了统一代码里的各种枚举,我们需要把 text 属性更名为 name,把 value 属性更名为 id,这个用正则很难精确匹配容易误伤,操作AST树还有些麻烦,用 GoGoCode 只需要这么替换一下就行了:

const $ = require('gogocode')

const input = `

const list = [
  {
    text: "A策略",
    value: 1,
    tips: "Atip",
  },
  {
    text: "B策略",
    value: 2,
    tips: "Btip",
  },
  {
    text: "C策略",
    value: 3,
    tips: "Ctip",
  },
];

// ts的类型标记,这种正则替换会被错误替换的,在 gogocode 里就不会
const text: string = ''
// 这一段因为没有 value 就不会被选择器匹配到,也不会被错误替换
const cfg = {
  text: ''
}

`

const output = $(input2).replace(
  '{ text: $_$1, value: $_$2, $$$ }',
  '{ name: $_$1, id: $_$2, $$$ }'
).generate();
复制代码

其中 $_$1$_$2 相当于正则中的通配符,但是在这里只会匹配代码里有效的 AST 节点,$$$ 则可以匹配剩下的节点,有点像 es6 里的 ... ,这段代码匹配出了 textvalue 这对应的值填给了 nameid,剩下的原封不动放回去。

而下半部分我刻意加了一些「干扰代码」,以往我通过字符串替换 text:name:的土办法遇到这样的就会误伤了,但 GoGoCode 不会。

再看前一段社区里的一个例子

正巧,前一段我在掘金看到了文章 像玩 jQuery 一样玩 AST,里面介绍了一个用 jscodeshift 进行 React jsx 代码转换的例子:

打算对这样一份代码做修改:

  • 从 @alifd/next 导入改成 antd
  • 转译前 改成 转译后
  • Button 中 type 参数转换:normal -> default,medium -> middle
  • Button 中有 text 参数的改成 type="link"
  • Button 中warning 参数的改成 danger
import * as React from 'react';
import styles from './index.module.scss';
import { Button } from "@alifd/next";

const Btn = () => {
  return (
    <div>
      <h2>转译前</h2>
      <div>
        <Button type="normal">Normal</Button>
        <Button type="primary">Prirmary</Button>
        <Button type="secondary">Secondary</Button>

        <Button type="normal" text>Normal</Button>
        <Button type="primary" text>Primary</Button>
        <Button type="secondary" text>Secondary</Button>

        <Button type="normal" warning>Normal</Button>
      </div>
    </div>
  );
};

export default Btn;
复制代码

大概是这样:

这种需求其实挺常见的,原文提供了一个 基于 jscodeshift 的实现,深入到了 AST 进行操作,但如果用 GoGoCode 就会直观很多:

// 省略依赖和 input
const output = $(input)
  .replace(`import { $$$ } from "@alifd/next"`, `import { $$$ } from "antd"`)
  .replace(`<h2>转译前</h2>`, `<h2>转译后</h2>`)
  .replace(
    `<Button type="normal" $$$></Button>`,
    `<Button type="default" $$$></Button>`
  )
  .replace(
    `<Button size="medium" $$$></Button>`,
    `<Button size="middle" $$$></Button>`
  )
  .replace(`<Button text $$$></Button>`, `<Button type="link" $$$></Button>`)
  .replace(`<Button warning $$$></Button>`, `<Button danger $$$></Button>`)
  .generate();
复制代码

相信你不要讲解也能知道这段代码是要做什么了~

开源了,希望能得到大家的反馈

我觉得这个项目挺有趣的,可以说是专门给代码做了一个 replace 程序,小姐姐说我看到得太浅显,其实这个项目不仅仅是 replace 这一招,这个项目支撑了我们几个几万行前端工程的架构升级计划,就算需求更复杂也是有办法搞定的,大家可以关注我们的账号或专栏,后面作者会发表更专业全面的介绍文章。

小姐姐是我们阿里妈妈 MUX 团队的叶兮,我们团队以前有过 iconfontRapMockJS 这样受到社区欢迎的项目,这一次我们把 GoGoCode 也开源到 Github:github.com/thx/gogocod…,希望能对同样有大量代码修改需求的朋友有所帮助。

我们很希望了解到大家会经常遇到怎样的转换难题,如果你用现在 GoGoCode 不方便解决或者出了错,希望你能提给我们(issue:github.com/thx/gogocod… )。

最后:新项目求 star 支持!

Github:github.com/thx/gogocod…
官网:gogocode.io
更多前端技术交流圈

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容