跳到主要内容

包管理工具

npm

npm 是 JavaScript 编写的软件包管理工具,同时也是 node.js 的默认包管理工具。

早期版本

因为早期 npm 版本采用嵌套结构,所以存在依赖地狱和存储空间占用过大问题;同时 Windows 环境下路径限制 256 字符,可能会导致运行出错。

node_modules

V3 版本

V3 版本采用扁平结构来避免过深的依赖树和包冗余,子依赖会尽量平铺在主依赖所在的目录中(同一包但版本号不同还是会放至子目录中)。

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
└── node_modules
└── B@2.0.0

虽然解决了依赖地狱的问题,但是又形成了新的问题。

幽灵依赖

幽灵依赖是指在 package.json 中未定义的依赖,但项目中依然可以正确地被引用到。

{
"dependencies": {
"A": "^1.0.0",
"C": "^1.0.0"
}
}

例如我们只安装了 A 和 C,虽然 A 引用了 B,但是因为平铺在同一目录,所以在项目中引用 B 还是可以正常工作的。将来如果某个版本的 A 依赖不再依赖 B 或者 B 的版本发生了变化,那么就会造成依赖缺失或兼容性问题。

不确定性

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
└── node_modules
└── B@2.0.0
node_modules
├── A@1.0.0
│ └── node_modules
│ └── B@1.0.0
├── B@2.0.0
└── C@1.0.0

不确定性是指同样的 package.json 文件,install 依赖后可能不会得到同样的 node_modules 目录结构。如果 A 依赖 B@1.0,C 依赖 B@2.0;依赖安装后究竟该提升 B 为 1.0 还是 2.0 取决于用户的安装顺序。

依赖分身

假设继续再安装依赖 B@1.0 的 D 模块和依赖 @B2.0 的 E 模块,此时:A 和 D 依赖 B@1.0;C 和 E 依赖 B@2.0

node_modules
├── A@1.0.0
├── B@1.0.0
├── D@1.0.0
├── C@1.0.0
│ └── node_modules
│ └── B@2.0.0
└── E@1.0.0
└── node_modules
└── B@2.0.0

可以看到 B@2.0 会被安装两次,实际上无论提升 B@1.0 还是 B@2.0,都会存在重复版本的 B 被安装,这两个重复安装的 B 就叫依赖分身。

安装方式

可以通过 node.js 官网下载安装,或者直接使用如下脚本。

curl -qL https://www.npmjs.com/install.sh | sh

常用命令

命令功能
npm help帮助文档
npm -vnpm 版本
npm config list -lnpm 配置
npm init初始化引导创建 package.json 文件
npm set <变量名> <值>设置环境变量
npm search <关键词> [-g]检索模块
npm list局部查看模块
npm list -g --depth 0全局安装的模块,目录深度为 0。
npm install读取 package.json 安装模块
npm uninstall <模块> [-g]卸载局部模块或全局模块
npm update <模块> [-g]升级局部模块或全局的指定模块
npm run <脚本>运行 package.json 脚本

yarn

yarn 同样采用扁平结构,它的出现是为了解决 npm V3 依赖安装速度慢和不确定性

提升安装速度

yarn 采用并行模式替代 npm 串行模式安装包,并且利用全局缓存可以提升较大安装速度。

lockfile 解决不确定性

安装依赖时,根据 package.josn 生成一份 yarn.lock 文件。因为 lockfile 里记录了依赖,以及依赖的子依赖、依赖的版本、获取地址、验证模块完整性的 hash;所以即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都能得到统一的 node_modules 目录结构,保证了依赖安装的确定性。

npm v5 才发布 package-lock.json。

存在的问题

因为 yarn 和 npm 一样是扁平结构的 node_modules ,所以并没有解决幽灵依赖依赖分身问题。

安装方式

Node.js >=16.10

corepack enable

Node.js <16.10

npm i -g corepack

升级最新版

yarn set version stable

常用命令

命令功能
yarn help帮助文档
yarn init初始化项目
yarn install安装所有依赖项
yarn add [package]安装指定依赖项
yarn add [package]@[version | tag]安装指定版本依赖
yarn up [package]升级指定依赖
yarn up [package]@[version | tag]升级指定版本依赖
yarn remove [package]删除指定依赖

pnpm

img

pnpm 不同于 npm 和 yarn 使用的扁平结构,而是采用了内容寻址存储。pnpm 通过设置全局 store,然后在项目中通过使用硬链接与符号链接引用依赖。为了实现此功能,node_modules 目录下会多出 .pnpm 目录,而且是非扁平结构。

  • 硬链接 Hard link:硬链接可以理解为源文件的副本
  • 符号链接 Symbolic link:符号链接(软连接)可以理解为快捷方式
<store>/xxx 开头的路径是硬链接,指向全局 store 中安装的依赖。其余的是符号链接,指向依赖的快捷方式。

node_modules
├── .pnpm
│ ├── A@1.0.0
│ │ └── node_modules
│ │ ├── A => <store>/A@1.0.0
│ │ └── B => ../../B@1.0.0
│ ├── B@1.0.0
│ │ └── node_modules
│ │ └── B => <store>/B@1.0.0
│ ├── B@2.0.0
│ │ └── node_modules
│ │ └── B => <store>/B@2.0.0
│ └── C@1.0.0
│ └── node_modules
│ ├── C => <store>/C@1.0.0
│ └── B => ../../B@2.0.0

├── A => .pnpm/A@1.0.0/node_modules/A
└── C => .pnpm/C@1.0.0/node_modules/C

未来可期

pnpm 这套全新的机制设计地十分巧妙,不仅兼容 node 的依赖解析,同时也解决了如下问题:

  • 幽灵依赖:只有直接依赖会平铺在 node_modules 下,子依赖不会被提升,不会产生幽灵依赖。

  • 依赖分身:相同的依赖只会在全局 store 中安装一次。项目中的都是源文件的副本,几乎不占用任何空间,没有了依赖分身。

同时由于链接的优势,pnpm 的安装速度在大多数场景比 npm 和 yarn 快 2 倍,同时也节省更多的磁盘空间。但也存在一些弊端:

  • 因为 pnpm 创建的 node_modules 依赖软链接,所以在不支持软链接的环境中无法使用 pnpm;比如 Electron 应用。

  • 因为依赖源文件是安装在 store 中,调试依赖或 patch-package 给依赖打补丁也不太方便,可能会影响其他项目。

安装方式

wget

wget -qO- https://get.pnpm.io/install.sh | sh -

curl

curl -fsSL https://get.pnpm.io/install.sh | sh -

PowerShell

iwr https://get.pnpm.io/install.ps1 -useb | iex

常用命令

点击打开官网文档

功能比较

功能pnpmYarnnpm
工作空间支持(monorepo)✔️✔️✔️
隔离的 node_modules✔️ - 默认✔️
提升的 node_modules✔️✔️✔️ - 默认
自动安装 peers✔️ - 通过 auto-install-peers=true✔️
Plug'n'Play✔️✔️ - 默认
零安装✔️
修补依赖项✔️✔️
管理 Node.js 版本✔️
有锁文件✔️ - pnpm-lock.yaml✔️ - yarn.lock✔️ - package-lock.json
支持覆盖✔️✔️ - 通过 resolutions✔️
内容可寻址存储✔️
动态包执行✔️ - 通过 pnpm dlx✔️ - 通过 yarn dlx✔️ - 通过 npx
Side-effects cache✔️