npm
依赖嵌套
在 npm@3 版本之前,npm 使用的是依赖嵌套安装的方式,每个依赖性都有自己的 node_modules 文件夹,这导致了两个问题。首先,由于依赖数的深度,目录路径可能会变得过长。其次,当多个包需要同一个依赖时,例如包 A 和包 B 都需要包 C,那么在本地磁盘中会存储多个相同的包 C。
扁平化结构
为了解决这个问题,npm@3 和 Yarn 都采用了扁平化的结构,将所有包放在同一层级,相当于将包 C 提升到了顶层。需要注意的是,多个版本的包只有一个被提升到顶层,其余版本的包会被嵌套安装在各自的依赖中。哪个版本的包被提升取决于package.json 中包 A 和包 B 的位置。
幽灵依赖
然而,扁平化结构也引入了新的问题,即依赖结构的不确定性导致了扁平化结果的不确定性。因此,出现了第二个问题是幽灵依赖,即某个包没有在 package.json 中被显式依赖,但用户却可以直接引用这个包。这是因为扁平化结构导致的,容易出现包版本的问题。
npm 分身
第三个问题是 npm 分身,即提升机制可能会导致大量的依赖被重复安装,从而造成一定的性能损失。
yarn
于npm相比的优点
- 快速安装:yarn 使用并行下载和缓存机制,能够显著提高安装依赖包的速度。同时,yarn 还会自动解决依赖项的版本冲突,避免了在安装过程中可能出现的错误。
- 锁定版本:yarn 使用 yarn.lock 文件来锁定每个依赖项的精确版本,从而确保在不同的环境中和不同的构建之间使用相同的依赖版本。这可以避免由于依赖项的版本差异导致的潜在构建错误或运行时错误。
- 更好的缓存机制:yarn 会自动将下载过的依赖项保存到本地缓存中,以便在后续的安装中可以直接从缓存中获取,从而节省了带宽和时间。此外,yarn 还支持离线安装,可以在没有网络连接的情况下使用缓存安装依赖项。
- 更简洁的输出:yarn 的命令行输出通常比 npm 更简洁和易读,提供更好的用户体验和调试信息。它会以一种更加清晰的方式显示依赖树和安装进度,便于开发者进行排查和调试。
- 支持工作区:yarn 支持工作区(Workspaces),允许在多个相关的项目中共享依赖项,并且可以在工作区之间共享代码和运行脚本,从而简化了多包管理和跨项目的协作。这对于管理复杂的项目结构或者项目集合的情况下非常有用。
- 自动清理:yarn 在安装依赖时会自动清理不再使用的依赖项,从而减少项目中不必要的文件和文件夹,帮助保持项目的整洁性和简洁性。
pnpm
平铺存储
与此不同,pnpm 会创建一个隐藏的文件夹 .pnpm,以平铺的方式存储所有的包,通过包名和版本号来实现同一模块的不同版本之间的隔离和复用。由于不存在提升,也就不存在之前的幽灵依赖问题。
硬链接
同时,npm 或 Yarn 中相同的包在多个项目中每次安装时都会被重新下载,而 pnpm 则使用名为 .pnpm-store 的存储目录来存储所有项目依赖包信息的硬链接。通过访问这些硬链接,可以直接访问文件资源。如果依赖在 store 中已经存在,则可以直接在 store 目录里面创建硬链接,不存在则下载一次。这样相同的依赖只会在全局 store 中安装一次,解决了 npm 分身的问题。
软链接
软链接创建了一个指向目标文件或目录的链接,类似于快捷方式。
由于硬链接只能用于文件而不能用于目录,因此项目中还需要通过软链接将 .pnpm 的 node_modules 目录链接到项目中。
版本控制
版本控制:pnpm 使用一种类似于 yarn.lock 的机制,通过一个独立的文件(shrinkwrap.yaml)来锁定每个依赖项的精确版本。pnpm 通过硬链接将锁定的版本与实际安装的依赖项关联起来,从而确保在不同的环境中和不同的构建之间使用相同的依赖版本。