Monorepo 和 Multirepo
單一倉庫(Monorepo)架構,可以理解為:利用單一倉庫來管理多個packages的一種策略或手段;與其相對的是多倉庫(Multirepo)架構
Monorepo 目錄中除了會有公共的package.json
依賴以外,在每個sub-package
子包下面,也會有其特有的package.json
依賴。
兄弟模組之間可以透過模組 package.json
定義的 name
相互引用,保證模組之間的獨立性
# monorepo目錄結構
monorepo-demo
├── packages
│ ├─ module-a
│ │ ├─ src # 模組 a 的原始碼
│ │ ├─ node_modules # 模組 a 的 node_modules
│ │ └─ package.json # 僅模組 a 的依賴
│ └─ module-b
│ ├─ src # 模組 b 的原始碼
│ └─ package.json # 僅模組 b 的依賴
├── .eslintrc # 配置檔案,對整個專案生效
├── node_modules # 所有子包公共的 node_modules
└── package.json # 所有子包公共的依賴
Multirepo 更傾向與在專案制中,將一個個專案使用不同的倉庫進行隔離,每一個專案下使用獨有的package.json
來管理依賴
# multirepo-a目錄結構
multirepo-a
├── src
├── .eslintrc
├── node_modules
└── package.json
# multirepo-b目錄結構
multirepo-b
├── src
├── .eslintrc
├── node_modules
└── package.json
Monorepo 工具
在採用 Monorepo(單一倉庫)架構的軟體開發中,工具的選擇是至關重要的。合適的 Monorepo 工具能夠幫助團隊更高效地管理大規模程式碼庫、提升協同開發體驗以及最佳化構建和部署流程。
直至 2024 年,目前在前端界比較流行的 Monorepo 工具有 Pnpm Workspaces
、Yarn Workspaces
、npm Workspaces
、Rush
、
Turborepo
、Lerna
、Yalc
、和 Nx
強烈推薦使用Pnpm Workspaces
作為 Monorepo 專案的依賴管理工具😍😍😍
那麼 Monorepo 與包管理工具(npm、yarn、pnpm)之間是一種怎樣的關係?
這些包管理工具與 monorepo 的關係在於,它們可以為 monorepo 提供依賴安裝與依賴管理的支援,藉助自身對 workspace 的支援,允許在 monorepo 中的不同子專案之間共享依賴項,並提供一種管理這些共享依賴項的方式,這可以簡化依賴項管理和構建過程,並提高開發效率。
Monorepo 專案搭建
背景
傳統的多倉庫 Multirepo 模式,通常都是一個倉庫存放一個專案。比如現在你有三個專案,就需要建立三個遠端倉庫,並且需要為每個專案單獨安裝和升級依賴
而單一倉庫 Monorepo 模式,就是在一個倉庫中管理多個專案,這些專案可以是獨立的,也可以相互依賴。透過 Monorepo,多個專案可以共享依賴。比如多個專案都需要 lodash
,那我們也只需安裝一次即可
pnpm i lodash -w
當然,Monorepo 中除了公共的package.json
依賴以外,在每個sub-package
子包下面,也會有其私有的package.json
依賴
我們本次選擇Pnpm Workspaces
作為 Monorepo 專案的依賴管理工具,一起來搭建一個 monorepo 專案✨
安裝包管理工具
全域性安裝 pnpm
npm i pnpm -g
初始化專案
建立一個新的專案目錄 pnpm-monorepo
,根目錄執行 pnpm init
建立 package.json 檔案
然後根目錄新建一個資料夾 packages
,用於儲存子包
新建 packages/libc-shared
( 共享包 ),用於存放多個專案或元件之間共享的程式碼 。執行 pnpm init
建立 package.json 檔案,修改 package.json 的 name 為 "@libc/shared"
;修改 package.json 的 main 入口檔案路徑欄位為"src/index.js"
{
"name": "@libc/shared",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
}
新建 packages/libc-ui
( 公共元件包 ),即UI元件庫,這裡我們直接 clone 了 iview-ui-plus 程式碼。執行 pnpm install
安裝依賴,
修改 package.json 的 name 為 "@libc/ui"
;修改 package.json 的 main 入口檔案路徑欄位為"src/index.js"
然後我們在 packages
下建立兩個 vue 專案,vue-dom1
和 vue-dom2
,執行指令碼pnpm create vue@latest
。由於兩個專案的依賴是完全一樣的,我們可以將 dependencies、devDependencies 複製到外層 package.json 中當做公共依賴,然後pnpm install
安裝一次即可
到了這一步,vue 專案還是不能執行,必須要先配置 workspace,用於支援多包儲存庫💥讓子包 vue 專案可以訪問到我們的公共依賴💥💥
配置workspace
根目錄新建一個 pnpm-workspace.yaml
,將 packages 下所有的目錄都作為包進行管理💥💥💥
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
pnpm-monorepo 最終專案結構
pnpm-monorepo/
├── packages/
│ ├── libc-shared/
│ ├── libc-ui/
│ ├── vue-dome1/
│ └── vue-dome2/
├── package.json
└── pnpm-workspace.yaml
子包共享💥
此時,pnpm-workspace.yaml
工作空間下的每個子包都可以共享我們的公共依賴了。還有個問題是,兄弟模組之間如何共享呢?
之前我們說過,子包之間可以透過 package.json
定義的 name
相互引用,一起看下兩個實際場景
- 如何把子包 libc-shared 共享出去?
用--workspace
引數去安裝共享子包,會去 workspace工作空間中找依賴項並安裝
pnpm install @libc/shared --workspace -w
package.json
中就會自動新增如下依賴,"workspace:"
只會解析本地 workspace 包含的 package
"dependencies": {
"@libc/shared": "workspace:^"
}
此時,vue 專案就可以使用公共包 libc-shared
裡的方法,import 引入即可
import { isObject } from '@libc/shared'
- 如何把子包 libc-ui 共享出去?
重複一下上面的步驟,然後我們去引用一個 button
元件,發現報錯了 Failed to resolve import "./base" from "../libc-ui/src/components/typography/title.vue". Does the file exist?
vite.config.js 中新增 extensions
即可解決,配置一下省略的副檔名列表
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
}
雖然 button
元件引用成功了,但是發現沒有任何樣式效果。在 libc-ui/src/index.js 檔案中匯入一下樣式檔案就行了
import "./styles/index.less";
依賴
公共依賴
全域性安裝公共依賴 lodash
。需要加-w
(在工作空間的根目錄中啟動 pnpm)
pnpm install lodash -w
這樣,vue-dom1 和 vue-dom2 這兩個 vue專案就都可以使用 lodash
庫了
區域性依賴
如果只有 vue-dom1 專案用到了 lodash
,我們也可以安裝到 vue-dom1 專案內部,不作為公共依賴項,有兩種方法可以實現
- cd 到 src/packages/vue-dom1 目錄下,直接安裝
pnpm install lodash
- 在任意目錄下,使用
--filter
引數進行安裝;package_selector:package.json 對應的 name 欄位
pnpm install lodash --filter <package_selector>
shamefully-hoist
shamefully-hoist
,預設 false
-
false:
node_modules
下只能看到直接依賴的套件,次級依賴在node_modules/.pnpm
目錄下;無法訪問其他子包區域性安裝的依賴項,例如,vue-dome2 安裝的 lodash,vue-dome1 是訪問不到的 -
true:將所有套件都拉昇到
node_modules
目錄下,能訪問到其他子包區域性安裝的依賴項,例如,vue-dome2 安裝的 lodash,vue-dome1 是能訪問到的
// .npmrc
# pnpm 配置
shamefully-hoist=false
配套程式碼
GitHub - burc-li/pnpm-monorepo: vue3 + pnpm + monorepo 專案 demo 🍎
參考文件
為什麼 pnpm+monorepo 是元件庫專案的最佳實踐
突破專案瓶頸:2024 年 Monorepo 工具選擇和實踐 | BEEZEN
GitHub - Tyh2001/vue3-pnpm-monorepo: 🐠 vue3 + pnpm + monorepo 專案 demo