pnpm 管理依賴包是如何節省磁碟空間的?

2023-11-20 21:00:41

npm 存在的問題

我們經常使用 npm 來管理 node 專案中的包,從 package.json 中讀取設定將依賴下載到本地,以保障專案的正常執行。

當專案數量多時,這樣的包管理方式會非常的佔用電腦記憶體。由於每個專案都有屬於自己的依賴,每個專案都需要安裝,即使 npm 會對依賴進行快取,但是每個專案仍然需要安裝到自己的 node_modules 資料夾下,此時每個專案安裝的每一份依賴都會在磁碟中儲存一份,即使各個專案中依賴的版本可能相同。

pnpm 就是針對以上問題出現的解決方案,它使用統一的倉庫來存放專案中的包,在專案中使用硬連結+軟連線的方式找到依賴所在磁碟的位置。

硬連結和軟連線

想要清晰的知道 pnpm 管理依賴的原理,首先要了解硬連結和軟連線、拷貝操作的區別。

拷貝操作會在磁碟中複製一份新的資料,比如拷貝 a.js 為 a_copy.js,兩個檔案在拷貝後就互不關聯,修改 a.js 不會影響 a_copy.js,刪除 a_copy.js 也不會影響 a.js。

硬連結通過定址的方式找到磁碟中的資料,比如新建 b_hard.js 與 b.js 建立硬連結,兩者指向的是同一個磁碟資料,所以修改其中一個檔案,另一個檔案也會發生變化。

軟連線就是我們平時常見的建立快捷方式(檔案後面會存在一個向右的小箭頭),它只是儲存著檔案的路徑,不可以編輯,直接雙擊就會找到原始的檔案。如果原檔案被刪除,通過軟連線將無法找到磁碟中的資料。

我們可以通過命令來進行連線操作,windows 是這樣的

/*拷貝*/ copy a.js a_copy.js
/*硬連結*/ mklink /H b_hard.js b.js
/*軟連線*/ mklink c_soft.js c.js

pnpm原理

使用 npm 或者 yarn 時,如果有100個專案,並且所有專案都有一個相同的依賴包,那麼在磁碟上就需要儲存100份該相同依賴的包

如果使用 pnpm,依賴包將被放在統一的位置,當安裝包時,其包含的所有檔案會硬連結到這個位置,不會另外佔用磁碟空間,這樣不同專案之間就可以共用相同版本的依賴。

如果對同一依賴包使用相同的版本,那麼磁碟上只有這個依賴包的一份檔案,如果對同一依賴包使用不同的版本,那麼只有版本之間不同的檔案被儲存起來。

比如 a/b/c 三個專案都使用 axios,axios 的所有檔案都儲存在 pnpm 上,axios 這些檔案對應著磁碟的資料,直接 a/b/c 專案的axios 通過硬連結指向磁碟裡的資料。 這樣有兩個好處:
(1)效率非常高,無需下載、查詢快取解壓等操作
(2)節省磁碟空間,每個專案不需要再下載一份

pnpm 依賴包統一儲存的位置可以使用命令 pnpm store path 來檢視

非扁平的 node_modules 目錄

使用 npm 或者 yarn安裝的依賴包會將所有的子級依賴全部平鋪到 node_modules 資料夾中,即扁平化的目錄結構,這樣會導致原始碼可以存取本來不屬於當前專案所設定的依賴包。

比如安裝 axios ,同時會安裝非常多的其它的庫如 form-data,雖然在 package.json 中是沒有設定的,但在原始碼中可以直接通過require('form-data') 參照,這樣就會有隱患,如果專案某天刪除了 axios,form-data 就不存在了。

使用 npm 和 pnpm 分別只安裝 axios,npm 會將 axios 所需的其它依賴平鋪,而 pnpm 的 node_modules 根目錄下只有 axios 和 .pnpm 資料夾,這樣就可以避免非主動下載的其它依賴包可隨意存取的情況。

如果直接按照這樣的層級下載包,可能會帶來新的問題,如多個包依賴同一個包時,就會被重複安裝。

▾ node_modules
    ▾ axios
        ▾ node_modules
            ▸ form-data
    ▾ xxx
        ▾ node_modules
            ▸ form-data

那 pnpm 是如何做到非扁平化並且不重複安裝的呢?答案就是它使用硬連結與軟連線結合的方式來與依賴包關聯。

在 node_modules 根目錄有一個資料夾 .pnpm,這裡包含了專案所有依賴。
根目錄下 axios 軟連線到 .pnpm 目錄下的 axios 資料夾中,展開 .pnpm/[email protected] 的node_modules 資料夾,其中有 axios 所需的依賴,包含 axios、follow-redirects、form-data、proxy-from-env,其中 axios 硬連結到磁碟中(即與 pnpm 倉庫儲存的地址一致),其它檔案軟連線到 .pnpm 的自身位置。

node_modules 根目錄下的依賴,軟連線到 .pnpm 資料夾中,如果有相互依賴的關係,仍然通過軟連線,只有找到依賴自身,才會通過硬連結找到磁碟中的位置,這樣可以保證同一個專案裡不同依賴也不會重複安裝,同時不同專案之間的相同依賴也無需在磁碟中儲存多份。