npm:
嵌套结构
我们都知道,执行 npm install
后,依赖包被安装到了 node_modules
,下面我们来具体了解下,npm
将依赖包安装到 node_modules
的具体机制是什么。
在 npm
的早期版本, npm
处理依赖的方式简单粗暴,以递归的形式,严格按照 package.json
结构以及子依赖包的 package.json
结构将依赖安装到他们各自的 node_modules
中。直到有子依赖包不在依赖其他模块。
扁平结构
为了解决以上问题,NPM
在 3.x
版本做了一次较大更新。其将早期的嵌套结构改为扁平结构:
- 安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在
node_modules
根目录。 npm 3.x
版本并未完全解决老版本的模块冗余问题,加入你的项目没有依赖B,但是依赖了不同B版本的A@1和A@2,由于在执行npm install,按照package.json依赖的顺序依次解析,则A@1和A@2在package.json的位置决定了node_modules的依赖结构,可能先依赖A@1也可能先依赖A@2,造成了不确定性。
Lock文件
为了解决 npm install
的不确定性问题,在 npm 5.x
版本新增了 package-lock.json
文件,而安装方式还沿用了 npm 3.x
的扁平化的方式。
package-lock.json
的作用是锁定依赖结构,即只要你目录下有 package-lock.json
文件,那么你每次执行 npm install
后生成的 node_modules
目录结构一定是完全相同的。
整体流程
好了,我们再来整体总结下上面的流程:
检查
.npmrc
文件:优先级为:项目级的.npmrc
文件 > 用户级的.npmrc
文件> 全局级的.npmrc
文件 > npm 内置的.npmrc
文件检查项目中有无
lock
文件。无
1
lock
文件:
从
npm
远程仓库获取包信息根据
1
package.json
构建依赖树,构建过程:
- 构建依赖树时,不管其是直接依赖还是子依赖的依赖,优先将其放置在
node_modules
根目录。 - 当遇到相同模块时,判断已放置在依赖树的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的
node_modules
下放置该模块。 - 注意这一步只是确定逻辑上的依赖树,并非真正的安装,后面会根据这个依赖结构去下载或拿到缓存中的依赖包
- 构建依赖树时,不管其是直接依赖还是子依赖的依赖,优先将其放置在
在缓存中依次查找依赖树中的每个包
- 不存在缓存:
- 从
npm
远程仓库下载包 - 校验包的完整性
- 校验不通过:
- 重新下载
- 校验通过:
- 将下载的包复制到
npm
缓存目录 - 将下载的包按照依赖结构解压到
node_modules
- 将下载的包复制到
- 从
存在缓存:将缓存按照依赖结构解压到
node_modules
- 不存在缓存:
将包解压到
node_modules
生成
lock
文件
pnpm
pnpm好处:
安装速度快(非扁平的包结构,没有复杂的扁平算法,只更新变化的文件)
节省磁盘空间,统一安装到磁盘的某个位置,项目中的node_modules通过hard-link的方式链接到实际安装地址
pnpm 与 npm/yarn 另外一个很大的不同就是支持了 monorepo,体现在各个子命令的功能上,比如在根目录下
pnpm add A -r
, 那么所有的 package 中都会被添加 A 这个依赖,当然也支持--filter
字段来对 package 进行过滤。
原理:
比如安装bar包,根目录只包含安装的包bar,而node_modules目录下的bar包会软链接到.pnpm/bar/node_modules/bar@…,被bar依赖的包会被提升到.pnpm的根目录,其他依赖该包的也会软链接到这里,而bar,foo包硬链接到.pnpm store;总之,软链接解决了磁盘占用的问题,而硬链接解决了包的同步更新和统一管理问题。
嵌套结构的问题在于:
- 包文件的目录可能会非常长
- 重复安装包
- 相同包的实例不能共享
而扁平结构也同样存在问题:
- 依赖结构的不确定性(不同包依赖某个包的不同版本 最终安装的版本具有不确定性)可通过lock文件确定安装版本
- 扁平化算法复杂,耗时
- 非法访问未声明的包
monorepo:
多个项目或者包文件放到一个git仓库管理,解决代码复用问题,开发流程统一,高效管理多项目/包