npm包---前世此生(二)

**npm管理機制**

Nodejs成功離不開 npm 優秀的依賴管理系統。在介紹整個依賴系統以前,必需要了解 npm如何管理依賴包的版本,本章將介紹 npm包 的版本發佈規範、如何管理各類依賴包的版本以及一些關於包版本的最佳實踐。html

![](https://pic4.zhimg.com/80/v2-93e39e7ffb7927172ca314f24c6381a6_1440w.jpg)

2.1 查看npm包版本

你能夠執行 npm view package version 查看某個 package 的最新版本。node

執行 npm view conard versions 查看某個 package 在npm服務器上全部發布過的版本。react

![](https://pic3.zhimg.com/80/v2-455593cafdefd078aa93a73c3e660e1b_1440w.jpg)

執行 npm ls 可查看當前倉庫依賴樹上全部包的版本信息。git

![](https://pic3.zhimg.com/80/v2-ee4a5aaa37db08a924f5aaf4de719589_1440w.jpg)

2.2 SemVer規範

npm包 中的模塊版本都須要遵循 SemVer規範——由 Github 起草的一個具備指導意義的,統一的版本號表示規則。實際上就是 Semantic Version(語義化版本)的縮寫。github

SemVer規範官網: semver.org/docker

標準版本

SemVer規範的標準版本號採用 X.Y.Z 的格式,其中 X、Y 和 Z 爲非負的整數,且禁止在數字前方補零。X 是主版本號、Y 是次版本號、而 Z 爲修訂號。每一個元素必須以數值來遞增。npm

  • 主版本號(major):當你作了不兼容的API 修改
  • 次版本號(minor):當你作了向下兼容的功能性新增
  • 修訂號(patch):當你作了向下兼容的問題修正。

例如:1.9.1 -> 1.10.0 -> 1.11.0json

先行版本

當某個版本改動比較大、並不是穩定並且可能沒法知足預期的兼容性需求時,你可能要先發佈一個先行版本。緩存

先行版本號能夠加到「主版本號.次版本號.修訂號」的後面,先加上一個鏈接號再加上一連串以句點分隔的標識符和版本編譯信息。安全

  • 內部版本(alpha):
  • 公測版本(beta):
  • 正式版本的候選版本rc: 即 Release candiate

React的版本

下面咱們來看看 React 的歷史版本:

![](https://pic3.zhimg.com/v2-0085428d8f8f6bfa9de2f2ccdcebfc63_b.jpg)

可見是嚴格按照 SemVer 規範來發版的:

  • 版本號嚴格按照 主版本號.次版本號.修訂號 格式命名
  • 版本是嚴格遞增的,:16.8.0 -> 16.8.1 -> 16.8.2
  • 發佈重大版本或版本改動較大時,先發布alphabetarc等先行版本

發佈版本

在修改 npm 包某些功能後一般須要發佈一個新的版本,咱們一般的作法是直接去修改 package.json 到指定版本。若是操做失誤,很容易形成版本號混亂,咱們能夠藉助符合 Semver 規範的命令來完成這一操做:

  • npm version patch : 升級修訂版本號
  • npm version minor : 升級次版本號
  • npm version major : 升級主版本號

2.3 版本工具使用

在開發中確定少不了對一些版本號的操做,若是這些版本號符合 SemVer規範 ,咱們能夠藉助用於操做版本的npm包semver來幫助咱們進行比較版本大小、提取版本信息等操做。

Npm 也使用了該工具來處理版本相關的工做。

npm install semver
複製代碼
  • 比較版本號大小
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
複製代碼
  • 判斷版本號是否符合規範,返回解析後符合規範的版本號。
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
複製代碼
  • 將其餘版本號強制轉換成semver版本號
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
複製代碼
  • 一些其餘用法
semver.clean('  =v1.2.3   ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.minVersion('>=1.0.0') // '1.0.0'
複製代碼

以上都是semver最多見的用法,更多詳細內容能夠查看 semver文檔:github.com/npm/node-se…

2.4 依賴版本管理

咱們常常看到,在 package.json 中各類依賴的不一樣寫法:

"dependencies": {
    "signale": "1.4.0",
    "figlet": "*",
    "react": "16.x",
    "table": "~5.4.6",
    "yargs": "^14.0.0"
  }
複製代碼

前面三個很容易理解:

  • "signale": "1.4.0": 固定版本號
  • "figlet": "*": 任意版本(>=0.0.0
  • "react": "16.x": 匹配主要版本(>=16.0.0 <17.0.0
  • "react": "16.3.x": 匹配主要版本和次要版本(>=16.3.0 <16.4.0

再來看看後面兩個,版本號中引用了 ~^ 符號:

  • ~: 當安裝依賴時獲取到有新版本時,安裝到 x.y.zz 的最新的版本。即保持主版本號、次版本號不變的狀況下,保持修訂號的最新版本。
  • ^: 當安裝依賴時獲取到有新版本時,安裝到 x.y.zyz 都爲最新版本。 即保持主版本號不變的狀況下,保持次版本號、修訂版本號爲最新版本。

package.json 文件中最多見的應該是 "yargs": "^14.0.0" 這種格式的 依賴, 由於咱們在使用 npm install package 安裝包時,npm 默認安裝當前最新版本,而後在所安裝的版本號前加 ^ 號。

注意,當主版本號爲 0 的狀況,會被認爲是一個不穩定版本,狀況與上面不一樣:

  • 主版本號和次版本號都爲 0: ^0.0.z~0.0.z 都被看成固定版本,安裝依賴時均不會發生變化。
  • 主版本號爲 0: ^0.y.z 表現和 ~0.y.z 相同,只保持修訂號爲最新版本。

1.0.0 的版本號用於界定公共 API。當你的軟件發佈到了正式環境,或者有穩定的API時,就能夠發佈1.0.0版本了。因此,當你決定對外部發佈一個正式版本的npm包時,把它的版本標爲1.0.0。

2.5 鎖定依賴版本

lock文件

實際開發中,常常會由於各類依賴不一致而產生奇怪的問題,或者在某些場景下,咱們不但願依賴被更新,建議在開發中使用 package-lock.json

鎖定依賴版本意味着在咱們不手動執行更新的狀況下,每次安裝依賴都會安裝固定版本。保證整個團隊使用版本號一致的依賴。

每次安裝固定版本,無需計算依賴版本範圍,大部分場景下能大大加速依賴安裝時間。

使用 package-lock.json 要確保npm的版本在5.6以上,由於在5.0 - 5.6中間,對 package-lock.json的處理邏輯進行過幾回更新,5.6版本後處理邏輯逐漸穩定。

關於 package-lock.json 詳細的結構,咱們會在後面的章節進行解析。

按期更新依賴

咱們的目的是保證團隊中使用的依賴一致或者穩定,而不是永遠不去更新這些依賴。實際開發場景下,咱們雖然不須要每次都去安裝新的版本,仍然須要定時去升級依賴版本,來讓咱們享受依賴包升級帶來的問題修復、性能提高、新特性更新。

![](https://picb.zhimg.com/80/v2-3fba91503855869cce7f0d2816a45217_1440w.jpg)

使用 npm outdated 能夠幫助咱們列出有哪些尚未升級到最新版本的依賴:

  • 黃色表示不符合咱們指定的語意化版本範圍 - 不須要升級
  • 紅色表示符合指定的語意化版本範圍 - 須要升級

執行 npm update 會升級全部的紅色依賴。

2.6 依賴版本選擇的最佳實踐

版本發佈

  • 對外部發佈一個正式版本的npm包時,把它的版本標爲1.0.0
  • 某個包版本發行後,任何修改都必須以新版本發行。
  • 版本號嚴格按照 主版本號.次版本號.修訂號 格式命名
  • 版本號發佈必須是嚴格遞增的
  • 發佈重大版本或版本改動較大時,先發布alpha、beta、rc等先行版本

依賴範圍選擇

  • 主工程依賴了不少子模塊,都是團隊成員開發的npm包,此時建議把版本前綴改成~,若是鎖定的話每次子依賴更新都要對主工程的依賴進行升級,很是繁瑣,若是對子依賴徹底信任,直接開啓^每次升級到最新版本。
  • 主工程跑在docker線上,本地還在進行子依賴開發和升級,在docker版本發佈前要鎖定全部依賴版本,確保本地子依賴發佈後線上不會出問題。

保持依賴一致

  • 確保npm的版本在5.6以上,確保默認開啓 package-lock.json 文件。
  • 由初始化成員執行 npm inatall 後,將 package-lock.json 提交到遠程倉庫。不要直接提交 node_modules到遠程倉庫。
  • 按期執行 npm update 升級依賴,並提交 lock 文件確保其餘成員同步更新依賴,不要手動更改 lock 文件。

依賴變動

  • 升級依賴: 修改 package.json文件的依賴版本,執行 npm install
  • 降級依賴: 直接執行 npm install package@version(改動package.json不會對依賴進行降級)
  • 注意改動依賴後提交lock文件

3、剖析 npm install 原理

![](https://picb.zhimg.com/80/v2-fb73956086aae2f5908bbd8410fbeb53_1440w.jpg)

npm install 大概會通過上面的幾個流程,這一章就來說一講各個流程的實現細節、發展以及爲什麼要這樣實現。

3.1 嵌套結構

咱們都知道,執行 npm install 後,依賴包被安裝到了 node_modules ,下面咱們來具體瞭解下,npm 將依賴包安裝到 node_modules 的具體機制是什麼。

npm 的早期版本, npm 處理依賴的方式簡單粗暴,以遞歸的形式,嚴格按照 package.json 結構以及子依賴包的 package.json 結構將依賴安裝到他們各自的 node_modules 中。直到有子依賴包不在依賴其餘模塊。

舉個例子,咱們的模塊 my-app 如今依賴了兩個模塊:bufferignore

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
  }
}
複製代碼

ignore是一個純 JS 模塊,不依賴任何其餘模塊,而 buffer 又依賴了下面兩個模塊:base64-jsieee754

{
  "name": "buffer",
  "dependencies": {
    "base64-js": "^1.0.2",
    "ieee754": "^1.1.4"
  }
}
複製代碼

那麼,執行 npm install 後,獲得的 node_modules 中模塊目錄結構就是下面這樣的:

![](https://pic2.zhimg.com/80/v2-d7da835e621c0579241326f45c08269b_1440w.jpg)

這樣的方式優勢很明顯, node_modules 的結構和 package.json 結構一一對應,層級結構明顯,而且保證了每次安裝目錄結構都是相同的。

可是,試想一下,若是你依賴的模塊很是之多,你的 node_modules 將很是龐大,嵌套層級很是之深:

![](https://pic1.zhimg.com/80/v2-8eca811ae31469bc34199c4787ab2f5c_1440w.jpg)
  • 在不一樣層級的依賴中,可能引用了同一個模塊,致使大量冗餘。
  • Windows 系統中,文件路徑最大長度爲260個字符,嵌套層級過深可能致使不可預知的問題。

3.2 扁平結構

爲了解決以上問題,NPM3.x 版本作了一次較大更新。其將早期的嵌套結構改成扁平結構:

  • 安裝模塊時,無論其是直接依賴仍是子依賴的依賴,優先將其安裝在 node_modules 根目錄。

仍是上面的依賴結構,咱們在執行 npm install 後將獲得下面的目錄結構:

![](https://pic1.zhimg.com/80/v2-2f49bf3c2b697738f973871723974437_1440w.jpg)
![](https://pic1.zhimg.com/80/v2-8a6918b1d37ec0adbc8f3bb09a62e7a7_1440w.jpg)

此時咱們若在模塊中又依賴了 base64-js@1.0.1 版本:

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
    "base64-js": "1.0.1",
  }
}
複製代碼
  • 當安裝到相同模塊時,判斷已安裝的模塊版本是否符合新模塊的版本範圍,若是符合則跳過,不符合則在當前模塊的 node_modules 下安裝該模塊。

此時,咱們在執行 npm install 後將獲得下面的目錄結構:

![](https://pic4.zhimg.com/80/v2-d842224747e17bd2512174de4fc8d723_1440w.jpg)
![](https://pic1.zhimg.com/80/v2-08abe604de74c679f2ee4ce702aa9e34_1440w.jpg)

對應的,若是咱們在項目代碼中引用了一個模塊,模塊查找流程以下:

  • 在當前模塊路徑下搜索
  • 在當前模塊 node_modules 路徑下搜素
  • 在上級模塊的 node_modules 路徑下搜索
  • ...
  • 直到搜索到全局路徑中的 node_modules

假設咱們又依賴了一個包 buffer2@^5.4.3,而它依賴了包 base64-js@1.0.3,則此時的安裝結構是下面這樣的:

![](https://pic2.zhimg.com/80/v2-9ee4ba8b5a28f91619b689cafed32b7d_1440w.jpg)

因此 npm 3.x 版本並未徹底解決老版本的模塊冗餘問題,甚至還會帶來新的問題。

試想一下,你的APP假設沒有依賴 base64-js@1.0.1 版本,而你同時依賴了依賴不一樣 base64-js 版本的 bufferbuffer2。因爲在執行 npm install 的時候,按照 package.json 裏依賴的順序依次解析,則 bufferbuffer2package.json 的放置順序則決定了 node_modules 的依賴結構:

先依賴buffer2

![](https://picb.zhimg.com/80/v2-57c40745ff1ef48b2465b6e8861cdec3_1440w.jpg)

先依賴buffer

![](https://pic2.zhimg.com/80/v2-eb4099221631e091a0ffe87a5a804a8e_1440w.jpg)

另外,爲了讓開發者在安全的前提下使用最新的依賴包,咱們在 package.json 一般只會鎖定大版本,這意味着在某些依賴包小版本更新後,一樣可能形成依賴結構的改動,依賴結構的不肯定性可能會給程序帶來不可預知的問題。

3.3 Lock文件

爲了解決 npm install 的不肯定性問題,在 npm 5.x 版本新增了 package-lock.json 文件,而安裝方式還沿用了 npm 3.x 的扁平化的方式。

package-lock.json 的做用是鎖定依賴結構,即只要你目錄下有 package-lock.json 文件,那麼你每次執行 npm install 後生成的 node_modules 目錄結構必定是徹底相同的。

例如,咱們有以下的依賴結構:

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
    "base64-js": "1.0.1",
  }
}
複製代碼

在執行 npm install 後生成的 package-lock.json 以下:

{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "base64-js": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz",
      "integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg="
    },
    "buffer": {
      "version": "5.4.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
      "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
      "requires": {
        "base64-js": "^1.0.2",
        "ieee754": "^1.1.4"
      },
      "dependencies": {
        "base64-js": {
          "version": "1.3.1",
          "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
          "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
        }
      }
    },
    "ieee754": {
      "version": "1.1.13",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
    },
    "ignore": {
      "version": "5.1.4",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
      "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A=="
    }
  }
}
複製代碼

咱們來具體看看上面的結構:

![](https://pic1.zhimg.com/80/v2-4aa8fa0e62c4a026111bf7801cd7c77c_1440w.jpg)

最外面的兩個屬性 nameversionpackage.json 中的 nameversion ,用於描述當前包名稱和版本。

dependencies 是一個對象,對象和 node_modules 中的包結構一一對應,對象的 key 爲包名稱,值爲包的一些描述信息:

  • version:包版本 —— 這個包當前安裝在 node_modules 中的版本
  • resolved:包具體的安裝來源
  • integrity:包 hash 值,基於 Subresource Integrity 來驗證已安裝的軟件包是否被改動過、是否已失效
  • requires:對應子依賴的依賴,與子依賴的 package.jsondependencies的依賴項相同。
  • dependencies:結構和外層的 dependencies 結構相同,存儲安裝在子依賴 node_modules 中的依賴包。

這裏注意,並非全部的子依賴都有 dependencies 屬性,只有子依賴的依賴和當前已安裝在根目錄的 node_modules 中的依賴衝突以後,纔會有這個屬性。

例如,回顧下上面的依賴關係:

![](https://pic1.zhimg.com/80/v2-08abe604de74c679f2ee4ce702aa9e34_1440w.jpg)

咱們在 my-app 中依賴的 base64-js@1.0.1 版本與 buffer 中依賴的 base64-js@^1.0.2 發生衝突,因此 base64-js@1.0.1 須要安裝在 buffer 包的 node_modules 中,對應了 package-lock.jsonbufferdependencies 屬性。這也對應了 npm 對依賴的扁平化處理方式。

因此,根據上面的分析, package-lock.json 文件 和 node_modules 目錄結構是一一對應的,即項目目錄下存在 package-lock.json 可讓每次安裝生成的依賴目錄結構保持相同。

另外,項目中使用了 package-lock.json 能夠顯著加速依賴安裝時間。

咱們使用 npm i --timing=true --loglevel=verbose 命令能夠看到 npm install 的完整過程,下面咱們來對比下使用 lock 文件和不使用 lock 文件的差異。在對比前先清理下npm 緩存。

不使用 lock 文件:

![](https://picb.zhimg.com/v2-3de6cffeec99e99371f0348da81035ec_b.jpg)

使用 lock 文件:

![](https://picb.zhimg.com/v2-544d2d5af4d191ccc8c102f56c29b135_b.jpg)

可見, package-lock.json 中已經緩存了每一個包的具體版本和下載連接,不須要再去遠程倉庫進行查詢,而後直接進入文件完整性校驗環節,減小了大量網絡請求。

使用建議

開發系統應用時,建議把 package-lock.json 文件提交到代碼版本倉庫,從而保證全部團隊開發者以及 CI 環節能夠在執行 npm install 時安裝的依賴版本都是一致的。

在開發一個 npm包 時,你的 npm包 是須要被其餘倉庫依賴的,因爲上面咱們講到的扁平安裝機制,若是你鎖定了依賴包版本,你的依賴包就不能和其餘依賴包共享同一 semver 範圍內的依賴包,這樣會形成沒必要要的冗餘。因此咱們不該該把package-lock.json 文件發佈出去( npm 默認也不會把 package-lock.json 文件發佈出去)。

3.4 緩存

在執行 npm installnpm update命令下載依賴後,除了將依賴包安裝在node_modules 目錄下外,還會在本地的緩存目錄緩存一份。

經過 npm config get cache 命令能夠查詢到:在 LinuxMac 默認是用戶主目錄下的 .npm/_cacache 目錄。

在這個目錄下又存在兩個目錄:content-v2index-v5content-v2 目錄用於存儲 tar包的緩存,而index-v5目錄用於存儲tar包的 hash

npm 在執行安裝時,能夠根據 package-lock.json 中存儲的 integrity、version、name 生成一個惟一的 key 對應到 index-v5 目錄下的緩存記錄,從而找到 tar包的 hash,而後根據 hash 再去找緩存的 tar包直接使用。

咱們能夠找一個包在緩存目錄下搜索測試一下,在 index-v5 搜索一下包路徑:

grep "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz" -r index-v5
複製代碼
![](https://picb.zhimg.com/80/v2-9ee25a740512d4ae457d48d31b643eb7_1440w.jpg)

而後咱們將json格式化:

{
  "key": "pacote:version-manifest:https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz:sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=",
  "integrity": "sha512-C2EkHXwXvLsbrucJTRS3xFHv7Mf/y9klmKDxPTE8yevCoH5h8Ae69Y+/lP+ahpW91crnzgO78elOk2E6APJfIQ==",
  "time": 1575554308857,
  "size": 1,
  "metadata": {
    "id": "base64-js@1.0.1",
    "manifest": {
      "name": "base64-js",
      "version": "1.0.1",
      "engines": {
        "node": ">= 0.4"
      },
      "dependencies": {},
      "optionalDependencies": {},
      "devDependencies": {
        "standard": "^5.2.2",
        "tape": "4.x"
      },
      "bundleDependencies": false,
      "peerDependencies": {},
      "deprecated": false,
      "_resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz",
      "_integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=",
      "_shasum": "6926d1b194fbc737b8eed513756de2fcda7ea408",
      "_shrinkwrap": null,
      "bin": null,
      "_id": "base64-js@1.0.1"
    },
    "type": "finalized-manifest"
  }
}
複製代碼

上面的 _shasum 屬性 6926d1b194fbc737b8eed513756de2fcda7ea408 即爲 tar 包的 hashhash的前幾位 6926 即爲緩存的前兩層目錄,咱們進去這個目錄果真找到的壓縮後的依賴包:

![](https://picb.zhimg.com/80/v2-f3fb960446d2d3ee3bc9f89824405bdf_1440w.jpg)

以上的緩存策略是從 npm v5 版本開始的,在 npm v5 版本以前,每一個緩存的模塊在 ~/.npm 文件夾中以模塊名的形式直接存儲,儲存結構是{cache}/{name}/{version}。

npm 提供了幾個命令來管理緩存數據:

  • npm cache add:官方解釋說這個命令主要是 npm 內部使用,可是也能夠用來手動給一個指定的 package 添加緩存。
  • npm cache clean:刪除緩存目錄下的全部數據,爲了保證緩存數據的完整性,須要加上 --force 參數。
  • npm cache verify:驗證緩存數據的有效性和完整性,清理垃圾數據。

基於緩存數據,npm 提供了離線安裝模式,分別有如下幾種:

  • --prefer-offline: 優先使用緩存數據,若是沒有匹配的緩存數據,則從遠程倉庫下載。
  • --prefer-online: 優先使用網絡數據,若是網絡數據請求失敗,再去請求緩存數據,這種模式能夠及時獲取最新的模塊。
  • --offline: 不請求網絡,直接使用緩存數據,一旦緩存數據不存在,則安裝失敗。

3.5 文件完整性

上面咱們屢次提到了文件完整性,那麼什麼是文件完整性校驗呢?

在下載依賴包以前,咱們通常就能拿到 npm 對該依賴包計算的 hash 值,例如咱們執行 npm info 命令,緊跟 tarball(下載連接) 的就是 shasum(hash) :

![](https://pic4.zhimg.com/80/v2-c91b4b2cfe672ff7c8db55592fc64224_1440w.jpg)

用戶下載依賴包到本地後,須要肯定在下載過程當中沒有出現錯誤,因此在下載完成以後須要在本地在計算一次文件的 hash 值,若是兩個 hash 值是相同的,則確保下載的依賴是完整的,若是不一樣,則進行從新下載。

3.6 總體流程

好了,咱們再來總體總結下上面的流程:

  • 檢查 .npmrc 文件:優先級爲:項目級的 .npmrc 文件 > 用戶級的 .npmrc 文件> 全局級的 .npmrc 文件 > npm 內置的 .npmrc 文件

  • 檢查項目中有無 lock 文件。

  • lock 文件:

  • npm 遠程倉庫獲取包信息

  • 根據 package.json 構建依賴樹,構建過程:

  • 構建依賴樹時,無論其是直接依賴仍是子依賴的依賴,優先將其放置在 node_modules 根目錄。

  • 當遇到相同模塊時,判斷已放置在依賴樹的模塊版本是否符合新模塊的版本範圍,若是符合則跳過,不符合則在當前模塊的 node_modules 下放置該模塊。

  • 注意這一步只是肯定邏輯上的依賴樹,並不是真正的安裝,後面會根據這個依賴結構去下載或拿到緩存中的依賴包

  • 在緩存中依次查找依賴樹中的每一個包

  • 不存在緩存:

  • npm 遠程倉庫下載包

  • 校驗包的完整性

  • 校驗不經過:

  • 從新下載

  • 校驗經過:

  • 將下載的包複製到 npm 緩存目錄

  • 將下載的包按照依賴結構解壓到 node_modules

  • 存在緩存:將緩存按照依賴結構解壓到 node_modules

  • 將包解壓到 node_modules

  • 生成 lock 文件

  • lock 文件:

  • 檢查 package.json 中的依賴版本是否和 package-lock.json 中的依賴有衝突。

  • 若是沒有衝突,直接跳過獲取包信息、構建依賴樹過程,開始在緩存中查找包信息,後續過程相同

![](https://picb.zhimg.com/80/v2-fb73956086aae2f5908bbd8410fbeb53_1440w.jpg)

上面的過程簡要描述了 npm install 的大概過程,這個過程還包含了一些其餘的操做,例如執行你定義的一些生命週期函數,你能夠執行 npm install package --timing=true --loglevel=verbose 來查看某個包具體的安裝流程和細節。

3.7 yarn

![](https://picb.zhimg.com/80/v2-44c3ecdb7ac0fbcae1d37eb8f76bfc24_1440w.jpg)

yarn 是在 2016 年發佈的,那時 npm 還處於 V3 時期,那時候尚未 package-lock.json 文件,就像上面咱們提到的:不穩定性、安裝速度慢等缺點常常會受到廣大開發者吐槽。此時,yarn 誕生:

![](https://pic3.zhimg.com/80/v2-6ccdf2832eb5d94dce06efbf61aac8b1_1440w.jpg)

上面是官網提到的 yarn 的優勢,在那個時候仍是很是吸引人的。固然,後來 npm 也意識到了本身的問題,進行了不少次優化,在後面的優化(lock文件、緩存、默認-s...)中,咱們多多少少能看到 yarn 的影子,可見 yarn 的設計仍是很是優秀的。

yarn 也是採用的是 npm v3 的扁平結構來管理依賴,安裝依賴後默認會生成一個 yarn.lock 文件,仍是上面的依賴關係,咱們看看 yarn.lock 的結構:

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

base64-js@1.0.1:
  version "1.0.1"
  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.0.1.tgz#6926d1b194fbc737b8eed513756de2fcda7ea408"
  integrity sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=

base64-js@^1.0.2:
  version "1.3.1"
  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==

buffer@^5.4.3:
  version "5.4.3"
  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
  integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
  dependencies:
    base64-js "^1.0.2"
    ieee754 "^1.1.4"

ieee754@^1.1.4:
  version "1.1.13"
  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==

ignore@^5.1.4:
  version "5.1.4"
  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
  integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
複製代碼

可見其和 package-lock.json 文件仍是比較相似的,還有一些區別就是:

  • package-lock.json 使用的是 json 格式,yarn.lock 使用的是一種自定義格式
  • yarn.lock 中子依賴的版本號不是固定的,意味着單獨又一個 yarn.lock 肯定不了 node_modules 目錄結構,還須要和 package.json 文件進行配合。而 package-lock.json 只須要一個文件便可肯定。

yarn 的緩策略看起來和 npm v5 以前的很像,每一個緩存的模塊被存放在獨立的文件夾,文件夾名稱包含了模塊名稱、版本號等信息。使用命令 yarn cache dir 能夠查看緩存數據的目錄:

![](https://pic2.zhimg.com/80/v2-8c0786d57b357bcfdff92d2774754e76_1440w.jpg)

yarn 默認使用 prefer-online 模式,即優先使用網絡數據,若是網絡數據請求失敗,再去請求緩存數據。

參考

相關文章
相關標籤/搜索