pnpm憑什麼這麼快

前端包管理器層出不窮,pnpm算是一個後起之秀。它和npm有什麼不同,為什麼有了npm還要造一個pnpm?

npm的問題

npm是最早的包管理器,安裝nodejs自帶npm,v3版本之前,npm安全依賴的目錄結構是這樣的:

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

結構清晰而直觀,但是存在一個明顯的問題,當依賴層級過多時,文件嵌套非常深,然而window系統對文件路徑長度是有限制的,超過256字元就會出現目標路徑太長,從而無法操作深層級文件的問題,從v3版本開始,npm將每個package平鋪到node_modules,就像這樣

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

平鋪的方式解決了長路徑的問題,但是又存在另一些問題:

package依賴不直觀,所有package都是平級的,無法看出來誰依賴了誰

項目中可以直接import間接依賴的package,比如模組A依賴了模組B,開發者可以直接在項目中import模組B,當模組A升級之後,可能依賴的模組B版本也升級了,之前直接import模組B的API可能不再兼容;

除此之外,npm的另外一個問題是,如果你有10個項目中依賴了模組A,模組A將被安裝10次,並且在你的硬碟上保存了10份一模一樣的程式碼,佔用了大量磁碟空間

pnpm的作者意識到這些問題,站出來造了pnpm這個新輪子,加入了一些創新

pnpm的解決方案

pnpm的口號是「快速的,節省磁碟空間的包管理工具」,這就是pnpm名字的由來,pnpm代表 performant(高性能的)npm,那麼它是怎麼做到快且節省磁碟空間的?

當項目中安裝依賴包時,pnpm將所有依賴包存儲在磁碟的某一個位置,簡稱.pnpm store,下次再安裝同一個包的時候,如果.pnpm store已經存在這個包,將會在項目中創建一個硬鏈接到.pnpm store什麼是硬鏈接?),如果.pnpm store不存在這個包,會先保存這個包到.pnpm store,然後再創建硬鏈接。這樣設計,即使是10個項目都依賴了同一個版本的模組A,模組A也只在磁碟上保存1份程式碼,這就是pnpm又快又節省磁碟空間的原因

pnpm的實現方式

pnpm的node_modules並不是平鋪的,舉例:

某個項目使用了模組[email protected],模組[email protected]又依賴了模組[email protected],那麼安裝依賴後的node_modules結構是這樣的:

node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
    ├── [email protected]
    │   └── node_modules
    │       └── bar -> <store>/bar
    └── [email protected]
        └── node_modules
            ├── foo -> <store>/foo
            └── bar -> ../../[email protected]/node_modules/bar

node_modules根目錄下只有項目直接依賴的foo模組的軟鏈接和一個.pnpm隱藏文件夾,.pnpm文件夾內,以平鋪的方式存放著所有包,每個包文件夾都能一眼看出依賴關係

foo模組以及依賴的bar模組的真實文件,都是存放在.pnpm store,通過硬鏈接的方式使用

pnpmstore

總結

pnpm在重複安裝依賴包時,不需要複製文件,所以速度非常快,通過硬鏈接的方式共享同一份程式碼,極大的節省了磁碟空間

本文永久地址GitHub