yarn or npm 版本固化如何選擇


前言

作為前端開發者,npm這個包管理工具的重要性顯而易見。優點不再表述,但一些缺點是為使用者詬病比較多的:速度慢、版本控制。下面主要討論下npm的版本固化問題,即lock文件。

npm語義化版本管理

對於npm來說,依賴相關的信息體現在package.json的dependencies里,這里使用了Semver(語義化版本來控制)關於語義化版本的規范可以查看
大致准則如下:

  • 軟件的版本通常由三位組成,形如:X.Y.Z
  • 版本是嚴格遞增的,此處是:16.2.0 -> 16.3.0 -> 16.3.1
  • 在發布重要版本時,可以發布alpha, rc等先行版本
  • alpha和rc等修飾版本的關鍵字后面可以帶上次數和meta信息

版本格式:

發布者應該關注的是版本格式的規則

主版本號.次版本號.修訂號

不同版本號遞增規則如下:

  • 主版本號(major):當你做了不兼容的 API 修改,
  • 次版本號(minor):當你做了向下兼容的功能性新增,可以理解為Feature版本,
  • 修訂號(patch):當你做了向下兼容的問題修正,可以理解為Bug fix版本。

package.json里面的依賴版本要求遵循上述規則的。
這樣才能保證使用者引到期望的版本。

版本控制符

對於使用者來說,版本前面的控制符是需要關注的,這決定引用依賴是否與期望相同。
npm 支持的符號是比較豐富的,下面的版本符號均支持:

{ "dependencies" :
  { "foo" : "1.0.0 - 2.9999.9999",// 大於等於1.0.0 小於 2.9999.9999
  "bar" : ">=1.0.2 <2.1.2", // 比較清晰  左閉右開
  "baz" : ">1.0.2 <=2.3.4", // 左開右閉
   "boo" : "2.0.1", // 規定版本  
   "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0", // 表達式也算清晰
   "asd" : "http://asdf.com/asdf.tar.gz"// 指定下載地址代替版本
    , "til" : "^1.2.3"// 同一主版本號,不小於1.2.3 即 1.x.y  x>=2 y>=3
  , "elf" : "~1.2.3" // 同一主版本和次版本號 即1.2.x x>= 2
  , "two" : "2.x" // 這個比較形象,x>=0  即2.0.0 以上均可
  , "thr" : "3.3.x" // 同上 x>= 0  即3.3.0 以上
  , "lat" : "latest" // 最新版本
  , "dyl" : "file:../dyl" // 從本地下載
  }
}

根據上面的注釋,應該能看清不同符號的意思了。這里有一篇文章比較生動的闡述了不同符號的范圍,有興趣可以詳細看下

npm install 默認使用^

這樣的目的在於接受指定版本的更新,例如依賴包的優化和小版本更新。

問題

不同環境依賴不一致

從上面可以看到,語義化版本是沒有強制約束的,需要開發者自覺遵守規范定義。
常見情況如下:

測試環境完成之后上線出問題,細究原因在於上線前某個依賴版發布了不兼容或者有bug 版本,恰好在發布時裝了新版本。

所以才會有下面固化版本即鎖版本需求的出現。

固化版本,保證不同環境或者時間安裝的都是相同依賴。至於是否都應該固化版本,下面再討論。

固化版本方式

固話版本,有下面三種方式:

npm-shrinkwrap.json

該方式是比較早的鎖定版本的方式,
與package-lock.json功能類似,區別在於npm包發布的時候可以發布上去。

推薦的使用情況是,通過倉庫上的發布過程來部署的應用,即非庫或者工具類。
例如:emons和命令行工具,想要被全局安裝或者依賴,此時強烈不建議坐着發布該文件,因為將會阻止終端用戶控制傳遞依賴的更新。

另外如果package-lock.json和npm-shrinkwrap.json同時存在於項目根目錄,package-lock.json將會被忽略。

即該方式會將鎖版本依賴通過npm發布,所以類庫或者組件需要慎重。

使用方式:

// 生成依賴  默認不包括dev dependencies
npm shrinkwrap
// 將dev-dependencies計算在內
npm shrinkwrap--dev 

package-lock.json

相對於npm-shrinkwrap ,其不會被發布到npm,適用於應用程序,即我們非工具類的項目。

npm5 以后 依賴都會默認增加該文件,不過迭代了這么多版本,不同版本npm對package-lock.json的實現是不同的。是在一直迭代和發展的

1、npm 5.0.x 版本,
不管package.json怎么變,npm i 時都會根據lock文件下載。

2、5.1.0版本后
npm install 會無視lock文件 去下載最新的npm包

3、5.4.2版本
如果改了package.json,且package.json和lock文件不同,那么執行npm i時npm會根據package中的版本號以及語義含義去下載最新的包,並更新至lock。
如果兩者是同一狀態,那么執行npm i 都會根據lock下載,不會理會package實際包的版本是否有新。
該段內容參考自知乎用戶,詳情請轉https://www.zhihu.com/question/264560841

這樣帶來一個問題,不同環境不同npm版本,對於同一項目,依賴還是可能不同的。。。。

非扁平依賴

對於同一npm包不同版本的管理,npmlock是非完全扁平化的處理:
所有的包的依賴順序列出來,第一次出現的包名會提升到頂層,后面重復出現的將會放入被依賴包的node_modules當中
例如下面這個例子:
第一個依賴,提升為頂層依賴

// 頂層聲明了loader-utils的依賴,版本為1.0.4
  "loader-utils": {
      "version": "1.0.4",
      "resolved": "http://r.npm.sankuai.com/loader-utils/download/loader-utils-1.0.4.tgz",
      "integrity": "sha1-E/Vhl/FSOjBYkSSLTHJEVAhIQmw=",
      "requires": {
        "big.js": "^3.1.3",
        "emojis-list": "^2.0.0",
        "json5": "^0.5.0"
      }
    }
    }

對於頂級依賴滿足需求的,則不再安裝、

"sass-loader": {
      "version": "7.1.0",
      "resolved": "http://r.npm.sankuai.com/sass-loader/download/sass-loader-7.1.0.tgz",
      "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=",
      "dev": true,
      "requires": {
         // ^1.0.1 頂級依賴滿足需求
        "loader-utils": "^1.0.1"
      }
    }

對於某些依賴不滿足的,則會在對應文件夾下面根據依賴安裝符合版本。例如less-loader

"less-loader": {
      "version": "4.1.0",
      "resolved": "http://r.npm.sankuai.com/less-loader/download/less-loader-4.1.0.tgz",
      "requires": {
        // 1.0.4 不滿足 ^1.1.0
        "loader-utils": "^1.1.0",
      },
      "dependencies": {
        "loader-utils": {
          "version": "1.2.3",
          "resolved": "http://r.npm.sankuai.com/loader-utils/download/loader-utils-1.2.3.tgz",
          "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=",
          "dev": true,
          "requires": {
            "big.js": "^5.2.2",
            "emojis-list": "^2.0.0",
            "json5": "^1.0.1"
          }
        }
     }
    }

package-lock.json和npm-shrinkwrap.json差別

  • package-lock.json不會被發布到npm,而npm-shrinkwrap會被默認發布
  • 非頂層的package-lock.json會被忽略,而相同狀態的shrinkwrap文件都會被保留。
  • npm-shrinkwrap.json在npm 2,3,4版本均支持,package-lock.json是npm5以后引入
  • 兩者同時存在,npm-shrinkwrap.json的優先級高於package-lock.json

yarn.lcok

yarn畢竟是針對npm的缺點而生,所以其自帶版本控制,默認依賴都會生成yarn.lock文件,該文件會通過包名+版本來確定具體信息。

yarn-lock語法

Yarn 用的是自己設計的格式,語法上有點像 YAML(Yarn 2.0 中將會采用標准的 YAML)。# 開頭的行是注釋。

第一行記錄了包的名稱及其語義化版本(由 package.json 定義)。

接下來的都做了縮進,表示這些是該包的信息。

version 字段記錄了包的確切版本。

resolved 字段記錄了包的 URL。此外,hash 中的值是 shasum。Yarn 記錄的這個 shasum 來自於包的 versions[:version].dist.shasum(手動訪問 https://registry.npmjs.org/:package 會得到一個 JSON,解析此 JSON 可得)

dependencies 記錄了包的依賴。也許包的依賴還有依賴,但不會在這里記錄。
如下所示:

pkg-dir@^1.0.0:
  version "1.0.0"
  resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
  integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q=
  dependencies:
    find-up "^1.0.0"

pkg-dir@^2.0.0:
  version "2.0.0"
  resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
  integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
  dependencies:
    find-up "^2.1.0"

pkg-dir@^3.0.0:
  version "3.0.0"
  resolved "http://r.npm.sankuai.com/pkg-dir/download/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
  integrity sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=
  dependencies:
    find-up "^3.0.0"

不過Yarn 僅以 flatten 格式 描述各個包之間的依賴關系,並依賴於其當前實現來創建目錄結構。這意味着如果其內部算法發生變化,結構也會發生變化。

提升
你會發現,有很多包你是沒有直接依賴它們的,但它們都出現在了 yarn.lock 中的頂層。這就是提升,它有兩個意義:

  • 記錄依賴的依賴
    正如上面所述,依賴的依賴不會被直接記錄在依賴的信息下——它們會被提升,這樣可以簡化整個 yarn.lock,到時安裝依賴的時候處理也變得簡單,因為你不必一層一層的嵌套下去來查找依賴的依賴的信息。

  • 便於解決依賴版本沖突
    依賴版本沖突是難免的,當然有時候並不是版本沖突,而只是語義化版本格式的版本記錄不同。舉個例子,^5.0.0 與 5.x.x 在很多時候並不矛盾,因此信息可以被合並。如:

chalk@^2.0.0, chalk@^2.0.1:
  version "2.3.2"
  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
  dependencies:
    ansi-styles "^3.2.1"
    escape-string-regexp "^1.0.5"
    supports-color "^5.3.0"

注意第一行,yarn.lock 記錄了 ^2.0.0 和 ^2.0.1,而在添加 chalk 這個依賴的時候,符合語義化版本的最新版本是 2.3.2(version 字段),這個版本對於 ^2.0.0 和 ^2.0.1 這兩個要求來說,都滿足了,因此信息可以合並。

對於固化還是建議使用yran.lock實現,npm的lock在不同版本下存在的差異讓人頭疼。

是否應鎖版本

這個爭論是很正常的,開始使用時,我們也有過這樣的討論。
大家可以看下我們的場景再討論:
1、組內項目都依賴了自己開發的一個工具包a
2、該工具類依賴了一些第三方開源包
場景一:
當時某個知名包升級之后移除了某項功能的支持,被a依賴,導致該段時間后上線的項目全都出了問題。

場景二:
a發現出了個bug,統一修復,各個業務項目無需自行修改。

結合來看還是要具體分析,對於自行維護或者確認無誤的項目可以不鎖版本。對於第三方需要鎖版本,保證當前是可用的。對於后期的bug修復,不自行升級,對於bugfix等小版本升級,驗證完成后再次鎖版本。

.gitignore是否應該忽略lock文件

對於 是否應該package-lock.json 不應寫進 .gitignore,可以看下賀師俊大佬的解釋:
如果你使用 lock 機制,則應該將 package-lock.json 提交到 repo 中。比如 Vue 采取了該策略。如果你不使用 lock 機制,則應該加入 .npmrc 文件,內容為 package-lock=false ,並提交到 repo 中。比如 ESLint 采取了該策略。

還是回到了那個問題,是否應該鎖版本。
對於類庫而言,鎖定依賴版本是 絕對不可行 的。否則只要應用中使用了兩個以上的依賴,都有概率出現絕對不存在可兼容版本的情況。這樣只是單純的把問題拋給了最終應用,並沒有解決問題。
最終應用是否鎖也有待考慮。

問題出在源碼的可靠性不得到保證,本身語義化沒有問題。但是又bug正常,所以業務項目才鎖

結束語

參考文章

https://docs.npmjs.com/files/package.json

https://juejin.im/post/5ad413ba6fb9a028b5485866#heading-1
https://stackoverflow.com/questions/44258235/what-is-the-difference-between-npm-shrinkwrap-json-and-package-lock-json

針對是否應該固化版本和如何固化版本,因為水平有限也只是給出了自己的一點看法。希望能對有需要的同學有所幫助。


免責聲明!

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



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