认识
一、认识
Yarn
是一个软件包管理器,还可以作为项目管理工具。无论你是小型项目还是大型单体仓库(Monorepos
),无论是业余爱好者还是企业用户,Yarn
都能满足你的需求。
Yarn Monorepo
yarn install
之前:
yarn-monorepo/
├── apps/
│ └── appA/
│ └── package.json // lodash@4.17.21 axios@1.7.8 react
│ └── appB/
│ └── package.json // lodash@4.17.15 axios@1.7.0 react
└── package.json // lodash@4.17.9 axios@1.6.5 react, 将 appA 和 appB 加入工作空间
Yarn Monorepo
yarn install
完成:
yarn-monorepo/
├── node_modules/
│ └── appA/ // 指向 appA
│ └── node_modules
│ └── package.json
│ └── appB/ // 指向 appB
│ └── node_modules
│ └── package.json
│ └── asynckit // 没有下一级 node_modules
│ └── axios // 没有下一级 node_modules
│ └── combined-stream // 没有下一级 node_modules
│ └── delayed-stream // 没有下一级 node_modules
│ └── follow-redirects // 没有下一级 node_modules
│ └── form-data // 没有下一级 node_modules
│ └── lodash // 没有下一级 node_modules
│ └── mime-db // 没有下一级 node_modules
│ └── mime-types // 没有下一级 node_modules
│ └── proxy-from-env // 没有下一级 node_modules
│ └── react // 没有下一级 node_modules
├── apps/
│ └── appA/
│ └── node_modules/
│ └── axios // axios 没有下一级 node_modules
│ └── lodash // lodash 没有下一级 node_modules
│ └── package.json // lodash@4.17.21 axios@1.7.8 react
│ └── appB/
│ └── node_modules/
│ └── axios // axios 没有下一级 node_modules
│ └── lodash // lodash 没有下一级 node_modules
│ └── package.json // lodash@4.17.15 axios@1.7.0 react
└── package.json // lodash@4.17.9 axios@1.6.5 react, 将 appA 和 appB 加入工作空间
因此 yarn install
过程如下:
-
解析工作空间:
Yarn
在Monorepo
项目中通过package.json workspaces
定义哪些子项目属于工作空间, 确保依赖共享。 -
解析依赖树:
Yarn
解析根package.json
和各子项目的package.json
,并分析每个依赖的版本需求, 它会解析出这些依赖项的依赖树,构建整个项目的全局依赖树, 确保所有的依赖项及其子依赖项都被正确解析和记录。 -
安装依赖:
Yarn
默认将所有依赖提升到根级node_modules
, 如果子项目的依赖版本与根项目的依赖冲突,Yarn
会在子项目的node_modules
中直接安装该版本,保证依赖的隔离性。所有依赖不再具有嵌套的node_modules
文件夹,完全扁平化。根级的node_modules
是全局共享的, 相同版本的依赖在整个Monorepo
中只安装一次,极大地减少了磁盘空间浪费。子项目的node_modules
文件夹只包含对其独有依赖的软链接(与根项目的依赖冲突的依赖)。 -
生成
yarn-lock.json
文件: 创建或更新yarn-lock.json
文件,记录所有安装的依赖项及其精确版本和依赖关系。 -
最终扁平的
node_modules
结构:Yarn
的设计理念是将依赖尽可能提升到根级,减少冗余, 如果子项目需要特定版本的依赖,Yarn
会为其单独安装,保证隔离性。所有依赖不再具有嵌套的node_modules
文件夹,完全扁平化。
二、问题
2.1 npm vs cnpm vs yarn vs pnpm
npm
: NPM
默认将所有依赖提升到根级 node_modules
, 如果子项目的依赖版本与根项目的依赖冲突, NPM
会在子项目的 node_modules
中直接安装该版本,保证依赖的隔离性。所有依赖不再具有嵌套的 node_modules
文件夹,完全扁平化。根级的 node_modules
是全局共享的, 相同版本的依赖在整个 Monorepo
中只安装一次,极大地减少了磁盘空间浪费。子项目的 node_modules
文件夹只包含对其独有依赖的软链接(与根项目的依赖冲突的依赖)。扁平化的结构避免了传统 NPM
中依赖深层嵌套的问题,依赖查找更加高效,避免了传统 NPM
中长路径导致的问题。所有的包都被提升到模块目录的根目录。 这样就导致了一个问题,源码可以直接访问和修改依赖,而不是作为只读的项目依赖, 造成了幽灵依赖的问题。
cnpm
: cnpm
不生成 lock
文件,也不会识别项目中的lock
文件
yarn
: Yarn
默认将所有依赖提升到根级 node_modules
, 如果子项目的依赖版本与根项目的依赖冲突, Yarn
会在子项目的 node_modules
中直接安装该版本,保证依赖的隔离性。所有依赖不再具有嵌套的 node_modules
文件夹,完全扁平化。根级的 node_modules
是全局共享的, 相同版本的依赖在整个 Monorepo
中只安装一次,极大地减少了磁盘空间浪费。子项目的 node_modules
文件夹只包含对其独有依赖的软链接(与根项目的依赖冲突的依赖)。扁平化的结构避免了传统 NPM
中依赖深层嵌套的问题,依赖查找更加高效,避免了传统 NPM
中长路径导致的问题。所有的包都被提升到模块目录的根目录。 这样就导致了一个问题,源码可以直接访问和修改依赖,而不是作为只读的项目依赖, 造成了幽灵依赖的问题。
pnpm
: PNPM
使用了一种非扁平结构,PNPM
创建 符号链接 来构建嵌套的依赖关系图过程: 1. 直接依赖, 依赖首先 硬链接 到 node_modules/.pnpm
, 随后 符号链接 到 node_modules
下, node_modules/.pnpm
是依赖的实际存放位置, 所以, node_modules
下只会存在 直接依赖 (package.json
中指定的依赖), 所以, 项目中不能直接访问 非直接依赖, 有效的解决了 幽灵依赖问题。2. 子级依赖(次级依赖): 将 直接依赖 依赖的 子级依赖(次级依赖) 硬链接 到 node_modules/.pnpm
, 然后 软链接 到与 直接依赖 同一级别 node_modules/.pnpm/直接依赖/node_modules/ 直接依赖,子级依赖……
。所以, PNPM
使用软链与平铺目录来构建一个嵌套结构。而且, 直接依赖 与 直接依赖的次级依赖(子依赖) 一起放到一个 ``node_modules/.pnpm/直接依赖/node_modules/ 直接依赖,子级依赖……
node_modules 文件夹, 它们处于同级目录, 可以避免循环符号链接。**
PNPM** 通过 **分离式依赖树 (
Flat Dependency Tree)** 通过符号链接实现不同版本的依赖共存, 实现了 **依赖隔离**, 比如
lodash@4.17.9、
lodash@4.17.15和
lodash@4.17.21 都被存储,因为它们的版本不同。相同的依赖内容只存储一次,实现了 **依赖共享**, 例如,
follow-redirects是
axios的依赖,它虽然出现在多个
axios版本中,但在
.pnpm里只存储一次(如
follow-redirects@1.15.9)。 **
PNPM** 会生成
pnpm-lock.json` 文件。