聊聊package.json 和 package-lock.json

 

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

先看下dependencies與devDependencies

npm install 安裝依賴的時候,能夠經過以下方式,把依賴寫入package.jsonreact

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

dependencies與devDependencies的區別:npm

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

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

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.二、3.0.一、2.1.1 之間有什麼不一樣呢。antd

語義化版本規則規定,版本格式爲:主版本號.次版本號.修訂號,而且版本號的遞增規則以下:app

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

主版本號的更新一般意味着大的修改和更新,升級主版本後可能會使你的程序報錯,所以升級主版本號需謹慎,可是這每每也會帶來更好的性能和體驗。ide

次版本號的更新則一般意味着新增了某些特性,好比 antd 的版本從 3.1.1 升級到 3.1.2,以前的 Select 組件不支持搜索功能,升級以後支持了搜索。性能

修訂號的更新則每每意味着進行了一些 bug 修復。所以次版本號和修訂號應該保持更新,這樣能讓你以前的代碼不會報錯還能獲取到最新的功能特性。

可是,每每咱們不會指定依賴的具體版本,而是指定版本範圍,好比上面的 package.json 文件裏的 react、redux 以及 lodash,這三個依賴分別使用了三個符號來代表依賴的版本範圍。語義化版本範圍規定:

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

所以,上面的 package.json 文件安裝的依賴版本範圍以下:

react@~16.0.1:>=react@16.0.1 && < react@16.1.0
redux@^3.7.2:>=redux@3.7.2 && < redux@4.0.0
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 後將獲得下面的目錄結構:

此時咱們若在模塊中又依賴了 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 後將獲得下面的目錄結構:

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

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

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

因此 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 中依賴的 base64-js@1.0.1 版本與 buffer 中依賴的 base64-js@^1.0.2 發生衝突,因此 base64-js@1.0.1 須要安裝在 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也是能夠的)。

相關文章
相關標籤/搜索