JavaScript常见面试题:npm跟pnpm有什么区别?
npm的发展
最早期的npm
早期的npm的依赖会被嵌套安装,也就是说:
{
"dependencies": {
"A": "^1.0",
"B": "^1.0",
"C": "^1.0"
}
}
如果我A,B,C三个包均引用了D包,但是A、B引用的是D@1.0.0
,而C引用的是D@2.0.0
,他们会分别安装到自己的node_modules底下。
// 项目的根node_modules
node_modules
A
node_modules
D@1.0.0
B
node_modules
D@1.0.0
C
node_modules
D@2.0.0
但是这样会导致依赖地狱。会出现依赖路径过长、以及文件被多次复制的问题!
npm3
为了解决依赖路径过长的问题,在npm3之后,依赖就被扁平化管理了。依赖被顶到了顶层,但是当出现上面的情况的时候,依赖的表现是怎么样的?
这时候先安装的包,会把他依赖的相应版本提前,后面安装的D包如果版本跟被置顶的版本号不一致,会被安装到其node_modules下。
// 项目的根node_modules
node_modules
A
B
C
node_modules
D@2.0.0
D(@1.0.0 )
但是这个会出现一个问题,就是如果根据安装的顺序进行依赖提升,用户在npm i
的时候,得到的结果是不确定的。因为npm也做了相对应的优化,把引用次数多的包扁平化管理,但当两个引用次数一样的时候,那必然带来的不确定性
npm5
为了解决上面的问题,在package.json的基础上,又新增了 package-lock.json 文件。
虽然v5.0.x跟v5.1.0的版本不一样,我们无需记住这个,只需要稍微了解即可。
-
npm@5.0.x 里,不管package.json怎么变,
npm i
都会根据lock文件下载。 -
npm@5.1.0版本后,
npm i
会无视package-lock.json文件,直接下载新的npm包; -
npm@5.4.2版本后,如果package.json和package.lock文件不同那么,
npm i
时会根据package的版本进行下载并更新package-lock;如果两个文件相同则会根据package-lock文件下载,不管package有无更新
但是尽管这样,他会有幽灵依赖的问题。
幽灵依赖
幽灵依赖在npm@3.x的版本中就已经出现了,因为有了提升的特性,上述例子中,虽然项目中没有在package.json中显性声明要安装D@1.0.0,但是npm已经将他提升到根部,此时在项目中引用D并进行使用是不会报错的。但是由于我们没有显性声明,假如一旦依赖A不再依赖D或者版本有变化那么此时install后代码就会因为找不到依赖而报错!!!
当然,npm还有另一个问题,就是依赖分身。比如我们A,B引用的是D@1.0.0,而C,E引用的是D@2.0.0,项目中D@1.0.0已经被依赖提升到顶部了,那么C,E的node_modules种均会有 D@2.0.0 的依赖,因此他会被重复安装。
pnpm
pnpm 号称 performance npm,与npm的依赖提升和扁平化不同。pnpm采取了一套新的策略:内容寻址储存;
还是使用上面的例子: 项目依赖了A、B、C,之后A依赖D@1.0,B依赖D@2.0,而C也依赖D@1.0,使用 pnpm 安装依赖后 node_modules 结构如下
// 项目的根node_modules
node_modules
.pnpm
A@1.0.0
node_modules
A => <store>/A@1.0.0
D => ../../D@1.0.0
D@1.0.0
node_modules
D => <store>/D@1.0.0
B@1.0.0
node_modules
B => <store>/B@1.0.0
D => ../../D@2.0.0
C@1.0.0
node_modules
C => <store>/C@1.0.0
D => ../../D@1.0.0
A => .pnpm/A@1.0.0/node_modules/A
B => .pnpm/B@1.0.0/node_modules/B
C => .pnpm/C@1.0.0/node_modules/C
我们看到,pnpm拥有自己的.pnpm目录,他会以平铺的方式来存储所有包,以依赖名加上版本号的名字为命名,实现了版本的复用。而且他不是通过拷贝机器缓存中的依赖到项目目录下,而是通过硬链接的方式,这能减少空间占用。
至于根目录下用于项目使用的依赖,则是通过符号链接的方式,链接到它的 .pnpm 目录下的对应位置。
shamefully-hosit
默认情况下,通过pnpm的node_modules你只能访问到在 package.json 文件中声明的依赖,只有依赖项才能访问未声明的依赖项。你可能需要需要再.npmrc文件中声明了 shamefully-host=true,他才会像npm平铺的方式,我们才能使用package.json没有显性声明的幽灵依赖。
不过事实上,pnpm的严格模式能够帮助我们避免一些低级错误。正常情况下,是不推荐使用羞耻提升的。