pnpm纔是前端工程化項目的未來

前言

相信小夥伴們都接觸過npm/yarn,這兩種包管理工具想必是大家工作中用的最多的包管理工具,npm作爲node官方的包管理工具,它是隨着node的誕生一起出現在大家的視野中,而yarn的出現則是爲了解決npm帶來的諸多問題,雖然yarn提高了依賴包的安裝速度與使用體驗,但它依舊沒有解決npm的依賴重複安裝等致命問題。pnpm的出現完美解決了依賴包重複安裝的問題,並且實現了yarn帶來的所有優秀體驗,所以說pnpm纔是前端工程化項目的未來

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公衆號首發,關注 前端南玖 第一時間獲取最新文章~

npm 與 yarn 存在的問題

早期的npm

在npm@3之前,node_modules結構可以說是整潔可預測的,因爲當時的依賴結構是這樣的:

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

每個依賴下面都維護着自己的node_modules,這樣看起來確實非常整潔,但同時也帶來一些較爲嚴重的問題:

  • 依賴包重複安裝
  • 依賴層級過多
  • 模塊實例無法共享

依賴包重複安裝

從上面的依賴結構我們可以看出,依賴A與依賴C同時引用了依賴B,此時的依賴B會被下載兩次。此刻我們想想要是某一個依賴被引用了n次,那麼它就需要被下載n次。(此時心裏是不是在想,怎麼會有如此坑的設計)

01203040_0.jpeg

依賴層級過多

我們再來看另外一種依賴結構:

node_modules 
└─ 依賴A 
   ├─ index.js 
   ├─ package.json 
   └─ node_modules 
       └─ 依賴B 
       ├─ index.js 
       ├─ package.json
       └─ node_modules 
           └─ 依賴C 
           ├─ index.js 
           ├─ package.json 
           └─ node_modules 
               └─ 依賴D 
               ├─ index.js 
               └─ package.json

這種依賴層級少還能接受,要是依賴層級多了,這樣一層一層嵌套下去,就像一個依賴地獄,不利於維護。

npm@3與yarn

爲了解決上述問題,npm3yarn都選擇了扁平化結構,也就是說現在我們看到的node_modules裏面的結構不再有依賴嵌套了,都是如下依賴結構:

node_modules 
└─ 依賴A  
    ├─ index.js 
    ├─ package.json 
    └─ node_modules 
└─ 依賴C   
    ├─ index.js 
    ├─ package.json 
    └─ node_modules 
└─ 依賴B 
    ├─ index.js 
    ├─ package.json 
    └─ node_modules 

node_modules下所有的依賴都會平鋪到同一層級。由於require尋找包的機制,如果A和C都依賴了B,那麼A和C在自己的node_modules中未找到依賴C的時候會向上尋找,並最終在與他們同級的node_modules中找到依賴包C。 這樣就不會出現重複下載的情況。而且依賴層級嵌套也不會太深。因爲沒有重複的下載,所有的A和C都會尋找並依賴於同一個B包。自然也就解決了實例無法共享數據的問題

由於這個扁平化結構的特點,想必大家都遇到了這樣的體驗,自己明明就只安裝了一個依賴包,打開node_modules文件夾一看,裏面卻有一大堆。

nz2.jpeg

這種扁平化結構雖然是解決了之前的嵌套問題,但同時也帶來了另外一些問題:

  • 依賴結構的不確定性
  • 扁平化算法的複雜度增加
  • 項目中仍然可以非法訪問沒有聲明過的依賴包(幽靈依賴)

依賴結構的不確定性

這個怎麼理解,爲什麼會產生這種問題呢?我們來仔細想想,加入有如下一種依賴結構:

依賴1.png

A包與B包同時依賴了C包的不同版本,由於同一目錄下不能出現兩個同名文件,所以這種情況下同一層級只能存在一個版本的包,另外一個版本還是要被嵌套依賴。

那麼問題又來了,既然是要一個扁平化一個嵌套,那麼這時候是如何確定哪一個扁平化哪一個嵌套的呢?

依賴2.png

這兩種結構都有可能,準確點說哪個版本的包被提升,取決於包的安裝順序!

這就是爲什麼會產生依賴結構的不確定問題,也是 lock 文件誕生的原因,無論是package-lock.json(npm 5.x 纔出現)還是yarn.lock,都是爲了保證 install 之後都產生確定的node_modules結構。

儘管如此,npm/yarn 本身還是存在扁平化算法複雜package 非法訪問的問題,影響性能和安全。

pnpm

前面說了那麼多的npmyarn的缺點,現在再來看看pnpm是如何解決這些尷尬問題的。

什麼是pnpm

快速的,節省磁盤空間的包管理工具

就這麼簡單,說白了它跟npmyarn沒有區別,都是包管理工具。但它的獨特之處在於:

  • 包安裝速度極快
  • 磁盤空間利用非常高效

特性

安裝包速度快

p1.png

從上圖可以看出,pnpm的包安裝速度明顯快於其它包管理工具。那麼它爲什麼會比其它包管理工具快呢?

我們來可以來看一下各自的安裝流程

  • npm/yarn

npm&yarn.png

  1. resolving:首先他們會解析依賴樹,決定要fetch哪些安裝包。

  2. fetching:安裝去fetch依賴的tar包。這個階段可以同時下載多個,來增加速度。

  3. wrting:然後解壓包,根據文件構建出真正的依賴樹,這個階段需要大量文件IO操作。

  • pnpm

pnpm.png

上圖是pnpm的安裝流程,可以看到針對每個包的三個流程都是平行的,所以速度會快很多。當然pnpm會多一個階段,就是通過鏈接組織起真正的依賴樹目錄結構。

磁盤空間利用非常高效

pnpm 內部使用基於內容尋址的文件系統來存儲磁盤上所有的文件,這個文件系統出色的地方在於:

  • 不會重複安裝同一個包。用 npm/yarn 的時候,如果 100 個項目都依賴 lodash,那麼 lodash 很可能就被安裝了 100 次,磁盤中就有 100 個地方寫入了這部分代碼。但在使用 pnpm 只會安裝一次,磁盤中只有一個地方寫入,後面再次使用都會直接使用 hardlink
  • 即使一個包的不同版本,pnpm 也會極大程度地複用之前版本的代碼。舉個例子,比如 lodash 有 100 個文件,更新版本之後多了一個文件,那麼磁盤當中並不會重新寫入 101 個文件,而是保留原來的 100 個文件的 hardlink,僅僅寫入那一個新增的文件

支持monorepo

pnpm 與 npm/yarn 另外一個很大的不同就是支持了 monorepo,pnpm內置了對monorepo的支持,只需在工作空間的根目錄創建pnpm-workspace.yaml和.npmrc配置文件,同時還支持多種配置,相比較lerna和yarn workspace,pnpm解決monorepo的同時,也解決了傳統方案引入的問題。

monorepo 的宗旨就是用一個 git 倉庫來管理多個子項目,所有的子項目都存放在根目錄的packages目錄下,那麼一個子項目就代表一個package

依賴管理

pnpm使用的是npm version 2.x類似的嵌套結構,同時使用.pnpm 以平鋪的形式儲存着所有的包。然後使用Store + Links和文件資源進行關聯。簡單說pnpm把會包下載到一個公共目錄,如果某個依賴在 sotre 目錄中存在了話,那麼就會直接從 store 目錄裏面去 hard-link,避免了二次安裝帶來的時間消耗,如果依賴在 store 目錄裏面不存在的話,就會去下載一次。通過Store + hard link的方式,使得項目中不存在NPM依賴地獄問題,從而完美解決了npm3+和yarn中的包重複問題。

store.jpeg

我們分別用npmpnpm來安裝vite對比看一下

npm pnpm
npm-demo.png pnpm-demo.png
所有依賴包平鋪在node_modules目錄,包括直接依賴包以及其他次級依賴包 node_modules目錄下只有.pnpm和直接依賴包,沒有其他次級依賴包
沒有符號鏈接(軟鏈接) 直接依賴包的後面有符號鏈接(軟鏈接)的標識

pnpm安裝的vite 所有的依賴都軟鏈至了 node_modules/.pnpm/ 中的對應目錄。 把 vite 的依賴放置在同一級別避免了循環的軟鏈。

軟鏈接 和 硬鏈接 機制

pnpm 是通過 hardlink 在全局裏面搞個 store 目錄來存儲 node_modules 依賴裏面的 hard link 地址,然後在引用依賴的時候則是通過 symlink 去找到對應虛擬磁盤目錄下(.pnpm 目錄)的依賴地址。

這兩者結合在一起工作之後,假如有一個項目依賴了 [email protected][email protected] ,那麼最後的 node_modules 結構呈現出來的依賴結構可能會是這樣的:

node_modules
└── A // symlink to .pnpm/[email protected]/node_modules/A
└── B // symlink to .pnpm/[email protected]/node_modules/B
└── .pnpm
    ├── [email protected]
    │   └── node_modules
    │       └── A -> <store>/A
    │           ├── index.js
    │           └── package.json
    └── [email protected]
        └── node_modules
            └── B -> <store>/B
                ├── index.js
                └── package.json

node_modules 中的 A 和 B 兩個目錄會軟連接到 .pnpm 這個目錄下的真實依賴中,而這些真實依賴則是通過 hard link 存儲到全局的 store 目錄中。

store

pnpm下載的依賴全部都存儲到store中去了,storepnpm在硬盤上的公共存儲空間。

pnpmstore在Mac/linux中默認會設置到{home dir}>/.pnpm-store/v3;windows下會設置到當前盤符的根目錄下。使用名爲 .pnpm-store的文件夾名稱。

項目中所有.pnpm/依賴名@版本號/node_modules/下的軟連接都會連接到pnpmstore中去。

原文首發地址點這裏,歡迎大家關注公衆號 「前端南玖」,如果你想進前端交流羣一起學習,請點這裏

我是南玖,我們下期見!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章