找兼職做酒店網(wǎng)站瀏覽器廣告投放
1.前言
通常,我們會(huì)根據(jù)自身業(yè)務(wù)的實(shí)際情況,將通用的組件、邏輯等提取成NPM包,方便以后復(fù)用。但這些提取出來(lái)的NPM包可能互相之間存在依賴,如果仍然采用 Multirepo 的形式進(jìn)行管理,則在包的版本管理、依賴管理、調(diào)試等諸多方面存在不便。
Monorepo 能很好的解決上述問(wèn)題,為更加方便的使用 Monorepo 來(lái)管理我們的項(xiàng)目,我們需要一些趁手的工具,Lerna + Yarn Workspace 的組合就是這樣一件優(yōu)秀的工具。然而 Lerna 中途經(jīng)歷過(guò)較大的改動(dòng),其文檔也不是那么容易看明白,中文材料大多使用較老版本的 Lerna,使用上和較新版本存在差異。
本文將介紹較新版本的 Lerna 如何結(jié)合 Yarn Workspace 管理 Monorepo 項(xiàng)目,后續(xù)操作在 Lerna V7 和 V8 中經(jīng)過(guò)實(shí)踐。
2.Lerna + Yarn Workspace
其實(shí)除了 Lerna + Yarn Workspace 的方案外,還有其他方案也可以幫助我們管理 Monorepo 項(xiàng)目,比如 pnpm 等,但我選擇 Lerna + Yarn Workspace 一方面是由于習(xí)慣了使用 yarn,只要引入 Lerna 即可,另一方面公司的基建對(duì) yarn 也有很好的支持,所以選擇了 Lerna + Yarn Workspace。
在使用 Lerna + Yarn Workspace 時(shí),兩者的分工有所不同:
- Yarn 負(fù)責(zé)依賴管理,在 install 時(shí)將子包相同的依賴安裝到根目錄下的 node_modules 中,避免了在子包中重復(fù)安裝。同時(shí) Yarn 也可以處理子包之間的相互依賴關(guān)系,使用軟鏈的方式連接各個(gè)子包。
- Lerna 則負(fù)責(zé)子包的發(fā)布,包括版本號(hào)更新、依賴更新、發(fā)布等。
3.入門實(shí)踐
3.1.初始化
3.1.1.項(xiàng)目初始化
首先你需要初始化 Monorepo 項(xiàng)目,新建一個(gè)目錄 my-monorepo-project,進(jìn)入到目錄中執(zhí)行:
yarn init
這一步完成后,項(xiàng)目根目錄下會(huì)出現(xiàn) package.json,這里需要將 private 字段設(shè)為 true。
3.1.2.Workspaces 初始化
接下來(lái)進(jìn)行 Workspaces 初始化,在根目錄新建 packages 文件夾,并在 packages.json 中新增 workspaces 配置:
"workspaces": ["packages/*"
]
workspaces 字段定義了一個(gè)包含多個(gè)路徑的數(shù)組,這些路徑指向了項(xiàng)目中的各個(gè)子包的位置。如上配置就是將 packages 目錄下的所有項(xiàng)目都看做是子包。
這一步完成之后,就可以在根目錄下執(zhí)行 yarn 安裝依賴了,假設(shè)我們的項(xiàng)目目錄如下:
/my-monorepo-project
|-- packages
| |-- package1
| |-- package2
| |-- package3
|-- package.json
|-- yarn.lock
其中 package1 依賴了 package2,則 yarn 之后,my-monorepo-project 的根目錄下會(huì)出現(xiàn) node_modules 文件夾,在 node_modules 查看各個(gè)子包對(duì)應(yīng)的目錄,會(huì)發(fā)現(xiàn)它們是以軟鏈的形式鏈接到各自的源碼目錄中。
值得注意的是,此時(shí)就算 package1 的 package.json 中沒(méi)有聲明對(duì) package2 的依賴,但也可以直接在 package1 中引用 package2 了。但當(dāng)我們發(fā)布后,由于 package1 沒(méi)有聲明對(duì) package2 的依賴,用戶在安裝 package1 時(shí)不會(huì)同時(shí)安裝 package2,就會(huì)造成運(yùn)行異常。正確的做法仍然需要在 package1 的 package.json 中聲明對(duì) package2 的依賴,不過(guò)不要通過(guò) yarn workspace package1 add package2
的方式添加依賴,這樣 package2 會(huì)從 npm 上下載,而不是鏈接到本地源碼。
3.1.3.Lerna初始化
現(xiàn)在就可以引入 Lerna 了,在根目錄中繼續(xù)執(zhí)行:
yarn add lerna -D
npx lerna init
如此一來(lái),在根目錄下就會(huì)新增 lerna.json 文件,其內(nèi)容是:
{"$schema": "node_modules/lerna/schemas/lerna-schema.json","version": "independent","npmClient": "yarn"
}
在較早版本的 Lerna 生成的 lerna.json 文件中,還需要設(shè)置 useWorkspaces 字段,不過(guò)Lerna 7 和 8 中已經(jīng)不用設(shè)置該字段了。
另外需要注意 version 字段的值。version 可以設(shè)置為具體的版本號(hào),比如 0.0.1,此時(shí) Lerna 將采用固定模式(Fixed/Locked mode)管理子包版本,即有的子包將共享同一個(gè)版本號(hào)。Lerna 默認(rèn)采用該方式,version 初始值為 0.0.0。
也可以將 version 設(shè)置為 independent,表示每個(gè)子包都可以獨(dú)立地管理自己的版本號(hào)(Independent mode),當(dāng)發(fā)布時(shí) Lerna 會(huì)讓你為子包選擇版本號(hào)。由于我是將已有的項(xiàng)目改造為 Monorepo 項(xiàng)目,各子包已經(jīng)獨(dú)立發(fā)版很長(zhǎng)時(shí)間了,所以將 version 改成了 independent。
3.2.開發(fā)
完成初始化后,我們就可以開發(fā)我們的子包了。在開發(fā)過(guò)程中,我們可能需要添加第三方依賴,也有可能需要運(yùn)行子包的 scripts 腳本。我們來(lái)看看如何執(zhí)行這些常見(jiàn)的操作。
3.2.1.添加依賴
如果需要添加第三方依賴,可以將依賴添加到 workspaces 根目錄:
# 在任何目錄執(zhí)行
yarn add <依賴名> -W# 或者在根目錄中執(zhí)行
yarn add <依賴名>
這樣依賴會(huì)被安裝到根目錄的 node_modules 中,所有子包都可以引用。
如果需要為某個(gè)子包單獨(dú)添加某個(gè)依賴,可以執(zhí)行:
# 在任何目錄執(zhí)行
yarn workspace <子包名> add <依賴名># 或者在進(jìn)入子包對(duì)應(yīng)的目錄中執(zhí)行
yarn add <依賴名>
這樣依賴只會(huì)被會(huì)被安裝到子包對(duì)應(yīng)目錄下的 node_modules 中,其他子包不能直接引用。注意子包名
指的是子包 package.json 中 name 字段的值,不是子包的目錄名。
我們可以通過(guò)如下命令查看子包間的依賴關(guān)系,防止日后理不清子包的依賴關(guān)系:
yarn workspaces info
3.2.2.運(yùn)行script
如果需要執(zhí)行某個(gè)子包中的腳本,可以執(zhí)行:
yarn workspace <子包名> run build
這樣就會(huì)運(yùn)行子包的 build 命令。
3.3.發(fā)布
整個(gè)開發(fā)過(guò)程都是由 Yarn 進(jìn)行管理,但 Yarn Workspace 不具備發(fā)布的能力,這時(shí)就需要借助 Lerna 的能力了。
3.3.1.檢查更新
需要檢查自上次發(fā)布以來(lái)在 Git 中已經(jīng)提交但尚未發(fā)布的修改,執(zhí)行:
npx lerna changed
該命令的結(jié)果形如:
# 沒(méi)有未發(fā)布的修改
lerna notice cli v7.4.2
lerna info versioning independent
lerna notice Current HEAD is already released, skipping change detection.
lerna info No changed packages found# 有未發(fā)布的修改
lerna notice cli v7.4.2
lerna info versioning independent
lerna info Looking for changed packages since package1@0.0.1
package1
package2
該命令的結(jié)果不僅包含發(fā)生修改的子包本身,還包括依賴了該子包的其他子包。比如 package2 發(fā)生修改,則依賴它的 package1 也會(huì)被識(shí)別為需要發(fā)布,因?yàn)樾枰薷?package1 依賴的 package2 的版本。
3.3.2.更新版本號(hào)
之前說(shuō)過(guò),在 lerna.json 中配置 version 為 independent 表示每個(gè)子包可以獨(dú)立地更新版本號(hào)。假設(shè)我們修改了 package1 的代碼,首先需要提交修改,否則發(fā)布時(shí)會(huì)因?yàn)闆](méi)有提交記錄而被 Lerna 卡住。
接下來(lái)需要更新版本號(hào),這一步不需要直接修改 package.json,而是由 Lerna 幫我們修改,執(zhí)行:
npx lerna version --no-private
–no-private 表示不更新私有包,因?yàn)樗麄儾粫?huì)被發(fā)布。命令執(zhí)行之后,Lerna 會(huì)自動(dòng)識(shí)別需要更新版本的子包(即 lerna changed 的結(jié)果),并向用戶確認(rèn)下一個(gè)版本號(hào)是多少。用戶依次確認(rèn)所有需要更新的子包的新版本號(hào)后,Lerna 會(huì)打 Tag,并push。
這一步不僅會(huì)修改 package.json 中的 version,也會(huì)修改依賴的版本。比如 package2 更新了,則在 package1 的 package.json 中,不僅自身的版本號(hào)會(huì)更新,其依賴的 package1 的版本也會(huì)跟著改變。
3.3.3.發(fā)布
發(fā)布就很簡(jiǎn)單了,執(zhí)行:
npx lerna publish from-package
即可將更新的子包發(fā)布到NPM。
4.踩坑
我是將已經(jīng)獨(dú)立發(fā)版一段時(shí)間的好幾個(gè)子包改造為使用 Monorepo 架構(gòu)進(jìn)行管理。由于 Lerna 基于 Tag 判斷有哪些子包需要發(fā)布,由于之前發(fā)布子包時(shí)沒(méi)有打Tag,所以執(zhí)行 npx lerna changed
會(huì)提示 lerna success found 9 packages ready to publish
,意思是所有的子包都要發(fā)布。但執(zhí)行 npx lerna publish from-package
時(shí)由于本地包和 npm 上的版本相同,會(huì)提示 No unpublished release found
,意思是沒(méi)有需要發(fā)布的包。
為了解決上述問(wèn)題,需要補(bǔ)一下Tag,具體方法是執(zhí)行 npx lerna version --no-private
,Lerna 會(huì)讓填寫每個(gè)子包的新版本號(hào),沒(méi)有修改的子包版本號(hào)可以保持不變。執(zhí)行完畢后再執(zhí)行 npx lerna changed
會(huì)提示 Current HEAD is already released, skipping change detection... No changed packages found
,即達(dá)成目的。
隨后執(zhí)行 npx lerna publish from-package
,發(fā)布需要更新的子包即可。如果沒(méi)有要發(fā)布的子包,這一步也可以不執(zhí)行。
5.參考
- Yarn文檔
- Yarn Workspaces
- Lerna文檔
- Lerna文檔翻譯
- Lerna 備忘清單
- Lerna + Yarn workspace 使用總結(jié)