前端包管理器的演变
npm v3 版本之前 存在嵌套依赖的问题
npm v3 版本之后 引入了扁平化依赖的概念,1. 但是有存在包可以互相访问的问题 2. 扁平比的操作成本非常高,速度慢 3. 可能会引发一些 bug
lerna 可以做的事
初始化lerna项目
包管理 涉及 add link bootstrap create 命令
批量执行命令
批量更新版本
批量发布
pnpm 是怎么解决以上问题
通过建立连接的方式,建立依赖树(通过软链接的方式 解决嵌套依赖的问题)
优势
- 非扁平目录,避免幽灵依赖及耗时问题(扁平化后导致的包之间可以相互访问)
- 全局缓存,硬连接包实现空间节省,安装快速。安装之后先是通过软链接到node_modules,然后通过硬链接到 pnpm store。
- pnpm workspaces 支持 monorepo 场景
monorepo 方案的劣势
项目粒度的权限管理变得非常复杂:无论是 Git 还是其他 VCS 系统,在支持 monorepo 策略中项目粒度的权限管理上都没有令人满意的方案,这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。(好在我们可以将 monorepo 策略实践在「项目级」这个层次上,这才是我们这篇文章的主题,我们后面会再次明确它);
新员工的学习成本变高:不同于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,当然这个成本可以通过新人文档的方式来解决,但维护文档的新鲜又需要消耗额外的人力;
对于公司级别的 monorepo 策略而言,需要专门的 VFS 系统,自动重构工具的支持:设想一下 Google 这样的企业是如何将十亿行的代码存储在一个仓库之中的?开发人员每次拉取代码需要等待多久?各个项目代码之间又如何实现权限管理,敏捷发布?任何简单的策略乘以足够的规模量级都会产生一个奇迹(不管是好是坏),对于中小企业而言,如果没有像 Google,Facebook 这样雄厚的人力资源,把所有项目代码放在同一个仓库里这个美好的愿望就只能是个空中楼阁。
Lerna的痛点
- 依赖安装性能问题
- 痛点描述:Lerna本身没有对依赖安装过程进行优化。在大型项目中,当使用
npm
或yarn
作为底层安装工具时,安装速度可能会很慢。因为每次执行安装操作,npm
或yarn
会重新下载和处理每个包的依赖,即使这些依赖在其他子包中已经安装过,也可能会导致大量的重复下载,而且依赖安装过程可能会占用较多磁盘空间。 - 示例场景:在一个包含多个子包的项目中,每个子包都依赖于一些相同的基础库,如
lodash
和axios
。当使用Lerna结合npm
进行安装时,npm
会在每个子包的node_modules
目录下分别安装这些基础库,浪费磁盘空间和安装时间。
- 痛点描述:Lerna本身没有对依赖安装过程进行优化。在大型项目中,当使用
- 依赖版本管理复杂性
- 痛点描述:在固定模式下,所有子包共享一个版本号,这可能导致在某些子包不需要更新时也必须更新版本。在独立模式下,虽然每个子包可以有独立的版本号,但管理起来仍然比较复杂,尤其是当子包之间有复杂的依赖关系时。例如,当一个子包更新了它所依赖的另一个子包的版本,需要确保所有依赖这个子包的其他子包也能正常工作,这可能需要手动检查和更新多个
package.json
文件中的版本号。 - 示例场景:有三个子包A、B、C,A依赖B,B更新了版本,那么需要确保A也能兼容新的B版本,并且如果C也依赖A,还需要考虑A的更新是否会影响C,这种复杂的依赖关系在版本更新时容易出现问题。
- 痛点描述:在固定模式下,所有子包共享一个版本号,这可能导致在某些子包不需要更新时也必须更新版本。在独立模式下,虽然每个子包可以有独立的版本号,但管理起来仍然比较复杂,尤其是当子包之间有复杂的依赖关系时。例如,当一个子包更新了它所依赖的另一个子包的版本,需要确保所有依赖这个子包的其他子包也能正常工作,这可能需要手动检查和更新多个
- 幽灵依赖和依赖分身问题(间接)
- 痛点描述:由于Lerna通常依赖
npm
或yarn
的扁平化node_modules
结构,在这种结构下可能会出现幽灵依赖(项目未在package.json
中声明但可以访问的依赖)和依赖分身(同一依赖的不同版本被错误地当作相同版本使用)的问题。虽然这些问题主要是npm
和yarn
的扁平化结构导致的,但Lerna没有直接解决这些问题。 - 示例场景:在一个子包的代码中意外地访问到了另一个子包的依赖,而这个依赖并没有在当前子包的
package.json
中声明,这就是幽灵依赖。或者两个子包依赖同一个库的不同版本,在扁平化结构下可能会导致错误的版本被使用。
- 痛点描述:由于Lerna通常依赖
Pnpm对Lerna痛点的解决情况
- 依赖安装性能问题的解决
- 解决方式:Pnpm的非扁平化存储和共享存储库机制可以很好地解决依赖安装性能问题。它通过复用已经安装的依赖,避免了重复下载。当在Lerna管理的项目中使用Pnpm时,无论有多少个子包依赖相同的库,Pnpm只会在共享存储库中存储一份该库的副本,然后通过硬链接和符号链接的方式在各个子包中引用,大大提高了安装速度并节省磁盘空间。
- 示例场景:在前面提到的包含多个子包依赖
lodash
和axios
的项目中,使用Pnpm后,这些基础库只会在共享存储库中存储一次,所有子包都可以通过链接访问,安装速度会明显加快,磁盘空间占用也会减少。
- 依赖版本管理复杂性的部分解决
- 解决方式:Pnpm本身在依赖版本管理上更加精确。它通过符号链接和非扁平化存储可以更好地处理依赖关系,使得在更新子包的依赖版本时,能够更准确地反映在整个项目中。虽然Pnpm不能完全解决Lerna在版本管理方面的复杂性,如子包之间复杂的依赖关系和版本更新的协调问题,但它可以减少因依赖安装结构混乱导致的版本管理困难。
- 示例场景:当使用Pnpm时,每个依赖的版本和位置都更加清晰,在更新子包的依赖版本时,可以更容易地追踪和管理。例如,通过查看Pnpm的存储结构和链接关系,可以明确知道每个子包依赖的具体版本和路径,减少了版本冲突和错误引用的可能性。
- 幽灵依赖和依赖分身问题的解决
- 解决方式:Pnpm的非扁平化存储结构可以有效避免幽灵依赖和依赖分身问题。因为它不会像
npm
和yarn
的扁平化结构那样,容易出现依赖的混乱。每个子包的依赖都有明确的路径和引用,只有在package.json
中声明的依赖才会被正确安装和引用,避免了意外访问未声明的依赖,同时也能准确区分同一依赖的不同版本。 - 示例场景:在Pnpm管理的项目中,子包无法意外地访问到其他子包未声明的依赖,而且同一依赖的不同版本会被正确存储和引用,不会出现因为扁平化结构导致的版本混淆问题。
- 解决方式:Pnpm的非扁平化存储结构可以有效避免幽灵依赖和依赖分身问题。因为它不会像
lerna vs pnpm
不能简单地说pnpm优于Lerna,它们有不同的功能重点和适用场景:
一、功能重点
- Lerna:
- Lerna主要侧重于多个JavaScript/TypeScript包的版本管理和发布流程。它提供了方便的工具来协调多个子包之间的版本发布,特别是在处理大型项目中多个相互关联的组件或库的版本更新时非常有用。例如,在一个包含多个UI组件的项目中,Lerna可以帮助开发者更好地管理这些组件的版本,决定是一起发布所有组件还是独立发布每个组件。
- 它还提供了本地开发时方便的子包链接功能(
lerna bootstrap
),使得在开发过程中,不同子包之间的相互引用和调试更加便捷。
- Pnpm:
- Pnpm重点在于优化包的安装过程,包括提高安装速度和减少磁盘空间占用。它的非扁平化存储和共享存储库机制是其核心优势,能够有效解决依赖管理中的幽灵依赖和依赖分身等问题。例如,在一个有多个项目或者一个大型项目中有复杂依赖关系的场景下,Pnpm可以确保依赖的准确安装和高效复用。
二、适用场景
- Lerna适用场景:
- 大型项目的多包管理与发布:如果你的项目是一个大型的代码库,包含多个可以独立发布的包,并且这些包之间存在一定的版本关联和发布策略(如需要同时发布或者按照一定顺序发布),Lerna是一个很好的选择。例如,像Babel这样的项目,它有多个不同功能的子包,Lerna可以帮助管理这些子包的版本发布流程。
- 本地开发多包引用频繁的场景:在开发过程中,当需要频繁地在本地不同子包之间进行引用和调试时,Lerna的本地链接功能可以提高开发效率。比如开发一个包含多个微服务的项目,这些微服务在本地开发阶段需要相互调用,Lerna可以方便地处理这种情况。
- Pnpm适用场景:
- 对安装性能和磁盘空间敏感的项目:无论是小型项目还是大型项目,只要对安装速度和磁盘空间有要求,Pnpm都能发挥优势。例如,在一个持续集成/持续部署(CI/CD)环境中,大量的项目需要频繁安装依赖,Pnpm可以大大缩短安装时间并减少磁盘占用。
- 需要精确依赖管理的项目:对于那些对依赖版本准确性要求很高,需要避免幽灵依赖和依赖分身问题的项目,Pnpm是更好的选择。比如开发一个复杂的前端应用,有多个插件和库相互依赖,Pnpm可以确保依赖关系的清晰和正确。
三、结论
- 在实际应用中,它们甚至可以结合使用。例如,在一个使用Lerna管理多个子包的大型项目中,可以使用Pnpm作为包管理器来优化依赖安装过程。这样可以充分发挥Lerna在版本管理和多包开发方面的优势,同时利用Pnpm在依赖安装和管理方面的长处。所以,不能单纯地判断哪个更优,而是要根据项目的具体需求和痛点来选择合适的工具。