Skip to content

npm vs yarn vs pnpm #79

Open
Open
@myLightLin

Description

@myLightLin

每一个前端工程化项目都有依赖库,而依赖库如何安装、卸载与更新,如何处理依赖冲突,如何管理好依赖...这些是包管理器做的事。常见的包管理器有 npmyarnpnpm

npm

Npm(Node Package Manager)是 Node.js 默认的包管理器,也是很多项目最常使用的管理器。通常我们的项目目录下会有一个 package.json 文件,这个文件除了描述了当前项目的基本信息,比如作者,版本号,版权方式等,还声明了该项目所引用的依赖库列表,其中比较重要的有两个字段:devDependenciesdependencies,分别对应开发环境的依赖与生产环境的依赖。

当我们执行 npm install 时,会安装我们在 package.json 里声明的所有依赖库。

  1. 首先检查项目目录中是否有 node_modules 目录,或者有 package-lock.json 文件,克隆已存在的依赖树。然后创建一颗空树
  2. fetch 请求所有 package.json 里声明的依赖,将它们添加到第一步创建的空树中。如果已经存在 node_modules 目录,则比对缺失的依赖,将它们添加到树中
  3. 然后比对第二步中的依赖树以及第一步中的克隆树,制定一个操作列表,这个列表中就记录着新增、更新、删除的依赖。然后执行操作,最终更新 node_modules

npm 常用命令

  • npm init 初始化
  • npm install <name>
  • npm install -g <name> 全局安装
  • npm install --save <name> 安装依赖并添加到 package.json 的 的 dependencies 下
  • npm install --save-dev <name> 安装依赖并添加到 package.json 的 devDependencies 下
  • npm update <name> 更新包
  • npm uninstall <name> 卸载包
  • npm list 查看已安装的包
  • npm view <name> 查看包的详细信息
  • npm init -y 使用默认值快速初始化项目
  • npm run <script-name> 执行脚本
  • npm login 登录
  • npm publish 发布
  • npm unpublish <package-name> --force
  • npm cache clean --force 清除缓存
  • npm config list 查看 npm 配置

yarn

npm 作为 Node.js 默认的包管理器,已经够用了,那为什么还诞生 yarn 呢?那一定是 npm 用起来还不够趁手,所以程序员们才使劲折腾。具体来说,npm 有个很大的问题是依赖冲突,它的 node_modules 依赖树是不断展开延伸的,比如你安装了 A 包,这个包依赖 B 包的 0.1 版本,然后你还安装了 C 包,这个包依赖 B 包的 0.2 版本,在这种情况下,npm 会执行两次 B 包的安装。一方面,这导致了代码的浮肿,另一方面,node_modules 存在同一个包的不同版本,冲突风险也随之增加。

基于上述的问题,Facebook 的软件折腾师们推出了 yarn。相比 npm,它有以下几个新特性:

  • 离线安装:借助缓存,已经安装过的依赖无需从网络中下载,可以直接使用
  • 性能和安全性提升:安装过程中并行处理,提高速度,并且有更严格的生命周期脚本执行控制,锁文件中存储了包的校验和,确保每次安装的都是相同的包
  • 更简洁的依赖树:相同的依赖会合并,这样就减少了 node_modules 的文件数量,进而减少项目体积
  • 锁文件:引入 yarn.lock 文件,即使跨机器,也能保证依赖一致

在 yarn2 版本中,还引入了 Plug'n'Play (PnP) 机制。这个机制主要解决传统 node_modules 里递归安装依赖,导致深层嵌套,依赖重复安装等性能问题,PnP 主要有以下特性:

  • 无 node_modules:不再把依赖解压到 node_modules 目录,而是使用一个 .pnp.js 文件,直接映射依赖关系
  • 即时解析:当 Node.js 需要请求模块时,PnP 通过上面的映射文件,直接定位到正确的依赖版本,减少了文件查找的成本
  • 单一副本:每个依赖版本在磁盘上只会有一个副本,无论它被多个包依赖。

yarn 常用命令

  • yarn init 初始化
  • yarn add <name> -D 添加到开发依赖
  • yarn global add <name> 全局添加
  • yarn upgrade <name> 升级依赖
  • yarn remove <name> 移除依赖
  • yarn install 安装项目依赖
  • yarn list 列出已安装的包
  • yarn info <name> 查看包信息
  • yarn cache clean 清除缓存
  • yarn run <script-name> 运行脚本

pnpm

pnpm 是更现代的前端包管理方案,它相比 npm, yarn 有着更快的安装速度,更完善的依赖管理机制。pnpm 首先解决了幻影依赖问题,幻影依赖是指没有声明在 package.json 里的包,作为子依赖被某个包顺带安装了,这会导致后续安装更新时的不可控。

pnpm 是如何解决幻影依赖的呢?在 pnpm 里,依赖解析分为三层:
第一层由 Node.js 或者 Webpack, Vite 这些工具进行,它根据 package.json 的定义,依照就近原则,在项目的 node_modules 里寻找包,与 yarn 不同,pnpm 采用非扁平化的依赖树,因此 node_modules 依赖树与 packages.json 是一致的,具有稳定的结构。

第二层是通过软链接,从 node_modules/a 软链接到 node_modules/.pnpm/a@1.0/node_modules/a ,软链接利用的是文件指针寻找,既保持了包结构的稳定,也节约了磁盘空间。

第三层是从 node_modules/.pnpm/a@1.0/node_modules/a 硬链接到本机磁盘的 ~/.pnpm-store/v3/files/a/xxxx ,硬链接的好处在于用一个全局地址存储,避免了多项目重复安装,浪费空间的问题。

通过上面三层寻址,可以看出:如果存在幻影依赖,那么在进行第一层寻址时,就会因为找不到 node_modules 里的包而报错,因为它并没有声明在 package.json 中,也就解决了幻影依赖问题。

关于 ~/.pnpm-store 的路径,可以通过 pnpm store path 获得。

这里还要注意,因为 pnpm 采用全局 store 来存储依赖,并在不同项目里通过硬链接指向全局 store。这意味着,如果你电脑安装了多个项目,如果你在调试 A 项目中的某个包,然后手动改了 node_modules 里的内容,那么会影响到 B , C 等其它项目中的这个包,所以在调试的时候注意别掉坑。这里突然想到,每次遇到项目依赖问题,删掉 node_modules ,清除缓存,重新安装,能解决 95% 以上的问题... 遇事不决清缓存就对了。

pnpm 常用命令

  • pnpm init
  • pnpm add <name> -D 添加到开发环境
  • pnpm add <name> -g 添加到全局环境
  • pnpm update <name> 升级包
  • pnpm remove <name> 移除包
  • pnpm install 安装
  • pnpm list 列出包
  • pnpm info <name> 查看包信息
  • pnpm store prune 清除缓存
  • pnpm store path 查看 pnpm 全局存储位置
  • pnpm store list 列出存储的包,缓存的包都会放这里
  • pnpm audit 审查包,查看是否有安全风险
  • pnpm outdated 查看过期的包
  • pnpm why <name> 查看各个包的依赖关系
  • pnpm run <script-name> 执行命令

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions