pnpm升级以及npm相关知识点整理

2,233 阅读6分钟

简介

pnpm performant npm(高性能的)替换原有的npm进行组件包的管理工作。本文档用于记录与分享。中心思想为:

  1. 为什么要做pnpm升级
  2. 升级中遇到的重难点分析
  3. npm, pnpm的一些概念讲解

因为伪城运项目中逻辑复杂,npm包的关系难以梳理。此处分别发布三个组件包testa,testb,testc。其引入关系为testb引入testa,testc也引入testa。之后构建testProject与pnmpTestProject对这三个组件包进行操作分析

devdependencies 和 dependencies

使用--save-dev和-D安装的包存于devdependencies中,--save和-S安装的包存于dependencies中。

在网上对于这两个的区别解释都是统一口径的,一个是开发依赖,一个是线上依赖。其实非常容易误解。而官方的文档中也没有明确这两个的实质差别,只是提了一句【请不要将测试工具、Transpiler或其他“开发”工具放在依赖项对象中。 】,有点不明所以。我这边用案例说明。

首先testb和testc中用--save-dev安装testa, 此时testb的package.json中如下:

此时在testProject中安装testb,testc并调用。首先观察testProject的node_modules:

发现并没有testa。接着运行项目:

证实testa包并没有被引入。接下来,将testa置于testb,testc的dependencies中:

重复上述操作,结果如下:

所以实际结论如下:dependencies和devDependcies在项目中,并不是是否携带与生产环境中的区别,在项目打包时,无论处于dependencies还是devDependcies中都会被携带。但是在npm publish中,只有处于dependencies中的依赖才会一起携带。

npm依赖包的版本规范

虽然三个依赖包已经成功发布,但是还有一点存在疑问。在testb中引入了^1.0.3版本的testa, testc中引入了^1.0.4的testa。但是最终的结果输出都是1.0.4版本的testa,和预期存在出入。

我们首先可以看到npm文档中对版本语义的解析

这里不一个一个分析,详情可以直接看semver文档。直接找到上面^1.0.3的解释 “与版本兼容”,表示不能看懂。去semever中查找得到如下结果:

叙述不用看了,单看示例也懂了。所以^1.0.3和^1.0.4同时出现时,直接使用了1.0.4这个版本。这也就是“与版本兼容”的含义(悟空组件的版本应该也参照这个规范)。后面的操作将testb和testc中的版本换为特定的1.0.3和1.0.4版本进行后续测试。

npm存在的问题

处理完成后,可以查看输出情况和node_modules的情况

可以看到,此时输出情况已经符合预期。但是node_modules中在一级目录存在一个testa(1.0.3),在testc中也存在另一个testa(1.0.4).

这时候添加testd npm包,其内容和testc包完全一致。最终node_modules结果为:

这时候可以发现,testc和testd中都存在一个testa,并且其版本都是1.0.4。这就是node_modules管理的第一个严重问题重复依赖问题,1. 这个问题不仅仅是引入了重复的包导致体积增加,2. 同时引入一个包的多个版本也可能导致全局变量冲突等问题 3. 使用扁平化算法很复杂,耗时过高,虽然node_modules已经试图使用扁平化管理来尽可能复用相同的包,但是在面对不同版本时依然没有好的处理方案。

接下来看个更有意思的,直接在testProject中使用testa

注意,此时的testProject的依赖中是没有引入testa的:

最终的输出结果,testa也能正确执行。这就是node_modules的第二个重大问题:可以非法访问没有声明过依赖的包。这种访问存在很大的安全隐患,首先导致项目的依赖包不好管理,同时如果testb中的testa升级,testProject中的使用方式可能无法正常使用,导致莫名其妙的问题(幽灵依赖)。

  1. pnpm包的对比测试

之后使用pnpm进行打包测试,使用pnpm install testb testc testd。 得到如下结构:

可以看到,node_modules中只存在testb, testc, testd。而不存在testa。而npm使用的扁平化管理则在.pnpm中实现。并且testa的两个版本同时存在。

首先我们先来分析一下这个包的结构:

首先是node_modules下的testb, testc, testd。这三个其实是软链接(通俗来说,硬链接类似于存储内容的指针,软链接类似于存储内容指针的指针软链接与硬链接),并不是真实的文件。例如node_modules/chk-testb的真实地址,其实是存在于node_modules.pnpm\chk-testb@2.0.2\node_modules\chk-testb中的。

其次是.pnpm文件夹:这个文件夹下存放了所有版本的依赖包真实地址,与npm的node_modules类似。但是每个版本的包都只存放一个,其余都是用软链接代替。这样便解决了npm包管理的冗余和多版本同时引入导致的冲突。.lock.yaml是pnpm的版本锁文件,类似与package-lock。

.modules.yaml文件:配置文件,具体功能带研究

主目录下还存在一个pnpm-lock.yaml文件:看内容和.lock.yaml文件类似,同属于版本锁文件。

接下来来试试幽灵依赖,在pnpm的项目中直接调用testa函数。发现直接报错,无法使用不在dependecies中的依赖。

所以综上所述,pnpm对比npm,存在如下优势:

  1. 打包速度快,直接拿数据对比

  1. 包体积非常小,这点主要原因就是是用来软链接去除了重复的包
  2. 不存在幽灵依赖,node_modules中包的依赖非常清晰
  3. 同样是使用package.json当作配置文件,兼容npm,便于升级
  4. 支持 monorepo,城运项目中未涉及,暂不讨论

项目的pnpm升级

1.  时间分析

首先,先使用原npm打包,得到结果如下

从47.03 - 48:19,打包耗时76s

使用pnpm升级,安装包后报错:

peerDependencies可以看看官网对其的解释:其表示包的必要依赖,比如上图就是说,想使用@babel/plugin-propsal-optional-chaining则必须要安装@babel/core@^7.0.0-0版本依赖。在npm2中,该依赖会被自动安装,而npm3,pnpm不会自动安装,而是只会做校验后提醒用户安装。

然后伪城运的依赖真是。。。想重新搭一个项目

打包时间02.18 - 03.30 耗时72s 结果耗时差不多可还行。

  1. 体积分析:

  打包前的node_modules体积

打包后的node_modules体积

看起来是小了20%左右,应该是去除了重复依赖的问题。

参考文档

package.json|Docs

Semver npm版本语义生成器

node_modules的困境

pnpm官网