作为一个程序员,当然总是期望自己的代码能「一次编写,四处运行」,但真实经验往往是「一处修改,百处填坑」,依赖落后了好几个版本了想要升级、老代码已经看着很不爽了打算重构,都需要下坚决的决心,毕竟哪里漏掉了或者改错了都可能酿成大祸,我们一般都怎么搞呢?
放弃不搞
顿时就减轻了痛苦有木有…但如果你坚持要搞,请往下看!
土办法的真香和局限
对于一些简单的需求,比如最近在掘金上看到了一个例子,去掉项目中 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 里的 ...
,这段代码匹配出了 text
和 value
这对应的值填给了 name
和 id
,剩下的原封不动放回去。
而下半部分我刻意加了一些「干扰代码」,以往我通过字符串替换 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 (
转译前
);
};
export default Btn;
复制代码
大概是这样:
这种需求其实挺常见的,原文提供了一个 基于 jscodeshift 的实现,深入到了 AST 进行操作,但如果用 GoGoCode 就会直观很多:
// 省略依赖和 input
const output = $(input)
.replace(`import { $$$ } from "@alifd/next"`, `import { $$$ } from "antd"`)
.replace(`转译前
`, `转译后
`)
.replace(
``,
``
)
.replace(
``,
``
)
.replace(``, ``)
.replace(``, ``)
.generate();
复制代码
相信你不要讲解也能知道这段代码是要做什么了~
开源了,希望能得到大家的反馈
我觉得这个项目挺有趣的,可以说是专门给代码做了一个 replace 程序,小姐姐说我看到得太浅显,其实这个项目不仅仅是 replace 这一招,这个项目支撑了我们几个几万行前端工程的架构升级计划,就算需求更复杂也是有办法搞定的,大家可以关注我们的账号或专栏,后面作者会发表更专业全面的介绍文章。
小姐姐是我们阿里妈妈 MUX 团队的叶兮,我们团队以前有过 iconfont、Rap、MockJS 这样受到社区欢迎的项目,这一次我们把 GoGoCode 也开源到 Github:github.com/thx/gogocod…,希望能对同样有大量代码修改需求的朋友有所帮助。
我们很希望了解到大家会经常遇到怎样的转换难题,如果你用现在 GoGoCode 不方便解决或者出了错,希望你能提给我们(issue:github.com/thx/gogocod… )。
最后:新项目求 star 支持!
Github:github.com/thx/gogocod…
官网:gogocode.io
更多前端技术交流圈