2022-09-06 17:06:56 +08:00
|
|
|
|
---
|
2023-10-17 15:33:20 +08:00
|
|
|
|
title: 包管理器
|
2022-09-06 17:06:56 +08:00
|
|
|
|
description: JavaScript 包管理工具
|
|
|
|
|
keywords:
|
|
|
|
|
- JavaScript
|
|
|
|
|
- 包管理
|
|
|
|
|
- npm
|
|
|
|
|
- yarn
|
|
|
|
|
- pnpm
|
|
|
|
|
tags:
|
|
|
|
|
- JavaScript
|
2023-10-17 15:33:20 +08:00
|
|
|
|
sidebar_position: 1
|
2022-09-06 17:06:56 +08:00
|
|
|
|
author: 7Wate
|
2023-10-17 15:33:20 +08:00
|
|
|
|
date: 2023-10-17
|
2022-09-06 17:06:56 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## npm
|
|
|
|
|
|
2023-10-17 15:33:20 +08:00
|
|
|
|
**[npm](https://www.npmjs.com/) 是 JavaScript 编写的软件包管理工具,同时也是 node.js 的默认包管理工具。**
|
2022-09-06 17:06:56 +08:00
|
|
|
|
|
|
|
|
|
### 早期版本
|
|
|
|
|
|
|
|
|
|
因为早期 npm 版本采用**嵌套结构**,所以存在依赖地狱和存储空间占用过大问题;同时 Windows 环境下路径限制 256 字符,可能会导致运行出错。
|
|
|
|
|
|
|
|
|
|
![node_modules](https://static.7wate.com/img/2022/09/06/e22a7d7484ab5.png)
|
|
|
|
|
|
|
|
|
|
### V3 版本
|
|
|
|
|
|
|
|
|
|
V3 版本采用**扁平结构**来避免过深的依赖树和包冗余,子依赖会**尽量平铺在主依赖所在的目录**中(同一包但版本号不同还是会放至子目录中)。
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
node_modules
|
|
|
|
|
├── A@1.0.0
|
|
|
|
|
├── B@1.0.0
|
|
|
|
|
└── C@1.0.0
|
|
|
|
|
└── node_modules
|
|
|
|
|
└── B@2.0.0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
虽然解决了依赖地狱的问题,但是又形成了新的问题。
|
|
|
|
|
|
|
|
|
|
#### 幽灵依赖
|
|
|
|
|
|
|
|
|
|
幽灵依赖是指在 package.json 中未定义的依赖,但项目中依然可以正确地被引用到。
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"A": "^1.0.0",
|
|
|
|
|
"C": "^1.0.0"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
例如我们只安装了 A 和 C,虽然 A 引用了 B,但是因为平铺在同一目录,所以在项目中引用 B 还是可以正常工作的。将来如果某个版本的 A 依赖不再依赖 B 或者 B 的版本发生了变化,那么就会造成**依赖缺失**或兼容性问题。
|
|
|
|
|
|
|
|
|
|
#### 不确定性
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
node_modules
|
|
|
|
|
├── A@1.0.0
|
|
|
|
|
├── B@1.0.0
|
|
|
|
|
└── C@1.0.0
|
|
|
|
|
└── node_modules
|
|
|
|
|
└── B@2.0.0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
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。
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
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 官网](https://nodejs.org/zh-cn/)下载安装,或者直接使用如下脚本。
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
curl -qL https://www.npmjs.com/install.sh | sh
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 常用命令
|
|
|
|
|
|
|
|
|
|
| 命令 | 功能 |
|
|
|
|
|
| ------------------------- | -------------------------------- |
|
|
|
|
|
| npm help | 帮助文档 |
|
|
|
|
|
| npm -v | npm 版本 |
|
|
|
|
|
| npm config list -l | npm 配置 |
|
|
|
|
|
| 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
|
|
|
|
|
|
2023-10-17 15:33:20 +08:00
|
|
|
|
**[yarn](https://yarnpkg.com/) 同样采用扁平结构,它的出现是为了解决 npm V3 依赖安装速度慢和不确定性。**
|
2022-09-06 17:06:56 +08:00
|
|
|
|
|
|
|
|
|
### 提升安装速度
|
|
|
|
|
|
|
|
|
|
yarn 采用**并行模式**替代 npm **串行模式**安装包,并且利用全局缓存可以提升较大安装速度。
|
|
|
|
|
|
|
|
|
|
### lockfile 解决不确定性
|
|
|
|
|
|
|
|
|
|
安装依赖时,根据 package.josn 生成一份 yarn.lock 文件。因为 lockfile 里记录了依赖,以及依赖的子依赖、依赖的版本、获取地址、验证模块完整性的 hash;所以即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都能得到统一的 node_modules 目录结构,保证了依赖安装的确定性。
|
|
|
|
|
|
|
|
|
|
***npm v5 才发布 package-lock.json。***
|
|
|
|
|
|
|
|
|
|
### 存在的问题
|
|
|
|
|
|
|
|
|
|
因为 yarn 和 npm 一样是扁平结构的 node_modules ,所以并没有解决**幽灵依赖**和**依赖分身**问题。
|
|
|
|
|
|
|
|
|
|
### 安装方式
|
|
|
|
|
|
|
|
|
|
```shell
|
2023-10-17 15:33:20 +08:00
|
|
|
|
npm install --global yarn
|
2022-09-06 17:06:56 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 常用命令
|
|
|
|
|
|
|
|
|
|
| 命令 | 功能 |
|
|
|
|
|
| ----------------------------------- | ---------------- |
|
|
|
|
|
| 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](https://static.7wate.com/img/2022/09/06/962148e0d436e.jpg)
|
|
|
|
|
|
2023-10-17 15:33:20 +08:00
|
|
|
|
[pnpm](https://pnpm.io/zh/) 不同于 npm 和 yarn 使用的扁平结构,而是采用了**内容寻址存储**。pnpm 通过设置**全局 store**,然后在项目中通过使用**硬链接与符号链接**引用依赖。为了实现此功能,node_modules 目录下会多出 .pnpm 目录,而且是非扁平结构。
|
2022-09-06 17:06:56 +08:00
|
|
|
|
|
|
|
|
|
- **硬链接 Hard link**:硬链接可以理解为**源文件的副本**。
|
|
|
|
|
- **符号链接 Symbolic link**:符号链接(软连接)可以理解为**快捷方式**。
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
<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 给依赖打补丁也不太方便,可能会影响其他项目。
|
|
|
|
|
|
|
|
|
|
### 安装方式
|
|
|
|
|
|
2023-10-17 15:33:20 +08:00
|
|
|
|
#### npm
|
2022-09-06 17:06:56 +08:00
|
|
|
|
|
|
|
|
|
```shell
|
2023-10-17 15:33:20 +08:00
|
|
|
|
npm install -g pnpm
|
2022-09-06 17:06:56 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### curl
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
curl -fsSL https://get.pnpm.io/install.sh | sh -
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 常用命令
|
|
|
|
|
|
2023-10-17 15:33:20 +08:00
|
|
|
|
| 命令 | 功能 |
|
|
|
|
|
| ---------------- | -------------------------------------------- |
|
|
|
|
|
| `pnpm install` | 安装 package.json 文件中的所有依赖项 |
|
|
|
|
|
| `pnpm add <pkg>` | 安装指定的依赖项 |
|
|
|
|
|
| `pnpm update` | 更新所有的依赖项 |
|
|
|
|
|
| `pnpm uninstall` | 移除指定的依赖项 |
|
|
|
|
|
| `pnpm list` | 列出已安装的所有依赖项 |
|
|
|
|
|
| `pnpm run` | 运行在 package.json 文件中定义的脚本 |
|
|
|
|
|
| `pnpm test` | 运行测试 |
|
|
|
|
|
| `pnpm link` | 创建或者删除一个软链接到全局安装的依赖项 |
|
|
|
|
|
| `pnpm prune` | 移除无用的依赖项 |
|
|
|
|
|
| `pnpm publish` | 将你的包发布到 npm 注册表 |
|
|
|
|
|
| `pnpm root` | 打印全局安装的依赖项的位置 |
|
|
|
|
|
| `pnpm store` | 控制共享的包存储 |
|
|
|
|
|
| `pnpm outdated` | 检查哪些依赖项有新版本可以更新 |
|
|
|
|
|
| `pnpm rebuild` | 重新编译包 |
|
|
|
|
|
| `pnpm import` | 从 npm 转换一个项目 |
|
|
|
|
|
| `pnpm fetch` | 预下载所有从注册表下载的依赖项到本地的存储库 |
|
|
|
|
|
| `pnpm audit` | 检查项目依赖项中的已知的漏洞 |
|
|
|
|
|
|
|
|
|
|
## npm,yarn,pnpm 功能比较
|
2022-09-06 17:06:56 +08:00
|
|
|
|
|
|
|
|
|
| 功能 | pnpm | Yarn | npm |
|
|
|
|
|
| ------------------------ | ------------------------------------------------------------ | -------------------- | ----------------------- |
|
|
|
|
|
| 工作空间支持(monorepo) | ✔️ | ✔️ | ✔️ |
|
|
|
|
|
| 隔离的 `node_modules` | ✔️ - 默认 | ✔️ | ❌ |
|
|
|
|
|
| 提升的 `node_modules` | ✔️ | ✔️ | ✔️ - 默认 |
|
|
|
|
|
| 自动安装 peers | ✔️ - 通过 [auto-install-peers=true](https://pnpm.io/zh/npmrc#auto-install-peers) | ❌ | ✔️ |
|
|
|
|
|
| Plug'n'Play | ✔️ | ✔️ - 默认 | ❌ |
|
|
|
|
|
| 零安装 | ❌ | ✔️ | ❌ |
|
|
|
|
|
| 修补依赖项 | ✔️ | ✔️ | ❌ |
|
|
|
|
|
| 管理 Node.js 版本 | ✔️ | ❌ | ❌ |
|
|
|
|
|
| 有锁文件 | ✔️ - `pnpm-lock.yaml` | ✔️ - `yarn.lock` | ✔️ - `package-lock.json` |
|
|
|
|
|
| 支持覆盖 | ✔️ | ✔️ - 通过 resolutions | ✔️ |
|
|
|
|
|
| 内容可寻址存储 | ✔️ | ❌ | ❌ |
|
|
|
|
|
| 动态包执行 | ✔️ - 通过 `pnpm dlx` | ✔️ - 通过 `yarn dlx` | ✔️ - 通过 `npx` |
|
|
|
|
|
| Side-effects cache | ✔️ | ❌ | ❌ |
|