小蝌蚪傳記:前端實用技巧,通過babel精準操作js檔案

第一名的小蝌蚪發表於2021-12-09

今天是暗戀她的第90天,但我馬上就要失戀了

因為組織架構變動,我要換到一個離她很遠的地方

暗戀她的90天裡,我一直在997

每天都在跟同類互相殘殺

我厭倦了和一群老男人加班的日子

她是這段黑暗時間裡,唯一的光

她曾是年會的女主持

萬千男人暗戀的女神,而我只是個加班狗

她身邊都是鮮花和掌聲

我身邊全是摳腳大漢和LSP

她每天穿著好看的衣服和名牌包包

而我只有一件粑黃色上衣,和一個裝電腦的大黑包

我配不上她,但又被她深深的吸引

結婚7年來,我每天下班就回家

不抽菸不喝酒不近女色

騰訊男德第一人

現在居然對她產生了情愫

本以為我很專一,遇見她以後才明白

原來男人真的可以同時愛上好幾個女人

離別之前,不知道要不要跟她表白

如果表白,她一定會說我是個好人

如果自己長的再好看點,是不是就不用自卑了

可惜沒有如果,我是個完美的垃圾

就這樣心煩意亂的上線了一版程式碼,配置檔案還修改錯了

直接崩出線上大bug

所有網頁全部 404 not found

1000封報警郵件狂轟濫炸

boss委婉的發來1星績效的暗示

都要哭了

配置檔案這種東西,人工去修改,太容易受情緒影響而改錯了

這些重複且易出錯的操作,應該用工程化、自動化手段去解決

babel修改js配置檔案實現原理

完整程式碼參考:github

像那些js配置檔案,裡面可能有很多的非配置程式碼,而且一次可能要修改好幾個檔案

比如我們在前端專案,要插入一個頁面,需要修改router、menus等配置檔案,還要手動拷貝頁面模板等等

這些高重複機械化操作,人工修改非常容易出錯

我們可以直接用babel來操作AST抽象語法樹,通過工程化去精準修改。讓babel去幫我們找到指定位置,並正確插入配置程式碼。我們在做工程化開發的時候,經常會用到babel去操作AST。

首先我們瞭解一下什麼是AST

AST,抽象語法樹(Abstract Syntax Tree)它是原始碼語法結構的一種抽象表示。它以樹狀的形式表現程式語言的語法結構。

我們使用babel來轉化和操作AST,主要分為三個步驟:解析(parser)、轉換(traverse)、生成(generator)

操作AST三大階段

如下圖,如果我們想通過babel,在配置檔案裡面插入一段配置程式碼,應該怎麼實現呢

解析(parser)

第一步:讀取配置檔案程式碼,並生成AST抽象語法樹

let configJsData = fs.readFileSync(configJsPath, "utf8");

然後將配置檔案程式碼生成AST抽象語法樹

const parser = require("@babel/parser");

let configJsTree = parser.parse(`${configJsData}`,{
    sourceType: "module",
    plugins: [
      "jsx",
      "flow",
    ],
  });

configJsTree就是我們的AST了

加上sourceType: "module"這個配置屬性,是為了讓babel支援解析export和import

轉換(traverse)

轉換(traverse)階段,就是要遍歷整個AST抽象語法樹,找到指定的位置,然後插入對應的配置程式碼。

程式碼如下:

const traverse = require("@babel/traverse").default;

traverse(configJsTree, {
    ObjectProperty(path) {
      // 插入配置檔案程式碼
    },
  });

我們使用@babel/traverse的traverse方法進行遍歷整個AST

其中ObjectProperty的作用是在遍歷AST過程中,識別出所有的Object物件,因為我們是要將配置程式碼插入一個Object物件,所以使用的是ObjectProperty。如果要將配置插入陣列中,就使用ArrayExpression

然後我們開始進行配置程式碼的插入,將程式碼

{
  key: "testPath",
  icon: HomeOutlined,
  exact: true,
}

插入如下的位置

我們需要在traverseObjectProperty進行位置的查詢和程式碼插入

首先我們要找到key: 'home'的位置

程式碼如下:

traverse(configJsTree, {
    ObjectProperty(path) {
      if ( path.node.key.name === "key" && path.node.value.value === "home" ) {
        // 這就是 key: 'home'的位置
      }
    },
  });

通過path.node.key.namepath.node.value.value找到物件屬性為key並且物件值為home的Object物件

找到位置後開始插入程式碼

traverse(configJsTree, {
    ObjectProperty(path) {
      // 搜尋並識別出配置檔案裡key: "home" 這個object物件位置
      if ( path.node.key.name === "key" && path.node.value.value === "home" ) {
        path.parent.properties.forEach(e=>{
          if ( e.key.name === "children" ) {
           // 找到children屬性
          }
        })
      }
    },
  });

通過path.parent.properties找到物件的父級,然後遍歷父級下的所有屬性,找到children這個屬性。這就是我們要插入的位置。

接下來我們要構造要插入的資料

{
   key: "testPath",
   icon: HomeOutlined,
   exact: true,
}

構造資料的程式碼如下:

const t = require("@babel/types");

const newObj = t.objectExpression([
    t.objectProperty(
      t.identifier("key"),
      t.stringLiteral("testPath")
    ),
    t.objectProperty(
      t.identifier("icon"),
      t.identifier("HomeOutlined")
    ),
    t.objectProperty(
      t.identifier("exact"),
      t.booleanLiteral(true)
    ),
  ]);

可以看到用dentifier來標識物件的屬性,用stringLiteral標識字串資料,booleanLiteral標識boolean值,這些型別都可以在@babel/types查詢到。

最後一步,將構造好的資料插入:

e.value.elements.push(newObj)

完成~!

將所有 traverse 階段程式碼彙總起來如下:

const traverse = require("@babel/traverse").default;
const t = require("@babel/types");

traverse(configJsTree, {
    ObjectProperty(path) {
      // 搜尋並識別出配置檔案裡key: "home" 這個object物件位置
      if ( path.node.key.name === "key" && path.node.value.value === "home" ) {
        path.parent.properties.forEach(e=>{
          if ( e.key.name === "children" ) {
            const newObj = t.objectExpression([
              t.objectProperty(
                t.identifier("key"),
                t.stringLiteral("testPath")
              ),
              t.objectProperty(
                t.identifier("icon"),
                t.identifier("HomeOutlined")
              ),
              t.objectProperty(
                t.identifier("exact"),
                t.booleanLiteral(true)
              ),
            ]);
            e.value.elements.push(newObj)
          }
        })
      }
    },
  });

生成(generator)

這個階段就是把AST抽象語法樹反解,生成我們常規的程式碼

const generate = require("@babel/generator").default;

const result = generate(configJsTree, { jsescOption: { minimal: true } }, "").code;

fs.writeFileSync(resultPath, result);

通過@babel/generator將AST抽象程式碼語法樹反解回原始碼,jsescOption: { minimal: true }配置是為了解決中文為unicode亂碼的問題。

至此我們們就完成了對js配置檔案插入程式碼,最終結果如下:

以上就是通過babel操作AST,然後精準插入配置程式碼的全流程,完整程式碼參考:github

結尾

線上一切都穩定後

回頭看了下女神

她正在照鏡子塗口紅

就靜靜坐著,都好喜歡好喜歡

她很愛笑、每週三都會去打籃球,早上10:30準時到公司、喜歡咖啡和奶茶

她的生活每天都是一首歌

而我只會敲程式碼,加班加到腿抽筋

據線人說,她已經有男朋友了,又高又帥又有錢

而我又老又醜、不僅窮還禿

聽說她還很年輕,而我已經32

還有不到3年就35歲,我的時間不多了

怎麼能留戀世間煙火呢

女人只會影響我敲程式碼的速度

哎。。。真該死

我還是默默地守護她、支援她、欣賞她吧

不打攪是屌絲最高階的告白

只是可惜的是

至始至終,我都不知道她叫什麼

我們從沒說過一句話

都是我一個人的獨角戲

魯迅說過:“我知道妳不是我的花,但能途經妳的盛放,我不勝榮幸”

。。。。。。

深夜空無一人的總部大樓

就這樣默默坐在彼此的身邊

就當我們也曾經在一起過吧

這次離別,可能就再也見不到了

不知道妳會不會想起我

也不知道我會不會愛上別的女孩

但是還是謝謝妳

驚豔了我每個加班的夜晚

goodbye my lover

————— yours 小蝌蚪

作者:第一名的小蝌蚪,公眾號:第一名的小蝌蚪

相關文章