<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          很多人上來就刪除的package-lock.json,還有這么多你不知道的(深度內(nèi)容)

          共 10831字,需瀏覽 22分鐘

           ·

          2021-04-16 22:48

          作者:wuwhs

          原文:https://segmentfault.com/a/1190000039684460

          點(diǎn)擊上方“前端簡報(bào)”,進(jìn)行關(guān)注

          第一時(shí)間關(guān)注技術(shù)干貨!

          0. 前言

          看完本文,你將從整體了解依賴版本鎖定原理,package-lock.json 或 yarn.lock 的重要性。首先要從最近接連出現(xiàn)兩起有關(guān) npm 安裝 package.json 中依賴包,由于依賴包版本更新 bug 造成項(xiàng)目出錯(cuò)問題說起。

          事件一:新版本依賴包本身 bug

          項(xiàng)目本地打包正常,但是線上使用 Jenkins 完成 DevOps 交付流水線打包出錯(cuò)問題。報(bào)出如下錯(cuò)誤:

          **17:15:32**  ERROR in ./node_modules/clipboard/dist/clipboard.js
          **17:15:32** Module build failed (from ./node_modules/babel-loader/lib/index.js):
          **17:15:32** Error: Couldn't find preset "@babel/env" relative to directory "/app/workspace/SIT/node_modules/clipboard"

          顯示錯(cuò)誤原因是 clipboard 插件沒有安裝 @babel/env 預(yù)設(shè)(preset)。明顯這個(gè)是插件問題了,去官方庫 `clipboard`[1] 查看源碼發(fā)現(xiàn)該庫依賴包很少,大部分是原生實(shí)現(xiàn)。再看 issue 別人有沒有出現(xiàn)同樣的問題,目前來看還沒有人提出。以此推斷可能是插件本身的 "問題" 了。

          但是我本地項(xiàng)目打包正常,線上的出錯(cuò),可能由于本地版本和線上版本不一致導(dǎo)致(某個(gè)小版本出現(xiàn)的 bug)的。通過查看package.json 配置的 clipboard: "^2.0.4",線上實(shí)際安裝版本是 2.0.7,而我本地實(shí)際安裝版本是 2.0.6因此定位到 2.0.7 出現(xiàn)的 “問題”。

          由于是插件本身“問題”,我的臨時(shí)解決辦法是鎖定到 2.0.4 版本,也就是 clipboard: "2.0.4",后面加上 package-lock.json。

          打破沙鍋問到底既然“問題”已經(jīng)定位到了 2.0.7 版本,進(jìn)一步通過對(duì)比此次版本提交文件內(nèi)容差異,發(fā)現(xiàn) .babelrc 文件用到的 preset 是 env

          2.0.7 版本用的是 @bable/env,將 babel 更新到了 7!

          問題基本定位到了,這里就順便給作者提了一個(gè) `issues`[2]

          事件二:依賴包的新版插件 bug

          一直正常使用的 braft-editor 優(yōu)秀的富文本編輯器插件,最近在其他小伙伴電腦或者在我本地電腦重新部署項(xiàng)目,啟動(dòng)后發(fā)現(xiàn) toHtml() 方法獲取富文本 html 內(nèi)容總是空!

          歷史版本是正常的,猜測可能又是版本更新造成。同樣的,去官方庫 braft\-editor[3]看看 issues 別人有沒有遇到同樣的問題。果然這次有,原因是它的依賴包 `draft-js`[4]更新后的問題,具體的看這個(gè) `issues`[5]。

          這個(gè)是由于插件的依賴包更新出現(xiàn)的問題,直接去鎖定當(dāng)前插件沒有作用,不會(huì)對(duì)它的依賴包產(chǎn)生約束(依賴包還是會(huì)去下載最新版本的包)。我的臨時(shí)解決辦法是嘗試將版本回退到后一個(gè)版本并鎖定。這樣做的原因是回退版本的依賴包版本肯定會(huì)低于現(xiàn)在的,之前的版本是正常的。

          經(jīng)驗(yàn)教訓(xùn)

          其實(shí)這兩起事件是同一個(gè)誘因?qū)е碌模簺]有鎖定當(dāng)前項(xiàng)目依賴樹模塊的版本。下面就來探究一下依賴包的版本管理。

          1. 語義化版本(semver)

          package.json 在前端工程化中主要用來記錄依賴包名稱、版本、運(yùn)行指令等信息字段。其中,dependencies 字段指定了項(xiàng)目運(yùn)行所依賴的模塊,devDependencies 指定項(xiàng)目開發(fā)所需要的模塊。它們都指向一個(gè)對(duì)象。該對(duì)象的各個(gè)成員,分別由模塊名和對(duì)應(yīng)的版本要求組成,表示依賴的模塊及其版本范圍。對(duì)應(yīng)的版本可以加上各種限定,主要有以下幾種:

          • 指定版本:比如 1.2.2 ,遵循“大版本.次要版本.小版本”的格式規(guī)定,安裝時(shí)只安裝指定版本。
          • 波浪號(hào)(tilde)+指定版本:比如 ~1.2.2 ,表示安裝 1.2.x 的最新版本(不低于1.2.2),但是不安裝 1.3.x,也就是說安裝時(shí)不改變大版本號(hào)和次要版本號(hào)。
          • 插入號(hào)(caret)+指定版本:比如 ?1.2.2,表示安裝 1.x.x 的最新版本(不低于 1.2.2),但是不安裝 2.x.x,也就是說安裝時(shí)不改變大版本號(hào)。需要注意的是,如果大版本號(hào)為 0,則插入號(hào)的行為與波浪號(hào)相同,這是因?yàn)榇藭r(shí)處于開發(fā)階段,即使是次要版本號(hào)變動(dòng),也可能帶來程序的不兼容。
          • latest:安裝最新版本。

          當(dāng)我們使用比如 npm install package -save 安裝一個(gè)依賴包時(shí),版本是插入號(hào)形式。這樣每次重新安裝依賴包 npm install 時(shí)”次要版本“和“小版本”是會(huì)拉取最新的。一般的,主版本不變的情況下,不會(huì)帶來核心功能變動(dòng),API 應(yīng)該兼容舊版,但是這在開源的世界里很難控制,尤其在復(fù)雜項(xiàng)目的眾多依賴包中難免會(huì)引入一些意想不到的 bug。

          2. npm-shrinkwrap && package-lock

          npm-shrinkwrap

          正是存在這每次重新安裝,依賴樹模塊版本存在的不確定性,才有了相應(yīng)的鎖定版本機(jī)制。

          npm5 之前可以通過 npmshrinkwrap 實(shí)現(xiàn)。通過運(yùn)行 npm shrinkwrap,會(huì)在當(dāng)前目錄下生成一個(gè) npm-shrinkwrap.json 文件,它是 package.json 中列出的每個(gè)依賴項(xiàng)的大型列表,應(yīng)安裝的特定版本,模塊的位置(URI),驗(yàn)證模塊完整性的哈希,它需要的包列表,以及依賴項(xiàng)列表。運(yùn)行 npm install 的時(shí)候會(huì)優(yōu)先使用 npm-shrinkwrap.json 進(jìn)行安裝,沒有則使用 package.json 進(jìn)行安裝。

          package-lock

          在 npm5 版本后,當(dāng)我們運(yùn)行 npm intall 發(fā)現(xiàn)會(huì)生成一個(gè)新文件 package-lock.json,內(nèi)容跟上面提到的 npm-shrinkwrap.json 基本一樣。

          "vue-loader": {
          "version": "14.2.4",
          "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-14.2.4.tgz",
          "integrity": "sha512-bub2/rcTMJ3etEbbeehdH2Em3G2F5vZIjMK7ZUePj5UtgmZSTtOX1xVVawDpDsy021s3vQpO6VpWJ3z3nO8dDw==",
          "dev": true,
          "requires": {
          "consolidate": "^0.14.0",
          "hash-sum": "^1.0.2",
          "loader-utils": "^1.1.0",
          "lru-cache": "^4.1.1",
          "postcss": "^6.0.8",
          "postcss-load-config": "^1.1.0",
          "postcss-selector-parser": "^2.0.0",
          "prettier": "^1.16.0",
          "resolve": "^1.4.0",
          "source-map": "^0.6.1",
          "vue-hot-reload-api": "^2.2.0",
          "vue-style-loader": "^4.0.1",
          "vue-template-es2015-compiler": "^1.6.0"
          },
          "dependencies": {
          "postcss-load-config": {
          "version": "1.2.0",
          "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz",
          "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=",
          "dev": true,
          "requires": {
          "cosmiconfig": "^2.1.0",
          "object-assign": "^4.1.0",
          "postcss-load-options": "^1.2.0",
          "postcss-load-plugins": "^2.3.0"
          }
          },
          }
          },

          當(dāng)項(xiàng)目中已有 package-lock.json 文件,在安裝項(xiàng)目依賴時(shí),將以該文件為主進(jìn)行解析安裝指定版本依賴包,而不是使用 package.json 來解析和安裝模塊。因?yàn)?nbsp;package-lock 為每個(gè)模塊及其每個(gè)依賴項(xiàng)指定了版本,位置和完整性哈希,所以它每次創(chuàng)建的安裝都是相同的。無論你使用什么設(shè)備,或者將來安裝它都無關(guān)緊要,每次都應(yīng)該給你相同的結(jié)果。

          npm5 版本下 install 規(guī)則

          npm 并不是一開始就是按照現(xiàn)有這種規(guī)則制定的。

          5.0.x 版本

          不管 package.json 中依賴是否有更新,npm install 都會(huì)根據(jù) package-lock.json下載。針對(duì)這種安裝策略,有人提出了這個(gè) issue[6] ,然后就演變成了 5.1.0 版本后的規(guī)則。

          5.1.0 版本后

          當(dāng) package.json 中的依賴項(xiàng)有新版本時(shí),npm install 會(huì)無視 package-lock.json去下載新版本的依賴項(xiàng)并且更新 package-lock.json。針對(duì)這種安裝策略,又有人提出了一個(gè) issue[7] 參考 npm 貢獻(xiàn)者 iarna 的評(píng)論,得出 5.4.2 版本后的規(guī)則。

          5.4.2 版本后

          如果只有一個(gè) package.json 文件,運(yùn)行 npm install 會(huì)根據(jù)它生成一個(gè) package-lock.json 文件,這個(gè)文件相當(dāng)于本次 install 的一個(gè)快照,它不僅記錄了 package.json 指明的直接依賴的版本,也記錄了間接依賴的版本。

          如果 package.json 的 semver-range version 和 package-lock.json 中版本兼容(package-lock.json 版本在 package.json 指定的版本范圍內(nèi)),即使此時(shí)package.json 中有新的版本,執(zhí)行 npm install 也還是會(huì)根據(jù) package-lock.json下載。

          如果手動(dòng)修改了 package.json 的 version ranges,且和 package-lock.json 中版本不兼容,那么執(zhí)行 npm install 時(shí) package-lock.json 將會(huì)更新到兼容 package.json 的版本。

          3. yarn

          yarn 的出現(xiàn)主要目標(biāo)是解決上面描述的由于語義版本控制而導(dǎo)致的 npm 安裝的不確定性問題。雖然可以使用 npm shrinkwrap 來實(shí)現(xiàn)可預(yù)測的依賴關(guān)系樹,但它并不是默認(rèn)選項(xiàng),而是取決于所有的開發(fā)人員知道并且啟用這個(gè)選項(xiàng)。yarn 采取了不同的做法。每個(gè) yarn 安裝都會(huì)生成一個(gè)類似于npm-shrinkwrap.json 的 yarn.lock 文件,而且它是默認(rèn)創(chuàng)建的。除了常規(guī)信息之外,yarn.lock 文件還包含要安裝的內(nèi)容的校驗(yàn)和,以確保使用的庫的版本相同。

          yarn 的主要優(yōu)化

          yarn 的出現(xiàn)主要做了如下優(yōu)化:

          • 并行安裝:無論 npm 還是 yarn 在執(zhí)行包的安裝時(shí),都會(huì)執(zhí)行一系列任務(wù)。npm 是按照隊(duì)列執(zhí)行每個(gè) package,也就是說必須要等到當(dāng)前 package 安裝完成之后,才能繼續(xù)后面的安裝。而 yarn 是同步執(zhí)行所有任務(wù),提高了性能。
          • 離線模式:如果之前已經(jīng)安裝過一個(gè)軟件包,用 yarn 再次安裝時(shí)之間從緩存中獲取,就不用像 npm 那樣再從網(wǎng)絡(luò)下載了。
          • 安裝版本統(tǒng)一:為了防止拉取到不同的版本,yarn 有一個(gè)鎖定文件 (lock file) 記錄了被確切安裝上的模塊的版本號(hào)。每次只要新增了一個(gè)模塊,yarn 就會(huì)創(chuàng)建(或更新)yarn.lock 這個(gè)文件。這么做就保證了,每一次拉取同一個(gè)項(xiàng)目依賴時(shí),使用的都是一樣的模塊版本。
          • 更好的語義化yarn 改變了一些 npm 命令的名稱,比如 yarn add/remove,比 npm原本的 install/uninstall 要更清晰。

          4. 安裝依賴樹流程

          1. 執(zhí)行工程自身 preinstall。當(dāng)前 npm 工程如果定義了 preinstall 鉤子此時(shí)會(huì)被執(zhí)行。

          2. 確定首層依賴。模塊首先需要做的是確定工程中的首層依賴,也就是 dependencies和 devDependencies 屬性中直接指定的模塊(假設(shè)此時(shí)沒有添加 npm install 參數(shù))。工程本身是整棵依賴樹的根節(jié)點(diǎn),每個(gè)首層依賴模塊都是根節(jié)點(diǎn)下面的一棵子樹,npm 會(huì)開啟多進(jìn)程從每個(gè)首層依賴模塊開始逐步尋找更深層級(jí)的節(jié)點(diǎn)。

          3. 獲取模塊。獲取模塊是一個(gè)遞歸的過程,分為以下幾步:

            • 獲取模塊信息。在下載一個(gè)模塊之前,首先要確定其版本,這是因?yàn)?nbsp;package.json中往往是 semantic versionsemver,語義化版本)。此時(shí)如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有該模塊信息直接拿即可,如果沒有則從倉庫獲取。如 package.json 中某個(gè)包的版本是 ^1.1.0npm 就會(huì)去倉庫中獲取符合 1.x.x 形式的最新版本。
            • 獲取模塊實(shí)體。上一步會(huì)獲取到模塊的壓縮包地址(resolved 字段),npm 會(huì)用此地址檢查本地緩存,緩存中有就直接拿,如果沒有則從倉庫下載。
            • 查找該模塊依賴,如果有依賴則回到第 1 步,如果沒有則停止。
          4. 模塊扁平化(dedupe)。上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重復(fù)模塊。比如 A 模塊依賴于 loadsh,B 模塊同樣依賴于 lodash。在 npm3以前會(huì)嚴(yán)格按照依賴樹的結(jié)構(gòu)進(jìn)行安裝,因此會(huì)造成模塊冗余。yarn 和從 npm5 開始默認(rèn)加入了一個(gè) dedupe 的過程。它會(huì)遍歷所有節(jié)點(diǎn),逐個(gè)將模塊放在根節(jié)點(diǎn)下面,也就是 node-modules 的第一層。當(dāng)發(fā)現(xiàn)有重復(fù)模塊時(shí),則將其丟棄。這里需要對(duì)重復(fù)模塊進(jìn)行一個(gè)定義,它指的是模塊名相同且 semver 兼容。每個(gè) semver都對(duì)應(yīng)一段版本允許范圍,如果兩個(gè)模塊的版本允許范圍存在交集,那么就可以得到一個(gè)兼容版本,而不必版本號(hào)完全一致,這可以使更多冗余模塊在 dedupe 過程中被去掉。

          5. 安裝模塊。這一步將會(huì)更新工程中的 node_modules,并執(zhí)行模塊中的生命周期函數(shù)(按照 preinstallinstall、postinstall 的順序)。

          6. 執(zhí)行工程自身生命周期。當(dāng)前 npm 工程如果定義了鉤子此時(shí)會(huì)被執(zhí)行(按照 install、postinstall、prepublish、prepare 的順序)。

          5. 舉例說明

          插件 htmlparser2@^3.10.1 和 dom-serializer@^0.2.2 都有使用了 entities 依賴包,不過使用的版本不同,同時(shí)我們自己安裝一個(gè)版本的 entities 包。具體如下:

          --htmlparser2@^3.10.1
          |--entities@^1.1.1
          --dom-serializer@^0.2.2
          |--entities@^2.0.0
          --entities@^2.1.0

          通過 npm install 安裝后,生成的 package-lock.json 文件內(nèi)容和它的 node_modules 目錄結(jié)構(gòu):

          可以發(fā)現(xiàn):

          1. dom-serializer@^0.2.2 的依賴包 entities@^2.0.0 和我們自己安裝的 entities@^2.1.0 被實(shí)際安裝成 entities@^2.2.0,并放在 node_modules 的第一層。因?yàn)檫@兩個(gè)版本的semver 范圍相同,又先被遍歷,所有會(huì)被合并安裝在第一層;
          2. htmlparser2@^3.10.1 的依賴包 entities@^1.1.1 被實(shí)際安放在 dom-serializer包的 node_modules 中,并且和 package-lock.json 描述結(jié)構(gòu)保持一致。

          通過 yarn 安裝后,生成的 yarn.lock 文件內(nèi)容和它的 node_modules 目錄結(jié)構(gòu):

          可以發(fā)現(xiàn)與 npm install 不同的是:

          1. yarn.lock 中所有依賴描述都是扁平化的,即沒有依賴描述的嵌套關(guān)系;
          2. 在 yarn.lock 中, 相同名稱版本號(hào)不同的依賴包,如果 semver 范圍相同會(huì)被合并,否則,會(huì)存在多個(gè)版本描述。

          注意 cnpm 不支持 package-lock

          使用 cnpm install 時(shí)候,并不會(huì)生成 package-lock.json 文件。cnpm install 的時(shí)候,就算你項(xiàng)目中有 package-lock.json 文件,cnpm 也不會(huì)識(shí)別,仍會(huì)根據(jù) package.json 來安裝。所以這就是為什么之前你用 npm 安裝產(chǎn)生了 package-lock.json,后面的人用 cnpm 來安裝,可能會(huì)跟你安裝的依賴包不一致。

          因此,盡量不要直接使用 cnpm install 安裝項(xiàng)目依賴包。但是為了解決直接使用 npm install 速度慢的問題,可以設(shè)置 npm 代理解決。

          // 設(shè)置淘寶鏡像代理
          npm config set registry https://registry.npm.taobao.org

          // 查看已設(shè)置代理
          npm config get registry

          當(dāng)然,也可以通過 `nrm`[8] 工具,快捷操作設(shè)置代理。

          全局安裝

          $ npm install -g nrm

          查看已安裝代理列表

          $ nrm ls

          * npm ----- https://registry.npmjs.org/
          yarn ----- https://registry.yarnpkg.com
          cnpm ---- http://r.cnpmjs.org/
          taobao -- https://registry.npm.taobao.org/
          nj ------ https://registry.nodejitsu.com/
          skimdb -- https://skimdb.npmjs.com/registry

          切換代理

          $ nrm use cnpm  //switch registry to cnpm

          * Registry has been set to: http://r.cnpmjs.org/

          測速

          nrm test cnpm

          * cnpm --- 618ms

          然而,設(shè)置這些全局代理可能還是不能滿足下載一些特定依賴包(在沒有 VPN 情況下),比如:node-sass、puppeteerchromedriverelectron 等。可以通過 .npmrc 文件設(shè)置具體依賴包的國內(nèi)鏡像。該文件在項(xiàng)目 npm install 時(shí)會(huì)被加載讀取,優(yōu)先級(jí)高于 npm 全局設(shè)置。

          registry=https://registry.npm.taobao.org/
          sass_binary_site=http://npm.taobao.org/mirrors/node-sass
          chromedriver_cdnurl=http://npm.taobao.org/mirrors/chromedriver
          electron_mirror=http://npm.taobao.org/mirrors/electron/ npm install -g electron
          puppeteer_download_host=http://npm.taobao.org/mirrors/chromium-browser-snapshots/

          6. 總結(jié)

          項(xiàng)目在以后重新構(gòu)建,由于依賴樹中有版本更新,造成意外事故是不可避免的,究其原因是整個(gè)依賴樹版本沒有鎖死。解決方案分為如下四種:

          • package.json 中固定版本。注意:僅能鎖定當(dāng)前依賴包版本,不能控制整棵依賴樹版本。
          • npm+npm-shrinkwrap.json。
          • npm+package-lock.json。
          • yarn+yarn-lock.json。

          根據(jù)自身情況選擇~

          參考資料

          [1]

          clipboardhttps://github.com/zenorocha/clipboard.js

          [2]

          issueshttps://github.com/zenorocha/clipboard.js/issues/745

          [3]

          braft-editor: https://github.com/margox/braft-editor

          [4]

          draft-jshttps://github.com/facebook/draft-js

          [5]

          issueshttps://github.com/margox/braft-editor/issues/847

          [6]

          issue: https://github.com/npm/npm/issues/16866

          [7]

          issue: https://github.com/npm/npm/issues/17979

          [8]

          nrmhttps://www.npmjs.com/package/nrm


          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲视频,中文字幕 | 日卡av | 国产寡妇亲子伦一区二区三区四区 | 手机在线操B视频 | 欧美操色 |