[FE] pnpm 依賴管理淺析

背景

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 single node_modules (and get symlinked to their package node_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/ 目錄中只保留了最外層的依賴怔蚌。


參考

github: thzt/test-pnpm-monorepo
pnpm v6.25.1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巩步,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子桦踊,更是在濱河造成了極大的恐慌椅野,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籍胯,死亡現(xiàn)場(chǎng)離奇詭異竟闪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杖狼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門炼蛤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝶涩,你說(shuō)我怎么就攤上這事理朋。” “怎么了绿聘?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵嗽上,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我熄攘,道長(zhǎng)炸裆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任鲜屏,我火速辦了婚禮,結(jié)果婚禮上国拇,老公的妹妹穿的比我還像新娘洛史。我一直安慰自己,他們只是感情好酱吝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布也殖。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忆嗜。 梳的紋絲不亂的頭發(fā)上己儒,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音捆毫,去河邊找鬼闪湾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绩卤,可吹牛的內(nèi)容都是我干的途样。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼濒憋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼何暇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起凛驮,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裆站,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后黔夭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宏胯,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年纠修,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胳嘲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扣草,死狀恐怖了牛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辰妙,我是刑警寧澤鹰祸,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站密浑,受9級(jí)特大地震影響蛙婴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尔破,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一街图、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懒构,春花似錦餐济、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春篙悯,著一層夾襖步出監(jiān)牢的瞬間蚁阳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工鸽照, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留螺捐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓移宅,卻偏偏與公主長(zhǎng)得像归粉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漏峰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容