到2020年底,npm released the v7 of their package manager。最明显的变化是它现在为工作区提供了支持。这意味着您最终可以使用Node-In-In-In包装管理器管理MonorePo,而无需额外的步骤或工具。
在Hotjar,一个平台,一个使人们洞悉用户和客户行为的平台,我们决定从纱线转到NPM,以简化我们的工具,因此,NPM将再次成为我们的包装管理器,现在它为我们的团队提供了我们团队manage a monorepo的能力。
但是,我们没有花很长时间才意识到这可能不是最好的决定。
什么是NPM工作区?
Npm workspaces提供了在MonorePo中管理依赖项所需的工具。设置非常简单,在您的package.json
中使用新属性:
// package.json
{
"name": "workspace-example",
"workspaces": [
"./packages/*"
]
}
它可以自动安装和链接过程,并且有一个特殊的--workspace
标志来在monorepo项目的上下文中运行命令。
# example: run the tests for the workspace "foo"
npm run test --workspace foo
当Hotjar开始使用NPM工作区时,我们的MonorePo中的工作区少于20个。今天,我们有50多个!
因此,我们意识到npm并不是一个难以扩展的monorepo工具。我们的CI开始花费太长时间(我们到达了超过最大平均运行时的地步),node_modules
尺寸失控了,简单的NPM安装变成了密集的每日过程。实际上,我们并没有补充到我们的MonorePo,但是使用工作区并安装其依赖性变得累人和乏味。
参考:我们的node_modules
尺寸花费了3.2GB的空间磁盘,并且在CI上安装依赖项大约需要4分钟。这根本不是理想的,所以我们开始研究NPM CLI的工作原理。
NPM的缺点
npm文档一般都很棒。但是,当涉及到他们的CLI如何在引擎盖下工作时,您会失明。经过许多不良的研究,我们得出的结论是,NPM工作区是简单的monorepos的绝佳方法,但对于像我们在Hotjar这样的大量存储库而不是。为什么:
- npm每次安装依赖性的副本:如果我们使用依赖关系有100个项目,我们将有100份保存在磁盘上的依赖项。对于monorepos而言,这与完全没有用,因为NPM可以使用相同的版本删除依赖性,但是它仍然会复制一些依赖性树的不同版本的子依赖性。 。
- npm具有安装的阻塞阶段:例如,它可以使安装平行,因此每个依赖性都遵循解决方案的写作流程,每个依赖性必须在一个阶段之前完成一个阶段我们可以启动另一个
- npm没有适当的机制来意识到依赖性是您的monorepo 的另一个工作空间:这意味着每次您要安装依赖项时,NPM都会要求对注册表要求这种依赖性,然后在那里找不到依赖时,将尝试在本地链接它。
- npm提升器依赖项您安装到模块目录的根目录:结果,源代码可以访问未添加为项目依赖项的依赖项。这被称为“幻影依赖”,可能会以几种意外的方式打破您的依赖性或代码。
PNPM:NPM表演者
NPM的工作方式使得(如果不是不可能)适当地升级到工作空间,从而使我们的依赖性过程放慢速度,并变得难以管理和不安全。这就是为什么hotjar需要找到更快,更轻松,更安全的替代方案,该替代方案也适合我们的MonorePo缩放,因此添加更多的工作空间不会降低整个依赖关系的安装过程。
> 由Vercel和Prisma赞助的,在节点生态系统中为自己起名字,是NPM的替代品,它引起了Hotjar的特别关注。凭借其快速,高效,严格和支持的MonorePos的承诺,它看起来像是完美的包装经理候选人。您可以看到full benchmark comparison of JavaScript package managers here。
切换到快速monorepos
进行了一些测试后,我们估计安装,速度和磁盘空间使用方面的改进将大大增加。是时候再次离开NPM了,所以我们下班了。我们花了几个星期的时间来进行此迁移,然后最终切换到PNPM。
设置也很简单:
# pnpm-workspace.yaml
packages:
- 'packages/*'
整个安装和链接是智能自动化的,您可以在使用--filter
标志的工作区中运行所有命令:
# example: run the tests for the workspace "foo"
pnpm --filter foo test
pnpm还提供高级功能,例如workspace protocol或为工作区进行部分安装,使您可以对MonorePo进行详细的控制。
但是,最佳改进是以性能的形式出现的。让我们看一下Hotjar进行切换后发生的变化:
磁盘空间比较
npm(之前) | pnpm(之后) | îdelta | |
---|---|---|---|
docker图像大小 | 4.08GB | 2.54GB | 37%†|
node_modules size | 3.2GB | 1.3GB | 59% - |
安装时间比较(在本地开发设备中)
npm(之前) | pnpm(之后) | îdelta | |
---|---|---|---|
没有缓存,没有node_modules | 4min 50sec | 1分30秒 | 69% - |
使用缓存,没有node_modules | 5min | 48秒 | 84% - |
使用缓存和node_modules | 1min | 8秒 | 86% - |
结果?我们最终将CI恢复到了平均运行时。
我们无法对PNPM感到满意,并且可以推荐它足够的建议,因为我们继续使用它可以使您的MonorePo可扩展和可维护而无需添加任何复杂性。即使您有一个小或简单的MonorePo,也可以利用PNPM速度安装改进。在下一篇文章中,我们将谈论幻影依赖性以及它们如何使我们的迁移到PNPM。