接觸 node 之后,一直使用npm包管理工具, cnpm 一開始會用一些,但是並沒有覺得比 npm 快得多,使用 cnpm 的時候還經常安裝不成功,只能再用 npm 安裝一遍,漸漸的就棄用了 cnpm 。
最近在看《MongoDB高級技術棧全覆蓋前端 Vue+Node+MongoDB高級全棧開發》課程,才知道還有其他包管理工具,借此機會,好好的整理一下各個包管理工具之間的區別和聯系。
NPM
npm 是 Node.js 能夠如此成功的主要原因之一。npm 團隊做了很多的工作,以確保 npm 保持向后兼容,並在不同的環境中保持一致。
npm是圍繞着 語義版本控制(semver)的思想而設計。
給定一個版本號:主版本號.次版本號.補丁版本號, 以下這三種情況需要增加相應的版本號:
- 主版本號: 當API發生改變,並與之前的版本不兼容的時候
- 次版本號: 當增加了功能,但是向后兼容的時候
- 補丁版本號:當做了向后兼容的缺陷修復的時候
npm使用一個名為package.json
的文件,用戶可以通過npm install --save
命令把項目里所有的依賴項保存在這個文件里。
例如,運行npm install --save lodash
會將以下幾行添加到package.json
文件中。
-
"dependencies": {
-
"lodash": "^4.17.4"
-
}
解析,
^
字符,告訴npm,安裝主版本等於4的任意一個版本即可- 現在運行npm進行安裝,npm將安裝lodash的主版本為4的最新版,可能是 lodash@4.25.5(@是npm約定用來確定包名的指定版本的)
- 理論上,次版本號的變化並不會影響向后兼容性。因此,安裝最新版的依賴庫應該是能正常工作的,而且能引入自4.17.4版本以后的重要錯誤和安全方面的修復。
- 但是,即使不同的開發人員使用了相同的
package.json
文件,在他們自己的機器上也可能會安裝同一個庫的不同種版本,這樣就會存在潛在的難以調試的錯誤和“在我的電腦上…”的情形。
大多數npm庫都嚴重依賴於其他npm庫,這會導致嵌套依賴關系,並增加無法匹配相應版本的幾率。
雖然可以通過npm config set save-exact true
命令關閉在版本號前面使用^
的默認行為,但這個只會影響頂級依賴關系。由於每個依賴的庫都有自己的package.json
文件,而在它們自己的依賴關系前面可能會有^
符號,所以無法通過package.json
文件為嵌套依賴的內容提供保證。
為了解決這個問題,npm提供了shrinkwrap
命令。此命令將生成一個npm-shrinkwrap.json
文件,為所有庫和所有嵌套依賴的庫記錄確切的版本。
然而,即使存在npm-shrinkwrap.json
這個文件,npm也只會鎖定庫的版本,而不是庫的內容。即便npm現在也能阻止用戶多次重復發布庫的同一版本,但是npm管理員仍然具有強制更新某些庫的權力。
這是引用自shrinkwrap
文檔的內容:
如果你希望鎖定包中的特定字節,比如是為了保證能正確地重新部署或構建,那么你應該在源代碼控制中檢查依賴關系,或者采取一些其他的機制來校驗內容,而不是靠校驗版本。
npm 2
會安裝每一個包所依賴的所有依賴項。如果我們有這么一個項目,它依賴項目A,項目A依賴項目B,項目B依賴項目C,那么依賴樹將如下所示:
-
node_modules
-
- package-A
-
-- node_modules
-
--- package-B
-
----- node_modules
-
------ package-C
-
-------- some-really-really-really-long-file-name-in-package-c.js
這個結構可能會很長。這對於基於Unix的操作系統來說只不過是一個小煩惱,但對於Windows來說卻是個破壞性的東西,因為有很多程序無法處理超過260個字符的文件路徑名。
npm 3
采用了扁平依賴關系樹來解決這個問題,所以我們的3個項目結構現在看起來如下所示:
-
node_modules
-
- package-A
-
- package-B
-
- package-C
-
-- some-file-name-in-package-c.js
這樣,一個原來很長的文件路徑名就從./node_modules/package-A/node_modules/package-B/node-modules/some-file-name-in-package-c.js
變成了/node_modules/some-file-name-in-package-c.js
。
這種方法的缺點是,npm必須首先遍歷所有的項目依賴關系,然后再決定如何生成扁平的node_modules
目錄結構。npm必須為所有使用到的模塊構建一個完整的依賴關系樹,這是一個耗時的操作,是npm安裝速度慢的一個很重要的原因。
想當然的以為每次運行npm install
命令時,NPM都得從互聯網上下載所有內容。
但是,npm是有本地緩存的,它保存了已經下載的每個版本的壓縮包。本地緩存的內容可以通過npm cache ls
命令進行查看。本地緩存的設計有助於減少安裝時間。
總而言之,npm是一個成熟、穩定、並且有趣的包管理器。
cnpm
- cnpm跟npm用法完全一致,只是在執行命令時將npm改為cnpm。
- npm安裝插件是從國外服務器下載,受網絡影響大,可能出現異常,如果npm的服務器在中國就好了,於是淘寶團隊干了這事。來自官網:“這是一個完整 npmjs.org 鏡像,你可以用此代替官方版本(只讀),同步頻率目前為 10分鍾 一次以保證盡量與官方服務同步。”
- 官方地址:http://npm.taobao.org
- 安裝:$ npm install -g cnpm --registry=https://registry.npm.taobao.org
Yarn
Yarn發布於2016年10月,截至當前2018年7月,在Github上擁有了32.2k個Star。而npm只有16.8k個Start。這個項目由一些高級開發人員維護,包括了Sebastian McKenzie(Babel.js)和Yehuda Katz(Ember.js、Rust、Bundler等)。
Yarn一開始的主要目標是解決上一節中描述的由於語義版本控制而導致的npm安裝的不確定性問題。雖然可以使用npm shrinkwrap
來實現可預測的依賴關系樹,但它並不是默認選項,而是取決於所有的開發人員知道並且啟用這個選項。
Yarn采取了不同的做法。每個yarn安裝都會生成一個類似於npm-shrinkwrap.json
的yarn.lock
文件,而且它是默認創建的。除了常規信息之外,yarn.lock
文件還包含要安裝的內容的校驗和,以確保使用的庫的版本相同。
yarn是經過重新設計的嶄新的npm客戶端,它能讓開發人員並行處理所有必須的操作,並添加了一些其他改進。
- 運行速度得到了顯著的提升,整個安裝時間也變得更少
- 像npm一樣,yarn使用本地緩存。與npm不同的是,yarn無需互聯網連接就能安裝本地緩存的依賴項,它提供了離線模式。這個功能在2012年的npm項目中就被提出來過,但一直沒有實現。
- 允許合並項目中使用到的所有的包的許可證
通常情況下不建議通過npm進行安裝。npm安裝是非確定性的,程序包沒有簽名,並且npm除了做了基本的SHA1哈希之外不執行任何完整性檢查,這給安裝系統程序帶來了安全風險。
npm install -g yarn
強烈建議你通過最適合於你的操作系統的安裝方法來安裝yarn,進官網下載對應版本
yarn官方地址:https://yarnpkg.com/zh-Hans/ 點擊打開鏈接
pnpm
可閱讀pnpm的作者Zoltan Kochan發表的“為什么要用pnpm?”
- pnpm運行起來非常的快,超過了npm和yarn
- pnpm采用了一種巧妙的方法,利用硬鏈接和符號鏈接來避免復制所有本地緩存源文件,這是yarn的最大的性能弱點之一
- 使用鏈接並不容易,會帶來一堆問題需要考慮。
- pnpm繼承了yarn的所有優點,包括離線模式和確定性安裝
總結
- npm仍然提供了一個非常有用的解決方案,支持大量的測試用例。大多數開發人員使用原始npm客戶端仍然可以做得很好
- yarn的確定性安裝,可以避免很多潛在的問題,相對安全
- pnpm可能是一些測試用例的更好的選擇。例如,它可以在運行大量集成測試並希望盡可能快地安裝依賴關系的中小型團隊中發揮作用
與君共勉:再牛逼的夢想,也抵不住傻逼般的堅持!