# npm模块的安装机制
npm模块的安装步骤:
- 查询node_modules目录之中是否已经存在指定模块
- 若存在,不再重新安装
- 若不存在
- npm 向 registry 查询模块压缩包的网址
- 下载压缩包,存放在根目录下的.npm目录里
- 解压压缩包到当前项目的node_modules目录
实现过程:
- 执行
preinstall
钩子(如果有定义) - 首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块。npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
- 获取模块(递归)
- 获取模块版本信息。在下载一个模块之前,首先要确定其版本。此时如果版本描述文件(如package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如
packaeg.json
中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。 - 获取模块代码。上一步会获取到模块的压缩包地址,npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
- 查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。
- 获取模块版本信息。在下载一个模块之前,首先要确定其版本。此时如果版本描述文件(如package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如
- 模块扁平化
- 因为依赖树中可能存在相同的模块依赖(如A模块依赖axios、B模块也依赖axios),按照上面的安装方法就会造成模块冗余。
- 这时候就需要加入模块扁平化的过程了。遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块(模块名相同且 semver 兼容,每个 semver 都对应一段版本允许范围)时,则将其丢弃。
- 安装模块
更新node_modules,依次执行模块中的生命周期函数(按照
preinstall、install、postinstall
的顺序)。 - 执行工程的钩子函数
当前 npm 工程如果定义了钩子此时会被执行(按照
install、postinstall、prepublish、prepare
的顺序)。
【扩展】
package-lock.json有什么作用? 锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致。
dependencies中
"@types/node": "^8.0.33"
的^
是什么意思? 向上标号^是定义了向后(新)兼容依赖,指如果 types/node的版本是超过8.0.33,并在大版本号(8)上相同,就允许下载最新版本的 types/node库包。原来package.json文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工作量,测试/适配等,所以package-lock.json文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本。什么是npm hooks?它有什么作用?
prepublish: 在publish该包之前执行。(在包目录下执行npm install时也会执行)
postpublish: 在该包publish之后执行
preinstall: 在该包被install之前执行
postinstall: 在该包被install之后执行
preuninstall: 在该包被uninstall之前执行
postuninstall: 在该包被uninstall之后执行
preversion: 在修改该包的version之前执行
postversion: 在修改该包的version之后执行
pretest, posttest: 在该包内执行test时执行,其中pretest先于posttest
prestop, poststop: 在该包内执行stop时执行,其中prestop先于poststop
prestart,poststart: 在该包内执行start时执行,其中prestart先于poststart
prerestart, postrestart: 在该包内执行restart脚本时执行,其中prerestart先于postrestart。
注意: 如果没有在scripts里显示指定restart脚本,则会自动调用stop,然后再start
上面这些Hooks都是npm预定义好的,也就是说,当你执行npm install
时,如果你在scripts
里定义了preinstall
和postinstall
,那它们分别会在npm install
之前/后自动执行。