[譯] 理解 NPM 5 中的 lock 檔案

歐長坤發表於2017-06-16

理解 NPM 5 中的 lock 檔案

NPM 的下個主版本(NPM 5)在速度、安全性和一堆其他時髦的東西上,相比較前一個版本帶來了一些改進。然而從使用者的角度來看,最突出的就是全新的 lock 檔案,不止一個 lock 檔案。我們一會兒再談論這個。對於新手來說,一個 package.json 檔案使用了語義化版本規範,去描述對於其他包的直接依賴,而這些包可能依賴於其他包等等,以此類推。lock 檔案則是整個依賴關係樹的快照,包含了所有包及其解析的版本。

與之前版本相反,lock 檔案現在包含一個 integrity 欄位,它使用 Subresource Integrity 來驗證已安裝的軟體包是否被改動過,換句話來說,驗證包是否已失效。它依舊支援舊版本 NPM 中對包的加密演算法 SHA-1,但是以後將預設使用 SHA-512 進行加密。

這個檔案目前取消from 欄位。眾所周知,這個欄位和時常發生不一致的 version 欄位一起,給程式碼審檢視檔案改動差異時,帶來了不少痛苦。不過現在應該變得更加整潔了。

該檔案現在增加了 lockfileVersion 欄位來指定的 lock 格式的版本,並將其設定為1。這是為了使將來的格式更新時,不用去猜測該檔案使用什麼特定版本。以前的 lock 格式仍然支援並被識別為版本 0

{
  "name": "package-name",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "dependencies": {
    "cacache": {
      "version": "9.2.6",
      "resolved": "https://registry.npmjs.org/cacache/-/cacache-9.2.6.tgz",
      "integrity": "sha512-YK0Z5Np5t755edPL6gfdCeGxtU0rcW/DBhYhYVDckT+7AFkCCtedf2zru5NRbBLFk6e7Agi/RaqTOAfiaipUfg=="
    },
    "duplexify": {
      "version": "3.5.0",
      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
      "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
      "dependencies": {
        "end-of-stream": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
          "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4="
        },複製程式碼

你可能已經注意到了,指向特定 URI 的檔案的 resolved 欄位仍然得到了保留。注意,NPM 現在可以(根據 .npmrc 中的設定)解析機器配置使用的不同倉庫,這樣的話,與 integrity 欄位一起配合,只要簽名是匹配的,包的來源並無關緊要。

值得一提的是,lock 檔案精確描述了 node_modules 目錄中所列出的目錄的物理樹。其優點是,即使不同的開發人員使用不同版本的 NPM,他們仍然不僅能夠得到相同版本的依賴,還可以使用完全相同的目錄樹。 這與其他包管理器(如 Yarn )不同。 Yarn 僅以 flatten 格式 描述各個包之間的依賴關係,並依賴於其當前實現來建立目錄結構。這意味著如果其內部演算法發生變化,結構也會發生變化。如果你想了解更多關於 Yarn 和 NPM 5 之間 lock 檔案的區別,請檢視 Yarn determinism

雙 lock 檔案

上面已經提到過 lock 檔案不止一個。當安裝新的依賴關係或檔案不存在時,NPM 將自動生成一個名為 package-lock.json 的 lock 檔案。如開始所述,lock 檔案是當前依賴關係樹的快照,允許不同機器間的重複構建。因此,建議將它新增到您的版本控制中去。

你可能會認為,使用 npm shrinkwrap 及其 npm-shrinkwrap.json 可以實現同樣的效果。你的想法沒錯,但建立新 lock 檔案的原因是,這樣能夠更好的傳達一個資訊,就是 NPM 真正支援了 locking 機制,這在以前確實是一個顯著的問題。

不過還是有一些區別。首先,NPM 強制該 package-lock.json 不會被髮布。 即使你將其顯式新增到軟體包的 files 屬性中,它也不會是已釋出軟體包的一部分。這種情況同樣不適用於 npm-shrinkwrap.json 檔案,哪怕這個檔案可以是釋出包的一部分、即便存在巢狀的依賴關係,NPM 也會遵守它。你可以簡單的通過執行 npm pack 來檢視生成的歸檔內部的內容。

接下來,您可能會想知道在已經包含 package-lock.json 的目錄中執行 npm shrinkwrap 時會發生什麼。答案很簡單,NPM 僅僅會把 package-lock.json 重新命名為 npm-shrinkwrap.json。因為檔案的格式是完全一樣的。

最好奇的還會問,當兩個檔案都存在時會發生什麼。 在這種情況下,NPM將完全忽略 package-lock.json,只使用 npm-shrinkwrap.json。 當只使用 NPM 操縱檔案時,這種情況不應該發生。

總結:

  • NPM 會在安裝包時自動建立 package-lock.json,除非已經有 npm-shrinkwrap.json,並在必要時更新它。

  • 新的 package-lock.json 永遠不會被髮布,而且應該將其新增到你的版本控制系統中去。

  • 執行已經帶有 package-lock.json 檔案的 npm shrinkwrap 命令將只會對其重新命名為 npm-shrinkwrap.json

  • 當兩個檔案處於某些原因同時存在時,package-lock.json 將被忽略。

這很酷,但是什麼時候使用新的 lock 檔案而不是舊的 shrinkwrap? 它通常取決於您正在處理的包的型別。

當開發庫時

如果你正在開發一個庫(如其他人所依賴的軟體包),則應使用新的 lock 檔案。 另一種替代方案是使用 shrinkwrap,並確保它不會隨包釋出(新的 lock 檔案不會自動釋出)。 但為什麼不釋出 shrinkwrap 呢? 這是因為 NPM 遵守在包中找到的 shrinkwraps,並且由於 shrinkwrap 總是指向單個包的特定版本,所以你無法利用 NPM 可以使用相同的包來滿足多個包的要求(在 semver 允許範圍內)的優勢。 換句話說,通過不去強制 NPM 來安裝特定的版本,您可以讓 NPM 更好的複用包,並使結果更小更快地組合。

這裡有一個警告。當你正在開發庫時,因為倉庫中存在 package-lock.jsonnpm-shrinkwrap.json,所以每次都會獲得完全相同的依賴關係,這對於你的持續整合伺服器也是如此。現在想象你的 package.json 指定某個包的依賴關係為 ^1.0.0,也恰好是 lock 檔案中指定的版本,並且每次安裝。到目前為止一切正常。但如果依賴項釋出了一個新版本,並且意外的破壞了 semver 和你開發的包,這時候會發生什麼?

遺憾的是,在出現錯誤報告之前,你可能無法注意到這個問題。在沒有 lock 檔案的倉庫中,你的構建至少在 CI 伺服器上會失敗,因為它總是嘗試去安裝依賴的 latest 版本,從而執行出錯的版本(只要該版本定期執行,而不僅僅是針對 PR)。 然而,當 lock 檔案出現後,它將始終安裝能正常工作的被 lock 的版本。

然而,對於這個問題有幾個其他的解決方案。 首先,你可以犧牲問題重現的精確性,而將 lock 檔案新增到版本控制系統中。 其次,你可以做一個分離的配置來進行構建,在執行測試之前執行 npm update。 第三,你可以簡單的在你執行測試之前刪除 lock。 如何處理發現的損壞依賴是另一個話題了,其主要原因是因為 NPM 實現的 semver 不僅沒有涉及如此廣範圍的問題,而且還不支援特定版本的黑名單特性。

這當然就會引起一個問題,在開發庫的時候,是否真的值得將 lock 檔案新增到版本控制中去。要記住的是,lock 檔案不僅包含依賴關係,還包含 dev 的依賴關係。在這種意義下來講,開發庫與開發應用時類似(見下一節),無論什麼時候都有著完全相同的 dev 依賴關係,並且不同裝置也算一種優勢。

當開發應用時

好,那麼終端使用者在終端中使用的包或打包的可執行檔案會是個什麼情況?在這種情況下,包就是最終結果,即應用。你想要確保終端使用者總能獲得你釋出時所具有的確切依賴性。確保在安裝時讓 NPM 遵守規則,這就是您想要使用 shrinkwrap 的地方。 記住,使用 npm pack 釋出包時,你可以隨時檢視軟體包的情況。

注意,在 package.json 中指定一個特定版本依賴是不夠的,因為你希望確保終端使用者獲得完全相同的依賴關係樹,包括其所有子依賴關係。而 package.json 中的一個特定版本保證只會發生在頂層。

其他型別的應用怎麼樣,比如在倉庫內啟動的專案?這種情況並不重要。重要的是安裝正確的依賴項,而兩個 lock 都滿足這一點要求。隨你怎麼選。

結束

沒了,就這麼多。如果有哪裡不對或者有一些一般性的意見,請隨時在 Twitter 上聯絡我。如果你發現拼寫錯誤或語法問題,則可以在 GitHub 上找到這個文章。感謝你的幫助!

如果你喜歡這篇文章,你可以在 Twitter 上關注 @JiriPospisil 並通過 feed 訂閱。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章