在前端开发的包管理领域,npm 虽然应用广泛,但随着发展,yarn 和 pnpm 相继出现,它们各自针对 npm 当时存在的一些问题提供了有效的解决方案,下面我们就来深入了解一下它们。
一、yarn
- 诞生背景与开发团队:yarn 这个包管理器诞生于 2016 年,是由 Facebook、Google、Exponent 等几家公司的团队共同开发推出的。其官网为 https://yarnpkg.com/。
- 解决的 npm 问题:
- 安装速度方面:早期的 npm 在安装速度上表现欠佳,尤其是在大型项目中这种缓慢的情况尤为明显。而 yarn 使用了并行下载以及缓存的机制,显著提升了依赖安装的速度,大大提高了开发过程中的效率。
- 一致性方面:npm 在安装依赖时存在一个问题,同一个项目在不同时期进行安装,会产生不同的 node_module 结构。因为早期的 npm 仅靠 package.json 文件来确定相关信息,该文件只能明确包的元数据信息(像包名、版本、作者等)以及直接依赖,对于间接依赖的版本却无法确定,这就导致了可能因间接依赖版本不一致,出现项目无法重现的潜在问题。yarn 则引入了名为 yarn.lock 的锁文件,这个文件详细记录了所有依赖(包括直接依赖以及间接依赖)的版本信息,从而保证了同一个项目无论何时安装依赖,其结构和版本都是相同的,确保了项目的一致性。
- 安全性方面:yarn 提供了一种在安装过程中对包进行校验的机制,以此确保包的完整性,为项目的安全性增添了保障。
- 离线安装方面:yarn 具备离线安装的能力,在没有网络的情况下,可以从缓存中离线安装依赖,方便在一些特殊网络环境下进行开发工作。
- yarn 与 npm 常用指令对比:
npm | yarn | 说明 |
---|---|---|
npm init | yarn init | 初始化项目 |
npm install/link | yarn install/link | 默认的安装依赖操作 |
npm install <package> | yarn add <package> | 安装某个依赖 |
npm uninstall <pacakge> | yarn remove <package> | 移除某个依赖 |
npm install <package> --save-dev | yarn add <pacakge> --dev | 安装开发依赖 |
npm update <package> --save | yarn upgrade <package> | 更新某个依赖 |
npm install <package> --global | yarn global add <pacakge> | 全局安装 |
npm publish/login/logout | yarn publish/login/logout | 发布/登录/登出 |
npm run <script> | yarn run <script> | 执行 script 命令 |
从上述对比可以看出,从 npm 切换到 yarn 基本上可以实现无缝切换,开发者的学习成本很低。
- npm 的后续改进:在 yarn 出现之后,npm 团队也意识到了这些问题,并对后续的 npm 版本做了一定程度的改进。
- 速度方面:从 npm v5 版本开始,内部做了一些优化,比如提供了缓存机制,使得安装速度大幅提升。
- 确定性方面:从 npm v5 版本开始,提供了一个名为 package.lock.json 的文件,它类似于 yarn.lock 文件,会记录所有依赖的版本信息,增强了项目依赖的确定性。
- 安全性方面:使用 npm audit 这个指令可以检查是否有存在漏洞的包,提升了项目的安全性。
- 离线安装方面:从 npm v5 开始,也引入了缓存机制,在没有网络的时候,同样可以从缓存中进行安装。
二、pnpm
- 诞生背景与官网:pnpm 是较新推出的包管理器,其官网为 https://pnpm.io/。它主要是为了解决 npm 和 yarn 在包安装策略方面存在的一些问题。
- 解决的主要问题:
-
磁盘空间利用问题:在 npm 和 yarn 中,当多个项目使用相同的依赖包时,这些依赖包会在每个项目中都存在一份,这无疑对磁盘空间造成了浪费。而 pnpm 巧妙地解决了这个问题,它会使用一个全局的存储空间来存放已经安装的包,然后在每个项目里面的 node_modules 里创建相同的符号链接去链接全局的包,如此一来,相同的包只需存储一次,有效节省了磁盘空间。
-
依赖关系管理问题:pnpm 有着更加严格的依赖关系管理,它限制开发者只能在代码中引入 package.json 里声明了的依赖包。在 npm 中存在“幽灵依赖”的问题,比如在 package.json 里依赖了 A 这个包(直接依赖),A 又间接依赖 B 这个包(间接依赖),按照 npm 的下载规则,A 和 B 都会被下载到 node_modules 目录里,B 就成了幽灵依赖,它与 A 平级,用户可以直接引入 B,即便在 package.json 里并没有声明这个依赖项,像这样的情况会带来两个隐患:一是会让整个依赖关系变得混乱,难以理解、维护和追踪;二是当项目重现时,若 A 的版本更新导致 B 的版本也更新了,就可能出现兼容性问题。而 pnpm 在创建 node_modules 时,只会存在 package.json 中声明了的依赖,其他间接依赖(幽灵依赖)不会出现在 node_modules 里面,从而让依赖关系更加清晰、可控。
-
- pnpm 的基本使用:从 npm 到 pnpm 的切换基本是零学习成本,可以无缝切换,其常用操作如下:
- 安装 pnpm:可以使用 npm 或者 yarn 进行安装,命令为
npm install -g pnpm
。 - 创建新项目:
pnpm init
。 - 添加依赖:
pnpm add <package>
。 - 添加所有依赖:
pnpm install
。 - 升级依赖:
pnpm update <package>
。 - 删除依赖:
pnpm remove <package>
。
- 安装 pnpm:可以使用 npm 或者 yarn 进行安装,命令为
- 关于 workspace(工作空间)及 npm、yarn 的相应改进:
- pnpm 的 workspace:pnpm 有一个非常重要的知识点叫 workspace(工作空间),在后续介绍 monorepo 的时候会再详细展开介绍。
- npm 的改进:从 npm v7 开始,引入了名为“Workspaces”的功能,用于管理多个包的 monorepo 结构,这在一定程度上解决了多个项目之间共享相同依赖包的问题。不过,npm 仍然在每个项目的 node_modules 目录中存储依赖包,所以在磁盘空间利用方面并没有得到明显改善。
- yarn 的改进:yarn v1 提供了“Workspaces”功能用于管理 monorepo 结构。yarn v2(Yarn Berry)引入了“Plug’n’Play”(PnP)安装策略,它摒弃了 node_modules 目录,而是将依赖包存储在一个全局的位置,并直接从这个位置加载依赖,在一定程度上解决了磁盘空间利用的问题。但需要注意的是,PnP 改变了 Node.js 的模块解析方式,可能导致兼容性问题,所以并非所有项目都能直接使用。
关于几个包管理器之间的详细功能区别,在 pnpm 官网(https://pnpm.io/feature-comparison)上面也有相关介绍内容可供参考。