JavaScript常见面试题:npm跟pnpm有什么区别?

npm的发展

最早期的npm

早期的npm的依赖会被嵌套安装,也就是说:

{
  "dependencies": {
    "A": "^1.0",
    "B": "^1.0",
    "C": "^1.0"
  }
}

如果我A,B,C三个包均引用了D包,但是A、B引用的是[email protected],而C引用的是[email protected],他们会分别安装到自己的node_modules底下。

// 项目的根node_modules
node_modules
     A
           node_modules
                  [email protected]
     B
           node_modules
                  [email protected]
     C
           node_modules
                  [email protected]

但是这样会导致依赖地狱。会出现依赖路径过长、以及文件被多次复制的问题!

npm3

为了解决依赖路径过长的问题,在npm3之后,依赖就被扁平化管理了。依赖被顶到了顶层,但是当出现上面的情况的时候,依赖的表现是怎么样的?

这时候先安装的包,会把他依赖的相应版本提前,后面安装的D包如果版本跟被置顶的版本号不一致,会被安装到其node_modules下。

// 项目的根node_modules
node_modules
     A
     B
     C
           node_modules
                  [email protected]
     D(@1.0.0 )

但是这个会出现一个问题,就是如果根据安装的顺序进行依赖提升,用户在npm i的时候,得到的结果是不确定的。因为npm也做了相对应的优化,把引用次数多的包扁平化管理,但当两个引用次数一样的时候,那必然带来的不确定性

npm5

为了解决上面的问题,在package.json的基础上,又新增了 package-lock.json 文件。

虽然v5.0.x跟v5.1.0的版本不一样,我们无需记住这个,只需要稍微了解即可。

  • [email protected] 里,不管package.json怎么变,npm i都会根据lock文件下载。

  • [email protected]版本后,npm i会无视package-lock.json文件,直接下载新的npm包;

  • [email protected]版本后,如果package.json和package.lock文件不同那么,npm i时会根据package的版本进行下载并更新package-lock;如果两个文件相同则会根据package-lock文件下载,不管package有无更新

但是尽管这样,他会有幽灵依赖的问题。

幽灵依赖

幽灵依赖在[email protected]的版本中就已经出现了,因为有了提升的特性,上述例子中,虽然项目中没有在package.json中显性声明要安装[email protected],但是npm已经将他提升到根部,此时在项目中引用D并进行使用是不会报错的。但是由于我们没有显性声明,假如一旦依赖A不再依赖D或者版本有变化那么此时install后代码就会因为找不到依赖而报错!!!

当然,npm还有另一个问题,就是依赖分身。比如我们A,B引用的是[email protected],而C,E引用的是[email protected],项目中[email protected]已经被依赖提升到顶部了,那么C,E的node_modules种均会有 [email protected] 的依赖,因此他会被重复安装。

pnpm

pnpm 号称 performance npm,与npm的依赖提升和扁平化不同。pnpm采取了一套新的策略:内容寻址储存

还是使用上面的例子: 项目依赖了A、B、C,之后A依赖[email protected],B依赖[email protected],而C也依赖[email protected],使用 pnpm 安装依赖后 node_modules 结构如下

// 项目的根node_modules
node_modules
     .pnpm
           [email protected]
                  node_modules
                       A => /[email protected]
                       D => ../../[email protected]
           [email protected]
                  node_modules
                        D => /[email protected]
           [email protected]
                  node_modules
                       B => /[email protected]
                       D => ../../[email protected]
           [email protected]
                node_modules
                     C => /[email protected]
                     D => ../../[email protected]
      A => .pnpm/[email protected]/node_modules/A
      B => .pnpm/[email protected]/node_modules/B
      C => .pnpm/[email protected]/node_modules/C

我们看到,pnpm拥有自己的.pnpm目录,他会以平铺的方式来存储所有包,以依赖名加上版本号的名字为命名,实现了版本的复用。而且他不是通过拷贝机器缓存中的依赖到项目目录下,而是通过硬链接的方式,这能减少空间占用。

至于根目录下用于项目使用的依赖,则是通过符号链接的方式,链接到它的 .pnpm 目录下的对应位置。

shamefully-hosit

默认情况下,通过pnpm的node_modules你只能访问到在 package.json 文件中声明的依赖,只有依赖项才能访问未声明的依赖项。你可能需要需要再.npmrc文件中声明了 shamefully-host=true,他才会像npm平铺的方式,我们才能使用package.json没有显性声明的幽灵依赖。

不过事实上,pnpm的严格模式能够帮助我们避免一些低级错误。正常情况下,是不推荐使用羞耻提升的。

你可能感兴趣的:(JavaScript常见面试题:npm跟pnpm有什么区别?)