近幾年的前端開發(fā)者肯定了解npm
是什么占键,而且使用過程中也開始接觸yarn
,pnpm
等等元潘。那么到底為什么會有這么多的包管理工具畔乙,它們好像可以交替使用,但是好像又有點不同翩概。我這邊按照我的理解牲距,整理搜索一下包管理器發(fā)展的歷史。
一钥庇、包管理工具的發(fā)展
2010 年 1 月牍鞠,一款名為 npm 的包管理器誕生。它確立了包管理器工作的核心原則评姨。
npm 的發(fā)布誕生了一場革命难述,在此之前,項目依賴項都是手動下載和管理的吐句。npm 引入了文件和元數(shù)據(jù)字段胁后,將依賴項列表存儲在 package.json 文件中,并且將下載的文件保存到 node_modules 文件夾中嗦枢。
隨后攀芯,又因為npm的缺陷或者舊版本的不足,大牛們造出一個又一個替代npm來進行包管理的輪子文虏,例如:yarn
侣诺,yarn2
,pnpm
等等氧秘。
1.1 NPM
是 Node.js 自帶的包管理工具年鸳,也是最常用的包管理工具之一。它可以方便地安裝丸相、升級阻星、卸載依賴包,還可以發(fā)布自己的包到 NPM 倉庫。
很多人認為 npm 是 node package manager 的縮寫妥箕,但其實不是,官方對此有過辟謠更舞。
npm已經(jīng)迭代過很多版本畦幢,也嘗試解決大家對它缺陷或者不足的吐槽。
1.1.1 npm v1 & v2
此時期主要是采用簡單的遞歸依賴方法缆蝉,最后形成高度嵌套的依賴樹宇葱。這種模式雖然模塊依賴關系比較清晰,但是造成的問題更大刊头。
重復依賴嵌套地獄黍瞧,空間資源浪費:大量重復的包被安裝,文件體積超級大原杂。比如跟 foo 同級目錄下有一個baz印颤,兩者都依賴于同一個版本的lodash,那么 lodash 會分別在兩者的 node_modules 中被安裝穿肄,也就是重復安裝年局。
安裝速度過慢
文件路徑過長:尤其在 window 系統(tǒng)下,路徑過長會導致爆錯咸产,最多260多個字符矢否。
模塊實例不能共享:比如 React 有一些內(nèi)部變量,在兩個不同包引入的 React 不是同一個模塊實例脑溢,因此無法共享內(nèi)部變量僵朗,導致一些不可預知的 bug。
例如: 項目依賴了A@1.0和 B@1.0屑彻,而 A@1.0 和 B@1.0依賴了不同版本的 C@1.0 和 C@2.0验庙,node_modules 結(jié)構如下:
注意:為了方便,后邊版本號都簡單以兩位數(shù)字表示酱酬,不是正確的語義化版本號
node_modules
├── A@1.0
│ └── node_modules
│ └── C@1.0
└── B@1.0
└── node_modules
└── C@2.0
在我們真實使用過程中壶谒,隨著依賴的增多,重復冗余的包會越來越多膳沽,最終汗菜,node_modules會大量的占用磁盤。而且依賴嵌套的深度也會十分可怕挑社,這個就是我們常說的依賴地獄(Dependency Hell)陨界。
1.1.2 npm v3
npm v3 版本作了較大的更新,開始采取扁平化的依賴結(jié)構痛阻。
為了將嵌套的依賴盡量打平菌瘪,避免過深的依賴樹和包冗余,npm v3 將子依賴「提升」(hoist),采用扁平的 node_modules 結(jié)構俏扩,子依賴會盡量平鋪安裝在主依賴項所在的目錄中糜工。
我們繼續(xù)以上面的案例為例:
node_modules
├── A@1.0
├── B@1.0
└── node_modules
└── C@2.0
└── C@1.0
可以看到v3的版本中, A@1.0 的子依賴的 C@1.0 不再放在 A 1.0的 node_modules 下了录淡,而是與 A@1.0 同層級捌木。
而 B@1.0 依賴的 C@2.0 因為版本號原因還是嵌套在 B@1.0 的 node_modules 下。
這樣的依賴結(jié)構可以很好的解決重復依賴的依賴地獄問題嫉戚,層級也不會太深刨裆,但也形成了新的問題。
扁平化依賴算法耗時長:官方倉庫 issue 的解釋:npm@3 wants to be faster · Issue #8826 · npm/npm (github.com)
-
幽靈依賴 Phantom dependencies:在 package.json 中未定義的依賴彬檀,但項目中依然可以正確地被引用到帆啃。
比如上方的示例其實我們項目只安裝了 A@1.0 和 B@1.0,C@1.0其實是A@1.0的依賴:
// package.json dependencies
{
"dependencies": {
"A": "^1.0",
"B": "^1.0"
}
由于 C@1.0 在安裝時被提升到了和 A 1.0同樣的層級窍帝,所以在項目中引用 C@1.0 還是能正常工作的努潘。
幽靈依賴是由依賴的聲明丟失造成的,如果某天某個版本的 A 依賴不再依賴 C@1.0 或者 C@1.0的版本發(fā)生了變化盯桦,那么就會造成依賴缺失或兼容性問題慈俯。
不確定性 Non-Determinism:同樣的 package.json 文件,install 依賴后可能不會得到同樣的 node_modules 目錄結(jié)構拥峦。還是之前的例子贴膘,A@1.0 依賴 C@1.0,B@1.0依賴 C@2.0略号,依賴安裝后究竟應該提升 C 的 1.0 還是 2.0刑峡。取決于用戶的安裝順序。如果有 package.json 變更玄柠,本地需要刪除 node_modules 重新 install突梦,否則可能會導致生產(chǎn)環(huán)境與開發(fā)環(huán)境 node_modules 結(jié)構不同,代碼無法正常運行羽利。
-
依賴分身 Doppelgangers:假設繼續(xù)再安裝依賴 C@1.0 的 D 模塊和依賴 C@2.0 的 E 模塊宫患,此時:
- A 和 D 依賴 C@1.0
- B 和 E 依賴 C@2.0
以下是提升 C@1.0 的 node_modules 結(jié)構:
node_modules
├── A@1.0
├── B@1.0
│ └── node_modules
│ └── C@2.0
├── C@1.0
├── D@1.0
└── E@1.0
└── node_modules
└── C@2.0
可以看到 C@2.0 會被安裝兩次,實際上無論提升 C@1.0 還是 C@2.0这弧,都會存在重復版本的 C 被安裝娃闲,這兩個重復安裝的 C 就叫 `依賴分身 doppelgangers`。這會導致一些隱含的問題:
**1. 破壞單例模式**:假如模塊B匾浪、E中引入了模塊C2.0中導出的一個單例對象皇帮,但其實引用的不是同一個 C2.0,即使代碼里看起來加載的是同一模塊的同一版本蛋辈,但實際解析加載的是不同的module泉孩,引入的也是不同的對象。如果同時對該對象進行緩存或副作用操作蹂析,就會產(chǎn)生問題。
**2. types沖突**:雖然各個package的代碼不會相互污染尊浓,但是他們的types仍然可以相互影響,因此版本重復可能會導致全局的types命名沖突纯衍。
當時npm還沒解決這些問題的時候眠砾,社區(qū)就出來一個新的解決方案了,那就是 yarn
托酸。
1.1.3 npm v5
為了解決上面出現(xiàn)的扁平化依賴算法耗時長問題,npm 引入 package-lock.json
機制柒巫,package-lock.json
的作用是鎖定項目的依賴結(jié)構励堡,保證依賴的穩(wěn)定性。
當項目有package.json文件并首次執(zhí)行npm install安裝后堡掏,會自動生成一個package-lock.json
文件应结,該文件里面記錄了package.json依賴的模塊,以及模塊的子依賴泉唁。并且給每個依賴標明了版本鹅龄、獲取地址和驗證模塊完整性哈希值。通過package-lock.json
亭畜,保障了依賴包安裝的確定性與兼容性扮休,使得每次安裝都會出現(xiàn)相同的結(jié)果。
感興趣的話可以具體看看官方文檔:package.json | npm Docs (npmjs.com)
注:其實在 package-lock.json 機制出現(xiàn)之前拴鸵,可以通過 npm-shrinkwrap 實現(xiàn)鎖定依賴結(jié)構玷坠,但是 npm-shrinkwrap 默認關閉,需要主動執(zhí)行劲藐。
一致性
考慮上文案例八堡,初始時安裝生成package-lock.json如左圖所示,depedencies對象中列出的依賴都是提升的聘芜,每個依賴項中的requires對象中為子依賴項兄渺。此時更新A依賴到2.0版本,如右圖所示汰现,并不會改變提升的子依賴版本挂谍。因此重新生成的node_modules目錄結(jié)構將不會發(fā)生變化。
兼容性
語義化版本(Semantic Versioning)
依賴版本兼容性就不得不提到npm使用的SemVer版本規(guī)范服鹅,版本格式如下:
- 主版本號:不兼容的 API 修改
- 次版本號:向下兼容的功能性新增
- 修訂號:向下兼容的問題修正
在使用第三方依賴時凳兵,我們通常會在package.json中指定依賴的版本范圍,語義化版本范圍規(guī)定:
- ~:只升級修訂號
- ^:升級次版本號和修訂號
- *:升級到最新版本
語義化版本規(guī)則定義了一種理想的版本號更新規(guī)則企软,希望所有的依賴更新都能遵循這個規(guī)則庐扫,但是往往會有許多依賴不是嚴格遵循這些規(guī)定的。因此一些依賴模塊子依賴不經(jīng)意的升級,可能就會導致不兼容的問題產(chǎn)生形庭。因此package-lock.json給每個模塊子依賴標明了確定的版本铅辞,避免不兼容問題的產(chǎn)生。
1.2 Yarn
1.2.1 yarn
2016 年萨醒,yarn 發(fā)布0.x版本斟珊,隨后迭代正式版本1.x,yarn 也采用扁平化 node_modules 結(jié)構富纸。它的出現(xiàn)是為了解決 npm v3 幾個最為迫在眉睫的問題:依賴安裝速度慢囤踩,不確定性
yarn的一些特性是走在npm的前邊的。 yarn 出現(xiàn)時晓褪,此時 npm 處于 v3 時期堵漱,其實當時 yarn 解決的問題基本就是 npm v5 解決的問題,包括使用 yarn.lock 等機制涣仿,鎖定版本依賴勤庐,實現(xiàn)并發(fā)網(wǎng)絡請求,最大化網(wǎng)絡資源利用率好港,其次還有利用緩存機制愉镰,實現(xiàn)了離線模式
與npm v5之后推出的package-lock.json不同,yarn并沒有采用JSON格式的文件钧汹,而是使用了自定義的格式丈探,名字就叫做yarn.lock,與前者不同崭孤,后者的lockfile目錄結(jié)構并不能復制出完完全全一樣的node_modules拓撲結(jié)構类嗤,他只是把依賴到的所有庫 flat 成根目錄級別,這樣更方便做diff辨宠。
安裝速度
- 并行:在 npm 中安裝依賴時遗锣,安裝任務是串行的,會按包順序逐個執(zhí)行安裝嗤形,這意味著它會等待一個包完全安裝精偿,然后再繼續(xù)下一個。為了加快包安裝速度赋兵,yarn 采用了并行操作笔咽,在性能上有顯著的提高。
- 離線緩存:像npm一樣霹期,yarn使用本地緩存叶组。與npm不一樣的是,yarn的緩存機制是將每個包緩存在磁盤上历造,在下一次安裝這個包時甩十,無需互聯(lián)網(wǎng)鏈接就能安裝本地緩存的依賴項船庇,它提供了離線模式。這個功能在2012年的npm項目中就被提出來過侣监,但一直沒有實現(xiàn)鸭轮。
lockfile
yarn 更大的貢獻是發(fā)明了 yarn.lock。
在依賴安裝時橄霉,會根據(jù) package.josn 生成一份 yarn.lock 文件窃爷。
lockfile 里記錄了依賴,以及依賴的子依賴姓蜂,依賴的版本按厘,獲取地址與驗證模塊完整性的 hash。
即使是不同的安裝順序钱慢,相同的依賴關系在任何的環(huán)境和容器中刻剥,都能得到穩(wěn)定的 node_modules 目錄結(jié)構,保證了依賴安裝的確定性滩字。
所以 yarn 在出現(xiàn)時被定義為快速、安全御吞、可靠的依賴管理麦箍。而 npm 在一年后的 v5 才發(fā)布了 package-lock.json。
其實后面npm v5上能看到 yarn 的機制的影子陶珠,上面的機制目前 npm 基本也都實現(xiàn)了挟裂,就目前而言 npm 和 yarn 其實并沒有差異很大,具體使用 npm 還是 yarn 可以看個人需求揍诽。
弊端
yarn 依然和 npm 一樣是扁平化的 node_modules 結(jié)構诀蓉,并沒有解決幽靈依賴和依賴分身問題。
1.2.2 yarn v2(yarn berry)
在 pnpm 之后暑脆, yarn 感受到了對手的挑戰(zhàn)渠啤,于是在 2020 年, yarn 2誕生了
yarn 2(也叫yarn berry添吗,yarn 1 也叫 yarn classic)沥曹。它是對 yarn 的一次重大升級,其中一項重要更新就是 Plug’n’Play(Plug'n'Play = Plug and Play = PnP碟联,即插即用)妓美。
盡管yarn1看似并沒有對node_modules作出太大改動,但是他們的團隊并不是沒有意識到node_modules的缺憾鲤孵,他們做出了Plug’n’Play的嘗試。npm 與 yarn 的依賴安裝與依賴解析都涉及大量的文件 I/O普监,效率不高贵试。開發(fā) Plug’n’Play 最直接的原因就是依賴引用慢琉兜,依賴安裝慢。
首先node_modules本身的局限性在于解析锡移、安裝依賴時產(chǎn)生的大量IO操作:
- 解析:當require某個第三方文件時呕童,首先在當前目錄尋找node_modules,找不到再去父級淆珊,找到之后夺饲,再去這個node_modules的子目錄去尋找,直到找到該文件施符。因為node不認識包往声,只認識文件,而node_moduls的設計也就注定了他不允許包管理工具正確的刪除重復的包數(shù)據(jù)戳吝。
- 安裝:解析出某個具體的版本號浩销,下載 tar 包到離線鏡像,從鏡像解壓到本地緩存听哭;從緩存拷貝到node_modules慢洋,即使是pnpm的hard link,也只是優(yōu)化了最后一步陆盘。
因此普筹,berry做出了修改,與其讓node去查找軟件包隘马,不如直接簡明扼要的告訴node應該在哪里找到這個包太防。Plug’n’Play特性應運而生,他其實是省略了node_modules的拷貝酸员,轉(zhuǎn)而生成了一個.pnp.js的文件去記錄包的版本蜒车,以及映射到的磁盤位置,即把每個包看作整體幔嗦,壓縮成一個zip酿愧;一個.yarn文件夾,里面又有cache和unplugged目錄邀泉,前者存放壓縮過的依賴包寓娩,后者可以通過unplugin指令解壓某個想要手動修改的包。
berry一定程度解決了一些問題:
- 之前介紹的npm存在的兩個問題呼渣,berry因為不會生成node_modules目錄棘伴,因此不存在phantom dependency的問題,同時他采用的.pnp.js的靜態(tài)映射而不是copy的方式也避免了重復安裝依賴的問題屁置。
- 基于 .pnp.js 和 zip loading 實現(xiàn)的零安裝焊夸,即將.pnp.js及.yarn文件夾全部上傳至gitlab,在有些情況下是可行的蓝角,但是這里使用 create-react-app 進行實測阱穗,yarn 為144Mb饭冬,berry為62Mb,只是正常的壓縮體積的優(yōu)化揪阶;隨后拿React昌抠,Vue等包做了下實驗,也基本都是這個比例(7.9Mb VS 5.1Mb)(17Mb VS 8.5Mb)鲁僚。
- 最后一點說一下一些新的特性炊苫,如插件機制,方便我們在對berry的核心代碼并不熟悉的情況下開發(fā)基于berry的擴展功能冰沙,官方實現(xiàn)的官方實現(xiàn)的typescript插件侨艾,在yarn add 時自動添加@types等。
當然也存在一些問題拓挥,最明顯的就是首次安裝依賴的時間并沒有感覺到縮短唠梨,其次還有上面所說的.yarn/cache到底要不要放到遠程倉庫中也是有待商榷的事。
berry的改變
- 拋棄 node_modules
無論是 npm 還是 yarn侥啤,都具備緩存的功能当叭,大多數(shù)情況下安裝依賴時,其實是將緩存中的相關包復制到項目目錄中 node_modules 里盖灸。而 yarn PnP 則不會進行拷貝這一步科展,而是在項目里維護一張靜態(tài)映射表 pnp.cjs。
pnp.cjs 會記錄依賴在緩存中的具體位置糠雨,所有依賴都存在全局緩存中。同時自建了一個解析器徘跪,在依賴引用時甘邀,幫助 node 從全局緩存目錄中發(fā)現(xiàn)依賴,而不是查找 node_modules垮庐。
這樣就避免了大量的 I/O 操作同時項目目錄也不會有 node_modules 目錄生成松邪,同版本的依賴在全局也只會有一份,依賴的安裝速度和解析速度都有較大提升哨查。
注:pnpm 在 2020 年底的 v5.9 也支持了 PnP
- 脫離 node 生態(tài)
pnp 比較明顯的缺點是脫離了 node 生態(tài)逗抑。
- 因為使用 PnP 不會再有 node_modules 了,但是 Webpack寒亥,Babel 等各種前端工具都依賴 node_modules邮府。雖然很多工具比如 pnp-webpack-plugin 已經(jīng)在解決了,但難免會有兼容性風險溉奕。
- PnP 自建了依賴解析器褂傀,所有的依賴引用都必須由解析器執(zhí)行,因此只能通過 yarn 命令來執(zhí)行 node 腳本加勤。
1.3 pnpm
pnpm - performant npm仙辟,在 2017 年正式發(fā)布同波,定義為快速的,節(jié)省磁盤空間的包管理工具叠国,開創(chuàng)了一套新的依賴管理機制未檩,成為了包管理的后起之秀。
與依賴提升和扁平化的 node_modules 不同粟焊,pnpm 引入了另一套依賴管理策略:內(nèi)容尋址存儲冤狡。該策略會將包安裝在系統(tǒng)的全局 store 中,依賴的每個版本只會在系統(tǒng)中安裝一次吆玖。
內(nèi)容尋址存儲
pnpm 內(nèi)部使用基于內(nèi)容尋址的文件系統(tǒng)來存儲磁盤上所有的文件筒溃,這樣可以做到不會出現(xiàn)重復安裝,在項目中需要使用到依賴的時候沾乘,pnpm 只會安裝一次怜奖,之后再次使用都會直接硬鏈接指向該依賴,極大節(jié)省磁盤空間翅阵,并且加快安裝速度歪玲。
注:硬鏈接是多個文件名指向同一個文件的實際內(nèi)容,而軟鏈接(符號鏈接)是一個獨立的文件掷匠,指向另一個文件或目錄的路徑
在引用項目 node_modules 的依賴時滥崩,會通過硬鏈接與符號鏈接在全局 store 中找到這個文件。為了實現(xiàn)此過程讹语,node_modules 下會多出 .pnpm
目錄钙皮,而且是非扁平化結(jié)構。
- 硬鏈接 Hard link:硬鏈接可以理解為源文件的副本顽决,項目里安裝的其實是副本短条,它使得用戶可以通過路徑引用查找到全局 store 中的源文件,而且這個副本根本不占任何空間才菠。同時茸时,pnpm 會在全局 store 里存儲硬鏈接,不同的項目可以從全局 store 尋找到同一個依賴赋访,大大地節(jié)省了磁盤空間可都。
- 符號鏈接 Symbolic link:也叫軟連接,可以理解為快捷方式蚓耽,pnpm 可以通過它找到對應磁盤目錄下的依賴地址渠牲。
還是使用上面 A,B步悠,C 模塊的示例嘱兼,使用 pnpm 安裝依賴后 node_modules 結(jié)構如下:
node_modules
├── .pnpm
│ ├── A@1.0
│ │ └── node_modules
│ │ ├── A => <store>/A@1.0
│ │ └── B => ../../B@1.0
│ ├── B@1.0
│ │ └── node_modules
│ │ └── B => <store>/B@1.0
│ ├── B@2.0
│ │ └── node_modules
│ │ └── B => <store>/B@2.0
│ └── C@1.0
│ └── node_modules
│ ├── C => <store>/C@1.0
│ └── B => ../../B@2.0
│
├── A => .pnpm/A@1.0.0/node_modules/A
└── C => .pnpm/C@1.0.0/node_modules/C
<store>/xxx
開頭的路徑是硬鏈接,指向全局 store 中安裝的依賴贤徒。
其余的是符號鏈接芹壕,指向依賴的快捷方式汇四。
未來可期
這套全新的機制設計地十分巧妙,不僅兼容 node 的依賴解析踢涌,同時也解決了:
- 幽靈依賴問題:只有直接依賴會平鋪在 node_modules 下通孽,子依賴不會被提升,不會產(chǎn)生幽靈依賴睁壁。
- 依賴分身問題:相同的依賴只會在全局 store 中安裝一次背苦。項目中的都是源文件的副本,幾乎不占用任何空間潘明,沒有了依賴分身行剂。
同時,由于鏈接的優(yōu)勢钳降,pnpm 的安裝速度在大多數(shù)場景都比 npm 和 yarn 快 2 倍厚宰,節(jié)省的磁盤空間也更多。
但是遂填,其實這種模式也存在一些弊端:
- 由于 pnpm 創(chuàng)建的 node_modules 依賴軟鏈接铲觉,因此在不支持軟鏈接的環(huán)境中,無法使用 pnpm吓坚,比如 Electron 應用撵幽。
- 因為依賴源文件是安裝在 store 中,調(diào)試依賴或 patch-package 給依賴打補丁也不太方便礁击,可能會影響其他項目盐杂。
擴展
也許有人說 yarn 默認也是扁平化安裝方式,但是 yarn 有獨特的 PnP 安裝方式哆窿,可以直接去掉 node_modules链烈,將依賴包內(nèi)容寫在磁盤,節(jié)省了 node 文件 I/O 的開銷更耻,這樣也能提升安裝速度,但是 yarn PnP 和 pnpm 機制是不同的捏膨,且總體來說安裝速度 pnpm 是要快于 yarn PnP 的秧均,詳情請看下面官方文檔。
最后就是 pnpm 是默認支持 monorepo 多項目管理的号涯,在日漸復雜的前端多項目開發(fā)中尤其適用目胡,也就說我們不再需要 lerna 來管理多包項目,可以使用 pnpm + Turborepo 作為我們的項目管理環(huán)境
配置工作空間官方文檔:工作空間(Workspace) | pnpm
還有就是 pnpm 還能管理 nodejs 版本链快,可以直接替代 nvm誉己,命令如下所示
# 安裝 LTS 版本
pnpm env use --global lts
# 安裝指定版本
pnpm env use --global 16
1.4 總結(jié)
pnpm 起初看起來像 npm,因為它們的 CLI 用法相似域蜗,但管理依賴項卻大不相同巨双;pnpm 的方法帶來更好的性能和最佳的磁盤空間效率噪猾。Yarn Classic 仍然很受歡迎,但它被認為是遺留軟件筑累,并且在不久的將來可能會放棄支持袱蜡。Yarn Berry PnP 是新貴,但尚未看到它徹底改變包管理器領域的潛力慢宗。
目前還沒有完美的依賴管理方案坪蚁,可以看到在依賴管理的發(fā)展過程中,出現(xiàn)了:
- 不同的 node_modules 結(jié)構镜沽,有嵌套敏晤,扁平,甚至沒有 node_modules缅茉,不同的結(jié)構也伴隨著兼容與安全問題嘴脾。
- 不同的依賴存儲方式來節(jié)約磁盤空間,提升安裝速度宾舅。
- 每種管理器都伴隨新的工具和命令统阿,不同程度的可配置性和擴展性,影響開發(fā)者體驗筹我。
- 這些包管理器也對 monorepo 有不同程度的支持扶平,會直接影響項目的可維護性和速度。
庫與開發(fā)者能夠在這樣優(yōu)化與創(chuàng)新的發(fā)展過程中互相學習蔬蕊,站在巨人的肩膀上繼續(xù)前進结澄,不斷推動前端工程領域的發(fā)展。
多年來岸夯,許多用戶詢問誰使用哪些包管理器麻献,總體而言,人們似乎對 Yarn Berry PnP 的成熟度和采用特別感興趣猜扮。但是國內(nèi)我們能看到勉吻,pnpm似乎更受歡迎。
二旅赢、時間線梳理
請注意齿桃,以上只是列舉了一些比較重要或者具有改革意義的主要版本,每個包管理器的發(fā)布策略可能會因?qū)嶋H情況而有所不同煮盼。此外短纵,還有其它版本以及每個主要版本下可能還有許多次要版本和修訂版本。
我試圖嚴格的按發(fā)布順序來完整展示幾大包管理工具的歷史僵控,但是失敗了香到,因為每個管理器對于包的版本定義以及小版本迭代,還有對發(fā)布測試版本還是正式版本為準定義不同,信息比較混亂悠就,放棄了千绪,也沒太大意義,因為上面列舉的是我們了解比較代表性的版本理卑。下面十一張網(wǎng)圖:
下面是chatGPT給的一種可能的排序方式翘紊,但是它也提示可能會因?qū)嶋H發(fā)布策略而有所不同,如果您需要確切的版本發(fā)布日期藐唠,請參閱官方文檔帆疟、存儲庫或相應的發(fā)布歷史記錄,以獲取最準確和最新的信息宇立。
幾大包管理工具更多版本大體的發(fā)布順序如下:
- npm 1.x(2010年)
- Yarn 0.x(2016年)
- pnpm 1.x(2016年)
- npm 2.x(2014年)
- npm 3.x(2015年)
- Yarn 1.x(2017年)
- npm 4.x(2016年)
- npm 5.x(2017年)
- pnpm 2.x(2018年)
- npm 6.x(2018年)
- Yarn 2.x(2020年)
- npm 7.x(2020年)
- pnpm 3.x(2020年)
- ...
我們其實可以看到版本已經(jīng)迭代了很多踪宠,但是以上列舉的是比較能代表包管理工具從誕生,到改進妈嘹,互相學習又改革的大體流程柳琢。
三、pnpm遷移
遷移過程中主要有如下問題:因為使用 npm 或 yarn 安裝依賴項時润脸,所有包都被提升到模塊目錄的根目錄柬脸。因此,源代碼可以訪問未作為依賴項添加到項目的依賴項毙驯。但是默認情況下倒堕,pnpm 使用鏈接僅將項目的直接依賴項添加到模塊目錄的根目錄中
這意味著如果 package.json 沒有引用的依賴,那么它將無法解析爆价。這是遷移中的最大障礙垦巴。可以使用 auto-install-peers設置自動執(zhí)行此操作(默認情況下是false)
對于多個使用 npm 安裝依賴的項目铭段,單獨刪除依賴包很耗時間骤宣,我們可以使用 npkill ,該工具可以列出系統(tǒng)中的任何 node_modules 目錄以及它們占用的空間序愚。然后可以選擇要刪除的依賴以釋放空間
遷移流程
首先全局安裝包
npm i -g pnpm
遷移步驟如下
1.首先使用 npkill 刪除 node_modules 依賴包
2.項目根目錄創(chuàng)建 .npmrc
憔披,填寫如下內(nèi)容
auto-install-peers=true
3.導入依賴鎖定文件(pnpm-lock.yaml)
保證根目錄有如下依賴鎖定文件(npm-shrinkwrap.json,package-lock.json爸吮,yarn.lock)
然后執(zhí)行如下命令
pnpm import pnpm-lock.yaml
4芬膝,最后執(zhí)行 pnpm i
安裝依賴
問題
生成依賴文件警告
官方 issue 解釋: Unmet peer dependencies and The command -- pnpm/pnpm (github.com)
生成 pnpm-lock.yaml
文件時出現(xiàn)如下警告
?WARN? Issues with peer dependencies found
.
└─┬ vuepress 1.9.9
└─┬ @vuepress/core 1.9.9
└─┬ vue-loader 15.10.1
└─┬ @vue/component-compiler-utils 3.3.0
└─┬ consolidate 0.15.1
├── ? unmet peer react-dom@^16.13.1: found 15.7.0
└── ? unmet peer react@^16.13.1: found 15.7.0
這是因為在 npm 3 中,不會再強制安裝 peerDependencies
(對等依賴)中所指定的包拗胜,而是通過警告的方式來提示我們蔗候。pnpm 會在全局緩存已經(jīng)下載過的依賴包怒允,如果全局緩存的依賴版本與項目 package.json
中指定的版本不一致埂软,就會出現(xiàn)這種 hint
警告
我們可以在項目的 package.json
中配置 peerDependencyRules
忽略對應的警告提示
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"react"
]
}
}
}
或者說直接在 .npmrc
配置文件中直接關閉嚴格的對等依賴模式,可以添加 strict-peer-dependencies=false
到配置文件中,或者執(zhí)行如下命令
npm config set strict-peer-dependencies=false
然后也可能會出現(xiàn)警告 deprecated subdependencies found
勘畔,暫時可以忽略
幽靈依賴問題
在最后安裝依賴的時候可能會出現(xiàn)幽靈依賴
問題所灸,幽靈依賴就是沒有在package.json
中,但是項目中炫七,或者引用的包中使用到的依賴
舉個例子爬立,比如我們現(xiàn)在使用 npm 安裝了 v-viewer 依賴,同時 viewerjs
是 v-viewer
的依賴項万哪,由于扁平化依賴機制侠驯,我們可以在 node_modules/v-viewer/package.json
中看到聲明的 viewerjs
依賴,即使項目根目錄下的 package.json
沒有聲明 viewerjs
依賴奕巍,我們?nèi)耘f可以使用吟策,這就是幽靈依賴
而現(xiàn)在我們切換為 pnpm
后,在默認情況下不允許訪問未聲明的依賴的止,有以下兩種解決方案
1.自行安裝未聲明依賴項
幽靈依賴自動掃描工具:@sugarat/ghost - npm (npmjs.com)
pnpm i -S viewerjs
或者說某些版本 pnpm 會自動爆出幽靈依賴錯誤 missing peer ...
檩坚,也可以直接不使用上面的掃描工具,直接自行安裝后面的 ...
依賴
2.找到.npmrc
文件诅福,在其中配置 public-hoist-pattern
或者 shamefully-hoist
字段匾委,將依賴提升到根node_modules
目錄下解決,也就是所謂的依賴提升
依賴提升官方文檔:.npmrc | pnpm
# .npmrc
# 提升含有 eslint(模糊匹配)氓润、prettier(模糊匹配)赂乐、viewerjs(精確匹配) 的依賴包到根 node_modules 目錄下
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=viewerjs
# 提升所有依賴到根 node_modules 目錄下,相當于 public-hoist-pattern[]=*旺芽,與上面一種方式一般二選一使用
shamefully-hoist=true
當然沪猴,極不推薦用這樣的方式解決依賴問題,這樣沒有充分利用 pnpm
依賴訪問安全性的優(yōu)勢采章,又走回了 npm
/ yarn
的老路运嗜。
四、參考文章
pnpm悯舟、npm担租、yarn 包管理工具『優(yōu)劣對比』及『環(huán)境遷移』 - 知乎 (zhihu.com)