為什么 Bun 可能對 Node 降維打擊?
大家好,這里是大家的林語冰。持續(xù)關(guān)注,堅持閱讀,每天一次,進(jìn)步一點。
近年來,前端社區(qū)涌現(xiàn)了一大坨運行時,包括但不限于:
- 穩(wěn)如老狗的 Node.js
- 不破不立的 Deno
- 大破大立的 Bun
圖片
去年,“Bun 之父”J.S. 官宣 Bun 1.0 新鮮出爐,今年 Bun 團隊更是野心勃起,企圖用 Bun 打敗 Node。說是這樣說,氣勢不能輸,但私以為 Node 重度用戶的“路徑依賴”沒那么容易克服,大多數(shù)用戶(包括本人)大概率還是會在 Node 的舒適圈中“圈地自萌”。
話雖如此,還是有一大坨道友先質(zhì)疑、再質(zhì)疑:
- 為什么大家都拿 Bun 和人氣爆棚的 Node 相提并論?
- Bun 只是曇花一現(xiàn)之過眼云煙,還是可能終結(jié) Node 的長期壟斷?
Bun 到底是什么鬼物?
Bun 直男翻譯為“包子”,或者“小圓甜蛋糕”,我有一個大膽的想法:Bun 的含義大概是想成為像 Node 一樣前端愛好者生活必需的“面包”,抑或是企圖在運行時市場瓜分一塊“蛋糕”。
說巧不巧,初露頭角的 Bun 的頭像就是一大坨名副其實的包子,而成名多年的 Node 的吉祥物還在路上。
圖片
根據(jù) Bun 的官方公關(guān),Bun 是一款可以和 JS/TS “夢幻聯(lián)動”的 all-in-one toolkit(一體化工具人)。換而言之,Bun 是妥妥的“斜杠青年” —— Bun 是運行時/包管理器/打包器/測試運行器。Bun 主打的就是一條龍服務(wù) —— Node 有的我都有,Node 原生沒有的,不好意思我也有。
Bun 的產(chǎn)品定位
Node 于 2009 橫空出世,這位“00后”如今可謂人氣爆棚,以至于某些道友指貓為狗 —— Node 是一門“編程語言”,這大約就是“人怕出名貓怕胖”。
圖片
雖然但是,像 React 和 Angular 等前端技術(shù)一樣,隨著代碼屎山與日俱增,Node 的熵值也突破天際。
舉個栗子,臭名昭著的“npm 依賴地獄”,愛因斯坦看完都要重新審視相對論了。
圖片
再舉個栗子,Node 默認(rèn)的包管理工具 npm 差強人意,所以 Node 社區(qū)不得不“反復(fù)造輪子”,導(dǎo)致像我一樣的“選擇困難癥晚期患者”初學(xué) Node 時一臉懵逼:
- npm......
- yarn?
- pnpm~
- 都市傳說:Node 團隊成員又雙叒叕另起爐灶,搞了一個全新的 npm 備胎,日后再說
“Node 之父” R.D. 后知后覺,等到它想優(yōu)化 Node 的時候,Node 已經(jīng)形成“劣幣驅(qū)逐良幣”的不可抗力,就像強人工智能吊打卷毛狒狒一樣暴走失控了。于是乎,“Node 之父”為了避免在 Node 中一邊開飛機一邊修飛機,果斷切換賽道,化身成為“Deno 之父”。
作為“Node 之父”,R.D. 曾在公眾場合中毫無保留地公開處刑 Node 的“七大罪”,可謂罄竹難書、“父呲子笑”。反觀作為“Deno 之父”,R.D. 確實是模范爸爸。
Deno 是 JS/TS 的安全運行時,原生支持 TS,無需手動配置。與 Node 不同,Node 的腳本默認(rèn)具有廣泛的權(quán)限,Deno 則認(rèn)為“腳本千萬條,安全第一條”,要求開發(fā)者顯式賦予敏感操作的權(quán)限,比如文件系統(tǒng)的讀寫。這自然增加了我們的學(xué)習(xí)成本和心智負(fù)擔(dān),但 Deno 的魯棒性確實對 Node “降維打擊”。
Bun 的初衷大抵也是如此,為了對 Node 基建“降維打擊”,Bun 被設(shè)計為比 Node 更絲滑、更精簡的現(xiàn)代化競品,而不僅僅是備胎。
運行時測評
Node 主要使用 C艸 編寫,而 Bun 則使用 Zig (低階通用編程語言)編寫。本質(zhì)上而言,Bun 是一個 JS/TS 的運行時。所謂運行時,顧名思義就是一個提供使用和運行程序的環(huán)境。
運行時的關(guān)鍵組件之一是 JS 引擎,用于將 JS 代碼轉(zhuǎn)換為機器碼。Node 使用為 Chrome 瀏覽器提供支持的谷歌 V8 引擎,而 Bun 則使用 JSC(JavaScriptCore),此乃蘋果為 Safari 瀏覽器開發(fā)的開源 JS 引擎。
V8 和 JSC 各有千秋,兩者使用了不同的架構(gòu)和優(yōu)化策略。JSC 優(yōu)先考慮更快的啟動時間和更少的內(nèi)存占用,短板在于更慢的執(zhí)行時間。V8 優(yōu)先考慮更快的執(zhí)行和更多的運行時優(yōu)化,短板在于更多的內(nèi)存開銷。
圖片
如你所見,Bun 的運行性能比 Node 快 4.81 倍。
雖然 Node 是一個給力的 JS 運行時,但 Node 原生并不支持 TS。要在 Node 中跑 TS,需要訴諸第三方包。一種常見方案是,使用諸如 tsx/esbuild/babel 等依賴先將 TS 轉(zhuǎn)換為 JS,然后按需“優(yōu)雅降級”為低版兼容性代碼。
相比之下,Bun 內(nèi)置了 TS 轉(zhuǎn)譯器,原生支持 .js/.ts/.jsx/.tsx 文件,無需安裝任何外部依賴。Bun 的內(nèi)置轉(zhuǎn)譯器將各種亂七八糟的文件無縫轉(zhuǎn)換為平平無奇的 JS,無需額外步驟就能直接跑 TS。
尤其在跑 TS 文件時,這種性能跑分會被放大,因為 Node 在運行前需要足夠的前戲 —— 一個多余的轉(zhuǎn)譯步驟。
圖片
如你所見,Bun 跑 TS 時對 Node 生態(tài)“降維打擊”。
Node 生態(tài)的另一個“阿喀琉斯之踵”在于模塊系統(tǒng),模塊系統(tǒng)允許我們將代碼組織成可復(fù)用片段,目前人氣爆棚的兩個模塊系統(tǒng)是:
- CJS(CommonJS)
- ESM(ES 模塊)
CJS 源自 Node,使用 require/module.exports 處理同步模塊,適合服務(wù)端操作。ES6 強勢引入 ESM 則采用 import/export 語法,提供靜態(tài)異步模塊,且可以針對 Vite 等現(xiàn)代構(gòu)建工具優(yōu)化,比如 tree-shaking(樹搖優(yōu)化)。
Node 原生支持 CJS,漸進(jìn)實驗性支持 ESM。作為前端愛好者,一般初戀都是瀏覽器,后來和 Node 貼貼可能會很折磨,因為 CJS 和 ESM 再次讓我們選擇困難,最終導(dǎo)致決策癱瘓。
在 Node 中使用 ESM 常見方案,包括但不限于:
- 在 package.json 中添加 "type": "module" 屬性
- 使用 .mjs 擴展名取代 js 擴展名
Node 從 CJS 過渡到 ESM 走了很長的路,花了整整 5 年才在沒有實驗標(biāo)志的情況下支持 ESM。不管是學(xué)習(xí)成本、開發(fā)體驗還是心智模型,模塊的兼容性始終是壓在 Node 心頭的一只胖橘。
Bun 原生兼容 CJS/ESM,無需任何配置。Bun 的亮點功能是,它能夠在同一文件中同時支持 import/require(),類似于舊版 TS 的奇葩模塊語法,這在 Node 中是不可能事件:
// Bun 中的混合模塊語法
import vue from 'vue'
const react = require('react')
雖然但是,私以為混合模塊可能是“設(shè)計失誤”,或者說“在飆歷史倒車”。從兼容性看,混合模塊在技術(shù)上是一個自然延伸的功能,但對于用戶而言,拋開學(xué)習(xí)成本和心智模型不談,混合模塊明顯增加了維護的熵值。我的個人心證是,建議大家不管在瀏覽器還是 Node 中,都盡量擁抱標(biāo)準(zhǔn)的 ESM。
舉個栗子,Vite 是一個人氣爆棚的現(xiàn)代化工具,Vite 在開發(fā)環(huán)境擁抱標(biāo)準(zhǔn)的 ESM,在生產(chǎn)構(gòu)建則按需轉(zhuǎn)譯模塊語法。盡管如此,還是存在一大坨 corner case(極端用例),這是 Vite 使用 rollup 構(gòu)建時無法完美兼容的,尤大一度在 ViteConf 國際大會上瘋狂吐槽。老粉都知道,去年我共享的 Vite 前沿資訊有提及,Vite 已經(jīng)直接棄用 CJS。猶豫就會敗北,私以為 ESM 只會比 CJS 越來越流行,這就是標(biāo)準(zhǔn)的魔力,就像專一的鏟屎官更能被貓貓青睞。
總而言之,個人建議在使用 Bun 時,盡量避免使用混合模塊語法,因為一點也不符合人體工程學(xué)。
Vite 等現(xiàn)代化工具的福利之一是熱重載,在代碼更改時可以自動刷新或重新加載 App,無需完全重啟,真正做到一邊開飛機、一邊修飛機,提高開發(fā)效率和開發(fā)體驗。
Node 以前原生不支持熱重載,常見方案包括但不限于:
- 安裝 nodemon 等第三方包來硬重啟
- Node 18+ 實驗性支持 --watch flag
雖然但是,nodemon 可能會中斷,比如斷開 HTTP 和 WebSocket 連接,而 --watch 還處于實驗階段。
Bun 使用 --hot flag 原生支持熱重載,與需要重啟整個進(jìn)程的 Node 不同,Bun 會就地重載代碼,而不會終止舊進(jìn)程。這可以確保 HTTP 和 WebSocket 的連接不間斷,并保留 App 狀態(tài),提供更絲滑的開發(fā)體驗。
除了 JS 的標(biāo)準(zhǔn)(比如模塊),對瀏覽器標(biāo)準(zhǔn)的 Web API(比如 WebSocket),Node 的支持也不一致。
舉個栗子,Node 的早期版本不支持瀏覽器中常用的 fetch API,我們必須依賴 node-fetch 等第三方模塊來“曲線救國”。雖然但是,Node 18+ 開始實驗性支持 fetch,目測未來可期。
Bun 則內(nèi)置支持這些 Web 標(biāo)準(zhǔn) API,我們可以直接使用穩(wěn)定的 fetch/Request/Response 等 API,無需任何額外依賴。由于這些 API 是 Bun 的原生實現(xiàn),所以其性能比第三方備胎更快、更可靠。
使用 Web 標(biāo)準(zhǔn) API 設(shè)置 HTTP 服務(wù)器或 WebSocket 服務(wù)器,它每秒處理的請求比 Node 多 4 倍,處理的 WebSocket 消息比 Node 的 ws 包多 5 倍。
簡而言之,Node 生態(tài)的大部分功能需要我們手動安裝第三方包來“曲線救國”,而 Bun 不僅原生支持,而且青出于藍(lán)。
包管理器
Bun 本身還是一個功能強大的包管理器。
舉一反一,CRUD 相關(guān)命令不能說是毫無關(guān)系,只能說是一毛一樣:
Bun | npm | 目的 |
|
| 安裝 |
|
| 將新依賴添加到項目中 |
|
| 添加新的開發(fā)依賴 |
|
| 從項目中刪除依賴 |
|
| 將指定包更新到最新版本 |
|
| 從 |
Bun 的命令似曾相識,沒有壓力山大的學(xué)習(xí)成本,只有無縫銜接的開發(fā)體驗。而且 Bun 采用每個操作系統(tǒng)可用的最快系統(tǒng)調(diào)用,確保最佳性能,擁有比 npm 快幾個數(shù)量級的安裝速度,充分利用全局模塊緩存,消除從 npm 注冊表的冗余下載,從此告別“npm 黑洞”,愛因斯坦看完不用再重新審視相對論了。
本人現(xiàn)在使用的是 pnpm,但還是欲求不滿,但是 Bun 可以真正讓我們幸福感拉滿:
圖片
天下武功,唯快不破。如你所見,Bun 啪的一下很快啊就下載完了。
打包器
所謂打包,指的是是獲取多個 JS 文件,并將其合并到一個或多個優(yōu)化包中的過程。此過程還可能涉及轉(zhuǎn)換,比如將 TS 轉(zhuǎn)換為 JS,或者壓縮代碼減小體積。Node 的打包通常由第三方工具而不是 Node 本身處理。Node 生態(tài)目前有一大坨人氣爆棚的打包器,包括但不限于
- Webpack
- Rollup
- Vite
它們都提供了代碼分割、樹搖優(yōu)化和熱模塊替換等功能。
Bun 本身也是一個打包器。它旨在打包各種平臺的 JS/TS 代碼,包括瀏覽器中的前端 App(Vue/React App)和 Node。Bun 比 esbuild 快 1.75 倍,并且對 Webpack 等其他打包器“降維打擊”。
圖片
Bun 的一個天秀功能是 JS 宏,這允許在打包期間執(zhí)行 JS 函數(shù),并將結(jié)果直接內(nèi)聯(lián)到最終打包中。
舉個栗子,在打包過程中利用 JS 宏來獲取貓貓的名字,該宏不是運行時的 API 調(diào)用,而是在打包時獲取數(shù)據(jù),將結(jié)果直接內(nèi)聯(lián)到最終產(chǎn)物中:
// cats.ts
export async function getCat() {
const response = await fetch('https://space.bilibili.com/3493137875994964?spm_id_from=333.1245.0.0')
const cat = await response.json()
return cat.name
}
// index.ts
// Bun 的 JS 宏
import { getCat } from './cats.ts' with { type: 'macro' }
const cat = await getCat()
// build/index.js
// 打包后直接內(nèi)聯(lián)數(shù)據(jù),比如貓貓的名字
var cat = await '人貓神話'
console.log(cat)
測試運行器
雖然 Node 一般依賴 Vitest/Jest 來滿足 TDD(測試驅(qū)動開發(fā)),但 Bun 內(nèi)置了測試運行器,它被設(shè)計為完全兼容 Jest。Jest 是一個以“expect”風(fēng)格 API 聞名的測試框架,這種兼容性確保熟悉 Vitest/Jest 愛好者可以無縫銜接到 Bun,沒有任何陡峭的學(xué)習(xí)曲線。
Bun 的測試運行器不僅涉及兼容性;還涉及速度。舉個栗子,Bun 中的 expect().toEqual() 比 Vitest 快 10 倍。
高能總結(jié)
Bun 和 Node 目前測評跑分的異同點,包括但不限于:
對照實驗 | Node | Bun |
編程語言 | C艸 | Zig |
JS 引擎 | 谷歌 Chrome V8 | 蘋果 Safari JSC |
TS 轉(zhuǎn)譯 | 第三方包 | 原生支持 |
模塊系統(tǒng) | 從 CJS 過渡到 ESM | 原生支持混合模塊 |
fetch | 第三方包/實驗性支持 | 原生支持 |
熱重載 | 第三方包/實驗性標(biāo)志 | 原生支持 |
包管理器 | npm/pnpm | 原生支持 |
打包工具 | Vite | 原生支持 |
TDD | Vitest | 原生支持 |
Bun 目前的痛點(個人向),包括但不限于:
- Windows 支持不盡如人意,不如 Linux/MacOS
- Node 生態(tài)兼容性差強人意,暗中觀察中
- 不像 Vite 完美擁抱 ESM,混合模塊疑似“飆歷史倒車”
- Zig 可能是世界上最好的語言,但開源不友好
Node 憑借其成熟的生態(tài),一直在前端運行時穩(wěn)坐頭等艙,強如 Deno 也難以撼動其霸主地位。雖然但是,Bun 正以一位不容小覷的挑戰(zhàn)者身份嶄露頭角。雖然 Bun 還未成年,但它名噪一時毋庸置疑,目測是一只潛力股。目前,Bun 針對 MacOS 和 Linux 進(jìn)行了優(yōu)化,而 Windows 的支持正在進(jìn)行中。不幸的是,本人日常開發(fā)使用的偏偏是 Windows 系統(tǒng)......