在我看來,nodejs 的成功緣由除了它採用了前端 js 相同的語法,直接吸引了一大波前端開發者做爲初始用戶以外,它內置的包管理器 npm 也居功至偉。npm 可以很好的管理 nodejs 項目的依賴,也使得開發者發佈本身的包變的異常容易。這樣一來,不論你使用別人的包,仍是本身發佈包給別人使用,成本都不大。這和我大學學習的 Java 1.x 相比就輕鬆愉快的多(如今 Java 已今非昔比,我不敢亂評論),開發者熱情高漲的話,整個生態就會更加活躍,進步速度也就更加快了。看一看 GitHub 上 JS 項目的佔比,再看看 npm 官網包的數量,就能略知一二。javascript
前陣子公司的一名新人問了我一個問題:如何區分項目的依賴中,哪些應該放在 dependencies,而哪些應該放在 devDependencies 呢?css
其實這個問題我在早先也有過,因此很是可以體會他的心情。爲了防止誤人子弟,我查閱了一些資料,發現其實 nodejs 中總共有 5 種依賴:前端
因此我趁此機會,整理了這篇文章,分享給更多仍有此迷茫的人們。java
這是 npm 最基本的依賴,經過命令 npm i xxx -S
或者 npm i xxx --save
來安裝一個包,而且添加到 package.json 的 dependencies 裏面(這裏 i
是 install
的簡寫,二者都可)。node
若是直接只寫一個包的名字,則安裝當前 npm registry 中這個包的最新版本;若是要指定版本的,能夠把版本號寫在包名後面,例如 npm i webpack@3.0.0 --save
。webpack
npm install
也支持 tag,tar 包地址等等,不過那些不太經常使用,能夠查看官方文檔。git
dependencies 比較簡單,我就再也不多作解釋了。注意一點:npm 5.x 開始能夠省略 --save
,即若是執行 npm install xxx
,npm 同樣會把包的依賴添加到 package.json 中去。要關閉這個功能,可使用 npm config set save false
。程序員
不少 nodejs 新手都分不清 dependencies 和 devDependencies,致使依賴隨便分配,或者把依賴通通都寫在 dependencies。這也是我編寫本文的初衷。github
先說定義。顧名思義,devDependencies 就是開發中使用的依賴,它區別於實際的依賴。也就是說,在線上狀態不須要使用的依賴,就是開發依賴。web
再說意義。爲何 npm 要把它單獨分拆出來呢?最終目的是爲了減小 node_modules 目錄的大小以及 npm install
花費的時間。由於 npm 的依賴是嵌套的,因此可能看上去 package.json 中只有幾個依賴,但實際上它又擴散到 N 個,而 N 個又擴散到 N 平方個,一層層擴散出去,可謂子子孫孫無窮盡也。若是可以儘可能減小不使用的依賴,那麼就可以節省線上機器的硬盤資源,也能夠節省部署上線的時間。
在實際開發中,大概有這麼幾類能夠歸爲開發依賴:
構建工具
如今比較熱門的是 webpack 和 rollup,以往還有 grunt, gulp 等等。這些構建工具會生成生產環境的代碼,以後在線上使用時就直接使用這些壓縮過的代碼。因此這類構建工具是屬於開發依賴的。
像 webpack 還分爲代碼方式使用(webpack
)和命令行方式使用 (webpack-cli
),這些都是開發依賴。另外它們可能還會提供一些內置的經常使用插件,如 xxx-webpack-plugin
,這些也都算開發依賴。
預處理器
這裏指的是對源代碼進行必定的處理,生成最終代碼的工具。比較典型的有 CSS 中的 less, stylus, sass, scss 等等,以及 JS 中的 coffee-script, babel 等等。它們作的事情雖然各有不一樣,但原理是一致的。
以 babel 爲例,經常使用的有兩種使用方式。其一是內嵌在 webpack 或者 rollup 等構件工具中,通常以 loader 或者 plugin 的形式出現,例如 babel-loader
。其二是單獨使用(小項目較多),例如 babel-cli
。babel 還額外有本身的插件體系,例如 xxx-babel-plugin
。相似地,less 也有與之對應的 less-loader
和 lessc
。這些都算做開發依賴。
在 babel 中還有一個注意點,那就是 babel-runtime
是 dependencies 而不是 devDependencies。具體分析我在以前的 babel 文章中提過,就再也不重複了。
測試工具
嚴格來講,測試和開發並非一個過程。但它們同屬於「線上狀態不須要使用的依賴」,所以也就納入開發依賴了。經常使用的如 chai
, e2e
, karma
, coveralls
等等都在此列。
真的是開發才用的依賴包
最後一類比較雜,很難用一個大類囊括起來,總之就是開發時須要使用的,而實際上線時要麼是已經打包成最終代碼了,要麼就是不須要使用了。好比 webpack-dev-server
支持開發熱加載,線上是不用的;babel-register
由於性能緣由也不能用在線上。其餘還可能和具體業務相關,就看各位開發者本身識別了。
把依賴安裝成開發依賴,則可使用 npm i -D
或者 npm i --save-dev
命令。
若是想達成剛纔說的縮減安裝包的目的,可使用命令 npm i --production
忽略開發依賴,只安裝依賴,這一般在線上機器(或者 QA 環境)上使用。所以還有一個最根本的識別依賴的方式,那就是用這條命令安裝,若是項目跑不起來,那就是識別有誤了。
若是僅做爲 npm 包的使用者,瞭解前兩項就足夠咱們平常的使用了。接下來的三種依賴都是做爲包的發佈者帶會使用到的字段,因此咱們轉換角色,以發佈者的身份來討論接下來的問題。
若是咱們開發一個常規的包,例如命名爲 my-lib
。其中須要使用 request
這個包來發送請求,所以代碼裏必定會有 const request = require('request')
。如上面的討論,這種狀況下 request
是做爲 dependencies 出如今 package.json 裏面的。那麼在使用者經過命令 npm i my-lib
安裝咱們的時候,這個 request
也會做爲依賴的一部分被安裝到使用者的項目中。
那咱們還爲何須要這個 peerDependencies 呢?
根據 npm 官網的文檔,這個屬性主要用於插件類 (Plugin) 項目。常規來講,爲了插件生態的繁榮,插件項目通常會被設計地儘可能簡單,經過數據結構和固定的方法接口進行耦合,而不會要求插件項目去依賴本體。例如咱們比較熟悉的 express 中間件,只要你返回一個方法 return function someMiddleware(req, res, next)
,它就成爲了 express 中間件,受本體調用,並經過三個參數把本體的信息傳遞過來,在插件內部使用。所以 express middleware 是不須要依賴 express 的。相似的狀況還包括 Grunt 插件,Chai 插件和 Winston transports 等。
但很明顯,這類插件脫離本體是不能單獨運行的。所以雖然插件不依賴本體,但想要本身可以實際運行起來,還得要求使用者把本體也歸入依賴。這就是介於「不依賴」和「依賴」之間的中間狀態,就是 peerDependencies 的主要使用場景。
例如咱們提供一個包,其中的 package.json 以下:
{
"name": "my-greate-express-middleware",
"version": "1.0.0",
"peerDependencies": {
"express": "^3.0.0"
}
}
複製代碼
在 npm 3.x 及之後版本,若是使用者安裝了咱們的插件,而且在本身的項目中沒有依賴 express 時,會在最後彈出一句提示,表示有一個包須要您依賴 express 3.x,所以您必須本身額外安裝。另外若是使用者依賴了不一樣版本的 express,npm 也會彈出提示,讓開發者本身決斷是否繼續使用這個包。
這是一種比起 peerDependencies 更加少見的依賴項,也能夠寫做 bundleDependencies (bundle 後面的 d 省略)。和上述的依賴不一樣,這個屬性並非一個鍵值對的對象,而是一個數組,元素爲表示包的名字的字符串。例如
{
"name": "awesome-web-framework",
"version": "1.0.0",
"bundledDependencies": [
"renderized", "super-streams"
]
}
複製代碼
當咱們但願以壓縮包的方式發佈項目時(好比你不想放到 npm registry 裏面去),咱們會使用 npm pack
來生成(如上述例子,就會生成 awesome-web-framework-1.0.0.tgz
)。編寫了 bundledDependencies 以後,npm 會把這裏面的兩個包 (renderized
, super-streams
) 也一塊兒加入到壓縮包中。這樣以後其餘使用者執行 npm install awesome-web-framework-1.0.0.tgz
時也會安裝這兩個依賴了。
若是咱們使用常規的 npm publish
的方式來發布的話,這個屬性不會生效;而做爲使用方的話,大部分項目也都是從 npm registry 中搜索並引用依賴的,因此使用到的場景也至關少。
這也是一種不多見的依賴項,從名字能夠得知,它描述一種」可選「的依賴。和 dependencies 相比,它的不一樣點有:
即便這個依賴安裝失敗,也不影響整個安裝過程
程序應該本身處理安裝失敗時的狀況
關於第二點,我想表達的意思是:
let foo
let fooVersion
try {
foo = require('foo')
fooVersion = require('foo/package.json').version
} catch (e) {
// 安裝依賴失敗時找不到包,須要本身處理
}
// 若是安裝的依賴版本不符合實際要求,咱們也須要本身處理,當作沒安裝到
if (!isSupportVersion(fooVersion)) {
foo = null
}
// 若是安裝成功,執行某些操做
if (foo) {
foo.doSomeThing()
}
複製代碼
須要注意的是,若是一個依賴同時出如今 dependencies 和 optionalDependencies 中,那麼 optionalDependencies 會得到更高的優先級,可能形成預期以外的效果,所以最好不要出現這種狀況。
在實際項目中,若是某個包已經失效,咱們一般會尋找他的替代者,或者壓根換一個實現方案。使用這種」不肯定「的依賴,一方面會增長代碼中的判斷,增長邏輯的複雜度;另外一方面也會大大下降測試覆蓋率,增長構造測試用例的難度。因此我不建議使用這個依賴項,若是你原先就不知道有這個,那就繼續當作不知道吧。
如上的 5 種依賴,除了 bundledDependencies,其餘四種都是須要寫版本號的。若是做爲使用者,使用 npm i --save
或者 npm i --save-dev
會自動生成依賴的版本號,不過我建議你們仍是略微瞭解下經常使用的版本號的寫法。
首先咱們得搞清三位版本號的定義,以 "a.b.c" 舉例,它們的含義是:
a - 主要版本(也叫大版本,major version)
大版本的升級極可能意味着與低版本不兼容的 API 或者用法,是一次顛覆性的升級(想一想 webpack 3 -> 4)。
b - 次要版本(也叫小版本,minor version)
小版本的升級應當兼容同一個大版本內的 API 和用法,所以應該對開發者透明。因此咱們一般只說大版本號,不多會精確到小版本號。
特殊狀況是若是大版本號是 0
的話,意味着整個包處於內測狀態,因此每一個小版本之間也可能會不兼容。因此在選擇依賴時,儘可能避開大版本號是 0
的包。
c - 補丁 (patch)
通常用於修復 bug 或者很細微的變動,也須要保持向前兼容。
以後咱們看一下常規的版本號寫法:
"1.2.3" - 無視更新的精確版本號
表示只依賴這個版本,任何其餘版本號都不匹配。在一些比較重要的線上項目中,我比較建議使用這種方式鎖定版本。前陣子的 npm 挖礦以及 ant-design 彩蛋,其實均可以經過鎖定版原本規避問題(彩蛋略難一些,挖礦是確定能夠規避)。
"^1.2.3" - 兼具更新和安全的折中考慮
這是 npm i xxx --save
以後系統生成的默認版本號(^
加上當前最新版本號),官方的定義是「可以兼容除了最左側的非 0 版本號以外的其餘變化」(Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple)。這句話很拗口,舉幾個例子你們就明白了:
"^1.2.3" 等價於 ">= 1.2.3 < 2.0.0"。即只要最左側的 "1" 不變,其餘均可以改變。因此 "1.2.4", "1.3.0" 均可以兼容。
"^0.2.3" 等價於 ">= 0.2.3 < 0.3.0"。由於最左側的是 "0",因此這個不算,順延到第二位 "2"。那麼只要這個 "2" 不變,其餘的都兼容,好比 "0.2.4" 和 "0.2.99"。
"^0.0.3" 等價於 ">= 0.0.3 < 0.0.4"。這裏最左側的非 0 只有 "3",且沒有其餘版本號了,因此這個也等價於精確的 "0.0.3"。
從這幾個例子能夠看出,^
是一個更新和安全兼容的寫法。通常大版本號升級到 1 就表示項目正式發佈了,而 0 開頭就表示還在測試版,這也是 ^
區別對待二者的緣由。
"~1.2.3" - 比 ^
更加安全的小版本更新
關於 ~
的定義分爲兩部分:若是列出了小版本號(第二位),則只兼容 patch(第三位)的修改;若是沒有列出小版本號,則兼容第二和第三位的修改。咱們分兩種狀況理解一下這個定義:
"~1.2.3" 列出了小版本號(2
),所以只兼容第三位的修改,等價於 ">= 1.2.3 < 1.3.0"。
"~1.2" 也列出了小版本號,所以和上面同樣兼容第三位的修改,等價於 ">= 1.2.0 < 1.3.0"。
"~1" 沒有列出小版本號,能夠兼容第二第三位的修改,所以等價於 ">= 1.0.0 < 2.0.0"
和 ^
不一樣的是,~
並不對 0
或者 1
區別對待,因此 "~0" 等價於 ">= 0.0.0 < 1.0.0",和 "~1" 是相同的算法。比較而言,~
更加謹慎。當首位是 0
而且列出了第二位的時候,二者是等價的,例如 ~0.2.3
和 ^0.2.3
。
在 nodejs 的上古版本(v0.10.26,2014年2月發佈的),npm i --save
默認使用的是 ~
,如今已經改爲 ^
了。這個改動也是爲了讓使用者能最大限度的更新依賴包。
"1.x" 或者 "1.*" - 使用通配符
這個比起上面那兩個符號就好理解的多。x
(大小寫皆可)和 *
的含義相同,都表示能夠匹配任何內容。具體來講:
"*" 或者 "" (空字符串) 表示能夠匹配任何版本。
"1.x", "1.*" 和 "1" 都表示要求大版本是 1
,所以等價於 ">=1.0.0 < 2.0.0"。
"1.2.x", "1.2.*" 和 "1.2" 都表示鎖定前兩位,所以等價於 ">= 1.2.0 < 1.3.0"。
由於位於結尾的通配符通常能夠省略,而常規也不太可能像正則那樣把匹配符寫在中間,因此大多數狀況通配符均可以省略。使用最多的仍是匹配全部版本的 *
這個了。
"1.2.3-beta.2" - 帶預發佈關鍵詞的,如 alpha, beta, rc, pr 等
先說預發佈的定義,咱們須要以包開發者的角度來考慮這個問題。假設當前線上版本是 "1.2.3",若是我做了一些改動須要發佈版本 "1.2.4",但我不想直接上線(由於使用 "~1.2.3" 或者 `^1.2.3" 的用戶都會直接靜默更新),這就須要使用預發佈功能。所以我可能會發布 "1.2.4-alpha.1" 或者 "1.2.4-beta.1" 等等。
理解了它誕生的初衷,以後的使用就很天然了。
">1.2.4-alpha.1",表示我接受 "1.2.4" 版本全部大於1的 alpha 預發佈版本。所以如 "1.2.4-alpha.7" 是符合要求的,但 "1.2.4-beta.1" 和 "1.2.5-alpha.2" 都不符合。此外若是是正式版本(不帶預發佈關鍵詞),只要版本號符合要求便可,不檢查預發佈版本號,例如 "1.2.5", "1.3.0" 都是承認的。
"~1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 1.3.0"。這樣 "1.2.5", "1.2.4-alpha.2" 都符合條件,而 "1.2.5-alpha.1", "1.3.0" 不符合。
"^1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 2.0.0"。這樣 "1.2.5", "1.2.4-alpha.2", "1.3.0" 都符合條件,而 "1.2.5-alpha.1", "2.0.0" 不符合。
版本號還有更多的寫法,例如範圍(a - b),大於小於號(>=a <b),或(表達式1 || 表達式2)等等,由於用的很少,這裏再也不展開。詳細的文檔能夠參見 semver,它同時也是一個 npm 包,能夠用來比較兩個版本號的大小,以及是否符合要求等。
除了版本號,依賴包還能夠經過以下幾種方式來進行依賴(使用的也不算太多,能夠粗略瞭解一下):
除了版本號以外,一般某個包還可能會有 Tag 來標識一些里程碑意義的版本。例如 express@next 表示即將到來的下一個大版本(可提早體驗),而 some-lib@latest 等價於 some-lib,由於 latest 是默認存在並指向最新版本的。其餘的自定義 Tag 均可以由開發者經過 npm tag
來指定。
由於 npm i package@version
和 npm i package@tag
的語法是相同的,所以 Tag 和版本號必須不能重複。因此通常建議 Tag 不要以數字或者字母 v 開頭。
能夠指定 URL 指明依賴包的源地址,一般是一個 tar 包,例如 "https://some.site.com/lib.tar.gz"
。這個 tar 包一般是經過 npm pack
來發布的。
順帶提一句:本質上,npm 的全部包都是以 tar 包發佈的。使用 npm publish
常規發佈的包也是被 npm 冠上版本號等後綴,由 npm registry 託管供你們下載的。
能夠指定一個 Git 地址(不單純指 GitHub,任何 git 協議的都可),npm 自動從該地址下載並安裝。這裏就須要指明協議,用戶名,密碼,路徑,分支名和版本號等,比較複雜。詳情能夠查看官方文檔,舉例以下:
git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27
複製代碼
做爲最大的 Git 代碼庫,若是使用的是 GitHub 存放代碼,還能夠直接使用 user/repo 的簡寫方式,例如:
{
"dependencies": {
"express": "expressjs/express",
"mocha": "mochajs/mocha#4727d357ea",
"module": "user/repo#feature\/branch"
}
}
複製代碼
npm 支持使用本地路徑來指向一個依賴包,這時候須要在路徑以前添加 file:
,例如:
{
"dependencies": {
"bar1": "file:../foo/bar1",
"bar2": "file:~/foo/bar2",
"bar3": "file:/foo/bar3"
}
}
複製代碼
從 npm 5.x 開始,在執行 npm i
以後,會在根目錄額外生成一個 package-lock.json。既然講到了依賴,我就額外擴展一下這個 package-lock.json 的結構和做用。
package-lock.json 內部記錄的是每個依賴的實際安裝信息,例如名字,安裝的版本號,安裝的地址 (npm registry 上的 tar 包地址)等等。額外的,它會把依賴的依賴也記錄起來,所以整個文件是一個樹形結構,保存依賴嵌套關係(相似之前版本的 node_modules 目錄)。一個簡單的例子以下:
{
"name": "my-lib",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"array-union": {
"version": "1.0.2",
"resolved": "http://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz",
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
"dev": true,
"requires": {
"array-uniq": "^1.0.1"
}
}
}
}
複製代碼
在執行 npm i
的時候,若是發現根目錄下只有 package.json 存在(這一般發生在剛建立項目時),就按照它的記錄逐層遞歸安裝依賴,並生成一個 package-lock.json 文件。若是發現根目錄下二者皆有(這一般發生在開發同事把項目 checkout 到本地以後),則 npm 會比較二者。若是二者所示含義不一樣,則以 package.json 爲準,並更新 package-lock.json;不然就直接按 package-lock 所示的版本號安裝。
它存在的意義主要有 4 點:
在團隊開發中,確保每一個團隊成員安裝的依賴版本是一致的。不然由於依賴版本不一致致使的效果差別,通常很難查出來。
一般 node_modules 目錄都不會提交到代碼庫,所以要回溯到某一天的狀態是不可能的。但如今 node_modules 目錄和 package.json 以及 package-lock.json 是一一對應的。因此若是開發者想回退到以前某一天的目錄狀態,只要把這兩個文件回退到那一天的狀態,再 npm i
就好了。
由於 package-lock.json 已經足以描述 node_modules 的大概信息(尤爲是深層嵌套依賴),因此經過這個文件就能夠查閱某個依賴包是被誰依賴進來的,而不用去翻 node_modules 目錄(事實上如今目錄結構打平而非嵌套,翻也翻不出來了)
在安裝過程當中,npm 內部會檢查 node_modules 目錄中已有的依賴包,並和 package-lock.json 進行比較。若是重複,則跳過安裝,能大大優化安裝時間。
npm 官網建議:把 package-lock.json 一塊兒提交到代碼庫中,不要 ignore。可是在執行 npm publish
的時候,它會被忽略而不會發布出去。
從 nodejs 誕生之初,npm 就是其內置的包管理器,而且以其易於使用,易於發佈的特色極大地助推了 nodejs 在開發者中的流行和使用。但事物總有其兩面性,易於發佈的確大大推進生態的繁榮,但同時也下降了發佈的門檻。包的數量在日新月異,一個項目的依賴項從幾個上升到幾十個,再加上內部的嵌套循環依賴,給使用者帶來了極大的麻煩,node_modules 目錄愈來愈大,npm install
的時間也愈來愈長。
在這種狀況下,Facebook 率先站出來,發佈了由他們開發的另外一個包管理器 yarn(1.0版本於2017年9月)。一旦有了挑戰者出現,勢必會引起雙方對於功能,穩定性,易用性等各方面的競爭,對於開發者來講也是極其有利的。從結果來講,npm 也吸取了很多從 yarn 借鑑來的優勢,例如上面談論的 package-lock.json,最先就出自 yarn.lock。因此咱們來粗略比較一下二者的區別,以及咱們應當如何選擇。
版本鎖定
這個在 package-lock.json 已經討論過了,再也不贅述。 在這個功能點上,二者都已具有。
多個包的管理 (monorepositories)
一個包在 npm 中能夠被稱爲 repositories。一般咱們發佈某個功能,其實就是發佈一個包,由它提供各類 API 來提供功能。但隨着功能愈來愈複雜以及按需加載,把全部東西所有放到一個包中發佈已經不夠優秀,所以出現了多個包管理的需求。
一般一個類庫會把本身的功能分拆爲核心部分和其餘部分,而後每一個部分是一個 npm repositories,能夠單獨發佈。而使用者一般在使用核心以後,能夠本身選擇要使用哪些額外的部分。這種方式比較常見的如 babel 和它的插件,express 和它的中間件等。
做爲一個多個包的項目的開發者/維護者,安裝依賴和發佈都會是一件很麻煩的事情。由於 npm 只認根目錄的 package.json,那麼就必須進入每一個包進行 npm install
。而發佈時,也必須逐個修改每一個包的版本號,併到每一個目錄中進行 npm publish
。
爲了解決這個問題,社區一個叫作 lerna 的庫經過增長 lerna.json 來幫助咱們管理全部的包。而在 yarn 這邊,引入了一個叫作工做區(workspace)的概念。所以這點上來講,應該是 yarn 勝出了,不過 npm 配合 lerna 也可以實現這個需求。
安裝速度
npm 被詬病最多的問題之一就是其安裝速度。有些依賴不少的項目,安裝 npm 須要耗費 5-10 分鐘甚至更久。形成這個問題的本質是 npm 採用串行的安裝方式,一個裝完再裝下一個。針對這一點,yarn 改成並行安裝,所以本質上提高了安裝速度。
離線可用
yarn 默認支持離線安裝,即安裝過一次的包,會在電腦中保留一份(緩存位置能夠經過 yarn config set yarn-offline-mirror
進行指定)。以後再次安裝,直接複製過來就能夠。
npm 早先是所有經過網絡請求的(爲了保持其時效性),但後期也借鑑了 yarn 建立了緩存。從 npm 5.x 開始咱們可使用 npm install xxx --prefer-offline
來優先使用緩存(意思是緩存沒有再發送網絡請求),或者 npm install xxx --offline
來徹底使用緩存(意思是緩存沒有就安裝失敗)。
控制檯信息
常年使用 npm 的同窗知道,安裝完依賴後,npm 會列出一顆依賴樹。這顆樹一般會很長很複雜,咱們不會過多關注。所以 yarn 精簡了這部分信息,直接輸出安裝結果。這樣萬一安裝過程當中有報錯日誌也不至於被刷掉。
不過 npm 5.x 也把這顆樹給去掉了。這又是一個互相借鑑提升的例子。
總結來講,yarn 的推出主要是針對 npm 早期版本的不少問題。但 npm 也意識到了來自競爭對手的強大壓力,所以在 5.x 開始逐個優化看齊。從 5.x 開始就已經和 yarn 不分伯仲了,所以如何選擇多數看是否有歷史包袱。若是是新項目的話,就看程序員我的的喜愛了。
本文從一個很小的問題開始,本意是想分享如何鑑別一個應用應該歸類在 dependencies 仍是 devDependencies。後來層層深刻,經過查閱資料發現了好多依賴相關的知識,例如其餘幾種依賴,版本鎖定的機制以及和 yarn 的比較等等,最終變成一篇長文。但願經過本文能讓你們瞭解到依賴管理的一些大概,在以後的搬磚道路上可以更加順利,也能反過來爲整個生態的繁榮貢獻本身的力量。