pnpm: 更快更节省磁盘的包管理器
什么是 pnpm ?
pnpm 是 npm 的替代者,命名是“Performant NPM”的简写,它更快、更节省空间,并且解决了 npm 的一些固有问题。在这篇文章中,我将会讨论它是如何工作的,并说明为什么 pnpm 是 npm 或 yarn 的完美替代品。
使用 npm 或者 yarn 的问题:
- 如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么你在硬盘上就需要保存 100 份该相同依赖包的副本,非常浪费空间。
- 使用扁平化的 node_modules,扁平化的依赖关系导致了一些问题,比如:
-
可以引用没在 package.json 文件中声明的依赖。比如你安装了 express,它有一个 debug 的依赖包,npm install 后的目录结构如下图,此时你可以在项目代码里面引用 debug package。如果未来 express 更新了 debug 大版本或者将其删除,会对你的工程代码造成很大的影响。
-
同一个项目中如果引用了不同版本的 package A,node_modules 文件夹里面会下载多份 package A, 比如: B 使用 A@1.0.0, 而 C 使用了 A@2.0.0, 此时就会下载两份 package A。
-
pnpm 怎么解决这些问题?
pnpm 使用硬链接和软链接来维护 node_modules 的结构, (硬链接和软链接有什么区别?请参考上一篇文章),还是使用上面express的例子。
上图显示了使用 pnpm 后项目文件的目录结构。你会发现工程代码无法访问到 debug 包了,因为它不在 node_modules 第一级目录。pnpm 创建了一个名为".pnpm "的文件夹,包含了所有模块的硬链接。
如果看一下express/4.0.0,express模块是一个指向全局pnpm-store的硬链接,debug 模块也是如此。
下图是实际的 pnpm-store 结构,它包含了这些 package, 所有下载的依赖都保存在这里。当你下载一个依赖项时,pnpm 首先检查该依赖项是否存在于这个仓库中。如果它在仓库中找到了该依赖项,pnpm 会通过创建一个硬链接来获取它。
由于这种方法,如果同样的软件包已经为另一个项目安装了,pnpm 就会重复使用这些软件包。
相对于 npm 或 yarn 的优势:
节省大量的磁盘空间。
如上所示,pnpm 使用了一个内容可寻址的文件系统来存储磁盘上的软件包和依赖项。这意味着相同的软件包不会被重复下载。即使是同一个软件包的不同版本,pnpm也会智能地重复使用最大的代码。如果一个包的版本 1.0.0 有 100 个文件,而版本 2.0.0 只多了一个文件,那么 pnpm 不会为版本 2.0.0 写 101 个文件;相反,它将创建一个硬链接到原来的 100 个文件,只新建一个新文件。
安装软件包更快。
从下图可以看到,用 pnpm 安装依赖的速度明显优于 npm 和 yarn。
内置了对 monorepo 的支持
目前 monorepo 可能还是老牌工具 lerna 使用的比较多,pnpm 内置了对 monorepo 的支持,更为简洁易用,具体可参考官方文档 workspaces
从 npm/yarn 迁移到 pnpm
如果你的项目使用了 npm 或 yarn,迁移到 pnpm 不是很困难,下面是npm、yarn和pnpm之间的命令比较。
npm command | Yarn command | pnpm equivalent |
---|---|---|
npm install | yarn | pnpm install |
npm install [pkg] | yarn add [pkg] | pnpm add [pkg] |
npm uninstall [pkg] | yarn remove [pkg] | pnpm remove [pkg] |
npm update | yarn upgrade | pnpm update |
npm list | yarn list | pnpm list |
npm run [scriptName] | yarn [scriptName] | pnpm [scriptName] |
npx [command] | yarn dlx [command] | pnpm dlx [command] |
npm exec | yarn exec [commandName] | pnpm exec [commandName] |
npm init [initializer] | yarn create [initializer] | pnpm create [initializer] |