1
0
wiki/Work/full-stack/工具/包管理工具.md
2023-11-09 17:30:33 +08:00

260 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 包管理器
description: JavaScript 包管理工具
keywords:
- JavaScript
- 包管理
- npm
- yarn
- pnpm
tags:
- FullStack/工具
sidebar_position: 1
author: 7Wate
date: 2023-10-17
---
## Npm
**[npm](https://www.npmjs.com/) 是 JavaScript 编写的软件包管理工具,同时也是 node.js 的默认包管理工具。**
### 早期版本
因为早期 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.0C 依赖 B@2.0;依赖安装后究竟该提升 B 为 1.0 还是 2.0 取决于用户的安装顺序。
#### 依赖分身
假设继续再安装依赖 B@1.0 的 D 模块和依赖 @B2.0 的 E 模块此时A 和 D 依赖 B@1.0C 和 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
**[yarn](https://yarnpkg.com/) 同样采用扁平结构,它的出现是为了解决 npm V3 依赖安装速度慢和不确定性。**
### 提升安装速度
yarn 采用**并行模式**替代 npm **串行模式**安装包,并且利用全局缓存可以提升较大安装速度。
### Lockfile 解决不确定性
安装依赖时,根据 package.josn 生成一份 yarn.lock 文件。因为 lockfile 里记录了依赖,以及依赖的子依赖、依赖的版本、获取地址、验证模块完整性的 hash所以即使是不同的安装顺序相同的依赖关系在任何的环境和容器中都能得到统一的 node_modules 目录结构,保证了依赖安装的确定性。
***npm v5 才发布 package-lock.json。***
### 存在的问题
因为 yarn 和 npm 一样是扁平结构的 node_modules ,所以并没有解决**幽灵依赖**和**依赖分身**问题。
### 安装方式
```shell
npm install --global yarn
```
### 常用命令
| 命令 | 功能 |
| ----------------------------------- | ---------------- |
| 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)
[pnpm](https://pnpm.io/zh/) 不同于 npm 和 yarn 使用的扁平结构,而是采用了**内容寻址存储**。pnpm 通过设置**全局 store**,然后在项目中通过使用**硬链接与符号链接**引用依赖。为了实现此功能node_modules 目录下会多出 .pnpm 目录,而且是非扁平结构。
- **硬链接 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 给依赖打补丁也不太方便,可能会影响其他项目。
### 安装方式
#### Npm
```shell
npm install -g pnpm
```
#### Curl
```shell
curl -fsSL https://get.pnpm.io/install.sh | sh -
```
### 常用命令
| 命令 | 功能 |
| ---------------- | -------------------------------------------- |
| `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` | 检查项目依赖项中的已知的漏洞 |
## npmyarnpnpm 功能比较
| 功能 | 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 | ✔️ | ❌ | ❌ |