別再用 Bash 寫(xiě)前端自動(dòng)化腳本了!
導(dǎo)讀
Vladimir[1] 發(fā)現(xiàn)自己一直討厭 bash 編寫(xiě)的自動(dòng)化流程腳本,并且在機(jī)緣巧合下發(fā)現(xiàn)同事們都有類(lèi)似的想法,因此他分享了他認(rèn)為 JavaScript 編寫(xiě)自動(dòng)化腳本的優(yōu)勢(shì),看看能不能說(shuō)服大家去共建更好的生態(tài)。
與之相關(guān)的是,谷歌的 zx[2] 項(xiàng)目正是為此而生,并且在去年的 JavaScript 工具流行趨勢(shì)調(diào)查中獲得了第一名。
今年最受歡迎的項(xiàng)目是谷歌的 zx,可在 JavaScript 或 TypeScript 中編寫(xiě)簡(jiǎn)單的命令行腳本。
zx 支持在代碼中嵌入任何 bash 表達(dá)式(ls、cat、git 等等),并借助 JavaScript 模板字面量獲得結(jié)果。
zx 涵蓋了多個(gè)軟件包提供的功能:
node-fetch:使用與瀏覽器中相同的 API 發(fā)出 HTTP 請(qǐng)求;
fs-extra:運(yùn)行文件系統(tǒng);
Globby:匹配給定用戶友好模式的文件名;
接下來(lái)是他所分享的一些看法:
我在日常的工作中也體會(huì)到,大家仿佛有共識(shí)一般默認(rèn)寫(xiě)自動(dòng)化構(gòu)建腳本時(shí)要去用 bash,希望這篇文章可以帶給大伙一些不一樣的思考,也許 JavaScript 來(lái)寫(xiě)會(huì)更好?
先看看幾個(gè)可能的優(yōu)點(diǎn):
- 你的團(tuán)隊(duì)可能對(duì) JS 最熟悉
- dev 和 CI 機(jī)器上很可能默認(rèn)安裝了 Node
- 直接可以訪問(wèn)其他 JS 工具
- Node 是跨平臺(tái)的運(yùn)行時(shí)
- 進(jìn)程間通信是異步的,而且相當(dāng)方便
如果你時(shí)間不多的話,不妨看看快速比較表格:
這是你團(tuán)隊(duì)的主要語(yǔ)言
相比于 bash,大多數(shù)前端團(tuán)隊(duì)都更熟悉 JS。Node 是具有特殊的 API,但總的來(lái)說(shuō)它有函數(shù)一等公民,循環(huán)和 promise 等熟悉特性。bash?我搞了幾年下來(lái)還是不確定它是咋工作的 —— 語(yǔ)法很熟悉,但在意想不到的地方又不一樣,大多數(shù)變量是字符串,到底存在模塊不?如果我錯(cuò)了,也不要糾正我,我不關(guān)心了。我一直只是用的時(shí)候去谷歌……
每個(gè)體面的程序員都需要學(xué)習(xí) bash?這是病態(tài)的!如果你的后端同事需要在你的項(xiàng)目中做一些緊急改動(dòng),那他應(yīng)該學(xué)習(xí)一些 JS。C 語(yǔ)言風(fēng)格的語(yǔ)法讓任何人都能大概了解代碼的意圖。當(dāng)然從這個(gè)角度來(lái)看 bash 也差不多,但 JS 在這里起碼并不比它差。
在 JS 優(yōu)先的團(tuán)隊(duì)中使用 JS 進(jìn)行自動(dòng)化腳本的編寫(xiě),是最合乎邏輯的選擇。
runtime 大概率已經(jīng)安裝了
你的 bash 腳本即使成功運(yùn)行了,麻煩也沒(méi)有結(jié)束,因?yàn)樗ǔ?huì)在另一臺(tái)機(jī)器上失敗(說(shuō)你呢,Alpine Docker 容器……)。各種 shells[3](SH,ASH,BASH,ZSH)都略有不同,在不同的 Linux 發(fā)行版上也不完全通用。你當(dāng)然可以手動(dòng)挑選必要的包,或者重新手寫(xiě)邏輯,但是真的很浪費(fèi)時(shí)間。
用 Node 的話,丟失的 runtimes 的問(wèn)題非常少見(jiàn) - CI 機(jī)器無(wú)論如何都可以運(yùn)行 npm / yarn,這些和 node 綁在一起。此外,一旦 node 程序編寫(xiě)完成,通常每臺(tái)計(jì)算機(jī)上都可以運(yùn)行。
開(kāi)箱即用的跨平臺(tái)特性
這就引出了下一點(diǎn) —— node 是一個(gè)跨平臺(tái)的運(yùn)行時(shí),在 linux、mac 和 windows 上運(yùn)行良好。對(duì),MacOS 是兼容 POSIX 的,但是許多命令在選項(xiàng)和輸出格式上仍然有細(xì)微的差異?,F(xiàn)在,你需要 Windows 支持嗎?雖然大多數(shù)前端開(kāi)發(fā)人員都使用 Mac,而且存在 Win 的 bash 端口。但是,免費(fèi)支持開(kāi)箱即用總是很好的:
- 降低了開(kāi)源項(xiàng)目的貢獻(xiàn)障礙。
- 一旦我需要匆忙在 Windows 服務(wù)器上啟動(dòng) dev 服務(wù)器的時(shí)候,一般都很不愉快。
- 經(jīng)理想玩玩你的項(xiàng)目,但他用的是 Win 電腦。
Node 團(tuán)隊(duì)花了大量時(shí)間抽象出操作系統(tǒng)之間的差異。忽視這一點(diǎn),而去堅(jiān)持使用 bash,會(huì)適得其反。
直接訪問(wèn)其他 JS 工具
前端工作流(webpack/parcel/babel/PostSS)中的大多數(shù)工具都開(kāi)放了 node APIs。甚至像 esbuild 和 swc 這樣的非 JS 工具也提供 node bindings。如果你的自動(dòng)化編排在 node 上運(yùn)行,那么訪問(wèn)這些 API 就很簡(jiǎn)單:只需導(dǎo)入包并調(diào)用函數(shù)。
在 bash 中,有兩個(gè)麻煩的選項(xiàng)可以與基于 node 的工具集成:
- 通過(guò)奇怪的選項(xiàng)格式調(diào)用 CLI。
- 編寫(xiě)一個(gè)最小的 JS 包裝器來(lái)調(diào)用 node API,從 bash 調(diào)用它。
另外一個(gè)好處是,由于許多工具的 CLI 位于單獨(dú)的軟件包中(如 @babel/CLI),如果直接使用 node API,可以跳過(guò)安裝,從而節(jié)省一點(diǎn) npm i時(shí)間。
體面的進(jìn)程間通信
node 作為自動(dòng)化運(yùn)行時(shí)的一個(gè)很棒的方面是它的 IPC 能力。有時(shí)候你更喜歡通過(guò) CLI 而不是 node API 使用其他工具。也可以 —— 在 node 中,這可以通過(guò) child_process[4] 異步且跨平臺(tái)地完成!你甚至可以在不同的進(jìn)程之間使用管道輸出,就像 shell 的管道操作符 |。雖然內(nèi)置的Stream和child_process API 可能不太符合人體工程學(xué),但你可以根據(jù)自己的口味使用包裝器——我比較喜歡execa。
bash 也擅長(zhǎng)于流程管理,但對(duì)我來(lái)說(shuō),有太多的可能性了——參考這個(gè) stackoverflow 問(wèn)題:里面提到有五種不同的并行運(yùn)行命令的方式[5],如果你不知道自己在做什么,這就很容易讓你搬起石頭砸自己的腳。
龐大的生態(tài)系統(tǒng)
npm 為各種各樣的問(wèn)題提供了很好的解決方案。我最喜歡的是管理子進(jìn)程的 execa[6]、處理 CLI 選項(xiàng)的yargs[7]和輸出樣式的chalk[8]。
是的,也存在類(lèi)似的許多命令行工具,但必須使用特定于操作系統(tǒng)的軟件包管理器(apt?brew?apk?)安裝它們。大伙真的不想處理這種問(wèn)題。此外,您安裝的任何 CLI 軟件包也可以通過(guò) spawn/exec 在 node 中使用。
因此,以下是我選擇 JS/node 來(lái)管理復(fù)雜自動(dòng)化工作流的主要原因:
- JS 是你們團(tuán)隊(duì)的主要語(yǔ)言!
- 節(jié)點(diǎn)運(yùn)行時(shí)通常安裝在本地和 CI 中,因?yàn)槟幚淼氖?npm/Spread。
- node 跨平臺(tái)運(yùn)行,與 bash 和 make 不同。
- node 可以直接訪問(wèn)其他 JS 工具。
- node IPC(用于編排 CLI 工具)非常合適,尤其是使用 execa 時(shí)。
- 在 node 中編寫(xiě) CLI 工具,有很多好用的軟件包。
當(dāng)然也有理由避免使用 node(比如缺少關(guān)于自動(dòng)化用例的教程,對(duì)于不熟悉 node 的人來(lái)說(shuō),異步的復(fù)雜性),但我仍然相信它是 JS 項(xiàng)目中構(gòu)建自動(dòng)化流程最可靠的選擇。
Vladimir:https://thoughtspile.github.io/2022/02/14/js-automation
參考資料
[1]Vladimir: https://twitter.com/thoughtspile
[2]zx: https://github.com/google/zx
[3]各種 shells: https://en.wikipedia.org/wiki/Shell_script
[4]child_process: https://nodejs.org/api/child_process.html
[5]里面提到有五種不同的并行運(yùn)行命令的方式: https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script
[6]execa: https://github.com/sindresorhus/execa
[7]yargs: https://github.com/yargs/yargs
[8]chalk: https://github.com/chalk/chalk