
隨著專案規模成長,越來越多團隊選擇將前端、後端、共用套件放進同一個 repository 管理,
也就是所謂的 Monorepo。
過去大家熟悉的組合可能是 pnpm workspaces + Turborepo,或是 Yarn workspaces + Lerna。
但自從 Bun 把套件管理員這塊做得又快又完整之後,越來越多團隊開始評估「能不能直接用 Bun 來管 Monorepo?」
今天這篇要來聊聊 Bun Workspaces 的實際用法、常見的專案結構,以及幾個上線前一定要注意的最佳實踐。
以官方展示的資料來說,Bun 安裝 Remix 這種規模的 Monorepo 大約只要 500ms,
比 npm 快 28 倍、比 Yarn v1 快 12 倍、比 pnpm 快 8 倍
(雖然最近 bun 到 1.4.0 版了 所以這數據有可能過時)
除了速度之外,Bun Workspaces 還有幾個吸引人的特點:
bun.lock):diff 起來乾淨易讀,在 PR review 時很好對照workspace: 協議:套件間互相依賴不需要額外設定bunfig.toml 做細部設定:不需要額外的 monorepo 工具就能滿足基本需求不過要先說清楚一個常見誤解:Bun workspaces 本身不是完整的 Monorepo 工具。
它負責的是套件安裝與本地連結,但不會做 task 快取,
也不會偵測某次 commit 影響了哪些套件——這代表如果你只用 Bun,
CI 每次都會重新跑過所有套件的任務,不管改動有多小。如果專案規模變大,
通常還是需要在 Bun 之上疊加 Turborepo 或 Nx 這類工具來做任務編排與快取。
一個典型的 Bun Monorepo 長這樣:
<root>
├── bun.lock
├── package.json
├── tsconfig.json
└── packages
├── pkg-a
│ ├── index.ts
│ └── package.json
├── pkg-b
│ ├── index.ts
│ └── package.json
└── pkg-c
├── index.ts
└── package.json
根目錄的 package.json 用 workspaces 欄位定義 glob pattern:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"]
}
幾個實務上的重點:
"private": true:這是慣例,用來避免不小心把根目錄套件發布到 npmpackage.json:明確宣告自己的依賴,不要依賴 hoist 上去的隱性套件如果你的專案同時有 apps(可執行的應用程式)跟 packages(共用邏輯),也可以拆成兩個目錄:
├── apps/
│ ├── web/
│ ├── api/
│ └── admin/
├── packages/
│ ├── ui/
│ ├── utils/
│ └── config/
└── package.json
Bun 的 workspaces 欄位也支援 negative pattern,可以用 ! 排除特定目錄,
例如 ["packages/*", "!packages/legacy"]
workspace: 協議要讓 monorepo 裡的套件互相引用,在 package.json 的 dependencies 裡指定 workspace: 開頭的版本:
{
"name": "@myorg/frontend",
"dependencies": {
"@myorg/shared": "workspace:*"
}
}
Bun 支援三種寫法:
workspace:* — 永遠指向本地最新版本workspace:^ — 發布時會轉換成 ^ 語義化版本範圍workspace:~ — 發布時會轉換成 ~ 語義化版本範圍值得注意的是,發布套件到 npm 時,Bun 會自動把 workspace: 替換成實際的版本號,
例如 workspace:* 會變成 1.0.1,這讓本地開發與正式發布可以無縫切換,不需要手動改版本字串。
加完依賴後,記得在專案根目錄執行一次 bun install,才會把本地套件連結(symlink)起來並更新 lockfile。
# 在根目錄安裝所有 workspace 的依賴
bun install
# CI 環境建議使用 frozen lockfile,避免意外更新版本
bun install --frozen-lockfile
大型 monorepo 常常不需要每次都動到全部套件,這時候 --filter 就很好用:
# 安裝所有以 pkg- 開頭、但排除 pkg-c 的套件
bun install --filter "pkg-*" --filter "!pkg-c"
# 也可以用路徑寫法
bun install --filter "./packages/pkg-*" --filter "!pkg-c"
cd packages/backend
bun add express
# 或從根目錄直接指定 workspace
bun add lodash --filter "@myorg/shared"
Bun 會自動偵測你目前在哪個 workspace 底下,把依賴加進對應套件的 package.json,同時更新根目錄的 lockfile。
多套件專案最常遇到的痛點之一,就是同一個依賴(例如 react、typescript)
在不同套件裡版本不一致,導致重複安裝或行為不一致。Bun 提供 Catalogs 機制來解決這個問題:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"],
"catalog": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.0.0",
"zod": "^3.22.0"
}
}
各子套件只要引用 catalog: 即可:
{
"name": "@myorg/frontend",
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:",
"zod": "catalog:"
}
}
如果需要針對不同用途(例如測試工具)維護不同的版本集合,也可以定義多組 catalog:
{
"catalogs": {
"default": { "typescript": "^5.0.0" },
"testing": {
"vitest": "^1.0.0",
"playwright": "^1.40.0"
}
}
}
這個機制的好處很直接:版本統一維護在一個地方,改一次全專案生效,也讓 code review 時很容易看出誰改了共用版本。
在 Monorepo 裡,TypeScript 的設定策略會直接影響開發體驗與 CI 效能。常見的兩種做法:
在根目錄的 tsconfig.json 用 paths 指向各套件原始碼:
{
"compilerOptions": {
"paths": {
"@myorg/shared/*": ["packages/shared/src/*"]
}
}
}
這種方式設定簡單,但整個 workspace 仍然被當成一個大型 TypeScript 專案處理,型別檢查與編譯效能不會因此變好,套件之間也沒有真正的邊界隔離。
透過 references 把每個套件拆成獨立的 TypeScript 專案:
{
"references": [
{ "path": "../shared" },
{ "path": "../ui" }
]
}
搭配 tsc --build 做增量編譯,好處是:
如果專案套件數量不多(單位數),path alias 通常已經夠用;但一旦套件數量成長到十幾二十個,project references 帶來的效能差異會越來越明顯。
Bun 內建的 test runner 速度很快,但放到 Monorepo 環境下需要注意幾件事:
--filter 平行跑測試:不需要每次都跑全部套件的測試,針對有變動的套件執行即可,大幅縮短本地開發與 CI 的等待時間。# 只跑特定套件的測試
bun test --filter "@myorg/api"
Bun 有自己的全域快取(預設在 ~/.bun/install/cache),CI 環境把這個目錄快取起來,可以大幅縮短重複安裝的時間:
# GitHub Actions 範例
- uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lock') }}
--frozen-lockfileCI 環境安裝依賴時務必加上 --frozen-lockfile,避免 CI 過程意外更新 lockfile 導致「本地測試過但 CI 壞掉」的狀況。
這是 Bun workspaces 目前比較弱的一塊——它不會幫你判斷「這次改動影響了哪些套件」。
如果專案套件數量還不多,全部重跑問題不大;
但套件一多,每次 push 都全跑會讓 CI 時間快速膨脹。這時候通常有兩個方向:
Bun workspaces 很適合中小型專案,但當團隊或程式碼庫成長到一定程度,會開始感受到以下痛點:
如果符合以上情況,可以考慮在 Bun 之上疊加 Turborepo 或 Nx
好消息是,這類工具通常設計成可以直接讀取既有的 package.json scripts 與 workspace 結構,
遷移成本不算太高——你不需要放棄 Bun 帶來的安裝速度優勢,只是多加一層任務編排與快取。
整理一個簡單的判斷原則:
| 情境 | 建議 |
|---|---|
| 小型 team、套件數量不多、追求開發迭代速度 | Bun workspaces 已足夠 |
| 中大型專案、需要 task caching 與 affected 偵測 | Bun workspaces + Turborepo / Nx |
| 大規模企業級、上百位開發者、對穩定性要求極高 | 可評估 pnpm 生態圈的成熟度優勢 |
Bun 在 2026 年的套件管理生態已經相當成熟,workspace: 協議、Catalogs、--filter
這些機制讓中小型 Monorepo 的日常開發體驗非常流暢。但它終究是「套件管理員」而非「Monorepo 建置系統」,
如果你的專案已經大到需要精確的任務快取與依賴影響分析,記得適時導入專門的 Monorepo 工具,
而不是硬把所有邏輯塞進 shell script 裡自己維護。