都 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作者對現有的包管理工具,尤其是npmyarn的性能特別失望,所以起名叫做perfomance npm,即pnpm(高性能 npm)

如何突顯pnpm的性能優勢?在pnpm官網上,提供了一個 benchmarks 圖表,它比對了項目在 npm、pnpm、yarn(正常版本和 PnP 版)中,installupdate場景下的耗時:

下面表格是上圖中的具體數據:

977lnC

可以看到pnpm(橘色)有很明顯性能提升,在我們項目實踐中(基於gitlib)提升更明顯(cache-pathsstore dir搭配使用後)

在討論性能提升原因之前,我們需要先了解下現有包管理工具中node_modules存在的問題

node_modules 安裝方式

目前有兩種安裝方式:Nested installationFlat installation

Nested installation 嵌套安裝

npm@3 之前,node_modules 結構是乾淨可預測的,因爲 node_modules 中的每個依賴項都有自己的 node_modules 文件夾,在 _package.json 中_指定了所有依賴項。例如下面所示,項目依賴了foofoo又依賴了bar,依賴關係如下圖所示:

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

上面結構有兩個嚴重的問題:

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)AC都依賴了不同版本的B,其中A依賴B 1.0C依賴B 2.0, 可以通過下圖清晰的看到npm2npm3+結構差異:

B 1.0被提升到了頂層,這裏需要注意的是,多個版本的包只能有一個被提升上來,其餘版本的包會嵌套安裝到各自的依賴當中(類似npm2的結構)。

至於哪個版本的包被提升,依賴於包的安裝順序!

依賴變更會影響提升的版本號,比如變更後,有可能是B 1.0 ,也有可能是 B 2.0被提升上來(但只能有一個版本提升)

細心的小夥伴可能發現,這其實並沒有解決之前的問題,反而又引入了新的問題

npm3 + 和 yarn 存在的問題

Phantom dependencies 幽靈依賴

Phantom dependencies 被稱之爲幽靈依賴或幻影依賴,解釋起來很簡單,即某個包沒有在package.json 被依賴,但是用戶卻能夠引用到這個包。

引發這個現象的原因一般是因爲 node_modules 結構所導致的。例如使用 npm 或 yarn 對項目安裝依賴,依賴裏面有個依賴叫做 foofoo 這個依賴同時依賴了 bar,yarn 會對安裝的 node_modules 做一個扁平化結構的處理,會把依賴在 node_modules 下打平,這樣相當於 foobar 出現在同一層級下面。那麼根據 nodejs 的尋徑原理,用戶能 require 到 foo,同樣也能 require 到 bar

nodejs 的尋址方式:(查看更多)

  1. 對於核心模塊(core module) => 絕對路徑 尋址

  2. node 標準庫 => 相對路徑尋址

  3. 第三方庫(通過 npm 安裝)到 node_modules 下的庫:       3.1.   先在當前路徑下,尋找 node_modules/xxx
           3.2    遞歸從下往上到上級路徑,尋找 ../node_modules/xxx
           3.3    循環第二步
           3.4    在全局環境路徑下尋找 .node_modules/xxx

NPM doppelgangers NPM 分身

這個問題其實也可以說是 hoist 導致的,這個問題可能會導致有大量的依賴的被重複安裝.

舉個例子:項目中有packageApackageBpackageCpackageDpackageA依賴 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 關聯。

還記得文章剛開始,放了兩張 beachmark 的圖表,圖表上可以看到很明顯的性能提升(如果你使用過,感觸會更明顯)!

pnpm 是怎麼做到如此大的提升的呢?一部分原因是使用了計算機當中的 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。

因爲這樣一個機制,導致每次安裝依賴的時候,如果是個相同的依賴,有好多項目都用到這個依賴,那麼這個依賴實際上最優情況 (即版本相同) 只用安裝一次。

而在npmyarn中,如何一個依賴被多個項目使用,會發生多次下載和安裝!

如果是 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 裏面刪掉它了。

該命令推薦偶爾進行使用,但不要頻繁使用,因爲可能某天這個不被引用的包又突然被哪個項目引用了,這樣就可以不用再去重新下載這個包了。

由於hark link只能用於文件不能用於目錄,但是pnpmnode_modules是樹形目錄結構,那麼如何鏈接到文件?通過symbolic link(也可稱之爲軟鏈或者符號鏈接)來實現!

通過前面的講解,我們知道了pnpm在全局通過Store來存儲所有的 node_modules 依賴,並且在.pnpm/node_modules中存儲項目的 hard links,通過hard link來鏈接真實的文件資源,項目中則通過symbolic link鏈接到.pnpm/node_modules目錄中,依賴放置在同一級別避免了循環的軟鏈。

pnpm 的 node_modules 結構一開始看起來很奇怪:

  1. 它完全適配了 Node.js。

  2. 包與其依賴被完美地組織在一起。

有 peer 依賴的包的結構更加複雜一些,但思路是一樣的:使用軟鏈與平鋪目錄來構建一個嵌套結構。

假設我們有個 mono repo,它有repo Arepo Brepo Crepo D4 個 repo。每個 repo 有各自的一些依賴項(包括 dependencies 和 peerDependencies),假定結構如下圖所示:(需要注意有個 peer dep)

下面是pnpm workspace中,比較清晰(不清晰的話留言,我可以改改!)說明了StoreLinks間的相互關係:

官網也更新了類似的調用關 圖,大家也可以看看!

PeerDependencies

pnpm 的最佳特徵之一是,在一個項目中,package 的一個特定版本將始終只有一組依賴項。這個規則有一個例外 - 那就是具有 peer dependenciespackage

通常,如果一個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 的功能差異

下面圖表摘自官網,感興趣的同學可以自行查閱。

wzQVcS

從圖表可以看到有兩個是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

pnpmnpmyarn一樣,也內置了對 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有三個值

它支持兩種引用方式:別名引用和相對引用。

別名引用

假如工作區有一個名爲 local-package 的包,可以通過這樣引用 "local-package": "workspace:"。如果是其它別名,可以這麼引用: "ref-package": "workspace:local-package@*"

相對引用

假如 packages 下有同層級的repoArepoB,其中repoA依賴於repoB,則可以寫作"repoA": "workspace:../repoB"

發佈前,這兩種引用方式,都會被替換爲常規的版本規範。

filter 過濾

通過該參數,允許我們將命令運用到指定的包上面,類似於jQueryDom的選擇器,寫法如下

pnpm <command> --filter <package_selector>

同時它也支持鏈式調用,可以一次寫多個調用,如下所示,

pnpm <command> --filter selector1 --filter selector2 -- filter=!selector3

Lerna 中也支持選擇器,參數是scope,下面的文章對比了Lernapnpm的選擇器,感興趣的同學可以看一下
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^...

這樣看的話會清晰很多。

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 排除

只要在開頭添加一個!,過濾規則選擇器都會變爲排除項。

gitbashzsh等 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 種安裝姿勢:

常用的參數選項

pnpm remove

別名: rm, uninstall, un

從 node_modules 和項目的 package.json 中移除包。參數跟add類似,不展開說了

pnpm install

別名: i

pnpm install 用於安裝項目所有依賴。在 CI 環境中, 如果存在需要更新的 lockfile 會安裝失敗,所以每次版本更新後,本地一定要 install 後再提交,否則會導致版本發佈失敗。

這裏講一下--fix-lockfile--shamefully-hoist

pnpm import

import命令支持從其它格式的 lock 文件生成pnpm-lock.yaml文件,目前支持三種格式源文件

個人認爲這個命令跟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,否則會造成循環執行。

總結

文章卸載這裏基本結束了,簡單的做個總結回顧下內容。

參考資料

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/E5tNAEsykP6shQLFdgjFeg