一文弄懂 npm & yarn 包管理機(jī)制(深度解析?。?/h1>
背景
使用 npm 或 yarn 管理項(xiàng)目依賴時(shí),可能會(huì)產(chǎn)生以下疑問:
項(xiàng)目依賴出現(xiàn)問題怎么辦?刪了重裝,即先刪除 node_modules 再重新 install,那這樣的操作會(huì)不會(huì)存在風(fēng)險(xiǎn)? 把所有依賴都安裝到 dependencies 中,不區(qū)分 devDependencies 會(huì)有問題嗎? 我們的應(yīng)用依賴了 pkg-a 和 pkg-b,同時(shí) pkg-a 也依賴了 pkg-b,那么 pkg-b 會(huì)被多次安裝或重復(fù)打包嗎? 一個(gè)項(xiàng)目中,我使用 npm 別人使用 yarn,這會(huì)引發(fā)什么問題? 我們是否要提交 lockfile(package-lock.json/yarn.lock) 到項(xiàng)目倉庫呢? lockfile 在 git 操作時(shí),時(shí)常會(huì)出現(xiàn)大量的沖突,你是怎么解決的呢?
npm 內(nèi)部機(jī)制和背后的思考
先來看下第一個(gè)問題,“刪除 node_modules,重新 npm install” 這樣解決依賴安裝問題百試不爽,其中的原理是什么?這樣做存在怎樣的風(fēng)險(xiǎn)?下面我們一起探究一下。
npm 的安裝機(jī)制非常值得探究。pip 是全局安裝,但 npm 的安裝機(jī)制秉承了不同的設(shè)計(jì)哲學(xué)。
npm 會(huì)優(yōu)先將依賴包安裝到項(xiàng)目目錄。 這樣做的好處是使不同項(xiàng)目的依賴各成體系,同時(shí)還減輕了包作者的 API 壓力;缺點(diǎn)也比較明顯,如果我們的 repo_a 和 repo_b 都有一個(gè)相同的依賴 pkg_c,那么這個(gè)公共依賴將在兩個(gè)項(xiàng)目中各被安裝一次。也就是說,同一個(gè)依賴可能在我們的電腦上多次安裝。
npm install
上圖是 npm 安裝依賴大致的過程,其中這樣幾個(gè)步驟需要關(guān)注:
檢查配置。包括項(xiàng)目級、用戶級、全局級、內(nèi)置的 .npmrc 文件。 確定依賴版本,構(gòu)建依賴樹。確定項(xiàng)目依賴版本有兩個(gè)來源,一是 package.json 文件,一是 lockfile 文件,兩個(gè)確認(rèn)版本、構(gòu)建依賴樹的來源,互不可少、相輔相成。如果 package-lock.json 文件存在且符合 package.json 聲明的的情況下,直接讀??;否則重新確認(rèn)依賴的版本。 下載包資源。下載前先確認(rèn)本地是否存在匹配的緩存版本,如果有就直接使用緩存文件,如果沒有就下載并添加到緩存,然后將包按依賴樹解壓到 node_modules 目錄。 生成 lockfile 文件。
可以確認(rèn)這樣幾個(gè)邏輯:
構(gòu)建依賴樹的過程中,版本確認(rèn)需要結(jié)合 package.json 和 package-lock.json 兩個(gè)文件。先確認(rèn) package-lock.json 安裝版本,符合規(guī)則就以此為準(zhǔn),否則由 package.json 聲明的版本范圍重新確認(rèn)。特別地,若是在開發(fā)中手動(dòng)更改包信息,會(huì)導(dǎo)致lockfile 版本信息異常,也可能由 package.json 確認(rèn)。確認(rèn)好的依賴樹會(huì)存到 package-lock.json 文件中,這里跟 yarn.lock 存在差異。 同一個(gè)依賴,更高版本的包會(huì)安裝到頂層目錄,即 node_modules 目錄;否則會(huì)分散在某些依賴的 node_modules 目錄,如:node_modules/expect-jsx/node_modules/react 目錄。 如果依賴升級,造成版本不兼容,需要多版本共存,那么仍然是將高版本安裝到頂層,低版本分散到各級目錄。 lockfile 的存在,保證了項(xiàng)目依賴結(jié)構(gòu)的確定性,保障了項(xiàng)目在多環(huán)境運(yùn)行的穩(wěn)定性。 ...
yarn 安裝理念以及破解依賴管理困境
yarn 作為區(qū)別于 npm 的依賴管理工具,誕生之初就是為了解決歷史上 npm 的某些不足,比如 npm 缺乏對于依賴的完整性和一致性保障,以及 npm 安裝速度過慢的問題等,盡管 npm 發(fā)展至今,已經(jīng)在很多方面向 yarn 看齊,但 yarn 的安裝理念仍然需要我們關(guān)注。yarn 提出的安裝理念很好的解決了當(dāng)時(shí) npm 的依賴管理問題:
確定性。通過 yarn.lock 等機(jī)制,保證了確定性,這里的確定性包括但不限于明確的依賴版本、明確的依賴安裝結(jié)構(gòu)等。即在任何機(jī)器和環(huán)境下,都可以以相同的方式被安裝。 模塊扁平化安裝。將依賴包的不同版本,按照一定策略,歸結(jié)為單個(gè)版本,以避免創(chuàng)建多個(gè)副本造成冗余。(npm 也有相同的優(yōu)化) 更好的網(wǎng)絡(luò)性能。Yarn 采用了請求排隊(duì)的理念,類似并發(fā)連接池,能夠更好地利用網(wǎng)絡(luò)資源;同時(shí)引入了更好的安裝失敗時(shí)的重試機(jī)制。(npm 較早的版本是順序下載,當(dāng)?shù)谝粋€(gè)包完全下載完成后,才會(huì)將下載控制權(quán)交給下一個(gè)包) 引入緩存機(jī)制,實(shí)現(xiàn)離線策略。(npm 也有類似的優(yōu)化)
yarn.lock 文件結(jié)構(gòu)
以 react 等依賴為例,先大致了解一下 yarn.lock 文件的結(jié)構(gòu)以及確定依賴版本的方式:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
expect-jsx@^5.0.0:
version "5.0.0"
resolved "[http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7](http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7 "http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7")"
integrity sha1-YXYbQzZfKFqA6ygMeF4Hg7vjYsc=
dependencies:
collapse-white-space "^1.0.0"
react "^16.0.0"
react-element-to-jsx-string "^13.0.0"
react-rater@^6.0.0:
version "6.0.0"
resolved "[http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927](http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927 "http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927")"
integrity sha512-NP1+rEeL3LyJqA5xF7U2fSHpISMcVeMgbQ0u/P1WmayiHccI7Ixx5GohygmJY82g7SxdJnIun2OOB6z8WTExmg==
dependencies:
prop-types "^15.7.2"
react "^16.8.0"
react-dom "^16.8.0"
//一或多個(gè)具有相同版本范圍的依賴聲明,確定一個(gè)可用的版本。這就是 lockfile 的確定性。
react@^16.0.0, react@^16.8.0:
version "16.14.0"
resolved "[http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d](http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d "http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d")"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
//如果同一個(gè)依賴存在多個(gè)版本,那么最高版本安裝在頂層目錄,即 node_modules 目錄。
react@^17.0.1:
version "17.0.2"
resolved "[http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037](http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037 "http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037")"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
從上面依賴版本描述的信息中,可以確定以下幾點(diǎn):
所有依賴,不管是項(xiàng)目聲明的依賴,還是依賴的依賴,都是扁平化管理。 依賴的版本是由所有依賴的版本聲明范圍確定的,具備相同版本聲明范圍的依賴歸結(jié)為一類,確定一個(gè)該范圍下的依賴版本。如果同一個(gè)依賴多個(gè)版本共存,那么會(huì)并列歸類。 每個(gè)依賴確定的版本中,是由以下幾項(xiàng)構(gòu)成: 多個(gè)依賴的聲明版本,且符合 semver 規(guī)范; 確定的版本號 version 字段; 版本的完整性驗(yàn)證字段 依賴列表
相比 npm,Yarn 一個(gè)顯著區(qū)別是 yarn.lock 中子依賴的版本號不是固定版本。 也就是說單獨(dú)一個(gè) yarn.lock 確定不了 node_modules 目錄結(jié)構(gòu),還需要和 package.json 文件進(jìn)行配合。
yarn install
以下是在 yarn 安裝依賴時(shí)的步驟:
1、檢查(checking)主要是檢查項(xiàng)目中是否存在一些 npm 相關(guān)的配置文件,如 package-lock.json 等。如果存在,可能會(huì)警告提示,因?yàn)樗鼈兛赡軙?huì)存在沖突。在這一階段,也會(huì)檢查系統(tǒng) OS、CPU 等信息。
2、解析包(resolving packages)這一步主要是解析依賴樹,確定版本信息等。首先獲取項(xiàng)目 package.json 中聲明的首層依賴,包括 dependencies, devDependencies, optionalDependencies 聲明的依賴。接著采用遍歷首層依賴的方式獲取依賴包的版本信息,以及遞歸查找每個(gè)依賴下嵌套依賴的版本信息,并將解析過和正在解析的包用一個(gè) Set 數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ),這樣就能保證同一個(gè)版本范圍內(nèi)的包不會(huì)被重復(fù)解析。
對于沒有解析過的包,首次嘗試從 yarn.lock 中獲取到版本信息,并標(biāo)記為已解析; 如果在 yarn.lock 中沒有找到包,則向 Registry 發(fā)起請求獲取滿足版本范圍的已知最高版本的包信息,獲取后將當(dāng)前包標(biāo)記為已解析。
總之,在經(jīng)過復(fù)雜的解析算法后,我們就確定了所有依賴的具體版本信息以及下載地址。
3、獲取包(fetching packages)這一步主要是利用系統(tǒng)緩存,到緩存中找到具體的包資源。首先會(huì)嘗試在緩存中查找依賴包,如果沒有命中緩存,則將依賴包下載到緩存中。對于沒有命中緩存的包,Yarn 會(huì)維護(hù)一個(gè) fetch 隊(duì)列,按照規(guī)則進(jìn)行網(wǎng)絡(luò)請求。這里也是 yarn 誕生之初解決 npm v3 安裝緩慢問題的優(yōu)化點(diǎn),支持并行下載。
如何判斷有沒有命中緩存?
判斷系統(tǒng)中存在符合 "cachefolder+slug+node_modules+pkg.name" 規(guī)則的路徑,如果存在則判斷為命中緩存,否則就會(huì)重新下載。值得注意的是,不同版本的包在緩存中是扁平化管理。以下是緩存中 webpack 的依賴緩存,可以通過 yarn cache dir 查看。
4、鏈接包(linking dependencies)這一步主要是將緩存中的依賴,復(fù)制到項(xiàng)目目錄下,同時(shí)遵循扁平化原則。前面說到,npm 優(yōu)先將依賴安裝到項(xiàng)目目錄,因此需要將全局緩存中的依賴復(fù)制到項(xiàng)目。在復(fù)制依賴前,Yarn 會(huì)先解析 peerDependencies,如果找不到符合 peerDependencies 聲明的依賴版本,則進(jìn)行 warning 提示(這并不會(huì)影響命令執(zhí)行),并最終拷貝依賴到項(xiàng)目中。
5、構(gòu)建包(building fresh package)如果依賴包中存在二進(jìn)制包需要進(jìn)行編譯,會(huì)在這一步進(jìn)行。
如果破解依賴管理困境
在 npm v2 時(shí)期,安裝的依賴會(huì)存在于引用依賴的 node_modules 目錄,如果依賴過多,會(huì)形成一顆巨大的依賴樹。這種結(jié)構(gòu)雖然簡單明了,但是對于大型項(xiàng)目十分不友好。依賴層級深對開發(fā)排查不利,并且依賴的復(fù)用也是問題。在 npm v3 中引入扁平化的概念??磶讉€(gè)場景的例子??:
場景一:不同 npm 版本安裝依賴的結(jié)構(gòu)
[email protected] 依賴 [email protected],npm v3 是扁平化管理依賴。
場景二:不同 npm 版本處理依賴多版本共存問題
在場景一的基礎(chǔ)上,安裝 [email protected],而它依賴另一個(gè)版本的 [email protected]。由于根目錄下已存在 [email protected] 的依賴,npm v3 會(huì)把 [email protected] 安裝到 [email protected] 依賴的 node_modules 目錄。
靚仔疑惑:為什么 [email protected] 在頂級,而 [email protected] 在子級呢?
場景三:依賴的多版本的數(shù)量與依賴版本分布關(guān)系
在場景二的基礎(chǔ)上,安裝 [email protected],而它也依賴 [email protected]。同樣的,由于根目錄下已存在 [email protected] 的依賴,npm v3 會(huì)把 [email protected] 安裝到 [email protected] 依賴的 node_modules 目錄。
靚仔疑惑:你可能會(huì)疑問,此時(shí)存在2個(gè) [email protected] 和1個(gè) [email protected],出現(xiàn)在頂級安裝目錄的不應(yīng)該是 v2 版本而非 v1 版本嘛?
其實(shí)這是由依賴的安裝順序決定的,真就是依賴的某個(gè)版本如果出現(xiàn)在合適的時(shí)間,那么它就會(huì)被安裝到頂級 node_modules 目錄。不同版本的出場順序?qū)е乱蕾嚱Y(jié)構(gòu)的差異,npm v3 注定不是穩(wěn)定的包管理工具。跟生活一樣,人物的出場順序很重要,它決定了你在哪里做什么事。
場景四:依賴版本存在重復(fù)和可用
在場景三的基礎(chǔ)上,安裝 [email protected],它依賴 [email protected]。由于頂級目錄已存在目標(biāo)版本,因此 npm v3 會(huì)跳過該依賴的安裝。
場景五:版本升級囧境在
場景三的基礎(chǔ)上,如果更新了 [email protected],同時(shí)它的依賴是 [email protected]。那么 npm v3 的執(zhí)行順序是,刪除 [email protected],安裝 [email protected],安裝 [email protected],留下了 [email protected] 在頂層目錄,因此 [email protected] 會(huì)安裝到其父依賴的 node_modules 目錄。
場景六:依賴版本多目錄存在且符合復(fù)用條件
在場景五的基礎(chǔ)上,更新 [email protected],它依賴了 [email protected]。那么 npm v3 的執(zhí)行順序是,刪除 [email protected],安裝 [email protected],刪除 [email protected],安裝 [email protected],于是出現(xiàn)以下結(jié)構(gòu)。
此時(shí)你會(huì)發(fā)現(xiàn),存在多個(gè) [email protected] 分布在不同的 node_modules 目錄,他們是不是只要在頂級目錄存在一份即可?沒錯(cuò),我們刪除 node_modules 目錄重裝,得到的就是你想的清晰的結(jié)構(gòu)。
實(shí)際上,更優(yōu)雅的方式是使用 npm dedupe 命令達(dá)到上述結(jié)構(gòu)。而 yarn 在安裝依賴時(shí)會(huì)自動(dòng)執(zhí)行 dedupe 命令。
正是由于上述一些 npm 歷史的坑,所以更建議使用 yarn 作為項(xiàng)目協(xié)作的包管理工具。當(dāng)然 npm 發(fā)展至今,很多問題已經(jīng)優(yōu)化掉,現(xiàn)在 yarn 和 npm 是兩款互相看齊、互相獲取靈感的依賴管理工具。
npm vs. yarn ??
這里簡單對比 npm v6 和 yarn v1. 這是我們生產(chǎn)開發(fā)常用的版本。
npm 和 yarn 作為兩款相似的包管理工具,在一些功能實(shí)現(xiàn)上它們互相獲取靈感。
相同點(diǎn):
package.json 作為項(xiàng)目依賴描述文件。 node_modules 作為依賴存儲(chǔ)目錄,yarn v2 不再是這樣。 lockfile 鎖定版本依賴,在 yarn 中叫 yarn.lock,在 npm 中叫 package-lock.json,在 npm v7 也支持了 yarn.lock。它確保在不同機(jī)器或不同環(huán)境中,能夠得到穩(wěn)定的 node_modules 目錄結(jié)構(gòu)。
差異:
依賴管理策略。 lockfile。package-lock.json 自帶版本鎖定+依賴結(jié)構(gòu),你想改動(dòng)一些依賴,可能影響的范圍要比表面看起來的復(fù)雜的多;而 yarn.lock 自帶版本鎖定,并沒有確定的依賴結(jié)構(gòu),使用 yarn 管理項(xiàng)目依賴,需要 package.json + yarn.lock 共同確定依賴的結(jié)構(gòu)。 性能。(對比 npm v6 和 yarn v1)目前 npm v7 優(yōu)化了緩存和下載網(wǎng)絡(luò)策略,性能的差異在縮小。

[拓展] npm 企業(yè)級部署私服原理
npm 中的源(registry),其實(shí)就是一個(gè)查詢服務(wù)。以 npmjs.org 為例,它的查詢服務(wù)網(wǎng)址是 https://registry.npmjs.org/ ,在這個(gè)網(wǎng)址后加上依賴的名字,就會(huì)得到一個(gè) JSON 對象,里面包含了依賴所有的信息。例如:
https://registry.npmjs.org/react https://registry.npm.taobao.org/react
我們可以通過 npm config set registry 命令來設(shè)置安裝源。你知道我們公司為什么要部署私有的 npm 鏡像嗎?雖然 npm 并沒有被屏蔽,但是下載第三方依賴包的速度依然較緩慢,這嚴(yán)重影響 CI/CD 流程或本地開發(fā)效率。通常我們認(rèn)為部署 npm 私服具備以下優(yōu)點(diǎn):
確保高速、穩(wěn)定的 npm 服務(wù) 確保發(fā)布私有模塊的安全性 審核機(jī)制可以保障私服上 npm 模塊質(zhì)量和安全
部署企業(yè)級私服,能夠獲得安全、穩(wěn)定、高速的保障。
管理項(xiàng)目依賴的小技巧(集思廣益...)
推薦使用 yarn 作為團(tuán)隊(duì)包管理工具,而不是 npm。盡管在 npm v6 之后的版本趨向穩(wěn)定和安全,但由于歷史原因和團(tuán)隊(duì)管理兼容性,仍然是推薦使用 yarn 作為團(tuán)隊(duì)統(tǒng)一的包管理工具。 項(xiàng)目中一定要存在 lockfile 文件,且禁止手動(dòng)修改,因?yàn)檫@是項(xiàng)目穩(wěn)定性運(yùn)行的保障。 如果 yarn.lock 在代碼合并的過程中出現(xiàn)了問題,可以嘗試使用 yarn install 解決問題。 ...
參考資料
npm install[1] yarn install[2] yarn.lock[3] NPM vs. Yarn: Which Package Manager Should You Choose?[4] Lockfiles should be committed on all projects[5]
參考資料
[1]npm install: https://docs.npmjs.com/cli/v7/commands/npm-install
[2]yarn install: https://classic.yarnpkg.com/en/docs/cli/install
[3]yarn.lock: https://classic.yarnpkg.com/en/docs/yarn-lock
[4]NPM vs. Yarn: Which Package Manager Should You Choose?: https://www.whitesourcesoftware.com/free-developer-tools/blog/npm-vs-yarn-which-should-you-choose/
[5]Lockfiles should be committed on all projects: https://classic.yarnpkg.com/blog/2016/11/24/lockfiles-for-all/
?? 謝謝支持
我們來自字節(jié)跳動(dòng),是旗下大力教育前端部門,負(fù)責(zé)字節(jié)跳動(dòng)教育全線產(chǎn)品前端開發(fā)工作。
我們圍繞產(chǎn)品品質(zhì)提升、開發(fā)效率、創(chuàng)意與前沿技術(shù)等方向沉淀與傳播專業(yè)知識及案例,為業(yè)界貢獻(xiàn)經(jīng)驗(yàn)價(jià)值。包括但不限于性能監(jiān)控、組件庫、多端技術(shù)、Serverless、可視化搭建、音視頻、人工智能、產(chǎn)品設(shè)計(jì)與營銷等內(nèi)容。
歡迎感興趣的同學(xué)在評論區(qū)或使用內(nèi)推碼內(nèi)推到作者部門拍磚哦 ??
字節(jié)跳動(dòng)校/社招內(nèi)推碼: WKVVX3V
投遞鏈接: https://jobs.toutiao.com/s/dLLnGv
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「 sherlocked_93 」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了
瀏覽
108
背景
使用 npm 或 yarn 管理項(xiàng)目依賴時(shí),可能會(huì)產(chǎn)生以下疑問:
項(xiàng)目依賴出現(xiàn)問題怎么辦?刪了重裝,即先刪除 node_modules 再重新 install,那這樣的操作會(huì)不會(huì)存在風(fēng)險(xiǎn)? 把所有依賴都安裝到 dependencies 中,不區(qū)分 devDependencies 會(huì)有問題嗎? 我們的應(yīng)用依賴了 pkg-a 和 pkg-b,同時(shí) pkg-a 也依賴了 pkg-b,那么 pkg-b 會(huì)被多次安裝或重復(fù)打包嗎? 一個(gè)項(xiàng)目中,我使用 npm 別人使用 yarn,這會(huì)引發(fā)什么問題? 我們是否要提交 lockfile(package-lock.json/yarn.lock) 到項(xiàng)目倉庫呢? lockfile 在 git 操作時(shí),時(shí)常會(huì)出現(xiàn)大量的沖突,你是怎么解決的呢?
npm 內(nèi)部機(jī)制和背后的思考
先來看下第一個(gè)問題,“刪除 node_modules,重新 npm install” 這樣解決依賴安裝問題百試不爽,其中的原理是什么?這樣做存在怎樣的風(fēng)險(xiǎn)?下面我們一起探究一下。
npm 的安裝機(jī)制非常值得探究。pip 是全局安裝,但 npm 的安裝機(jī)制秉承了不同的設(shè)計(jì)哲學(xué)。
npm 會(huì)優(yōu)先將依賴包安裝到項(xiàng)目目錄。 這樣做的好處是使不同項(xiàng)目的依賴各成體系,同時(shí)還減輕了包作者的 API 壓力;缺點(diǎn)也比較明顯,如果我們的 repo_a 和 repo_b 都有一個(gè)相同的依賴 pkg_c,那么這個(gè)公共依賴將在兩個(gè)項(xiàng)目中各被安裝一次。也就是說,同一個(gè)依賴可能在我們的電腦上多次安裝。
npm install
上圖是 npm 安裝依賴大致的過程,其中這樣幾個(gè)步驟需要關(guān)注:
檢查配置。包括項(xiàng)目級、用戶級、全局級、內(nèi)置的 .npmrc 文件。 確定依賴版本,構(gòu)建依賴樹。確定項(xiàng)目依賴版本有兩個(gè)來源,一是 package.json 文件,一是 lockfile 文件,兩個(gè)確認(rèn)版本、構(gòu)建依賴樹的來源,互不可少、相輔相成。如果 package-lock.json 文件存在且符合 package.json 聲明的的情況下,直接讀??;否則重新確認(rèn)依賴的版本。 下載包資源。下載前先確認(rèn)本地是否存在匹配的緩存版本,如果有就直接使用緩存文件,如果沒有就下載并添加到緩存,然后將包按依賴樹解壓到 node_modules 目錄。 生成 lockfile 文件。
可以確認(rèn)這樣幾個(gè)邏輯:
構(gòu)建依賴樹的過程中,版本確認(rèn)需要結(jié)合 package.json 和 package-lock.json 兩個(gè)文件。先確認(rèn) package-lock.json 安裝版本,符合規(guī)則就以此為準(zhǔn),否則由 package.json 聲明的版本范圍重新確認(rèn)。特別地,若是在開發(fā)中手動(dòng)更改包信息,會(huì)導(dǎo)致lockfile 版本信息異常,也可能由 package.json 確認(rèn)。確認(rèn)好的依賴樹會(huì)存到 package-lock.json 文件中,這里跟 yarn.lock 存在差異。 同一個(gè)依賴,更高版本的包會(huì)安裝到頂層目錄,即 node_modules 目錄;否則會(huì)分散在某些依賴的 node_modules 目錄,如:node_modules/expect-jsx/node_modules/react 目錄。 如果依賴升級,造成版本不兼容,需要多版本共存,那么仍然是將高版本安裝到頂層,低版本分散到各級目錄。 lockfile 的存在,保證了項(xiàng)目依賴結(jié)構(gòu)的確定性,保障了項(xiàng)目在多環(huán)境運(yùn)行的穩(wěn)定性。 ...
yarn 安裝理念以及破解依賴管理困境
yarn 作為區(qū)別于 npm 的依賴管理工具,誕生之初就是為了解決歷史上 npm 的某些不足,比如 npm 缺乏對于依賴的完整性和一致性保障,以及 npm 安裝速度過慢的問題等,盡管 npm 發(fā)展至今,已經(jīng)在很多方面向 yarn 看齊,但 yarn 的安裝理念仍然需要我們關(guān)注。yarn 提出的安裝理念很好的解決了當(dāng)時(shí) npm 的依賴管理問題:
確定性。通過 yarn.lock 等機(jī)制,保證了確定性,這里的確定性包括但不限于明確的依賴版本、明確的依賴安裝結(jié)構(gòu)等。即在任何機(jī)器和環(huán)境下,都可以以相同的方式被安裝。 模塊扁平化安裝。將依賴包的不同版本,按照一定策略,歸結(jié)為單個(gè)版本,以避免創(chuàng)建多個(gè)副本造成冗余。(npm 也有相同的優(yōu)化) 更好的網(wǎng)絡(luò)性能。Yarn 采用了請求排隊(duì)的理念,類似并發(fā)連接池,能夠更好地利用網(wǎng)絡(luò)資源;同時(shí)引入了更好的安裝失敗時(shí)的重試機(jī)制。(npm 較早的版本是順序下載,當(dāng)?shù)谝粋€(gè)包完全下載完成后,才會(huì)將下載控制權(quán)交給下一個(gè)包) 引入緩存機(jī)制,實(shí)現(xiàn)離線策略。(npm 也有類似的優(yōu)化)
yarn.lock 文件結(jié)構(gòu)
以 react 等依賴為例,先大致了解一下 yarn.lock 文件的結(jié)構(gòu)以及確定依賴版本的方式:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
expect-jsx@^5.0.0:
version "5.0.0"
resolved "[http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7](http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7 "http://registry.npmjs.org/expect-jsx/-/expect-jsx-5.0.0.tgz#61761b43365f285a80eb280c785e0783bbe362c7")"
integrity sha1-YXYbQzZfKFqA6ygMeF4Hg7vjYsc=
dependencies:
collapse-white-space "^1.0.0"
react "^16.0.0"
react-element-to-jsx-string "^13.0.0"
react-rater@^6.0.0:
version "6.0.0"
resolved "[http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927](http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927 "http://registry.npmjs.org/react-rater/-/react-rater-6.0.0.tgz#2e666b6e5e5c33b622541df6a7124f6c99606927")"
integrity sha512-NP1+rEeL3LyJqA5xF7U2fSHpISMcVeMgbQ0u/P1WmayiHccI7Ixx5GohygmJY82g7SxdJnIun2OOB6z8WTExmg==
dependencies:
prop-types "^15.7.2"
react "^16.8.0"
react-dom "^16.8.0"
//一或多個(gè)具有相同版本范圍的依賴聲明,確定一個(gè)可用的版本。這就是 lockfile 的確定性。
react@^16.0.0, react@^16.8.0:
version "16.14.0"
resolved "[http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d](http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d "http://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d")"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
//如果同一個(gè)依賴存在多個(gè)版本,那么最高版本安裝在頂層目錄,即 node_modules 目錄。
react@^17.0.1:
version "17.0.2"
resolved "[http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037](http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037 "http://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037")"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
從上面依賴版本描述的信息中,可以確定以下幾點(diǎn):
所有依賴,不管是項(xiàng)目聲明的依賴,還是依賴的依賴,都是扁平化管理。 依賴的版本是由所有依賴的版本聲明范圍確定的,具備相同版本聲明范圍的依賴歸結(jié)為一類,確定一個(gè)該范圍下的依賴版本。如果同一個(gè)依賴多個(gè)版本共存,那么會(huì)并列歸類。 每個(gè)依賴確定的版本中,是由以下幾項(xiàng)構(gòu)成: 多個(gè)依賴的聲明版本,且符合 semver 規(guī)范; 確定的版本號 version 字段; 版本的完整性驗(yàn)證字段 依賴列表 相比 npm,Yarn 一個(gè)顯著區(qū)別是 yarn.lock 中子依賴的版本號不是固定版本。 也就是說單獨(dú)一個(gè) yarn.lock 確定不了 node_modules 目錄結(jié)構(gòu),還需要和 package.json 文件進(jìn)行配合。
yarn install
以下是在 yarn 安裝依賴時(shí)的步驟:
1、檢查(checking)主要是檢查項(xiàng)目中是否存在一些 npm 相關(guān)的配置文件,如 package-lock.json 等。如果存在,可能會(huì)警告提示,因?yàn)樗鼈兛赡軙?huì)存在沖突。在這一階段,也會(huì)檢查系統(tǒng) OS、CPU 等信息。
2、解析包(resolving packages)這一步主要是解析依賴樹,確定版本信息等。首先獲取項(xiàng)目 package.json 中聲明的首層依賴,包括 dependencies, devDependencies, optionalDependencies 聲明的依賴。接著采用遍歷首層依賴的方式獲取依賴包的版本信息,以及遞歸查找每個(gè)依賴下嵌套依賴的版本信息,并將解析過和正在解析的包用一個(gè) Set 數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ),這樣就能保證同一個(gè)版本范圍內(nèi)的包不會(huì)被重復(fù)解析。
對于沒有解析過的包,首次嘗試從 yarn.lock 中獲取到版本信息,并標(biāo)記為已解析; 如果在 yarn.lock 中沒有找到包,則向 Registry 發(fā)起請求獲取滿足版本范圍的已知最高版本的包信息,獲取后將當(dāng)前包標(biāo)記為已解析。
總之,在經(jīng)過復(fù)雜的解析算法后,我們就確定了所有依賴的具體版本信息以及下載地址。
3、獲取包(fetching packages)這一步主要是利用系統(tǒng)緩存,到緩存中找到具體的包資源。首先會(huì)嘗試在緩存中查找依賴包,如果沒有命中緩存,則將依賴包下載到緩存中。對于沒有命中緩存的包,Yarn 會(huì)維護(hù)一個(gè) fetch 隊(duì)列,按照規(guī)則進(jìn)行網(wǎng)絡(luò)請求。這里也是 yarn 誕生之初解決 npm v3 安裝緩慢問題的優(yōu)化點(diǎn),支持并行下載。
如何判斷有沒有命中緩存?
判斷系統(tǒng)中存在符合 "cachefolder+slug+node_modules+pkg.name" 規(guī)則的路徑,如果存在則判斷為命中緩存,否則就會(huì)重新下載。值得注意的是,不同版本的包在緩存中是扁平化管理。以下是緩存中 webpack 的依賴緩存,可以通過 yarn cache dir 查看。
4、鏈接包(linking dependencies)這一步主要是將緩存中的依賴,復(fù)制到項(xiàng)目目錄下,同時(shí)遵循扁平化原則。前面說到,npm 優(yōu)先將依賴安裝到項(xiàng)目目錄,因此需要將全局緩存中的依賴復(fù)制到項(xiàng)目。在復(fù)制依賴前,Yarn 會(huì)先解析 peerDependencies,如果找不到符合 peerDependencies 聲明的依賴版本,則進(jìn)行 warning 提示(這并不會(huì)影響命令執(zhí)行),并最終拷貝依賴到項(xiàng)目中。
5、構(gòu)建包(building fresh package)如果依賴包中存在二進(jìn)制包需要進(jìn)行編譯,會(huì)在這一步進(jìn)行。
如果破解依賴管理困境
在 npm v2 時(shí)期,安裝的依賴會(huì)存在于引用依賴的 node_modules 目錄,如果依賴過多,會(huì)形成一顆巨大的依賴樹。這種結(jié)構(gòu)雖然簡單明了,但是對于大型項(xiàng)目十分不友好。依賴層級深對開發(fā)排查不利,并且依賴的復(fù)用也是問題。在 npm v3 中引入扁平化的概念??磶讉€(gè)場景的例子??:
場景一:不同 npm 版本安裝依賴的結(jié)構(gòu)
[email protected] 依賴 [email protected],npm v3 是扁平化管理依賴。
場景二:不同 npm 版本處理依賴多版本共存問題
在場景一的基礎(chǔ)上,安裝 [email protected],而它依賴另一個(gè)版本的 [email protected]。由于根目錄下已存在 [email protected] 的依賴,npm v3 會(huì)把 [email protected] 安裝到 [email protected] 依賴的 node_modules 目錄。
靚仔疑惑:為什么 [email protected] 在頂級,而 [email protected] 在子級呢?
場景三:依賴的多版本的數(shù)量與依賴版本分布關(guān)系
在場景二的基礎(chǔ)上,安裝 [email protected],而它也依賴 [email protected]。同樣的,由于根目錄下已存在 [email protected] 的依賴,npm v3 會(huì)把 [email protected] 安裝到 [email protected] 依賴的 node_modules 目錄。
靚仔疑惑:你可能會(huì)疑問,此時(shí)存在2個(gè) [email protected] 和1個(gè) [email protected],出現(xiàn)在頂級安裝目錄的不應(yīng)該是 v2 版本而非 v1 版本嘛?
其實(shí)這是由依賴的安裝順序決定的,真就是依賴的某個(gè)版本如果出現(xiàn)在合適的時(shí)間,那么它就會(huì)被安裝到頂級 node_modules 目錄。不同版本的出場順序?qū)е乱蕾嚱Y(jié)構(gòu)的差異,npm v3 注定不是穩(wěn)定的包管理工具。跟生活一樣,人物的出場順序很重要,它決定了你在哪里做什么事。
場景四:依賴版本存在重復(fù)和可用
在場景三的基礎(chǔ)上,安裝 [email protected],它依賴 [email protected]。由于頂級目錄已存在目標(biāo)版本,因此 npm v3 會(huì)跳過該依賴的安裝。
場景五:版本升級囧境在
場景三的基礎(chǔ)上,如果更新了 [email protected],同時(shí)它的依賴是 [email protected]。那么 npm v3 的執(zhí)行順序是,刪除 [email protected],安裝 [email protected],安裝 [email protected],留下了 [email protected] 在頂層目錄,因此 [email protected] 會(huì)安裝到其父依賴的 node_modules 目錄。
場景六:依賴版本多目錄存在且符合復(fù)用條件
在場景五的基礎(chǔ)上,更新 [email protected],它依賴了 [email protected]。那么 npm v3 的執(zhí)行順序是,刪除 [email protected],安裝 [email protected],刪除 [email protected],安裝 [email protected],于是出現(xiàn)以下結(jié)構(gòu)。
此時(shí)你會(huì)發(fā)現(xiàn),存在多個(gè) [email protected] 分布在不同的 node_modules 目錄,他們是不是只要在頂級目錄存在一份即可?沒錯(cuò),我們刪除 node_modules 目錄重裝,得到的就是你想的清晰的結(jié)構(gòu)。
實(shí)際上,更優(yōu)雅的方式是使用 npm dedupe 命令達(dá)到上述結(jié)構(gòu)。而 yarn 在安裝依賴時(shí)會(huì)自動(dòng)執(zhí)行 dedupe 命令。
正是由于上述一些 npm 歷史的坑,所以更建議使用 yarn 作為項(xiàng)目協(xié)作的包管理工具。當(dāng)然 npm 發(fā)展至今,很多問題已經(jīng)優(yōu)化掉,現(xiàn)在 yarn 和 npm 是兩款互相看齊、互相獲取靈感的依賴管理工具。
npm vs. yarn ??
這里簡單對比 npm v6 和 yarn v1. 這是我們生產(chǎn)開發(fā)常用的版本。
npm 和 yarn 作為兩款相似的包管理工具,在一些功能實(shí)現(xiàn)上它們互相獲取靈感。
相同點(diǎn):
package.json 作為項(xiàng)目依賴描述文件。 node_modules 作為依賴存儲(chǔ)目錄,yarn v2 不再是這樣。 lockfile 鎖定版本依賴,在 yarn 中叫 yarn.lock,在 npm 中叫 package-lock.json,在 npm v7 也支持了 yarn.lock。它確保在不同機(jī)器或不同環(huán)境中,能夠得到穩(wěn)定的 node_modules 目錄結(jié)構(gòu)。
差異:
依賴管理策略。 lockfile。package-lock.json 自帶版本鎖定+依賴結(jié)構(gòu),你想改動(dòng)一些依賴,可能影響的范圍要比表面看起來的復(fù)雜的多;而 yarn.lock 自帶版本鎖定,并沒有確定的依賴結(jié)構(gòu),使用 yarn 管理項(xiàng)目依賴,需要 package.json + yarn.lock 共同確定依賴的結(jié)構(gòu)。 性能。(對比 npm v6 和 yarn v1)目前 npm v7 優(yōu)化了緩存和下載網(wǎng)絡(luò)策略,性能的差異在縮小。

[拓展] npm 企業(yè)級部署私服原理
npm 中的源(registry),其實(shí)就是一個(gè)查詢服務(wù)。以 npmjs.org 為例,它的查詢服務(wù)網(wǎng)址是 https://registry.npmjs.org/ ,在這個(gè)網(wǎng)址后加上依賴的名字,就會(huì)得到一個(gè) JSON 對象,里面包含了依賴所有的信息。例如:
https://registry.npmjs.org/react https://registry.npm.taobao.org/react
我們可以通過 npm config set registry 命令來設(shè)置安裝源。你知道我們公司為什么要部署私有的 npm 鏡像嗎?雖然 npm 并沒有被屏蔽,但是下載第三方依賴包的速度依然較緩慢,這嚴(yán)重影響 CI/CD 流程或本地開發(fā)效率。通常我們認(rèn)為部署 npm 私服具備以下優(yōu)點(diǎn):
確保高速、穩(wěn)定的 npm 服務(wù) 確保發(fā)布私有模塊的安全性 審核機(jī)制可以保障私服上 npm 模塊質(zhì)量和安全
部署企業(yè)級私服,能夠獲得安全、穩(wěn)定、高速的保障。
管理項(xiàng)目依賴的小技巧(集思廣益...)
推薦使用 yarn 作為團(tuán)隊(duì)包管理工具,而不是 npm。盡管在 npm v6 之后的版本趨向穩(wěn)定和安全,但由于歷史原因和團(tuán)隊(duì)管理兼容性,仍然是推薦使用 yarn 作為團(tuán)隊(duì)統(tǒng)一的包管理工具。 項(xiàng)目中一定要存在 lockfile 文件,且禁止手動(dòng)修改,因?yàn)檫@是項(xiàng)目穩(wěn)定性運(yùn)行的保障。 如果 yarn.lock 在代碼合并的過程中出現(xiàn)了問題,可以嘗試使用 yarn install 解決問題。 ...
參考資料
npm install[1] yarn install[2] yarn.lock[3] NPM vs. Yarn: Which Package Manager Should You Choose?[4] Lockfiles should be committed on all projects[5]
參考資料
npm install: https://docs.npmjs.com/cli/v7/commands/npm-install
[2]yarn install: https://classic.yarnpkg.com/en/docs/cli/install
[3]yarn.lock: https://classic.yarnpkg.com/en/docs/yarn-lock
[4]NPM vs. Yarn: Which Package Manager Should You Choose?: https://www.whitesourcesoftware.com/free-developer-tools/blog/npm-vs-yarn-which-should-you-choose/
[5]Lockfiles should be committed on all projects: https://classic.yarnpkg.com/blog/2016/11/24/lockfiles-for-all/
?? 謝謝支持
我們來自字節(jié)跳動(dòng),是旗下大力教育前端部門,負(fù)責(zé)字節(jié)跳動(dòng)教育全線產(chǎn)品前端開發(fā)工作。
我們圍繞產(chǎn)品品質(zhì)提升、開發(fā)效率、創(chuàng)意與前沿技術(shù)等方向沉淀與傳播專業(yè)知識及案例,為業(yè)界貢獻(xiàn)經(jīng)驗(yàn)價(jià)值。包括但不限于性能監(jiān)控、組件庫、多端技術(shù)、Serverless、可視化搭建、音視頻、人工智能、產(chǎn)品設(shè)計(jì)與營銷等內(nèi)容。
歡迎感興趣的同學(xué)在評論區(qū)或使用內(nèi)推碼內(nèi)推到作者部門拍磚哦 ??
字節(jié)跳動(dòng)校/社招內(nèi)推碼: WKVVX3V
投遞鏈接: https://jobs.toutiao.com/s/dLLnGv
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「 sherlocked_93 」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

