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