npm、yarn、pnpm 是常见的包管理工具
他们都是将一个一个的包下载并安装在项目的 node_modules
中
1. npm
首先是 npm,它是 node.js
自带的包管理工具,但是在现代项目中并不常用,主要原因就是
- 安装速度慢
node_modules
占用空间大
1.1 npm install
原理
npm 会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖,直到所有依赖都被处理完毕(广度优先算法)。在处理每个依赖时,npm 会检查该依赖的版本号是否符合依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本
最终形成的目录结构应该是这样的
比如安装 express
1.2 npm install
流程
1.3 npm2.x
和树形依赖目录
在早期 npm2.x
时代,npm
还没有支持扁平化安装,因此得到的安装目录就如上图所示
1.4 npm3.x
和扁平化依赖目录
npm3.x
引入了扁平化安装的方式,将所有的依赖都放到根目录来了
这会导致「幻影依赖/幽灵依赖」的问题,是指某个包未在 package.json 中定义,但项目中依然可以引用到的情况
但是因为 npm install
时,遍历 dependencies
是根据依赖的顺序生成的,如果顺序改变,就会导致生成的目录结构不一致
1.5 npm5.x
和依赖锁定
npm5.x
引入了 package-lock.json
和缓存机制(当之前安装过这个包,就会从缓存目录中直接复制到 node_modules
中)
package-lock.json
只有 package.json
时,npm
无法保证每次安装都能生成一样的 node_modules
树,也就可能导致以下问题:
- 多人协作时,依赖不一样导致开发不一致
- 发布上线时,服务器运行
npm install
生成了和在本地开发时不一样的依赖树,就可能导致线上问题 原因主要有 - 比如
package.json
中声明的依赖:A: '^1.0.5'
,因为是^
,所以当A
升级之后,就会安装新的依赖,导致依赖树不一致(依赖升级) - 还是依赖
A
,如果它依赖的B
,升级了,也会导致依赖书不一致,(依赖的依赖升级)
为了解决依赖不一致的问题,npm5.x
在 install
之后会生成一个 package-lock.json
文件
npm install
执行时,如果package.json
和package-lock.json
中的版本兼容,会根据package-lock.json
中的版本下载;如果不兼容,将会根据package.json
的版本,更新package-lock.json
中的版本,然后再去下载实际包package-lock.json
文件锁定所有模块的版本号,包括主模块和所有依赖子模块
package-lock.json
解决了 npm
无法保证每次安装都能生成一样的 node_modules
树的问题
1.6 依赖的区别
dependencies
是无论开发环境还是生产环境都会安装的依赖devDependencies
是指可以在开发环境使用的依赖,一般指ESLint
、Prettier
这些。在打包工具中都会排除开发依赖进行打包,可以使用npm install --production
仅安装生产依赖optionalDependencies
指的是可以选择的依赖,这些依赖下载失败或者没有找到都不会影响npm
的安装,但是不要和dependencies
中重复peerDependencies
用于指定你当前的插件兼容的宿主必须要安装的包的版本,在npm2.x
会自动安装,npm3.x
之后就只会警告了,需要自行安装
1.7 依赖匹配符号
^
插入符号,会匹配到当前major version
的最新版本,比如"vue": "^3.4.27"
相当于"vue": "3.x.x"
~
波浪号,会匹配到当前minor version
的最新版本,也就是3.4.x
2. yarn
2.1 特点
- yarn 使用多线程的方式安装依赖,而 npm 是单线程
- yarn 可以复用本地缓存的依赖包,相比于 npm 的缓存机制,它支持离线安装
3. pnpm
3.1 pnpm 特点
- 极大节省了硬盘空间
- 生成树形结构目录,而非扁平化目录,防止幽灵依赖
3.2 如何生成 node_modules
- 使用硬连接,将已安装的包链接到
node_modules
- 使用符号链接生成
node_modules
的嵌套结构(依赖的相互依赖关系使用软连接链接)
参考资料