聊聊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:>=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也是可以的)。

參考鏈接:http://www.conardli.top/blog/article/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM