後續實戰文章發佈了:「GoGoCode 實戰」一口氣學會 30 個 AST 代碼替換小訣竅javascript
做爲一個程序員,固然老是指望本身的代碼能「一次編寫,四處運行」,但真實經驗每每是「一處修改,百處填坑」,依賴落後了好幾個版本了想要升級、老代碼已經看着很不爽了打算重構,都須要下堅定的決心,畢竟哪裏漏掉了或者改錯了均可能釀成大禍,咱們通常都怎麼搞呢?
css
頓時就減輕了痛苦有木有…但若是你堅持要搞,請往下看!
前端
對於一些簡單的需求,好比最近在掘金上看到了一個例子,去掉項目中 console.log(xxx)
代碼,我相信你們平時遇到這種需求第一個想法都是直接選擇編輯器批量文本替換成空字符串:java
利用正則表達式,咱們仍是能夠搞定不少需求的,但這樣真的能包含全部狀況麼?有的同事是真的喜歡回車。react
console
.log('aaa')
複製代碼
這種狀況下,若是面對更復雜的需求或者嚴謹的場景,要麼咱們編寫更復雜的正則表達式,要麼咱們就不得不去硬肝 AST 操做了。 git
上網搜索了一下,還真找到了利用 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 了。
es6
有一次正當我爲一個項目 API 大重構發愁,準備人肉爆肝的時候,我旁邊的小姐姐實在看不下去了——她的項目比我更早地作了重構,人家不只沒爆肝,還順手作了個工具 GoGoCode,這個工具借鑑了 jQuery 的兩大思想:
選擇器和鏈式調用
用這工具去掉 console.log(xxx)
,其實就是一句話的事:
github
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 代碼轉換的例子:
打算對這樣一份代碼作修改:
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 團隊的葉兮,咱們團隊之前有過 iconfont、Rap、MockJS 這樣受到社區歡迎的項目,這一次咱們把 GoGoCode 也開源到 Github:github.com/thx/gogocod…,但願能對一樣有大量代碼修改需求的朋友有所幫助。
咱們很但願瞭解到你們會常常遇到怎樣的轉換難題,若是你用如今 GoGoCode 不方便解決或者出了錯,但願你能提給咱們(issue:github.com/thx/gogocod… QQ羣:735216094)。
最後:新項目求 star 支持!
Github:github.com/thx/gogocod…
官網:gogocode.io