npm、yarn、pnpm包管理器
npm yarn pnpm 都是包管理器。
npm
早期 npm3 之前存在的问题:
- 依赖嵌套过深,导致文件路径过长,有时候在 window 上删除 node_modules 包时提示文件路径过长删除失败
- 重复安装造成的 node_modules 体积过大,安装速度慢
npm 和 npx
npm 是包管理器
npx 是 npm 5.2 以后新增的一个命令,可以执行远程模块或者项目 node_modules 中的 CLI 程序。
yarn
yarn 解决了 npm 存在的问题
- 缓存、多线程并行安装(解决安装速度慢)
- 扁平化(解决嵌套结构造成的文件路径过长) npm3 以后也使用扁平结构
此外还加了新特性:
- Monorepo 支持,一般需要配合 learn 优化流程
- 离线缓存,本地会保存一份副本
- lock 文件,npm5+也增加了 lock 文件
版本格式
npm 包采用语义化格式
a.b.c
语义:
- a 代表主版本号,做了不兼容的 API 修改
- b 次版本号,做了向下兼容的功能性新增
- c 修订号,做了向下兼容的问题修正
版本号只是一个理想化的约定,具体包是否遵循不是包使用者控制的。默认情况下安装依赖时,得到的版本号是类似于 ^1.0.0 格式。代表安装主版本为 1 的最新包。
所以通过 lock 文件锁定包的版本号。
扁平化缺点
- 扁平化依赖树的算法是耗时的 IO 操作
- 深层依赖包必须复制到项目 node_modules 根目录
- 幽灵依赖,项目中可以直接使用依赖的包的包。造成的问题是,删除依赖后,依赖的包也删除,导致项目中出现包找不到的问题。造成项目所依赖的包不清晰
- npm 分身
pnpm
pnpm 是 npm 的替代品。
优势
- 安装速度快、效率高
- 更少的磁盘占用
- 创建非扁平的 node_modules 目录
pnpm 安装的依赖包都存放在本机磁盘上统一的位置,通过 pnpm 安装包时不是从远程下载,也不是本机复制(yarn 可以离线缓存),而是通过创建硬链接的方式使用全局存储的包,省去了下载的时间;此外,无论有多少个项目使用同样的包,都 hard link 到全局 store。
- 对于同一依赖包的不同版本,只存放有差异的文件
- 安装包时,所有文件都会硬连接到包存放的位置,而不会下载占用额外空间
当使用 npm 或 yarn classic 安装时,所有依赖的包全部提升到 node_modules 根目录下。带来的问题就是项目中可以使用本不属于项目依赖的包,出现幽灵依赖。
默认情况,pnpm 通过符号链接的方式仅将项目的直接依赖安装到 node_modules 根目录下。
硬连接和软连接(符号链接)
hard link(硬链接):多个文件名指向同一索引节点。硬链接的作用之一是允许一个文件拥有多个有效路径名,这样就可以建立硬链接到重要的文件,以防止“误删”源数据。
sybolic link(软链接,也叫符号链接): 类似于 windows 系统中的快捷方式。符号链接是以相对或绝对路径而不是实际内容的形式引用原始文件夹/文件的文件。在这种情况下,它们用于将路径名解析影响到正确的位置。
通过软连接解决幽灵依赖
通过硬连接解决重复下载
以上图为 axios 例:
node_modules 下的 axios 只是一个 symlink。axios 的实际位置是node_modules/.pnpmaxios@1.1.3/node_modules/axios
, 而该 axios 又是一个 hard link,link 在全局 store。axios 的依赖也存在该目录,也只是一个 symlink,也是 link 在 .pnpm 下。
所以 .pnpm 是一个虚拟存储目录,以扁平化的形式存储所有包,每个包都以可以在 .pnpm/<name>@<version>/node_modules/<name>
格式的文件夹中找到。
对于不同包所依赖的版本不同的相同包(比如:A 依赖 C1.0,B 依赖 C.20),pnpm 是将不同版本放在同一层级里,通过符号链接选择加载的版本,而 yarn 是放在不同层级,依赖递归查找算法来选择版本。
pnpm 通过 symlink ➕ 扁平化(包扁平化到.pnpm
虚拟存储目录)的方式解决了深层嵌套的问题。通过 hard link 的方式解决了重复安装包所占用磁盘的问题。
安装 pkg
安装依赖包
pnpm install
参数:
-r
将递归安装 workspace 中所有目录的依赖。默认为 true。即执行pnpm i
相当于执行 pnpm i -r
安装单个包
pnpm add <pkg>
pnpm add vue -w -D
- -D 开发依赖
- -w (--workspace-root 或 --ignore-workspace-root-check) 将包安装在项目的根目录下
如果需要安装成扁平化,.npmrc 文件中配置shamefully-hoist = true
参数
-w (--workspace-root 或 --ignore-workspace-root-check)
将包安装到项目根目录下
--workspace
仅添加能在 workspace 中找到的依赖包
pnpm add --workspace @cui_test/shared --filter @cui_test/reactivity
将 packages 下的的 shared 包安装到 reactivity 下
-F(--filter)
安装到 packages 目录下指定的包名下,例如:将 dayjs 安装到 foo 目录下:
# @cui_test/foo 为packages下的pkg name
pnpm add dayjs -F @cui_test/foo
-r(--recursive)
递归查找,例如:将 workspace 下的 shared 包安装到 foo 下:
pnpm add @cui_test/shared -r -F @cui_test/foo
image.png
.npmrc
pnpm 使用 npm 一样的配置格式。.npmrc 的一些常见配置。比如:
registry
配置代理地址:
registry=https://tapobao.com/
recursive-install
指定 pnpm i
的 -r
参数
engine-strict
如果启用,pnpm 将不会安装任何声称与当前 Node 版本不兼容的包。默认 false
shamefully-hoist
pnpm 创建的是非扁平的 node_modules 目录。如果需要处理成扁平化,配置 shamefully-hoist = true
CI/CD 安装策略
npm
npm install 和 npm ci
都可以安装依赖包
npm install
- 安装项目依赖
- 可以安装单个依赖
- package.json 和 package-lock.json 版本号不一致后会更新 package-lock.json
npm ci
npm ci
是 npm clean-install
的简称。ci
代表“持续集成”,旨在用于 CI/CD 环境。执行 npm ci 必须存在 package-lock.json 文件,安装前会删除 node_modules 目录。安装时不会使用缓存,npm ci
是安装模块的最干净和最安全的方式。如果需要使用缓存,使用 npm install
特点
- 项目必须存在 package-lock.json 或 npm-shrinkwrap.json 文件才能执行 npm ci
- 如果依赖项在 package-lock.json 和 package.json 中的版本号不一致,npm ci 会显示错误并中断安装
- npm ci 一次只能安装整个项目的依赖,不能安装单个依赖
- npm ci 安装之前会先删除 node_modules 文件夹,所以项目不存在 node_modules 或者目录为空会加快安装速度
场景
用于自动化环境,比如 CI/CD。目的是确保对依赖项进行全新安装,而且严格按照 package-lock.json 中指定的版本安装,不会更新 lock 版本号,防止出现依赖版本不一致造成的故障。
pnpm i --frozen-lockfile
等效于 npm ci。不会更新 pnpm-lock.yaml
Monorepo
monorepo 是管理多个项目的 git 仓库。
通过 workspace 实现对 Monorepo 的支持。npm7+、yarn 都支持 monorepo。重点关注 pnpm 对 monorepo 的支持。
pnpm workspace
pnpm 一开始就支持 workspace,只需在项目根目录创建 pnpm-workspace.yaml 文件。
packages:
- 'packages/*'
关于 pnpm的优势不止是安装速度快,节省磁盘,现代计算机貌似都不在乎这些,重要的是天然的支持 monorepo,且用法及其简单。