什麼是 Monorepo?運用 npm workspaces 實作跨專案共用程式碼的完整教學
在現代的前端與全端開發中,隨著產品線的擴張,我們經常會面臨「多個專案需要共用同一套程式碼」的情境。例如:一個針對使用者的主網站(Client App),以及一個針對內部員工的管理後台(Admin Panel)。這兩者雖然獨立運行,但往往需要共用相同的 UI 元件庫、API 呼叫邏輯或是型別定義。
如果在兩個專案各自複製貼上相同的程式碼,未來一旦邏輯需要修改,工程師就必須在多個專案中反覆修改,極易產生遺漏與版本不一致的錯誤。為了解決這個問題,Monorepo(單一儲存庫) 架構應運而生,而 npm workspaces 則是目前 Node.js 生態系中最容易入門的實作工具。
什麼是 Monorepo 架構?

Monorepo(Monolithic Repository)指的是將多個不同的專案(Projects)或套件(Packages)全部整合在同一個 Git 儲存庫(Repository)中進行管理。與之相對的是傳統的 Polyrepo(Multi-repo),也就是每個專案都有自己獨立的儲存庫。
Monorepo 的核心優勢
- 單一真實來源 (Single Source of Truth):所有的程式碼都在同一個樹狀結構下,確保團隊成員看到的是一致的程式碼 庫。
- 輕鬆共用程式碼:跨專案引用共用模組只需要透過本地連結 (Symlink),不需要將共用模組發佈到 npm registry 就能立刻在本地端測試。
- 依賴管理一致性:能夠將第三方的依賴套件(如 React、Lodash)提升 (Hoist) 到根目錄,確保所有子專案使用的套件版本完全一致,避免版本衝突並節省硬碟空間。
- 大規模重構更容易:當共用模組的 API 發生改變時,由於依賴該模組的所有專案都在同一個儲存庫內,工程師可以一次性修改並透過 TypeScript 編譯器抓出所有潛在錯誤。
認識 npm workspaces
自從 npm v7 版本開始,npm 官方正式內建了對 Workspaces 的支援。它提供了一組 CLI 原生功能,專門用來管理多個封包(Packages)在同一個本地檔案系統內的依賴關係。只要簡單設定好根目錄的 package.json,npm 就會自動幫我們將各個子專案的依賴全部整理好,且能互相參照。
實戰教學:如何使用 npm workspaces 共用程式碼?
接下來我們將透過一個具體的範例,示範如何建立一個包含 project-a、project-b,以及一個共用模組 shared-utils 的 Monorepo。
步驟一:建立並初始化根目錄
首先,建立一個新的資料夾作為我們的 Monorepo 根目錄,並初始化專案:
mkdir my-monorepo
cd my-monorepo
npm init -y
接著,打開根目錄產生的 package.json,我們需要手動加入 workspaces 欄位,用來宣告這個 Monorepo 包含哪些目錄下的子專案。通常我們會將專案分為 apps(應用程式)和 packages(共用模組):
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
注意:根目錄的
package.json必須 設定為"private": true,避免你不小心將整個工作區發佈到公開的 npm 註冊表上。
步驟二:建立共用模組 (Shared Package)
現在,我們來建立一個放置共用邏輯的封包。在根目錄下新增 packages/shared-utils 資料夾。
mkdir -p packages/shared-utils
cd packages/shared-utils
npm init -y
編輯 packages/shared-utils/package.json,請特別留意 "name" 欄位,這將是其他專案引用此模組時所使用的名稱:
{
"name": "@my-org/shared-utils",
"version": "1.0.0",
"main": "index.js"
}
接著,在該資料夾下建立一個 index.js,寫入我們想要共用的程式碼函式:
// 共用的格式化日期函數
function formatDate(date) {
return new Intl.DateTimeFormat('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(date);
}
// 共用的加法函數
function add(a, b) {
return a + b;
}
module.exports = {
formatDate,
add
};
步驟三:建立兩個應用程式專案
回到根目錄,並在 apps/ 目錄下建立兩個獨立的專案(這邊為了示範簡便,我們直接初始化基礎的 Node.js 專案,實務上這可以是 Next.js、React 或是 Express 專案):
mkdir -p apps/project-a
mkdir -p apps/project-b
# 初始化 project-a
cd apps/project-a
npm init -y
# 初始化 project-b
cd ../project-b
npm init -y
將 apps/project-a/package.json 以及 apps/project-b/package.json 的 "name" 分別改為 "project-a" 及 "project-b"。
步驟四:將共用模組安裝至專案中
現在是最關鍵的一步:我們要讓 project-a 與 project-b 可以使用剛剛寫好的 @my-org/shared-utils。
在 npm workspaces 環境下,你不需要手動設定相對路徑 (../../packages/shared-utils),而是直接使用 npm 安裝指令,並搭配 -w (workspace) 參數:
# 在 Monorepo 根目錄執行
npm install @my-org/shared-utils -w project-a
npm install @my-org/shared-utils -w project-b
執行後,你會發現 project-a 的 package.json 裡面多了依賴:
"dependencies": {
"@my-org/shared-utils": "^1.0.0"
}
這個時候,npm 並不會去網路上抓取 @my-org/shared-utils,而是聰明地運用 Symlink(符號連結) 技術,將 apps/project-a/node_modules/@my-org/shared-utils 指向你本地端的 packages/shared-utils 資料夾!也就是說,你在共用模組修改的任何內容,都會即時反映在兩個應用程式專案中,無須重新編譯與安裝。
步驟五:在專案中呼叫共用程式碼
最後,我們在 project-a 裡面測試一下是否能順利讀取到共用代碼。建立 apps/project-a/index.js:
const { formatDate, add } = require('@my-org/shared-utils');
const today = new Date();
console.log(`【Project A】今天是:${formatDate(today)}`);
console.log(`【Project A】計算結果 10 + 20 = ${add(10, 20)}`);
回到根目錄執行:
node apps/project-a/index.js
如果順利印出日期的格式化字串與計算結果,恭喜你,你已經成功完成跨專案共用程式碼的實作了!
進階技巧與最佳實踐
除了基本的共用程式碼,要維護一個大型的 Monorepo,你可能還需要知道以下幾種最佳實踐機制:
1. 統一提取套件依賴 (Dependency Hoisting)
在傳統的 Polyrepo 中,每個專案都有自己肥大的 node_modules。而 npm workspaces 預設會將所有子專案「共通」的第三方依賴往上拉到「根目錄」的 node_modules 裡面。這不僅解決了依賴版本地雷(例如 React 雙重實體),也大大減少了安裝時間與硬碟空間。您只需要在根目錄執行一次 npm install 即可就緒。
2. 導入架構構建工具整合 (Turborepo)
雖然 npm workspaces 處理了依賴安裝的問題,但在執行「測試」、「建置」等腳本時,若手動進到每個目錄去運行是非常沒有效率的。
實務上強烈建議搭配 Turborepo 或 Lerna 這類型的構建工具。尤其是 Turborepo 具備了「雲端快取」與「並發執行 (Concurrency)」的能力。它能自動分析子專案之間的相依性拓樸圖,確保 shared-utils 建置完成後,才開始建置依賴它的 project-a,將建置時間縮減到極致。
3. TypeScript 路徑對應 (Path Mapping)
如果你的專案採用了 TypeScript,別忘了在根目錄設計一個 tsconfig.base.json,並利用 references (Project References) 來指示 TypeScript 重複使用共用封包的型別定義。如此不僅提升開發者體驗 (IDE 支援跳轉原始碼),更能減少記憶體爆炸的情形。
結語
導入 Monorepo 以及 npm workspaces,對於擁有多個關聯專案的團隊來說,幾乎是現代前端工程化必經的道路。雖然初期可能需要花一點時間調校 ESLint、TypeScript 與 CI/CD 流程的配置,但相對應換來的「重構信心」、「嚴謹的版本一致性」以及「極高的程式碼重用率」,絕對是值回票價的投資。
如果你的專案目前正深受「修改核心邏輯必須跨好幾個 Repo 發 PR」的痛苦,不妨現在就建立一個小型的 workspace 試試看吧!