從npm切換到pnpm小記

以下文章來源于大轉(zhuǎn)轉(zhuǎn)FE ,作者大轉(zhuǎn)轉(zhuǎn)FE

轉(zhuǎn)轉(zhuǎn)的 CI 系統(tǒng)和開發(fā)環(huán)境為什么要從 npm 切換到 pnpm 呢。因?yàn)樵谑褂?npm 的時(shí)候,遇到幾個(gè)問題。

磁盤空間占用過大
首次安裝速度慢
幽靈依賴導(dǎo)致一些報(bào)錯(cuò)
那 pnpm 又是怎么解決上面的問題呢?

什么是 pnpm
pnpm 是新一代包管理工具,為什么叫 pnpm 呢,是因?yàn)?pnpm 作者對(duì)現(xiàn)有的包管理工具,尤其是 npm 和 yarn 的性能特別失望,所以起名叫做 performance npm,即 pnpm(高性能 npm)

我們今天要使用 pnpm,那 pnpm 有哪些優(yōu)勢(shì)呢

1、npm 的問題
在 npm@3 之前,node_modules 結(jié)構(gòu)是干凈、可預(yù)測(cè)的,因?yàn)?node_modules 中的每個(gè)依賴項(xiàng)都有自己的 node_modules 文件夾,在 package.json 中指定了所有依賴項(xiàng),比如項(xiàng)目 a 依賴項(xiàng)目 b,項(xiàng)目 c 也依賴項(xiàng)目 b,這樣如果 a 和 c 依賴的 b 的版本不一致,也不會(huì)出問題

node_modules
└─ a
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ b
         ├─ index.js
         └─ package.json
   c
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ b
         ├─ index.js
         └─ package.json

上面結(jié)構(gòu)有兩個(gè)嚴(yán)重的問題:

package 中經(jīng)常創(chuàng)建太深的依賴樹,這會(huì)導(dǎo)致 Windows 上的目錄路徑過長(zhǎng)問題
當(dāng)一個(gè) package 在不同的依賴項(xiàng)中需要時(shí),它會(huì)被多次復(fù)制粘貼并生成多份文件
為了解決上面的問題,npm 提出了 node_modules 扁平化結(jié)構(gòu),在 npm@3+ 和 yarn 中,node_modules 結(jié)構(gòu)變成:

node_modules
├─ a
|  ├─ index.js
|  └─ package.json
└─ b
|  ├─ index.js
|  └─ package.json
└─ c
   ├─ index.js
   └─ package.json

hoist 機(jī)制下,b 包被提升到了頂層。如果同一個(gè)包的多個(gè)版本在項(xiàng)目中被依賴時(shí),node_modules 結(jié)構(gòu)又是怎么樣的?



包 B 1.0 被提升到了頂層,這里需要注意的是,多個(gè)版本的包只能有一個(gè)被提升上來,其余版本的包會(huì)嵌套安裝到各自的依賴當(dāng)中,至于哪個(gè)版本的包被提升,依賴于包的安裝順序!

扁平化之后,又引入了新的問題幽靈依賴,解釋起來很簡(jiǎn)單,即某個(gè)包沒有在 package.json 被依賴,但是用戶卻能夠引用到這個(gè)包。還有 NPM doppelgangers 問題,當(dāng)包有多個(gè)版本,會(huì)被重復(fù)安裝多次。

2、pnpm 的方案
pnpm 使用的是 npm version 2.x 類似的樹形結(jié)構(gòu),同時(shí)使用.pnpm 以平鋪的形式儲(chǔ)存著所有的包。

我們稱.pnmp 為虛擬存儲(chǔ)目錄,該目錄通過 <package-name>@<version> 來實(shí)現(xiàn)相同模塊不同版本之間隔離和復(fù)用,由于它只會(huì)根據(jù)項(xiàng)目中的依賴生成,并不存在提升,所以它不會(huì)存在之前提到的 Phantom dependencies 問題!

然后使用 Store + Links 和文件資源進(jìn)行關(guān)聯(lián)。簡(jiǎn)單說 pnpm 會(huì)把包下載到一個(gè)公共目錄,如果某個(gè)依賴在 sotre 目錄中存在了話,那么就會(huì)直接從 store 目錄里面去 hard-link,避免了二次安裝帶來的時(shí)間消耗,如果依賴在 store 目錄里面不存在的話,就會(huì)去下載一次。






通過 Store + hard link 的方式,不僅解決了項(xiàng)目中的 NPM doppelgangers 問題,項(xiàng)目之間也不存在該問題,從而完美解決了 npm3+和 yarn 中的包重復(fù)問題!



3、workspace
pnpm 除了安裝速度快,節(jié)省磁盤空間,避免幽靈依賴等優(yōu)化,也內(nèi)置了對(duì) monorepo 的支持。使用起來比較簡(jiǎn)單,在項(xiàng)目根目錄中新建 pnpm-workspace.yaml 文件,并聲明對(duì)應(yīng)的工作區(qū)就好。

packages:
  # 所有在 packages/ 子目錄下的 package
  - 'packages/**'
切換到 pnpm
轉(zhuǎn)轉(zhuǎn)的 CI 系統(tǒng)。一共有 6 臺(tái)機(jī)器,是前后端公用的。之前使用 npm 存在幾個(gè)問題。第一個(gè)就是安裝速度慢,導(dǎo)致編譯時(shí)間過長(zhǎng)。第二個(gè)是 node_modules 過大。導(dǎo)致每天必須清除一下機(jī)器上 node_modules,不然就會(huì)出現(xiàn)磁盤空間不足的問題。切換成 pnpm 之后,我們測(cè)試的結(jié)果,單臺(tái)機(jī)器最少能節(jié)省了 30G 的空間,安裝速度提升一倍以上。

因?yàn)橹苯訌?npm 切換到 pnpm,大多數(shù)的項(xiàng)目都會(huì)存在幽靈依賴的問題。導(dǎo)致項(xiàng)目報(bào)錯(cuò)。我們的切換方案是在項(xiàng)目設(shè)置中勾選是否使用 pnpm。默認(rèn)是 npm。單個(gè)項(xiàng)目在本地切換之后,在 CI 平臺(tái)上設(shè)置成 pnpm 就完成了升級(jí)。

切換 pnpm 的一些問題
使用 pnpm install --shamefully-hoist
如果依賴一直有問題,可以使用 pnpm install --shamefully-hoist 創(chuàng)建一個(gè)扁平 node_modules 目錄結(jié)構(gòu), 類似于 npm 或 yarn

解決幽靈依賴時(shí),安裝默認(rèn)的包導(dǎo)致報(bào)錯(cuò)
先使用 npm 安裝,生成 package-lock.json, 安裝缺少的包時(shí),使用 lock 里面的版本

即使刪除了 node_modules 和 lock 文件,安裝時(shí),特定的包還是報(bào)錯(cuò)
比如我們?cè)谏?jí)時(shí),一個(gè)包把最新的版本刪除了。導(dǎo)致安裝時(shí)一直失敗。可以嘗試使用 pnpm store prune 來刪除

作者:大轉(zhuǎn)轉(zhuǎn)FE


歡迎關(guān)注微信公眾號(hào) :前端民工