背景
pnpm 默認(rèn)會(huì)把所有 package 的依賴放到最外層的 node_modules
中铐伴,然后建立軟鏈接指向它們。
項(xiàng)目示例
github: thzt/test-pnpm-monorepo 是一個(gè)極簡(jiǎn)版的 monorepo 項(xiàng)目瓦糕,包含如下文件,
./monorepo
├── package.json
├── packages
| ├── app
| | ├── index.js
| | └── package.json
| └── lib
| ├── index.js
| └── package.json
└── pnpm-workspace.yaml
最外層有辣卒,
- package.json:最外層的依賴拧晕,或者可以說(shuō)是所有 package 的公共依賴
- pnpm-workspace.yaml:配置 monorepo 有哪些 package,各 package 的相對(duì)路徑是什么
- packages/:這個(gè)名字可以改蜒茄,取決于
pnpm-workspace.yaml
中的配置
monorepo 的每一個(gè) package唉擂,會(huì)被放到獨(dú)立的文件夾中,我們配置的路徑為 packages/**
檀葛。pnpm-workspace.yaml
文件內(nèi)容如下玩祟,
packages:
- 'packages/**'
本例中只包含兩個(gè) package,分別為 packages/app/
和 packages/lib/
屿聋。依賴關(guān)系如下空扎,
packages/app # packages/app 內(nèi)部依賴了 packages/lib
packages/lib
debug@4.3.3
ms@2.1.2
packages/lib # packages/lib 依賴了外部的 debug
debug@4.3.3 # debug 有自己的依賴 ms
ms@2.1.2
為了讓 packages/app/
能依賴 packages/lib/
需要設(shè)置各自的 package.json
中的 name
字段包含相同的 @scope(本例中為 @test
)藏鹊,如下,
# packages/app/package.json
{
"name": "@test/app",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@test/lib": "workspace:^1.0.0"
}
}
#packages/lib/package.json
{
"name": "@test/lib",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"debug": "^4.3.3"
}
}
依賴分析
(1)node_modules 中的文件結(jié)構(gòu)
當(dāng)我們?cè)谕鈱訄?zhí)行 pnpm install
的時(shí)候转锈,pnpm 會(huì)創(chuàng)建這些文件盘寡,
./monorepo
├── node_modules # [new] 最外層整個(gè) monorepo 項(xiàng)目的依賴
├── package.json
├── packages
| ├── app
| | ├── index.js
| | ├── node_modules # [new] packages/app 的依賴
| | └── package.json
| └── lib
| ├── index.js
| ├── node_modules # [new] packages/lib 的依賴
| └── package.json
├── pnpm-lock.yaml # [new] 整個(gè)項(xiàng)目 以及 package 的依賴信息
└── pnpm-workspace.yaml
我們來(lái)看一下各級(jí) node_modules
中都有什么內(nèi)容,依賴都被 “打平” 放到了 .pnpm/
目錄撮慨,
node_modules/
.bin/
tsc
tsserver
.pnpm/
node_modules/ # 這里是被 hoist 了竿痰,見下文解釋
debug/ -> [symlink] ../debug@4.3.3/node_modules/debug
ms/ -> [symlink] ../ms@2.1.2/node_modules/ms
debug@4.3.3/
node_modules/
debug/
ms/ -> [symlink] ../../ms@2.1.2/node_modules/ms
ms@2.1.2/
node_modules/
ms/
typescript@4.5.4/
node_modules/
typescript/
lock.yaml
typescript/ -> [symlink] .pnpm/typescript@4.5.4/node_modules/typescript
.modules.yaml
packages/
app/
node_modules/
@test/
lib/ -> [symlink] ../../../lib
lib/
node_modules/
debug/ -> [symlink] ../../../node_modules/.pnpm/debug@4.3.3/node_modules/debug
值得注意的是:pnpm 默認(rèn) hoist
配置為 true
(官方文檔),
當(dāng)
true
砌溺,所有依賴項(xiàng)都被提升到node_modules/.``pnpm
影涉。 這使得node_modules
所有包都可以訪問(wèn) 未列出的依賴項(xiàng)。
任何一個(gè)包抚吠,在自己的執(zhí)行路徑上找不到依賴時(shí)常潮,最終都會(huì)向上到 .pnpm/node_modules
中查找。
我們可以通過(guò)添加 .npmrc
配置楷力,來(lái)取消這一默認(rèn)選項(xiàng)喊式,
hoist=false
再執(zhí)行一次 pnpm
install
之后,.pnpm/node_modules
這個(gè)目錄就不存在了萧朝。
(2)依賴鏈路
然后我們觀察一下依賴鏈路岔留,發(fā)現(xiàn)每一級(jí)的依賴,都通過(guò) symlink(軟鏈接)梳理好了检柬,
packages/app
@test/lib -> packages/app/node_modules/@test/lib -> [symlink] packages/lib
debug -> packages/lib/node_modules/debug -> [symlink] node_modules/.pnpm/debug@4.3.3/node_modules/debug
ms -> node_modules/.pnpm/debug@4.3.3/node_modules/debug/node_modules/ms -> [上級(jí)目錄] node_modules/.pnpm/debug@4.3.3/node_modules/ms -> [symlink] node_modules/.pnpm/ms@2.1.2/node_modules/ms
因此献联,雖然所有依賴都 “打平” 放到了最外層 node_modules
中,但是仍然保證了依賴查找的正確性何址。
我們可以再重點(diǎn)看一下 ms@2.1.2
的查找過(guò)程里逆,
# debug@4.3.3 依賴了 ms
# debug@4.3.3 所在的目錄為 node_modules/.pnpm/debug@4.3.3/node_modules/debug
# 所以,優(yōu)先會(huì)從當(dāng)前所在目錄的 ./node_modules 中去找 ms
# 即 node_modules/.pnpm/debug@4.3.3/node_modules/debug/node_modules/ms
# 可是這個(gè)目錄并不存在沒有找到 ms用爪,因此按照 Node.js resolve module 規(guī)則原押,會(huì)到上層目錄找
# 即 node_modules/.pnpm/debug@4.3.3/node_modules/ms
# 這里恰好有 pnpm 創(chuàng)建的一個(gè) symlink,指向了 node_modules/.pnpm/ms@2.1.2/node_modules/ms
# 因此偎血,最后會(huì)找到最外層 node_modules/.pnpm 下面去
這里的關(guān)鍵在于 pnpm 會(huì)在 debug
實(shí)際執(zhí)行路徑的上級(jí)目錄诸衔,放一個(gè) ms
的軟鏈接。
node_modules/
.pnpm/
debug@4.3.3/
node_modules/
debug/ # debug 執(zhí)行路徑
ms/ -> [symlink] ../../ms@2.1.2/node_modules/ms
ms@2.1.2/
node_modules/
ms/ # debug 依賴的 ms 指向了這里
注意颇玷,向上級(jí)目錄查找時(shí)笨农,當(dāng)前路徑為 debug 的實(shí)際執(zhí)行路徑,
# packages/lib 引用 debug 的路徑
packages/lib/node_modules/debug -> [symlink]node_modules/.pnpm/debug@4.3.3/node_modules/debug
# 實(shí)際路徑
node_modules/.pnpm/debug@4.3.3/node_modules/debug
# 查找 ms 時(shí)
[Right] 從這里往上找 node_modules/.pnpm/debug@4.3.3/node_modules/debug
[Wrong] 從這里往上找 packages/lib/node_modules/debug
所以在手動(dòng)排查問(wèn)題時(shí)帖渠,經(jīng)常容易出錯(cuò)谒亦,要時(shí)刻注意當(dāng)前目錄是否在某個(gè)軟鏈接下。
分離 lock 文件
我們可以將每個(gè) package 的依賴安裝到自己獨(dú)立的 node_modules
中,用單獨(dú)的 pnpm-lock.yaml
進(jìn)行管理诊霹。只需要在項(xiàng)目根目錄添加 .npmrc
文件羞延,配置內(nèi)容如下,
shared-workspace-lockfile=false
可在官方文檔中找到 shared-workspace-lockfile 的說(shuō)明脾还,
If this is enabled, pnpm creates a single
pnpm-lock.yaml
file in the root of the workspace. This also means that all dependencies of workspace packages will be in a singlenode_modules
(and get symlinked to their packagenode_modules
folder for Node's module resolution).
這時(shí)在執(zhí)行 pnpm
install
伴箩,會(huì)新增這些文件,
./monorepo
├── node_modules # [new] 最外層整個(gè) monorepo 項(xiàng)目的依賴
├── package.json
├── packages
| ├── app
| | ├── index.js
| | ├── node_modules # [new] packages/app 的依賴
| | ├── package.json
| | └── pnpm-lock.yaml # [new] packages/app 的依賴信息 <- [關(guān)鍵]
| └── lib
| ├── index.js
| ├── node_modules # [new] packages/lib 的依賴
| ├── package.json
| └── pnpm-lock.yaml # [new] packages/lib 的依賴信息 <- [關(guān)鍵]
├── pnpm-lock.yaml
└── pnpm-workspace.yaml # [new] 外層項(xiàng)目的依賴信 <- [關(guān)鍵]
我們?cè)賮?lái)看一下各個(gè) node_modules
中的內(nèi)容鄙漏,
node_modules/
.bin/
tsc
tsserver
.pnpm/
typescript@4.5.4/
node_modules/
typescript/
lock.yaml
typescript/ -> [symlink] .pnpm/typescript@4.5.4/node_modules/typescript
.modules.yaml
packages/
app/
node_modules/
.pnpm/ -> [new]
lock.yaml
@test/
lib/ -> [symlink] ../../../lib
.module.yaml -> [new]
lib/
node_modules/
.pnpm/ -> [new]
node_modules/
ms -> [symlink] ../ms@2.1.2/node_modules/ms
debug@4.3.3/
node_modules/
debug/
ms/ -> [symlink] ../../ms@2.1.2/node_modules/ms
ms@2.1.2/
node_modules/
ms/
lock.yaml -> [new]
debug/ -> [symlink] .pnpm/debug@4.3.3/node_modules/debug [路徑變了]
.module.yaml
我們發(fā)現(xiàn)嗤谚,每個(gè) node_modules
中都有了一個(gè) .pnpm/
目錄。全局 .pnpm/
目錄中只保留了最外層的依賴怔蚌。