NPM的flatten, dedupe和shrinkwrap

今天碰到了一些NPM相關(guān)的問題, 花了些時間搞清楚, 記錄一下.

關(guān)于NPM包依賴的扁平化 (flatten)

接手的項目里面使用到了包flatten-packages, 這個包可以將npm包的嵌套依賴壓扁. 對這方面之前沒了解過, 于是做了一下調(diào)研.

為什么需要扁平化

在早期, npm包是以下形式保存的:

.
|--app
    |--node_modules
       |--sub_module
          |--node_modules
             |--sub_sub_module
                |-- ... and so on

即你的項目依賴包A, 那么node_modules里面就有一個A的文件夾, 在這個文件夾中, 又有一個node_modules, 所有A的依賴都放這個node_modules中, 以此類推. npm的包依賴就會成為一個非常深的文件夾結(jié)構(gòu), 深到有時你在windows系統(tǒng)直接刪除該文件夾會失敗, 因為文件目錄超過了windows限制的256個字符. 此時你可以打開Git Bash什么的rm -rf.

這會導(dǎo)致一個問題: 不同的包(A和B)可能依賴同一個包(C), 這樣npm install的時候, 安裝A的時候會下載一次C, 安裝B的時候又會下載一次C. 無形中多了很多沒必要的下載, 導(dǎo)致npm install的速度變慢很多.

所以這也就有了扁平化包依賴的需求 -- 讓重復(fù)的依賴盡量合并, 以加速包管理的速度.

扁平化的方法

flatten-packages

flatten-packages這個包可以解決這個問題. 跑一下flatten-packages即可.

但是, 新版本的npm會進行自動扁平化處理, 所以flatten-packages已經(jīng)沒有用了, 這個包也已經(jīng)停止更新了. 而且在解決包沖突方面也不如npm dedupe. 相關(guān)Issue

npm3的自動扁平化

npm第3版會自動進行包的扁平化. 詳見npm v3, 搜索flat.

npm3會盡可能地扁平化包依賴, 絕大多數(shù)情況下你的依賴包都會直接存在于node_modules里. 唯一的例外是, 某兩個包依賴互相沖突的時候.

為什么包依賴會有沖突?

簡單的例子就是同一個包C, A依賴于C 1.0.0, B依賴于C 2.0.0. 這樣如果你的app同時依賴于A和B, 那就沒法直接在node_modules里面放一個版本C來同時滿足A和B的依賴了. 這里的版本號是SemVer.

SemVer

SemVer (Semantic Versioning, 語義化版本).

SemVer最基本的結(jié)構(gòu)是major.minor.patch, 如1.2.3.
其中,

  • major為主版本號, 當(dāng)有非向后兼容(即breaking change)的時候, 更新major.
  • minor為次版本號, 當(dāng)有向后兼容的時候, 更新minor.
  • patch為補丁號, 當(dāng)有向后兼容的bug fix時, 更新patch.

npm使用SemVer來標(biāo)注包的版本, 這些配置寫入到了package.json中.

除了指定固定的版本號, package.json中還可以指定版本號范圍.

  • 1.2.x (x也可以用*代替), 相當(dāng)于>=1.2.0 <1.3.0
  • 1.x.x, 相當(dāng)于>=1.0.0 <2.0.0
  • 波浪線(Tilde), ~1.2.3相當(dāng)于>=1.2.3 <1.3.0, 即minor不能增加.
  • 破折號(Caret), ^1.2.3相當(dāng)于>=1.2.3 <2.0.0, 即major不能增加.

包去重 (dedupe)

想要去重只需運行npm dedupe

既然npm3自動進行了扁平化, 為什么還需要去重? 這個npm用了一篇文章npm3 Duplication and Deduplication進行講解, 我就不翻譯了. 簡單來說就是:

  1. 由于歷史原因, 某兩個包A和B依賴于同一個包C的不同版本1.0.0和2.0.0, 這個沖突導(dǎo)致C的依賴無法被合并.
  2. 后來A和B的某次更新使得他們依賴于同一個版本的C2.0.0, 但是仍然由于你的app直接依賴C1.5.0, 導(dǎo)致A和B依賴的C2.0.0依然無法被合并, 只能各自存放在A和B的目錄下.
    (問題: 如果這時候運行npm dedup是什么效果?)
  3. 后來你把直接依賴C1.5.0更新成了C2.0.0, 這會導(dǎo)致你有三個重復(fù)的C2.0.0, 需要運行npm dedupe去重.
    (問題: 為什么不能更新版本的時候自動運行dedupe呢?)

包依賴鎖定(shrinkwrap/lock)

package.json中的依賴包的版本號可能是版本范圍, 或者依賴包的依賴可能使用了版本范圍, 這會導(dǎo)致一個問題: 你今天npm install安裝了包A1.0.0, 一段時間后你的同事運行npm install, 可能就會安裝A1.2.0, 導(dǎo)致你們的運行環(huán)境不完全一樣. 想要將包依賴的版本號完全鎖定住, 就需要shrinkwrap/lock.

package-lock.json

npm5引入了package-lock.json, 即在你運行npm install的時候自動會生成package-lock.json文件, 這是一個描述依賴樹的文件, 它的好處是鎖定了所有依賴的版本甚至下載地址, 而且結(jié)構(gòu)清晰人能讀懂(相對于錯綜復(fù)雜的node_modules目錄結(jié)構(gòu)).

示例package-lock.json

{
  "name": "A",
  "version": "0.1.0",
  ...metadata fields...
  "dependencies": {
    "B": {
      "version": "0.0.1",
      "resolved": "https://registry.npmjs.org/B/-/B-0.0.1.tgz",
      "integrity": "sha512-DeAdb33F+"
      "dependencies": {
        "C": {
          "version": "git://github.com/org/C.git#5c380ae319fc4efe9e7f2d9c78b0faa588fd99b4"
        }
      }
    }
  }
}

如果有package-lock.json, 安裝過程會變成:

  1. 按照package-lock.json重建依賴包的樹形結(jié)構(gòu). 如果有"resolved"字段則使用該字段指向的文件下載文件, 否則使用"version".
  2. 若最后還有缺失的依賴包, 則使用普通的package.json安裝方法.

注意: npm install, npm rm, npm update都會自動更新package-lock.json. 如果你不想更新, 可以使用以下命令行參數(shù):
--no-save: 不更新package.json也不更新package-lock.json
--no-shrinkwrap: 更新package.json, 不更新package-lock.json和npm-shrinkwrap.json.

npm非常建議將package-lock.json存入版本控制, 以確保組內(nèi)所有成員, 持續(xù)集成(Continuous Integration, CI)和部署環(huán)境是用完全一致的依賴包.[package-locks]

npm-shrinkwrap.json

你可以使用npm shrinkwrap指令, 在package-lock.json的基礎(chǔ)上生成一個名為npm-shrinkwrap.json的文件.

package-lock.json和npm-shrinkwrap.json的內(nèi)容完全一樣, 唯一區(qū)別是: 當(dāng)發(fā)布包的時候, package-lock.json不會被包含在內(nèi), 但是npm-shrinkwrap.json會被一同發(fā)布. 如果兩者同時存在, package-lock.json會被完全忽略.

參考

  1. Semver: A Primer
  2. 為什么我不使用 shrinkwrap(lock)
  3. package-locks
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容