都 2022 年了,pnpm 快到碗裏來!
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 D
4 個 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/E5tNAEsykP6shQLFdgjFeg