<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>

          你可能不知道的 npm 依賴管理那些事

          共 6188字,需瀏覽 13分鐘

           ·

          2020-07-29 01:18

          點(diǎn)擊上方藍(lán)字關(guān)注我們


          npm 是 Node.js 默認(rèn)的、以 JavaScript 編寫的包管理工具,如今,它已經(jīng)成為世界上最大的包管理工具,是每個(gè)前端開發(fā)者必備的工具。不知你是否遇到過下面問題:

          哎?我本地明明是好的,線上的依賴怎么就報(bào)錯(cuò)不行了呢?一言不合就刪除整個(gè)node_modules目錄然后重新npm install

          今天我們聊聊npm模塊相關(guān)的東西。

          semver

          npm 依賴管理的一個(gè)重要特性是采用了語義化版本 (semver)?規(guī)范,作為依賴版本管理方案。

          semver規(guī)定的模塊版本號(hào)格式為:MAJOR.MINOR.PATCH,即主版本號(hào).次版本號(hào).修訂號(hào)。版本號(hào)遞增規(guī)則如下:

          1. 主版本號(hào):當(dāng)你做了不兼容的 API 修改,例如新增了breaking change。

          2. 次版本號(hào):當(dāng)你做了向下兼容的功能性新增,例如新增feature。

          3. 修訂號(hào):當(dāng)你做了向下兼容的問題,例如修復(fù)bug。

          對(duì)于npm包的引用者來說,經(jīng)常會(huì)在package.json文件里面看到使用semver約定的semver range來指定所需的依賴包版本號(hào)和版本范圍。常用的規(guī)則如下表:


          此外,任意兩條規(guī)則,用空格連接起來,表示“與”邏輯,即兩條規(guī)則的交集: 如?>=2.3.1 <=2.8.0?可以解讀為:?>=2.3.1?且?<=2.8.0。

          任意兩條規(guī)則,通過?||?連接起來,表示“或”邏輯,即兩條規(guī)則的并集: 如?^2 >=2.3.1 || ^3 >3.2。

          在修訂版本號(hào)的后面可以加上其他信息,用-連接,比如:

          • X.Y.Z-Alpha: 內(nèi)測版

          • X.Y.Z-Beta: 公測版

          • X.Y.Z-Stable: 穩(wěn)定版


          從 npm install 說起

          npm install 命令用來安裝模塊到 node_modules 目錄。npm install 的具體原理是什么呢?

          執(zhí)行工程自身 preinstall

          確定首層依賴模塊

          首層依賴是 package.json 中 dependencies 和 devDependencies 字段直接指定的模塊。每一個(gè)首層依賴模塊都是模塊依賴樹根節(jié)點(diǎn)下面的一顆子樹。

          獲取模塊

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

          1. 獲取模塊信息。在下載一個(gè)模塊之前,首先要確定其版本,這是因?yàn)?package.json?中的模塊版本往往是 semantic version。此時(shí)根據(jù)package.json和版本描述文件(npm-shrinkwrap.json?或?package-lock.json不同npm版本的策略不同,后續(xù)我們會(huì)詳細(xì)介紹)。如?package.json?中某個(gè)包的版本是?^1.1.0,npm 就會(huì)去倉庫中獲取符合1.x.x形式的最新版本。

          2. 獲取模塊實(shí)體。上一步會(huì)獲取到模塊的壓縮包地址(resolved 字段),npm 會(huì)用此地址檢查本地緩存,緩存中有就直接拿,如果沒有則從倉庫下載。

          3. 查找該模塊依賴,如果有依賴則回到第1步,如果沒有則停止。

          模塊扁平化 (npm3 后支持)

          上一步獲取到的是一顆完整的依賴樹,下面會(huì)根據(jù)依賴樹安裝模塊。模塊安裝機(jī)制有兩種:嵌套式安裝機(jī)制?和?扁平式安裝機(jī)制。

          例如某工程下直接依賴了A和B兩個(gè)包,且他們同時(shí)依賴了C包。

          • 嵌套式

          npm3之前使用的是嵌套式安裝機(jī)制,嚴(yán)格按照依賴樹的結(jié)構(gòu)進(jìn)行安裝,這可能會(huì)造成相同模塊大量冗余的問題。

          • 扁平式

          npm3之后使用的扁平式安裝機(jī)制,但是需要考慮一個(gè)問題:

          工程同時(shí)依賴一個(gè)模塊不同版本該如何解決?

          npm3 引入了 dedupe 過程來解決這個(gè)問題。它會(huì)遍歷所有節(jié)點(diǎn),逐個(gè)將模塊放在根節(jié)點(diǎn)下面,也就是 node-modules 的第一層。當(dāng)發(fā)現(xiàn)有重復(fù)模塊時(shí),則將其丟棄。

          重復(fù)模塊:semver兼容的相同模塊。例如lodash ^1.2.0lodash ^1.4.0。如果工程的兩個(gè)模塊版本范圍存在交集,就可以得到一個(gè)?兼容版本,不必版本號(hào)完全一致,這可以使得更多冗余模塊在dedupe過程中被去掉。

          上例中如果A包依賴[email protected],B包依賴[email protected],此時(shí)兩個(gè)版本并不兼容,則后面的版本仍會(huì)保留在依賴書中。如下圖所示:



          實(shí)際上,npm3仍然可能出現(xiàn)模塊冗余的情況,如下圖,因?yàn)橐患?jí)目錄下已經(jīng)有[email protected],所以所有的[email protected]只能作為二級(jí)依賴模塊被安裝



          npm提供了?npm dedupe?指令來優(yōu)化依賴樹結(jié)構(gòu)。這個(gè)命令會(huì)去搜索本地的node_modules中的包,并且通過移動(dòng)相同的依賴包到外層目錄去盡量簡化這種依賴樹的結(jié)構(gòu),讓公用包更加有效被引用。

          安裝模塊

          將會(huì)更新工程中的?node_modules,并執(zhí)行模塊中的生命周期函數(shù)(按照?preinstall、install、postinstall?的順序)

          執(zhí)行工程自身生命周期

          當(dāng)前 npm 工程如果定義了鉤子此時(shí)會(huì)被執(zhí)行(按照?install、postinstall、prepublish、prepare?的順序)。最后生成或者更新版本描述文件。


          鎖定npm依賴版本

          你是否遇到過本地開發(fā)時(shí)一切正常,發(fā)布線上代碼時(shí)因?yàn)榘惭b依賴的錯(cuò)誤導(dǎo)致服務(wù)不可用?如果是的話,你要一份版本描述文件。

          簡單的寫死當(dāng)前工程依賴模塊的版本并不能真正鎖定依賴版本,因?yàn)槟銦o法控制間接依賴,如果間接依賴更新了有問題的模塊,你的系統(tǒng)還是可能會(huì)有宕機(jī)的風(fēng)險(xiǎn)。

          lock 文件是當(dāng)前依賴關(guān)系樹的快照,允許不同機(jī)器間的重復(fù)構(gòu)建。其實(shí) npm5 之前已經(jīng)提供了lock文件——?npm-shrinkwrap.json。但是在 npm5 發(fā)布的時(shí)候創(chuàng)建了新的lock文件——?package-lock.json,其主要目的是希望能更好的傳達(dá)一個(gè)消息,npm真正支持了locking機(jī)制。不過二者還是有一些區(qū)別點(diǎn):

          1. ?發(fā)布npm包時(shí),package-lock.json?不會(huì)被發(fā)布, 即使你將其顯式添加到軟件包的?files?屬性中,它也不會(huì)是已發(fā)布軟件包的一部分。npm-shrinkwrap.json?可以被發(fā)布。

          2. npm-shrinkwrap.json向后兼容npm2、3、4版本,package-lock.json 只有 npm5 以上支持。

          3. 可以通過npm shrinkwrap命令將package-lock.json轉(zhuǎn)換成npm-shrinkwrap.json, 因?yàn)槲募母袷绞峭耆粯拥摹?/span>


          曲折的package-lock.json

          查閱資料得知,自npm 5.0版本發(fā)布以來,package-lock.json的規(guī)則發(fā)生了三次變化。

          1. npm 5.0.x版本,不管 package.json 怎么變,npm install都會(huì)根據(jù)lock文件下載。npm/npm#16866 控訴了這個(gè)問題,我明明手動(dòng)改了 package.json ,為啥不給我升包!然后就導(dǎo)致5.1.0的問題(是個(gè)bug)

          2. npm 5.1.0 - 5.4.1版本,npm insall會(huì)無視lock文件,去下載semver兼容的最新的包。導(dǎo)致lock文件并不能完全鎖住依賴樹。詳情見npm/npm#17979

          3. npm 5.4.2版本之后,如果手動(dòng)改了package.json,且package.json和lock文件不同,那么執(zhí)行npm install時(shí) npm 會(huì)根據(jù) package 中的版本號(hào)和語義含義去下載最新的包,并更新至 lock。

            如果兩者是同一狀態(tài),那么執(zhí)行?npm install都會(huì)根據(jù) lock 下載,不會(huì)理會(huì) package 實(shí)際包的版本是否更新。

          好的依賴管理方案

          • 使用 npm: >=5.4.2 版本, 保持?package-lock.json?文件默認(rèn)開啟配置

          • 初始化:第一作者初始化項(xiàng)目時(shí)使用?npm install ?安裝依賴包, 默認(rèn)保存?^X.Y.Z?依賴 range 到 package.json 中; 提交?package.json,?package-lock.json,?不要提交?node_modules?目錄

          • 初始化:項(xiàng)目成員首次 checkout/clone 項(xiàng)目代碼后,執(zhí)行一次?npm install?安裝依賴包

          • 升級(jí)依賴包:

            • 升級(jí)小版本: 本地執(zhí)行?npm update?升級(jí)到新的小版本

            • 升級(jí)大版本: 本地執(zhí)行?

            npm install @
            • 升級(jí)到新的大版本

            • 也可手動(dòng)修改 package.json 中版本號(hào)為要升級(jí)的版本(大于現(xiàn)有版本號(hào))并指定所需的 semver, 然后執(zhí)行?npm install

            • 地驗(yàn)證升級(jí)后新版本無問題后,提交新的?package.json,?package-lock.json?文件


          • 降級(jí)依賴包:

          • 正確:?

             npm install @

            驗(yàn)證無問題后,提交?package.json 和 package-lock.json 文件

          • 刪除依賴包:

            • Plan A:?

            npm uninstall 

            ????并提交?package.json?和?package-lock.json

            • Plan B: 把要卸載的包從 package.json 中 dependencies 字段刪除, 然后執(zhí)行?npm install?并提交?package.json?和?package-lock.json

          • 任何時(shí)候有人提交了?package.json,?package-lock.json?更新后,團(tuán)隊(duì)其他成員應(yīng)在?svn update/git pull?拉取更新后執(zhí)行?npm install?腳本安裝更新后的依賴包

          • 不要手動(dòng)修改 package-lock.json

          • 當(dāng) package-lock.json 出現(xiàn)沖突時(shí),這種是非常棘手的情況,最好不要手動(dòng)解決沖突,如果有一處沖突解決不正確可能會(huì)導(dǎo)致線上事故。
            建議的做法:將本地的 package-lock.json文件刪除,引入遠(yuǎn)程的 package-lock.json 文件,再執(zhí)行
            npm install命令更新package-lock.json文件。

            (這種做法能保證未修改的依賴不變,會(huì)存在一個(gè)風(fēng)險(xiǎn):在執(zhí)行npm install的時(shí)候,可能有些間接依賴包升級(jí),根據(jù)semver兼容原則導(dǎo)致本次安裝的和開發(fā)時(shí)的package-lock.json文件不同。這種情況就需要驗(yàn)證依賴包升級(jí)是否有影響)

          • 部署安裝依賴時(shí),執(zhí)行npm install命令。不要執(zhí)行

            npm install

            命令,因?yàn)檫@會(huì)導(dǎo)致 package-lock.json 文件同時(shí)被更新。


          問題來了

          上述最佳實(shí)踐提到了當(dāng)團(tuán)隊(duì)中有成員提交了?package.json,?package-lock.json?更新后,其他成員需要執(zhí)行?npm install 來保證本地依賴的及時(shí)性,那么能否寫一個(gè)插件將這個(gè)手動(dòng)的環(huán)節(jié)自動(dòng)化呢?答案是可以的,我們只需要在 git post-merge 鉤子中檢查
          git diff files(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)

          是否包含了 package.json 文件,如果包含了該文件,則執(zhí)行npm install命令。我們暫且給這個(gè)插件取名為 hawkeye 。當(dāng)然,這個(gè)插件能干的事情不僅于此。

          不知作為讀者的你聽到上述場景描述后,是否有種似曾相識(shí)的感覺?沒錯(cuò),lint-staged。

          lint-staged,從git staged files變化中匹配你想要的文件,再執(zhí)行你配置的commands。

          Hawkeye,從git diff files變化中匹配你想要的文件,再執(zhí)行你配置的commands。

          需要注意的是,他們都依賴于husky改造git hooks的能力。


          實(shí)現(xiàn)方案

          例子

          假設(shè)有一個(gè)已經(jīng)安裝了 hawkeye 和 husky 的項(xiàng)目, package.json 如下:

          {  "name": "My project",  "version": "0.1.0",  "scripts": {  },  "husky": {    "hooks": {      "post-merge": "hawkeye"    }  },  "hawkeye": {    "package.json": ["npm install"]  }}


          相關(guān)鏈接

          • semver 語義化版本?

            https://semver.org/lang/zh-CN/?spm=ata.13261165.0.0.552e2688ZKTpgz

          • semver(1) -- The semantic versioner for npm

            https://github.com/npm/node-semver?spm=ata.13261165.0.0.552e2688ZKTpgz

          • 2018 年了,你還是只會(huì) npm install 嗎?

            https://juejin.im/post/5ab3f77df265da2392364341?spm=ata.13261165.0.0.552e2688ZKTpgz

          • npm install algorithm

            https://docs.npmjs.com/cli/install?spm=ata.13261165.0.0.552e2688ZKTpgz#algorithm

          • npm dedupe

            https://docs.npmjs.com/cli/dedupe.html?spm=ata.13261165.0.0.552e2688ZKTpg

          • npm install的實(shí)現(xiàn)原理

            https://www.zhihu.com/question/66629910?spm=ata.13261165.0.0.552e2688ZKTpgz

          • [譯] 理解 NPM 5 中的 lock 文件

            https://juejin.im/post/5943849aac502e006b84ce07?spm=ata.13261165.0.0.552e2688ZKTpgz

          • package-lock.json file not updated after package.json file is changed

            https://github.com/npm/npm/issues/16866?spm=ata.13261165.0.0.552e2688ZKTpgz

          • why is package-lock being ignored?

            https://github.com/npm/npm/issues/17979?spm=ata.13261165.0.0.552e2688ZKTpgz

          • lint-staged

            https://github.com/okonet/lint-staged?spm=ata.13261165.0.0.552e2688ZKTpgz

          • hawkeye

            https://github.com/stormqx/hawkeye?spm=ata.13261165.0.0.552e2688ZKTpgz




          推薦閱讀




          我的公眾號(hào)能帶來什么價(jià)值?(文末有送書規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?


          瀏覽 57
          點(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>
                  www.操国产 | 微信约操情人高潮在线 | 免费观看av | 日韩三级免费观看 | 日本无码中文字幕 |