1. 程式人生 > 其它 >聊聊package.json 和 package-lock.json

聊聊package.json 和 package-lock.json

原文連結:https://www.cnblogs.com/yalong/p/15013880.html

package.json 跟 package-lock.json 的知識點挺多的, 這裡只聊一聊部分知識點

先看下dependencies與devDependencies

npm install 安裝依賴的時候,可以通過如下方式,把依賴寫入package.json

npm install --save     或者  npm install -S
npm install --save-dev 或者  npm install -D

dependencies與devDependencies的區別:

devDependencies下列出的模組,是我們開發時用的依賴項,像一些進行單元測試之類的包,比如jest,我們用寫單元測試,它們不會被部署到生產環境。dependencies下的模組,則是我們生產環境中需要的依賴,即正常執行該包時所需要的依賴項。

記著這句: "正常執行該包時所需要的依賴項"
後面的package-lock.json 中的 dependencies 對應的就是package.json中的 dependencies

Package.json 語義化版本

使用第三方依賴時,通常需要指定依賴的版本範圍,比如

"dependencies": {
    "antd": "3.1.2",
    "react": "~16.0.1",
    "redux": "^3.7.2",
    "lodash": "*"
  }

上面的 package.json 檔案表明,專案中使用的 antd 的版本號是 3.1.2,但是 3.1.1 和 3.1.2、3.0.1、2.1.1 之間有什麼不同呢。

語義化版本規則規定,版本格式為:主版本號.次版本號.修訂號,並且版本號的遞增規則如下:

  • 主版本號:當你做了不相容的 API 修改
  • 次版本號:當你做了向下相容的功能性新增
  • 修訂號:當你做了向下相容的問題修正

主版本號的更新通常意味著大的修改和更新,升級主版本後可能會使你的程式報錯,因此升級主版本號需謹慎,但是這往往也會帶來更好的效能和體驗。

次版本號的更新則通常意味著新增了某些特性,比如 antd 的版本從 3.1.1 升級到 3.1.2,之前的 Select 元件不支援搜尋功能,升級之後支援了搜尋。

修訂號的更新則往往意味著進行了一些 bug 修復。因此次版本號和修訂號應該保持更新,這樣能讓你之前的程式碼不會報錯還能獲取到最新的功能特性。

但是,往往我們不會指定依賴的具體版本,而是指定版本範圍,比如上面的 package.json 檔案裡的 react、redux 以及 lodash,這三個依賴分別使用了三個符號來表明依賴的版本範圍。語義化版本範圍規定:

  • ~:只升級修訂號
  • ^:升級次版本號和修訂號
  • *:升級到最新版本

因此,上面的 package.json 檔案安裝的依賴版本範圍如下:

react@~16.0.1:>[email protected] && < [email protected]
redux@^3.7.2:>[email protected] && < [email protected]
lodash@*:lodash@latest

語義化版本規則定義了一種理想的版本號更新規則,希望所有的依賴更新都能遵循這個規則,但是往往會有許多依賴不是嚴格遵循這些規定的。
因此,如何管理好這些依賴,尤其是這些依賴的版本就顯得尤為重要,否則一不小心就會陷入因依賴版本不一致導致的各種問題中。

npm的歷史變遷

巢狀結構

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

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

舉個例子,我們的模組 my-app 現在依賴了兩個模組:buffer、ignore:

{
  "name": "my-app",
  "dependencies": {
    "buffer": "^5.4.3",
    "ignore": "^5.1.4",
  }
}

ignore是一個純 JS 模組,不依賴任何其他模組,而 buffer 又依賴了下面兩個模組:base64-js 、 ieee754。

{
  "name": "buffer",
  "dependencies": {
    "base64-js": "^1.0.2",
    "ieee754": "^1.1.4"
  }
}

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

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

但是,試想一下,如果你依賴的模組非常之多,你的 node_modules 將非常龐大,巢狀層級非常之深:

  • 在不同層級的依賴中,可能引用了同一個模組,導致大量冗餘。
  • 在 Windows 系統中,檔案路徑最大長度為260個字元,巢狀層級過深可能導致不可預知的問題。

扁平結構

為了解決以上問題,NPM 在 3.x 版本做了一次較大更新。其將早期的巢狀結構改為扁平結構:

  • 安裝模組時,不管其是直接依賴還是子依賴的依賴,優先將其安裝在 node_modules 根目錄。
    還是上面的依賴結構,我們在執行 npm install 後將得到下面的目錄結構:

此時我們若在模組中又依賴了 [email protected] 版本:

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

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

對應的,如果我們在專案程式碼中引用了一個模組,模組查詢流程如下:

  • 在當前模組路徑下搜尋
  • 在當前模組 node_modules 路徑下搜素
  • 在上級模組的 node_modules 路徑下搜尋
  • ...
  • 直到搜尋到全域性路徑中的 node_modules

假設我們又依賴了一個包 buffer2@^5.4.3,而它依賴了包 [email protected],則此時的安裝結構是下面這樣的:

所以 npm 3.x 版本並未完全解決老版本的模組冗餘問題,甚至還會帶來新的問題。

另外,為了讓開發者在安全的前提下使用最新的依賴包,我們在 package.json 通常只會鎖定大版本,這意味著在某些依賴包小版本更新後,同樣可能造成依賴結構的改動,依賴結構的不確定性可能會給程式帶來不可預知的問題。

Lock檔案(package-lock.json)

為了解決 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=="
    }
  }
}

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

最外面的兩個屬性 name 、version 同 package.json 中的 name 和 version ,用於描述當前包名稱和版本。

dependencies 是一個物件,物件和 node_modules 中的包結構一一對應,物件的 key 為包名稱,值為包的一些描述資訊:

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

這裡注意,並不是所有的子依賴都有 dependencies 屬性,只有子依賴的依賴和當前已安裝在根目錄的 node_modules 中的依賴衝突之後,才會有這個屬性。

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

我們在 my-app 中依賴的 [email protected] 版本與 buffer 中依賴的 base64-js@^1.0.2 發生衝突,所以 [email protected] 需要安裝在 buffer 包的 node_modules 中,對應了 package-lock.json 中 buffer 的 dependencies 屬性。這也對應了 npm 對依賴的扁平化處理方式。

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

package-lock.json使用建議

開發系統應用時,建議把 package-lock.json 檔案提交到程式碼版本倉庫,從而保證所有團隊開發者以及 CI 環節可以在執行 npm install 時安裝的依賴版本都是一致的。

在開發一個 npm包 時,你的 npm包 是需要被其他倉庫依賴的,由於上面我們講到的扁平安裝機制,如果你鎖定了依賴包版本,你的依賴包就不能和其他依賴包共享同一 semver 範圍內的依賴包,這樣會造成不必要的冗餘。所以我們不應該把package-lock.json 檔案釋出出去( npm 預設也不會把 package-lock.json 檔案釋出出去,當然如果非要用package-lock.json也是可以的)。

參考連結:http://www.conardli.top/blog/article/