Node.js 中的依賴管理

在我看來,nodejs 的成功原因除了它採用了前端 js 相同的語法,直接吸引了一大波前端開發者作為初始用戶之外,它內置的包管理器 npm 也居功至偉。npm 能夠很好的管理 nodejs 項目的依賴,也使得開發者發佈自己的包變的異常容易。這樣一來,不論你使用別人的包,還是自己發佈包給別人使用,成本都不大。這和我大學學習的 Java 1.x 相比就輕鬆愉快的多(現在 Java 已今非昔比,我不敢亂評論),開發者熱情高漲的話,整個生態就會更加活躍,進步速度也就更加快了。看一看 GitHub 上 JS 項目的佔比,再看看 npm 官網包的數量,就能略知一二。

前陣子公司的一名新人問了我一個問題:如何區分項目的依賴中,哪些應該放在 dependencies,而哪些應該放在 devDependencies 呢?

其實這個問題我在早先也有過,所以非常能夠體會他的心情。為了防止誤人子弟,我查閱了一些資料,發現其實 nodejs 中總共有 5 種依賴:

  • dependencies (常用)
  • devDependencies (常用)
  • peerDependencies (不太常用)
  • bundledDependencies (我之前沒用過)
  • optionalDependencies (我之前沒用過)

所以我趁此機會,整理了這篇文章,分享給更多仍有此迷茫的人們。

dependencies

這是 npm 最基本的依賴,通過命令 npm i xxx -S 或者 npm i xxx —save 來安裝一個包,並且添加到 package.json 的 dependencies 裡面(這裡 i 是 install 的簡寫,兩者均可)。

如果直接只寫一個包的名字,則安裝當前 npm registry 中這個包的最新版本;如果要指定版本的,可以把版本號寫在包名後面,例如 npm i [email protected] —save。

npm install 也支持 tag,tar 包地址等等,不過那些不太常用,可以查看官方文檔。

dependencies 比較簡單,我就不再多做解釋了。注意一點:npm 5.x 開始可以省略 —save,即如果執行 npm install xxx,npm 一樣會把包的依賴添加到 package.json 中去。要關閉這個功能,可以使用 npm config set save false。

devDependencies

很多 nodejs 新手都分不清 dependencies 和 devDependencies,導致依賴隨便分配,或者把依賴統統都寫在 dependencies。這也是我編寫本文的初衷。

先說定義。顧名思義,devDependencies 就是開發中使用的依賴,它區別於實際的依賴。也就是說,在線上狀態不需要使用的依賴,就是開發依賴。

再說意義。為什麼 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 環境)上使用。因此還有一個最根本的識別依賴的方式,那就是用這條命令安裝,如果項目跑不起來,那就是識別有誤了。

peerDependencies

如果僅作為 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 也會彈出提示,讓開發者自己決斷是否繼續使用這個包。

bundledDependencies

這是一種比起 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 中搜索並引用依賴的,所以使用到的場景也相當少。

optionalDependencies

這也是一種很少見的依賴項,從名字可以得知,它描述一種”可選“的依賴。和 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

其他寫法

除了版本號,依賴包還可以通過如下幾種方式來進行依賴(使用的也不算太多,可以粗略瞭解一下):

Tag

除了版本號之外,通常某個包還可能會有 Tag 來標識一些里程碑意義的版本。例如 express@next 表示即將到來的下一個大版本(可提前體驗),而 some-lib@latest 等價於 some-lib,因為 latest 是默認存在並指向最新版本的。其他的自定義 Tag 都可以由開發者通過 npm tag 來指定。

因為 npm i package@version 和 npm i package@tag 的語法是相同的,因此 Tag 和版本號必須不能重複。所以一般建議 Tag 不要以數字或者字母 v 開頭。

URL

可以指定 URL 指明依賴包的源地址,通常是一個 tar 包,例如 “https://some.site.com/lib.tar.gz"。這個 tar 包通常是通過 npm pack 來發布的。

順帶提一句:本質上,npm 的所有包都是以 tar 包發佈的。使用 npm publish 常規發佈的包也是被 npm 冠上版本號等後綴,由 npm registry 託管供大家下載的。

Git URL

可以指定一個 Git 地址(不單純指 GitHub,任何 git 協議的均可),npm 自動從該地址下載並安裝。這裡就需要指明協議,用戶名,密碼,路徑,分支名和版本號等,比較複雜。詳情可以查看官方文檔,舉例如下:

git+ssh://[email protected]:npm/cli.git#v1.0.27
git+ssh://[email protected]:npm/cli#semver:^5.0
git+https://[email protected]/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"
}
}

package-lock.json

從 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 的時候,它會被忽略而不會發布出去。

yarn

從 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 的比較等等,最終變成一篇長文。希望通過本文能讓大家瞭解到依賴管理的一些大概,在之後的搬磚道路上能夠更加順利,也能反過來為整個生態的繁榮貢獻自己的力量。

參考文章

  • npm 官網的 dependencies 文檔
  • npm 官方微博的 peerDependencies 介紹 - 這篇有點老了,npm 依賴還是嵌套關係
  • Why use peerDependencies in npm for plugins - 比較簡略,不過說的在點上
  • Types of dependencies - 雖然是 yarn 的介紹,但概念和 npm 一致,且很精煉。
  • semver - npm 官方用來比較版本號的包
  • “npm install —save” No Longer Using Tildes - 早期的一篇博客,npm 對依賴版本號默認處理的變更
  • npm 官網的 package-lock.json 文檔
  • Workspaces in Yarn - yarn 官網介紹的 workspace 功能
  • Here’s what you need to know about npm 5 - 介紹 npm 5.x 的重要改進點
Node.js 中的依賴管理





分享到:


相關文章: