npm /yarn 原理
依賴地獄
早期的 npm 會(huì)直接安裝依賴舔箭,如果依賴存在依賴罩缴,則在依賴?yán)锛尤?node_modules。
假如有兩個(gè)依賴 A
, B
。 A
依賴于 C
D
,而 B
依賴于 C
E
靴庆。則依賴目錄是這樣的:
node_modules
├─ A
│ └─ node_modules
│ └─ C
│ └─ D
├─ B
│ └─ node_modules
│ └─ C
└─ └─ E
從依賴目錄可以看出,假如A
依賴于C@1.0.0
, B
也依賴于C@1.0.0
怒医。則會(huì)同時(shí)安裝兩份C@1.0.0
炉抒。很明顯,有一份C@1.0.0
是重復(fù)的稚叹。
對(duì)于大型項(xiàng)目焰薄,依賴會(huì)特別多,個(gè)別依賴也會(huì)嵌套比較深扒袖。這樣就會(huì)導(dǎo)致不同的依賴分支里會(huì)有大量的重復(fù)依賴塞茅,且依賴嵌套過深的話會(huì)導(dǎo)致 windows 目錄路徑過長問題。由于是嵌套導(dǎo)致的季率,這個(gè)時(shí)期的依賴安裝被稱為依賴地獄野瘦。
依賴扁平化
為了解決依賴嵌套的問題, npm3.x 開始(yarn 也是這個(gè)時(shí)期出現(xiàn)的)飒泻,開始依賴扁平化鞭光。也就是把依賴的依賴提升到 node_modules 根目錄下,重復(fù)的依賴不再安裝泞遗。這樣就可以實(shí)現(xiàn)依賴共享惰许。
比如上面這個(gè)例子,依賴目錄如下:
node_modules
├─ A
├─ B
├─ C@1.0.0
├─ D
└─ E
這樣好像解決了重復(fù)依賴的問題史辙?只能說是解決了汹买,但沒有完全解決。
假如 A
依賴于 C@1.0.0
和 D
聊倔,B
依賴于 C@2.0.0
和 E
晦毙,同時(shí) E
又依賴于 C@2.0.0
。則依賴扁平化的安裝如下:
node_modules
├─ A
├─ C@1.0.0
├─ D
├─ B
│ └─ node_modules
│ └─ C@2.0.0
│─ E
│ └─ node_modules
└─ └─ C@2.0.0
安裝的時(shí)候耙蔑,C@1.0.0
D
E
會(huì)被提升到 node_modules 根目錄结序。但是由于版本不一致,B
和 E
依賴的 C@2.0.0
就只能放到自身的 node_modules 下纵潦,而不能共享提升的 C@1.0.0
徐鹤。這樣一來 C@2.0.0
就被安裝了兩份。
C@2.0.0
被叫做分身依賴邀层。分身依賴就是相同版本的依賴被重復(fù)安裝返敬。
除了分身依賴之外,依賴扁平化還引入了新的問題寥院。
不確定性
第一個(gè)問題就是不確定性劲赠。
假如A
依賴于C@1.0.0
, B
依賴于C@2.0.0
。那么依賴安裝會(huì)提升的是C@1.0.0
還是C@2.0.0
呢?也就是下面這兩種結(jié)構(gòu):
node_modules
├─ A
│─ C@1.0.0
├─ B
│ └─ node_modules
└─ └─ C@2.0.0
還是
node_modules
├─ A
│ └─ node_modules
│ └─ C@1.0.0
├─ B
└─ C@2.0.0
網(wǎng)上大部分說法是會(huì)根據(jù) package.json
里面的順序決定誰會(huì)被提升凛澎,放在前面的包依賴的內(nèi)容會(huì)被優(yōu)先提升霹肝。
看了下最新版本的 npm 源碼,發(fā)現(xiàn) npm 其實(shí)會(huì)先調(diào)用 localeCompare
來對(duì)依賴進(jìn)行排序塑煎,也就是字典序在前面的包依賴的內(nèi)容會(huì)被優(yōu)先提升沫换。這樣即使修改package.json
里依賴的順序,也不會(huì)影響依賴提升最铁,解決了不確定性讯赏。
幽靈依賴
第二個(gè)問題就是幽靈依賴。
package.json
中不存在卻可以直接使用的依賴就是幽靈依賴冷尉。 在依賴扁平化后漱挎,被提升的依賴就是幽靈依賴。
假如A
依賴于C@1.0.0
雀哨。
node_modules
├─ A
└─ C@1.0.0
C@1.0.0
就是幽靈依賴磕谅。我們可以在項(xiàng)目中直接使用C@1.0.0
,但是package.json
中并沒有這個(gè)依賴項(xiàng)。
這看上去好像沒什么問題雾棺,但是設(shè)想這種情況:有人發(fā)現(xiàn)依賴 A 不再使用怜庸,于是刪除了 A。但是幽靈依賴C@1.0.0
依然在項(xiàng)目被使用著垢村,這就會(huì)引起報(bào)錯(cuò)割疾。
pnpm 的破局之法
pnpm 的出現(xiàn)解決了分身依賴和幽靈依賴的問題。那 pnpm 是如何解決的呢嘉栓?
假如 A
依賴于 C@1.0.0
宏榕,那么通過 pnpm 安裝后的依賴目錄如下:
node_modules
├─ .pnpm
│ └─ node_modules // 非直接依賴的包, 都是符號(hào)連接
│
│ └─ A@1.0.0
│ └─ node_modules
│ └─ A -> <store>/A
│ └─ C -> ../../C@1.0.0/node_modules/C
│
│ └─ C@1.0.0
│ └─ node_modules
│ └─ C -> <store>/C
│
└─ A -> ./.pnpm/A@1.0.0/node_modules/A
node_modules 根目錄下有一個(gè) .pnpm
文件夾和 A
。這里的 A 是個(gè)軟連接侵佃,指向它的硬連接麻昼。
可以簡單理解為:
硬連接是指向?qū)嶋H存儲(chǔ)位置的一個(gè)指針文件。
軟連接是指向硬連接的一個(gè)指針文件馋辈。
所有的依賴都會(huì)在 .pnpm
文件夾羅列出來(不同版本可以看做是不同的依賴)抚芦。每個(gè)依賴有個(gè) node_modules 文件夾,里面存放的是自身的硬連接和自身所依賴的依賴的軟連接迈螟。
用圖片來表示如下:
這樣 pnpm 就保證了同一個(gè)版本的依賴只安裝一份叉抡。依賴根目錄存放的是所有的直接依賴的軟連接。至于依賴之間的關(guān)系則同樣是通過軟連接來構(gòu)建答毫。
lock 文件
npm 的 package-lock.json
褥民,yarn 的 yarn.lock
,以及 pnpm 的 pnpm-lock.yaml
都是 lock 文件洗搂,用于鎖定依賴項(xiàng)的版本和下載源贪嫂。
如果項(xiàng)目里存在 lock 文件,則安裝依賴時(shí)候會(huì)按照 lock 文件里的版本安裝灭忠。如果不存在,則會(huì)按照 package.json
里的版本安裝贞岭,且根據(jù)你所安裝的依賴生成一份 lock 文件。
很多人傾向于直接寫死 package.json
里的版本。但是 package.json
里只能鎖定直接依賴的版本,不能保證依賴的依賴版本不變逞刷。
lock 文件不僅能夠鎖定直接依賴版本還能鎖定依賴的依賴版本。 個(gè)人比較建議上傳 lock 文件到 git译隘,這樣能夠保證團(tuán)隊(duì)的依賴一致。