您好,登錄后才能下訂單哦!
本篇內容介紹了“pnpm與npm/yarn的區別有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
下面是本文的思維導圖:
一、什么是 pnpm ?
pnpm 的官方文檔是這樣說的:
Fast, disk space efficient package manager
因此,pnpm 本質上就是一個包管理器,這一點跟 npm/yarn 沒有區別,但它作為殺手锏的兩個優勢在于:
包安裝速度極快;
磁盤空間利用非常高效。
它的安裝也非常簡單。可以有多簡單?
npm i -g pnpm
二、特性概覽
1. 速度快
pnpm 安裝包的速度究竟有多快?先以 React 包為例來對比一下:
可以看到,作為黃色部分的 pnpm,在絕多大數場景下,包安裝的速度都是明顯優于 npm/yarn,速度會比 npm/yarn 快 2-3 倍。
對 yarn 比較熟悉的同學可能會說,yarn 不是有 PnP 安裝模式嗎?直接去掉 node_modules,將依賴包內容寫在磁盤,節省了 node 文件 I/O 的開銷,這樣也能提升安裝速度。
接下來,我們以這樣一個倉庫為例,我們來看一看 benchmark 數據,主要對比一下 pnpm 和 yarn PnP:
從中可以看到,總體而言,pnpm 的包安裝速度還是明顯優于 yarn PnP的。
2. 高效利用磁盤空間
pnpm 內部使用基于內容尋址的文件系統來存儲磁盤上所有的文件,這個文件系統出色的地方在于:
不會重復安裝同一個包。用 npm/yarn 的時候,如果 100 個項目都依賴 lodash,那么 lodash 很可能就被安裝了 100 次,磁盤中就有 100 個地方寫入了這部分代碼。但在使用 pnpm 只會安裝一次,磁盤中只有一個地方寫入,后面再次使用都會直接使用 hardlink(硬鏈接,不清楚的同學詳見這篇文章(https://www.cnblogs.com/itech/archive/2009/04/10/1433052.html))。
即使一個包的不同版本,pnpm 也會極大程度地復用之前版本的代碼。舉個例子,比如 lodash 有 100 個文件,更新版本之后多了一個文件,那么磁盤當中并不會重新寫入 101 個文件,而是保留原來的 100 個文件的 hardlink,僅僅寫入那一個新增的文件。
3. 支持 monorepo
隨著前端工程的日益復雜,越來越多的項目開始使用 monorepo。之前對于多個項目的管理,我們一般都是使用多個 git 倉庫,但 monorepo 的宗旨就是用一個 git 倉庫來管理多個子項目,所有的子項目都存放在根目錄的packages目錄下,那么一個子項目就代表一個package。如果你之前沒接觸過 monorepo 的概念,建議仔細看看這篇文章以及開源的 monorepo 管理工具lerna,項目目錄結構可以參考一下 babel 倉庫。
pnpm 與 npm/yarn 另外一個很大的不同就是支持了 monorepo,體現在各個子命令的功能上,比如在根目錄下 pnpm add A -r, 那么所有的 package 中都會被添加 A 這個依賴,當然也支持 --filter字段來對 package 進行過濾。
4. 安全性高
之前在使用 npm/yarn 的時候,由于 node_module 的扁平結構,如果 A 依賴 B, B 依賴 C,那么 A 當中是可以直接使用 C 的,但問題是 A 當中并沒有聲明 C 這個依賴。因此會出現這種非法訪問的情況。但 pnpm 腦洞特別大,自創了一套依賴管理方式,很好地解決了這個問題,保證了安全性,具體怎么體現安全、規避非法訪問依賴的風險的,后面再來詳細說說。
三、依賴管理
npm/yarn install 原理
主要分為兩個部分, 首先,執行 npm/yarn install之后,包如何到達項目 node_modules 當中。其次,node_modules 內部如何管理依賴。
執行命令后,首先會構建依賴樹,然后針對每個節點下的包,會經歷下面四個步驟:
- 1. 將依賴包的版本區間解析為某個具體的版本號
- 2. 下載對應版本依賴的 tar 包到本地離線鏡像
- 3. 將依賴從離線鏡像解壓到本地緩存
- 4. 將依賴從緩存拷貝到當前目錄的 node_modules 目錄
然后,對應的包就會到達項目的node_modules當中。
那么,這些依賴在node_modules內部是什么樣的目錄結構呢,換句話說,項目的依賴樹是什么樣的呢?
在 npm1、npm2 中呈現出的是嵌套結構,比如下面這樣:
node_modules └─ foo ├─ index.js ├─ package.json └─ node_modules └─ bar ├─ index.js └─ package.json
如果 bar 當中又有依賴,那么又會繼續嵌套下去。試想一下這樣的設計存在什么問題:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
依賴層級太深,會導致文件路徑過長的問題,尤其在 window 系統下。
大量重復的包被安裝,文件體積超級大。比如跟 foo 同級目錄下有一個baz,兩者都依賴于同一個版本的lodash,那么 lodash 會分別在兩者的 node_modules 中被安裝,也就是重復安裝。
模塊實例不能共享。比如 React 有一些內部變量,在兩個不同包引入的 React 不是同一個模塊實例,因此無法共享內部變量,導致一些不可預知的 bug。
接著,從 npm3 開始,包括 yarn,都著手來通過扁平化依賴的方式來解決這個問題。相信大家都有這樣的體驗,我明明就裝個 express,為什么 node_modules里面多了這么多東西?
沒錯,這就是扁平化依賴管理的結果。相比之前的嵌套結構,現在的目錄結構類似下面這樣:
node_modules ├─ foo | ├─ index.js | └─ package.json └─ bar ├─ index.js └─ package.json
所有的依賴都被拍平到node_modules目錄下,不再有很深層次的嵌套關系。這樣在安裝新的包時,根據 node require 機制,會不停往上級的node_modules當中去找,如果找到相同版本的包就不會重新安裝,解決了大量包重復安裝的問題,而且依賴層級也不會太深。
之前的問題是解決了,但仔細想想這種扁平化的處理方式,它真的就是無懈可擊嗎?并不是。它照樣存在諸多問題,梳理一下:
依賴結構的不確定性。
扁平化算法本身的復雜性很高,耗時較長。
項目中仍然可以非法訪問沒有聲明過依賴的包
后面兩個都好理解,那第一點中的不確定性是什么意思?這里來詳細解釋一下。
假如現在項目依賴兩個包 foo 和 bar,這兩個包的依賴又是這樣的:
那么 npm/yarn install 的時候,通過扁平化處理之后,究竟是這樣
還是這樣?
答案是: 都有可能。取決于 foo 和 bar 在 package.json中的位置,如果 foo 聲明在前面,那么就是前面的結構,否則是后面的結構。
這就是為什么會產生依賴結構的不確定問題,也是 lock 文件誕生的原因,無論是package-lock.json(npm 5.x才出現)還是yarn.lock,都是為了保證 install 之后都產生確定的node_modules結構。
盡管如此,npm/yarn 本身還是存在扁平化算法復雜和package 非法訪問的問題,影響性能和安全。
pnpm 依賴管理
pnpm 的作者Zoltan Kochan發現 yarn 并沒有打算去解決上述的這些問題,于是另起爐灶,寫了全新的包管理器,開創了一套新的依賴管理機制,現在就讓我們去一探究竟。
還是以安裝 express 為例,我們新建一個目錄,執行:
pnpm init -y
然后執行:
pnpm install express
我們再去看看node_modules:
.pnpm .modules.yaml express
我們直接就看到了express,但值得注意的是,這里僅僅只是一個軟鏈接,不信你打開看看,里面并沒有 node_modules 目錄,如果是真正的文件位置,那么根據 node 的包加載機制,它是找不到依賴的。那么它真正的位置在哪呢?
我們繼續在 .pnpm 當中尋找:
? node_modules ? .pnpm ? accepts@1.3.7 ? array-flatten@1.1.1 ... ? express@4.17.1 ? node_modules ? accepts ? array-flatten ? body-parser ? content-disposition ... ? etag ? express ? lib History.md index.js LICENSE package.json Readme.md
好家伙!竟然在 .pnpm/express@4.17.1/node_modules/express下面找到了!
隨便打開一個別的包:
好像也都是一樣的規律,都是
再看看.pnpm,.pnpm目錄下雖然呈現的是扁平的目錄結構,但仔細想想,順著軟鏈接慢慢展開,其實就是嵌套的結構!
? node_modules ? .pnpm ? accepts@1.3.7 ? array-flatten@1.1.1 ... ? express@4.17.1 ? node_modules ? accepts -> ../accepts@1.3.7/node_modules/accepts ? array-flatten -> ../array-flatten@1.1.1/node_modules/array-flatten ... ? express ? lib History.md index.js LICENSE package.json Readme.md
將包本身和依賴放在同一個node_module下面,與原生 Node 完全兼容,又能將 package 與相關的依賴很好地組織到一起,設計十分精妙。
現在我們回過頭來看,根目錄下的 node_modules 下面不再是眼花繚亂的依賴,而是跟 package.json 聲明的依賴基本保持一致。即使 pnpm 內部會有一些包會設置依賴提升,會被提升到根目錄 node_modules 當中,但整體上,根目錄的node_modules比以前還是清晰和規范了許多。
四、再談安全
不知道你發現沒有,pnpm 這種依賴管理的方式也很巧妙地規避了非法訪問依賴的問題,也就是只要一個包未在 package.json 中聲明依賴,那么在項目中是無法訪問的。
但在 npm/yarn 當中是做不到的,那你可能會問了,如果 A 依賴 B, B 依賴 C,那么 A 就算沒有聲明 C 的依賴,由于有依賴提升的存在,C 被裝到了 A 的node_modules里面,那我在 A 里面用 C,跑起來沒有問題呀,我上線了之后,也能正常運行啊。不是挺安全的嗎?
還真不是。
第一,你要知道 B 的版本是可能隨時變化的,假如之前依賴的是C@1.0.1,現在發了新版,新版本的 B 依賴 C@2.0.1,那么在項目 A 當中 npm/yarn install 之后,裝上的是 2.0.1 版本的 C,而 A 當中用的還是 C 當中舊版的 API,可能就直接報錯了。
第二,如果 B 更新之后,可能不需要 C 了,那么安裝依賴的時候,C 都不會裝到node_modules里面,A 當中引用 C 的代碼直接報錯。
還有一種情況,在 monorepo 項目中,如果 A 依賴 X,B 依賴 X,還有一個 C,它不依賴 X,但它代碼里面用到了 X。由于依賴提升的存在,npm/yarn 會把 X 放到根目錄的 node_modules 中,這樣 C 在本地是能夠跑起來的,因為根據 node 的包加載機制,它能夠加載到 monorepo 項目根目錄下的 node_modules 中的 X。但試想一下,一旦 C 單獨發包出去,用戶單獨安裝 C,那么就找不到 X 了,執行到引用 X 的代碼時就直接報錯了。
這些,都是依賴提升潛在的 bug。如果是自己的業務代碼還好,試想一下如果是給很多開發者用的工具包,那危害就非常嚴重了。
npm 也有想過去解決這個問題,指定--global-style參數即可禁止變量提升,但這樣做相當于回到了當年嵌套依賴的時代,一夜回到解放前,前面提到的嵌套依賴的缺點仍然暴露無遺。
npm/yarn 本身去解決依賴提升的問題貌似很難完成,不過社區針對這個問題也已經有特定的解決方案: dependency-check,地址: https://github.com/dependency-check-team/dependency-check
但不可否認的是,pnpm 做的更加徹底,獨創的一套依賴管理方式不僅解決了依賴提升的安全問題,還大大優化了時間和空間上的性能。
五、日常使用
說了這么多,估計你會覺得 pnpm 挺復雜的,是不是用起來成本很高呢?
恰好相反,pnpm 使用起來十分簡單,如果你之前有 npm/yarn 的使用經驗,甚至可以無縫遷移到 pnpm 上來。不信我們來舉幾個日常使用的例子。
pnpm install
跟 npm install 類似,安裝項目下所有的依賴。但對于 monorepo 項目,會安裝 workspace 下面所有 packages 的所有依賴。不過可以通過 --filter 參數來指定 package,只對滿足條件的 package 進行依賴安裝。
當然,也可以這樣使用,來進行單個包的安裝:
// 安裝 axios pnpm install axios // 安裝 axios 并將 axios 添加至 devDependencies pnpm install axios -D // 安裝 axios 并將 axios 添加至 dependencies pnpm install axios -S
當然,也可以通過 --filter 來指定 package。
pnpm update
根據指定的范圍將包更新到最新版本,monorepo 項目中可以通過 --filter 來指定 package。
pnpm uninstall
在 node_modules 和 package.json 中移除指定的依賴。monorepo 項目同上。舉例如下:
// 移除 axios pnpm uninstall axios --filter package-a
pnpm link
將本地項目連接到另一個項目。注意,使用的是硬鏈接,而不是軟鏈接。如:
pnpm link ../../axios
另外,對于我們經常用到npm run/start/test/publish,這些直接換成 pnpm 也是一樣的,不再贅述。
可以看到,雖然 pnpm 內部做了非常多復雜的設計,但實際上對于用戶來說是無感知的,使用起來非常友好。并且,現在作者現在還一直在維護,目前 npm 上周下載量已經有 10w +,經歷了大規模用戶的考驗,穩定性也能有所保障。
因此,綜合來看,pnpm 是一個相比 npm/yarn 更優的方案,期待未來 pnpm 能有更多的落地。
“pnpm與npm/yarn的區別有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。