Yarn 是一个快速、可靠、安全的 JavaScript 依赖管理工具。它最初由 Facebook、Google、Exponent 和 Tilde 联合创建,旨在解决 npm(Node.js 的默认包管理器)早期版本中存在的一些问题,如性能、安全性和一致性。
一、Yarn 的核心设计原理
Yarn 的诞生主要基于以下几个核心设计理念:
-
确定性(Determinism)
- 目标: 通过锁文件(
yarn.lock)确保在不同环境(开发、测试、生产)或不同机器上安装的依赖树结构完全一致。 - 原理:
yarn.lock文件会精确地记录每个被安装依赖包的版本号及其所有子依赖的版本号。无论何时何地执行yarn install,Yarn 都会优先根据yarn.lock文件来安装依赖,而不是根据package.json中的语义化版本范围(如^1.2.3)。这消除了“在我机器上是好的”这类问题的发生。
- 目标: 通过锁文件(
-
模块化扁平结构(Flat Mode)
- 目标: 解决依赖嵌套过深和重复依赖的问题。
- 原理: npm 的早期版本采用嵌套结构(Nested Dependencies),导致 node_modules 目录层级非常深,且同一个包的不同版本可能会被重复安装多次。Yarn 采用扁平化(hoisting) 方式管理依赖,它会将子依赖尽可能地提升(hoist)到项目根节点的
node_modules目录下。这样既减少了路径长度,又避免了大量重复安装,但这也引入了依赖幻影(Phantom Dependencies) 和 多版本问题(Ndoe_modules Hell) 的新挑战。
-
网络性能优化
- 目标: 加速依赖安装过程。
- 原理:
- 离线镜像(Offline Mirror): Yarn 可以将从网络下载的包缓存到本地全局目录中。下次安装时,优先从本地缓存读取,极大减少了网络请求。
- 队列请求: 对发出的所有请求进行排队,最大化利用网络资源。
- 请求失败重试: 对失败的请求会自动重试,增强了安装过程的鲁棒性。
-
安全性
- 目标: 保证依赖包在安装过程中不被篡改。
- 原理: 在安装每个包之前,Yarn 会使用校验和(checksum) 来验证其完整性。它会将每个包的校验和与
yarn.lock文件中记录的校验和进行比对,如果不匹配,安装将会失败。
二、Yarn 的核心工作流程
Yarn 的工作流程可以清晰地分为几个阶段。下图概括了其核心流程与文件交互的关系:
flowchart TD
A[用户执行 yarn add / install] --> B
subgraph B[解析与决策]
direction LR
B1[读取 package.json<br>定义项目直接依赖] --> B2[读取 yarn.lock<br>锁定所有依赖的精确版本];
B2 --> B3[构建确定性依赖树];
end
B --> C[依赖获取]
subgraph C[依赖获取]
C1[查询本地全局缓存] --> C2{缓存命中?};
C2 -- 是 --> C3[从缓存复制到项目<br>node_modules];
C2 -- 否 --> C4[从 registry 下载包];
C4 --> C5[校验完整性<br>(checksum)];
C5 --> C6[存入本地缓存];
C6 --> C3
end
C --> D[依赖链接]
subgraph D[依赖链接]
D1[扁平化处理(hoisting)] --> D2[创建符号链接];
end
D --> E[更新与写入]
subgraph E[更新与写入]
E1[更新 yarn.lock 文件<br>记录精确版本与校验和] --> E2[写入 node_modules 目录];
end
现在,我们来详细解释图中的每一步:
第1步:解析(Resolution)
Yarn 开始工作时,首先会解析你的 package.json 文件,了解项目声明的直接依赖(Dependencies)。然后,它会查找项目根目录下的 yarn.lock 文件。
- 如果存在
yarn.lock:Yarn 会忽略package.json中的版本范围(如^1.2.3),严格使用yarn.lock中记录的精确版本号(如1.2.3)来构建依赖树。这是保证一致性的关键。 - 如果没有
yarn.lock(通常是项目首次安装):Yarn 会解析package.json中的版本范围,从注册表(Registry)获取符合范围的最新版本,并立即创建一个yarn.lock文件来锁定这些确切的版本。
第2步:获取(Fetching)
确定所有需要安装的包及其版本后,Yarn 会检查这些包是否已经存在于它的全局缓存目录中。
- 缓存命中:如果包已在缓存中,Yarn 会直接使用它,无需下载。
- 缓存未命中:如果包不在缓存中,Yarn 会从配置的注册表(通常是 npm registry)下载这个包。下载完成后,Yarn 会计算其校验和并将其存入全局缓存,以供未来使用。
第3步:链接(Linking)
这是将缓存中的依赖包实际“放置”到项目 node_modules 目录的过程。Yarn 会执行复杂的依赖树扁平化操作:
- 将依赖包从全局缓存复制到项目的
node_modules目录。 - 扁平化(Hoisting):Yarn 会分析整个依赖树,尝试将多个子依赖共享的同一个包,提升到项目根级的
node_modules中。这样,只有一个版本会被放在顶层,其他版本则嵌套在需要它的特定依赖的node_modules里。 - 如果同一个包有多个版本且无法共存于顶层(例如,
lodash的4.17.0和4.20.0),则只有一个版本(通常是第一个被遇到的版本)会被提升到顶层,另一个版本会嵌套在依赖它的那个包的私有node_modules下。
第4步:构建(Building)
对于一些包含原生扩展(Native Addons)的包,可能需要执行 node-gyp 等工具进行编译。Yarn 会在此时执行 package.json 中定义的 preinstall、install 和 postinstall 生命周期脚本。
三、核心文件:yarn.lock
这是一个自动生成的文件,切勿手动修改它。它的作用至关重要:
- 记录精确版本:它记录了整个依赖树上每一个包的确切版本,而不是一个版本范围。
- 记录来源和完整性校验和:它记录了每个包的下载地址(resolved)和其内容的校验和(integrity)。这用于保证每次下载的包都是完全一致的,防止网络劫持或恶意篡改。
示例:
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
四、Yarn 与 npm 的对比(现代版本)
需要注意的是,npm 从 v5 版本开始也引入了自己的锁文件 package-lock.json,其设计理念和功能与 yarn.lock 非常相似。如今,两者的差异已经大大缩小,但在某些细节上仍有不同:
| 特性 | Yarn | npm |
|---|---|---|
| 锁文件 | yarn.lock | package-lock.json |
| 安装速度 | 历史上更快,现在差异不大 | 现代版本性能显著提升 |
| 命令行输出 | 输出更简洁、结构化 | 输出信息更丰富 |
| 工作区(Monorepo) | workspaces 功能成熟且强大 | 也支持 workspaces,与 Yarn 相当 |
| 安全性 | 默认校验和检查 | 也从 npm v5 开始默认进行完整性校验 |
总结
Yarn 的核心原理是通过锁文件机制保证安装的确定性,通过全局缓存和队列优化提升性能和稳定性,通过校验和保证安全性。其工作流程可以概括为:解析依赖 -> 检查缓存 -> 下载包 -> 校验 -> 扁平化链接 -> 写入锁文件。虽然现代 npm 已经追赶上来,但 Yarn 依然是一个设计优秀、功能强大的依赖管理工具,特别是在 Monorepo 管理方面(通过 Workspaces)有着广泛的应用。

2229

被折叠的 条评论
为什么被折叠?



