一文讀懂 pnp
pnpm是一款當代備受關注的 新興 (問題較多) 包管理工具,使用過的同學們都會被它極快的安裝速度、極少的磁盤存儲空間所吸引!
首先,爲什麼會出現pnpm?作者一開始對yarn的發佈有很高的期待,但是發佈後並沒有滿足作者的一些期待,反而讓作者有些失望。
After a few days, I realized that Yarn is just a small improvement over npm. Although it makes installations faster and it has some nice new features, it uses the same flat node_modules structure that npm does (since version 3). And flattened dependency trees come with a bunch of issues
幾天後,我意識到 Yarn 只是對 npm 的一個小小的改進。儘管它使安裝速度更快,並且具有一些不錯的新功能,但它使用與 npm 相同的平面 node_modules 結構(自版本 3 起)。扁平化的依賴樹帶來了一系列問題(具體後面會講)
至於爲什麼叫pnpm?是因爲pnpm作者對現有的包管理工具,尤其是npm和yarn的性能特別失望,所以起名叫做perfomance npm,即pnpm(高性能 npm)
如何突顯pnpm的性能優勢?在pnpm官網上,提供了一個 benchmarks 圖表,它比對了項目在 npm、pnpm、yarn(正常版本和 PnP 版)中,install、update場景下的耗時:
下面表格是上圖中的具體數據:
可以看到pnpm(橘色)有很明顯性能提升,在我們項目實踐中(基於gitlib)提升更明顯(cache-paths跟store dir搭配使用後)
在討論性能提升原因之前,我們需要先了解下現有包管理工具中node_modules存在的問題
node_modules 安裝方式
目前有兩種安裝方式:Nested installation、Flat installation
Nested installation 嵌套安裝
在 npm@3 之前,node_modules 結構是乾淨、可預測的,因爲 node_modules 中的每個依賴項都有自己的 node_modules 文件夾,在 _package.json 中_指定了所有依賴項。例如下面所示,項目依賴了foo,foo又依賴了bar,依賴關係如下圖所示:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
上面結構有兩個嚴重的問題:
-
package 中經常創建太深的依賴樹,這會導致 Windows 上的目錄路徑過長問題
-
當一個 package 在不同的依賴項中需要時,它會被多次複製粘貼並生成多份文件
Flat installation 扁平安裝
爲了解決上述問題,npm 重新考慮了 node_modules 結構並提出了扁平化結構。在 npm@3+ 和 yarn 中,node_modules 結構變成如下所示:
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
可以看到,hoist機制下,bar被提升到了頂層。如果同一個包的多個版本在項目中被依賴時,node_modules 結構又是怎麼樣的?
例如:一個項目App直接依賴了A(version: 1.0)和C(version: 1.0),A和C都依賴了不同版本的B,其中A依賴B 1.0,C依賴B 2.0, 可以通過下圖清晰的看到npm2和npm3+結構差異:
包B 1.0被提升到了頂層,這裏需要注意的是,多個版本的包只能有一個被提升上來,其餘版本的包會嵌套安裝到各自的依賴當中(類似npm2的結構)。
至於哪個版本的包被提升,依賴於包的安裝順序!
依賴變更會影響提升的版本號,比如變更後,有可能是B 1.0 ,也有可能是 B 2.0被提升上來(但只能有一個版本提升)
細心的小夥伴可能發現,這其實並沒有解決之前的問題,反而又引入了新的問題
npm3 + 和 yarn 存在的問題
Phantom dependencies 幽靈依賴
Phantom dependencies 被稱之爲幽靈依賴或幻影依賴,解釋起來很簡單,即某個包沒有在package.json 被依賴,但是用戶卻能夠引用到這個包。
引發這個現象的原因一般是因爲 node_modules 結構所導致的。例如使用 npm 或 yarn 對項目安裝依賴,依賴裏面有個依賴叫做 foo,foo 這個依賴同時依賴了 bar,yarn 會對安裝的 node_modules 做一個扁平化結構的處理,會把依賴在 node_modules 下打平,這樣相當於 foo 和 bar 出現在同一層級下面。那麼根據 nodejs 的尋徑原理,用戶能 require 到 foo,同樣也能 require 到 bar。
nodejs 的尋址方式:(查看更多)
對於核心模塊(core module) => 絕對路徑 尋址
node 標準庫 => 相對路徑尋址
第三方庫(通過 npm 安裝)到 node_modules 下的庫: 3.1. 先在當前路徑下,尋找 node_modules/xxx
3.2 遞歸從下往上到上級路徑,尋找 ../node_modules/xxx
3.3 循環第二步
3.4 在全局環境路徑下尋找 .node_modules/xxx
NPM doppelgangers NPM 分身
這個問題其實也可以說是 hoist 導致的,這個問題可能會導致有大量的依賴的被重複安裝.
舉個例子:項目中有packageA、packageB、packageC、packageD。packageA依賴 packageX 1.0 和 packageY 1.0,packageB依賴 packageX 2.0 和 packageY 2.0,packageC依賴 packageX 1.0 和 packageY 2.0,packageD依賴 packageX 2.0 和 packageY 1.0。
在 npm2 時,結構如下
- package A
- packageX 1.0
- packageY 1.0
- package B
- packageX 2.0
- packageY 2.0
- package C
- packageX 1.0
- packageY 2.0
- package D
- packageX 2.0
- packageY 1.0
在 npm3 + 和 yarn 中,由於存在 hoist 機制,所以 X 和 Y 各有一個版本被提升了上來,目錄結構如下
- package X => 1.0版本
- package Y => 1.0版本
- package A
- package B
- packageX 2.0
- packageY 2.0
- package C
- packageY 2.0
- package D
- packageX 2.0
如上圖所示的 packageX 2.0 和 packageY 2.0 被重複安裝多次,從而造成 npm 和 yarn 的性能一些性能損失。
這種場景在 monorepo 多包場景下尤其明顯,這也是yarn workspace經常被吐槽的點,另外扁平化的算法實現也相當複雜,改動成本很高。那麼pnpm是如何解決這種問題的呢?
pnpm 的破解之道:網狀 + 平鋪的 node_modules 結構
一些背景知識:inode、hardl link 和 symbolic link 的基礎概念
pnpm的用戶可能會發現它node_modules並不是扁平化結構,而是目錄樹的結構,類似npm version 2.x版本中的結構,如下圖所示
同時還有個.pnpm目錄,如下圖所示
.pnpm 以平鋪的形式儲存着所有的包,正常的包都可以在這種命名模式的文件夾中被找到(peerDep 例外):
.pnpm/<organization-name>+<package-name>@<version>/node_modules/<name>
// 組織名(若無會省略)+包名@版本號/node_modules/名稱(項目名稱)
我們稱.pnmp爲虛擬存儲目錄,該目錄通過<package-name>@<version>來實現相同模塊不同版本之間隔離和複用,由於它只會根據項目中的依賴生成,並不存在提升,所以它不會存在之前提到的 Phantom dependencies 問題!
那麼它如何跟文件資源進行關聯的呢?又如何被項目中使用呢?
答案是Store + Links!
Store
pnpm資源在磁盤上的存儲位置。
pnpm 使用名爲 .pnpm-store 的 store dir,Mac/linux 中默認會設置到{home dir}>/.pnpm-store/v3;windows 下會設置到當前盤的根目錄下,比如 C(C/.pnpm-store/v3)、D 盤(D/.pnpm-store/v3)。
具體可以參考 @pnpm/store-path 這個 pnpm 子包中的代碼:
const homedir = os.homedir()
if (await canLinkToSubdir(tempFile, homedir)) {
await fs.unlink(tempFile)
// If the project is on the drive on which the OS home directory
// then the store is placed in the home directory
return path.join(homedir, relStore, STORE_VERSION)
}
由於每個磁盤有自己的存儲方式,所以 Store 會根據磁盤來劃分。如果磁盤上存在主目錄,存儲則會被創建在
<home dir>/.pnpm-store;如果磁盤上沒有主目錄,那麼將在文件系統的根目錄中創建該存儲。例如,如果安裝發生在掛載在/mnt的文件系統上,那麼存儲將在/mnt/.pnpm-store處創建。Windows 系統上也是如此。
可以在不同的磁盤上設置同一個存儲,但在這種情況下,pnpm 將複製包而不是硬鏈接它們,因爲硬鏈接只能發生在同一文件系統同一分區上。
windows store 如下圖所示
pnpm install的安裝過程中,我們會看到如下的信息,這個裏面的Content-addressable store就是我們目前說的Store
CAS 內容尋址存儲,是一種存儲信息的方式,根據內容而不是位置進行檢索信息的存儲方式。
Virtual store 虛擬存儲,指向存儲的鏈接的目錄,所有直接和間接依賴項都鏈接到此目錄中,項目當中的. pnpm 目錄
如果是 npm 或 yarn,那麼這個依賴在多個項目中使用,在每次安裝的時候都會被重新下載一次
如圖可以看到在使用 pnpm 對項目安裝依賴的時候,如果某個依賴在 sotre 目錄中存在了話,那麼就會直接從 store 目錄裏面去 hard-link,避免了二次安裝帶來的時間消耗,如果依賴在 store 目錄裏面不存在的話,就會去下載一次。
當然這裏你可能也會有問題:如果安裝了很多很多不同的依賴,那麼 store 目錄會不會越來越大?
答案是當然會存在,針對這個問題,pnpm 提供了一個命令來解決這個問題: pnpm store | pnpm。
同時該命令提供了一個選項,使用方法爲 pnpm store prune ,它提供了一種用於刪除一些不被全局項目所引用到的 packages 的功能,例如有個包 axios@1.0.0 被一個項目所引用了,但是某次修改使得項目裏這個包被更新到了 1.0.1 ,那麼 store 裏面的 1.0.0 的 axios 就就成了個不被引用的包,執行 pnpm store prune 就可以在 store 裏面刪掉它了。
該命令推薦偶爾進行使用,但不要頻繁使用,因爲可能某天這個不被引用的包又突然被哪個項目引用了,這樣就可以不用再去重新下載這個包了。
看到這裏,你應該對 Store 有了一些簡單的瞭解,接着我們來看下項目中的文件如何跟 Store 關聯。
Links(hard link & symbolic link)
還記得文章剛開始,放了兩張 beachmark 的圖表,圖表上可以看到很明顯的性能提升(如果你使用過,感觸會更明顯)!
pnpm 是怎麼做到如此大的提升的呢?一部分原因是使用了計算機當中的 Hard link ,它減少了文件下載的數量,從而提升了下載和響應速度。
hard link
通過hard link, 用戶可以通過不同的路徑引用方式去找到某個文件,需要注意的是一般用戶權限下只能硬鏈接到文件,不能用於目錄。
pnpm 會在Store(上面的 Store) 目錄裏存儲項目 node_modules 文件的 hard links ,通過訪問這些 link 直接訪問文件資源。
舉個例子,例如項目裏面有個 2MB 的依賴 react,在 pnpm 中,看上去這個 react依賴同時佔用了 2MB 的 node_modules 目錄以及全局 store 目錄 2MB 的空間 (加起來是 4MB),但因爲 hard link 的機制使得兩個目錄下相同的 2MB 空間能從兩個不同位置進行CAS尋址直接引用到文件,因此實際上這個react依賴只用佔用 2MB 的空間,而不是 4MB。
因爲這樣一個機制,導致每次安裝依賴的時候,如果是個相同的依賴,有好多項目都用到這個依賴,那麼這個依賴實際上最優情況 (即版本相同) 只用安裝一次。
而在npm和yarn中,如何一個依賴被多個項目使用,會發生多次下載和安裝!
如果是 npm 或 yarn,那麼這個依賴在多個項目中使用,在每次安裝的時候都會被重新下載一次。
如圖可以看到在使用 pnpm 對項目安裝依賴的時候,如果某個依賴在 store 目錄中存在了話,那麼就會直接從 store 目錄裏面去 hard-link,避免了二次安裝帶來的時間消耗,如果依賴在 store 目錄裏面不存在的話,就會去下載一次。
通過Store + hard link的方式,不僅解決了項目中的 NPM doppelgangers 問題,項目之間也不存在該問題,從而完美解決了npm3+和yarn中的包重複問題!
如果隨着項目越來越大,版本變更變多,歷史版本的資源會堆積,導致Store目錄越來越大,那如何解決這個問題呢?
針對這個現象,pnpm 提供了一個命令來解決這個問題: pnpm store | pnpm。
同時該命令提供了一個選項,使用方法爲 pnpm store prune ,它提供了一種用於刪除一些不被全局項目所引用到的 packages 的功能,例如有個包 axios@1.0.0 被一個項目所引用了,但是某次修改使得項目裏這個包被更新到了 1.0.1 ,那麼 store 裏面的 1.0.0 的 axios 就就成了個不被引用的包,執行 pnpm store prune 就可以在 store 裏面刪掉它了。
該命令推薦偶爾進行使用,但不要頻繁使用,因爲可能某天這個不被引用的包又突然被哪個項目引用了,這樣就可以不用再去重新下載這個包了。
symbolic link
由於hark link只能用於文件不能用於目錄,但是pnpm的node_modules是樹形目錄結構,那麼如何鏈接到文件?通過symbolic link(也可稱之爲軟鏈或者符號鏈接)來實現!
通過前面的講解,我們知道了pnpm在全局通過Store來存儲所有的 node_modules 依賴,並且在.pnpm/node_modules中存儲項目的 hard links,通過hard link來鏈接真實的文件資源,項目中則通過symbolic link鏈接到.pnpm/node_modules目錄中,依賴放置在同一級別避免了循環的軟鏈。
pnpm 的 node_modules 結構一開始看起來很奇怪:
-
它完全適配了 Node.js。
-
包與其依賴被完美地組織在一起。
有 peer 依賴的包的結構更加複雜一些,但思路是一樣的:使用軟鏈與平鋪目錄來構建一個嵌套結構。
假設我們有個 mono repo,它有repo A、repo B、repo C和repo D4 個 repo。每個 repo 有各自的一些依賴項(包括 dependencies 和 peerDependencies),假定結構如下圖所示:(需要注意有個 peer dep)
下面是pnpm workspace中,比較清晰(不清晰的話留言,我可以改改!)說明了Store和Links間的相互關係:
官網也更新了類似的調用關 圖,大家也可以看看!
PeerDependencies
pnpm 的最佳特徵之一是,在一個項目中,package 的一個特定版本將始終只有一組依賴項。這個規則有一個例外 - 那就是具有 peer dependencies 的 package。
通常,如果一個package沒有 peer 依賴項(peer dependencies),它會被硬鏈接到其依賴項的軟連接(symlinks)旁的 node_modules,就像這樣:
如果 foo 有 peer 依賴(peer dependencies),那麼它可能就會有多組依賴項,所以我們爲不同的 peer 依賴項創建不同的解析:
pnpm創建foo@1.0.0_bar@1.0.0+baz@1.0.0 或foo@1.0.0_bar@1.0.0+baz@1.1.0內到foo的軟鏈接。因此,Node.js 模塊解析器將找到正確的 peers。
peerDep的包命名規則如下 (看起來就很麻煩)
.pnpm/<organization-name>+<package-name>@<version>_<organization-name>+<package-name>@<version>/node_modules/<name>
// peerDep組織名(若無會省略)+包名@版本號_組織名(若無會省略)+包名@版本號/node_modules/名稱(項目名稱)
如果一個
package沒有 peer 依賴(peer dependencies),不過它的依賴項有 peer 依賴,這些依賴會在更高的依賴圖中解析, 則這個傳遞package便可在項目中有幾組不同的依賴項。例如,a@1.0.0具有單個依賴項b@1.0.0。b@1.0.0有一個 peer 依賴爲c@^1。a@1.0.0永遠不會解析b@1.0.0的 peer, 所以它也會依賴於b@1.0.0的 peer 。
如果需要解決 peerDep 引入的多實例問題,可以通過 .pnpmfile.cjs文件更改依賴項的依賴關係。
pnpm 和 npm、yarn 的功能差異
下面圖表摘自官網,感興趣的同學可以自行查閱。
從圖表可以看到有兩個是pnpm獨有的實現:管理 Node.js 版本和內容可尋址存儲(CAS)
其中 CAS 前面已經介紹過了,我們講一下管理 Node.js 版本。
這個在.npmrc文件中,Node 模塊設置中使用use-node-version進行配置(其它配置信息)
use-node-version用於指定應用於項目運行時的確切 Node.js 版本,支持 semver 版本設置。設置後, pnpm 將自動安裝指定版本的 Node.js 並將其用於執行 pnpm run 命令或 pnpm node 命令。
// 指定版本16.x
use-node-version=^16.x
當前安裝的是 14.x,使用上述配置後,在執行時會有一個 warning:WARN Unsupported engine: wanted: {"node":">=14.0.0"} (current: {"node":"^16.x","pnpm":"6.22.2"}) 但不影響執行結果
workspace
pnpm跟npm、yarn一樣,也內置了對 monorepo 的支持,使用起來比較簡單,在項目根目錄中新建pnpm-workspace.yaml文件,並聲明對應的工作區就好。
packages:
# 所有在 packages/ 子目錄下的 package
- 'packages/**'
workspace 工作空間協議
默認情況下,如果可用的packages與已聲明的可用範圍相匹配,pnpm 將從工作區鏈接這些packages。例如,如果 bar 中有 "foo":"^1.0.0" 的這個依賴項,則 foo@1.0.0 鏈接到 bar 。但是,如果 bar 的依賴項中有"foo": "2.0.0" ,而foo@2.0.0 在工作空間中並不存在,則將從 npm registry 安裝foo@2.0.0 。這種行爲帶來了一些不確定性。
幸運的是,pnpm從版本 3.7 開始支持工作區協議workspace: 。當使用此協議時,pnpm 將拒絕解析除本地工作區 package 之外的任何內容。因此,如果您設置爲 "foo": "workspace:2.0.0" 時,安裝將會失敗,因爲 "foo@2.0.0" 不存在於工作空間中。這個特性在 monorepo 當中特別有用。
可以通過修改配置link-workspace-packages來改變包的使用方式(遠端下載 or 本地)。
link-workspace-packages有三個值
-
true 默認,使用本地可用的 packages;
-
false 禁用後,將從 registry 下載安裝到本地,並被使用(類似 yarn\npm 安裝)
-
deep 自 v5 版本起可用,本地 packages 當中的依賴項(sub package)也可以被鏈接到並使用
它支持兩種引用方式:別名引用和相對引用。
別名引用
假如工作區有一個名爲 local-package 的包,可以通過這樣引用 "local-package": "workspace:"。如果是其它別名,可以這麼引用: "ref-package": "workspace:local-package@*"
相對引用
假如 packages 下有同層級的repoA、repoB,其中repoA依賴於repoB,則可以寫作"repoA": "workspace:../repoB"。
發佈前,這兩種引用方式,都會被替換爲常規的版本規範。
filter 過濾
通過該參數,允許我們將命令運用到指定的包上面,類似於jQuery跟Dom的選擇器,寫法如下
pnpm <command> --filter <package_selector>
同時它也支持鏈式調用,可以一次寫多個調用,如下所示,
pnpm <command> --filter selector1 --filter selector2 -- filter=!selector3
“
Lerna 中也支持選擇器,參數是
scope,下面的文章對比了Lerna和pnpm的選擇器,感興趣的同學可以看一下
Lerna 與 pnpm 選擇器”
matching 匹配
匹配規則支持三種維度匹配:packageName(包名)、dirName (目錄名)、git commit/branch(git提交或分支名)
packageName(包名)支持包名通配符匹配、包和依賴項 (直接和間接依賴) 匹配、依賴項 (直接和間接依賴) 匹配、包被依賴項 (直接和間接依賴) 匹配、被依賴項 (直接和間接依賴) 匹配
// 包名通配符匹配,其中scope是可選的,當單個scope時:`--filter=core` 將選擇 `@babel/core`;多scope不生效
pnpm test --filter "@babel/core"\
pnpm test --filter "@babel/*"\
pnpm test --filter "*core"
// 包和依賴項(直接和間接依賴)匹配,要選擇一個軟件包及其依賴項 (直接和非直接) 在包名稱後加上省略號: `<package_name>...`
pnpm test --filter foo...
pnpm test --filter "@babel/preset-*..." // 可選擇一組根目錄包
// 依賴項(直接和間接依賴)匹配,要選擇一個軟件包及其依賴項 (直接和非直接), 在包名前添加一個山形符號加上上面提到的省略號
pnpm test --filter foo^...
pnpm test --filter "@babel/preset-*^..." // 可選擇一組根目錄包
// 包被依賴項(直接和間接依賴)匹配,在包名前添加一個省略號: `...<package_name>`。
pnpm test --filter ...foo // 將運行 `foo` 以及依賴於它的所有包的測試:
// 被依賴項(直接和間接依賴)匹配
pnpm test --filter "...^foo" // 將運行所有依賴於 `foo` 的包的測試
細心的小夥伴可能發現了,核心就是:一個元素packageName + 兩個操作(...和^)的排列組合
-
packageName名稱,支持通配符 -
...選擇包及其依賴項,需要放在包名前或包名後。 -
...packageName被 依賴項匹配 -
packageName...依賴項匹配 -
^排除當前包,可以單獨使用,但是推薦和...(依賴項)搭配使用
“
packageName、 ...packageName、packageName...、...packageName...、...^packageName、packageName^...、...^packageName^...、...^packageName...、...packageName^...
”
這樣看的話會清晰很多。
dirName (目錄名)支持相對路徑引用(通常是 POSIX 格式)、指定目錄項目的形式(可以搭配操作一起使用)
// 相對路徑引用
pnpm <cmd> --filter ./packages
// 搭配操作符(`...`和`^`)
pnpm <cmd> --filter ...{<directory>}
pnpm <cmd> --filter {<directory>}...
pnpm <cmd> --filter ...{<directory>}...
git commit/branch(git提交或分支名),這個和packageName的用法類似,但是它可以和前面兩者搭配使用
// 運行自 `master` 以來所有變動過的包以及被其依賴的包的測試
pnpm test --filter "...[origin/master]"
// 和`package-name`搭配使用
pnpm <cmd> --filter "...@babel/*{components}[origin/master]"
// 和`dir-name`搭配使用
pnpm <cmd> --filter "...{packages}[origin/master]..."
excluding 排除
只要在開頭添加一個!,過濾規則選擇器都會變爲排除項。
在
gitbash、zsh等 shell 中, "!" 應轉義:\!. 否則會報bash: !{packageName}: event not found錯誤
multiplicity 混合
因爲filter支持鏈式調用,所以這兩種場景可以混合使用。例如:有個組織@fe,裏面存在包 A、B、C、D,我們需要選中不含 B 包的其它包來執行 build 命令,可以這麼寫
pnpm build --filter @fe/* --filter=!@fe/B
混合的情況下(Filter1、Filter2、...、FilterN),Filter1 會先生效,然是是 Filter2,直到 N,跟ECMAScript類似,當然真實業務中可能會更復雜,具體還需要按場景分析。
command 命令
參照官網,按照使用場景進行分類,列舉出部分命令的用法,所有命令請前往 官網 查看
manage depencies 管理依賴
pnpm add
add命令是老朋友了,跟yarn add類似,安裝 package 以及依賴的 package,默認是安裝到dependencies中。注意的是在 workspace 中,如果想要安裝在 root workspace 中需要添加-w或者--ignore-workspace-root-check,安裝到 packages 中需要使用--filter,否則會安裝失敗
5 種安裝姿勢:
-
npm(默認):workspace 中 會先確認改包是否被引用,是的話根據使用版本來安裝;非 workspace 中,默認會從
npm registry安裝最新的 package。例如:pnpm add express@nightly(tag)、pnpm add express@1.0.0(version)、pnpm add express@2 react@">=0.1.0 <0.2.0"(semantic versioning)。 -
workspace:workspace 安裝依賴時, 會從已配置的源處進行安裝,當然取決於是否設置了
link-workspace-packages, 以及是否使用了workspace: range protocol。 -
local file system:本地安裝有兩種安裝方式,源文件和本地目錄。
-
reomote tarball:遠端安裝必須鑰匙一個可訪問的 URL。
-
git repository:git 安裝通過 git clone 從 git 作者處安裝。
常用的參數選項
-
--save-prod, -P:安裝到 dependencies -
--save-dev, -D:安裝到 devDependencies -
--save-optional, -O:安裝到 optionalDependencies -
--save-peer:安裝到 peerDependencies 和 devDependencies 中 -
--global:安裝全局依賴。 -
--workspace:僅添加在 workspace 找到的依賴項。
pnpm remove
別名: rm, uninstall, un
從 node_modules 和項目的 package.json 中移除包。參數跟add類似,不展開說了
pnpm install
別名: i
pnpm install 用於安裝項目所有依賴。在 CI 環境中, 如果存在需要更新的 lockfile 會安裝失敗,所以每次版本更新後,本地一定要 install 後再提交,否則會導致版本發佈失敗。
這裏講一下--fix-lockfile和--shamefully-hoist。
-
--fix-lockfile參數自動修復損壞的 lock 文件入口,首次安裝時候特別有用,如果遇到某個包找不到,可能是幻影依賴的問題,需要手動添加依賴或者排查原因。 -
--shamefully-hoist創建一個扁平node_modules目錄結構, 類似於npm或yarn。 這是非常不推薦的,但是確實某些場景下可以解決遷移後無法使用額問題
pnpm import
import命令支持從其它格式的 lock 文件生成pnpm-lock.yaml文件,目前支持三種格式源文件
-
package-lock.json -
npm-shrinkwrap.json -
yarn.lock(v6.14.0 起)
個人認爲這個命令跟lerna import搭配起來使用更好,lerna import導入 git 提交歷史 (瞭解更多),一個負責生成pnpm-lock.yaml文件,這樣可以完全還原項目的提交歷史和版本依賴。
pnpm prune
prune移除項目中不需要的依賴包,配置項支持 --prod(刪除在 devDependencies 中指定的包) 和--no-optional(刪除在 optionalDependencies 中指定的包。).
當全局或者單例模式下使用store-dir時會尤其有用,可以用腳本週期性的刪除歷史版本依賴。
WARNING prune 命令目前不支持在
monorepo中遞歸執行。可以刪除一個只安裝 production 依賴的monorepo的幾個node_modules文件夾,然後重新再用pnpm install --prod安裝。
review dependencies 查看依賴
pnpm list
別名: ls。
此命令會以一個樹形結構輸出所有的已安裝package的版本及其依賴。添加參數--json後會輸出 JSON 格式的日誌。
run scripts 運行腳本
pnpm run
別名: run-script。
運行一個在 package的 manifest 文件中定義的腳本。
假如您有個 start 腳本配置在了package.json中,像這樣:
"scripts": {
"start": "start-storybook -s ./assets -p 23762 -c __storybook"
}
您現在可以使用 pnpm run start運行該腳本!很簡單吧?對於那些不喜歡敲鍵盤而浪費時間的人要注意的另一件事是,所有腳本都會有 pnpm 命令的別名,所以最終 pnpm run start 的簡寫是 pnpm start (僅適用於那些不與已有的 pnpm 命令相同名字的腳本)。注意不要命令裏面嵌套pnpm run command,否則會造成循環執行。
總結
文章卸載這裏基本結束了,簡單的做個總結回顧下內容。
-
node_modules隨着設計的變化,出現了嵌套安裝和扁平安裝兩種方式,當然它們都有各自的優缺點 -
pnpm是如何通過非扁平化的安裝方式解決現有node_modules出現的問題 -
特有的功能集:
管理 Node.js 版本和內容可尋址存儲(CAS) -
常用的命令:
install、add、remove、update、publish等 -
workspace如何使用 -
filter過濾選擇器的一些常見用法:matching匹配、excluding排除和multiplicity混合
參考資料
-
扁平的 node_modules 不是唯一的方法
-
Hard links and Symbolic links
-
https://www.kochan.io/nodejs/why-should-we-use-pnpm.html
-
https://npm.github.io/how-npm-works-docs/npm3/how-npm3-works.html
-
https://nodejs.org/api/modules.html#loading-from-the-global-folders
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/4pXTxgqpIzanfcm75_vNJQ