npm install 的实际流程
npm install是 Node Package Manager(NPM)中的一个命令,用于安装项目所需的依赖项。其实际流程主要包括以下几个步骤:
1. 解析命令和参数
- 解析命令:当你在终端输入
npm install时,npm 首先会解析这个命令以及可能附带的参数,例如-g(全局安装)、--save(将依赖添加到package.json的dependencies字段)、--save-dev(添加到devDependencies字段)等。 - 确定安装范围:根据参数和当前目录,npm 会明确是要进行全局安装还是在当前项目目录下安装依赖。
2. 查找 package.json 文件
- 定位文件:npm 会在当前工作目录下查找
package.json文件。该文件记录了项目的元数据以及所需的依赖项信息,包括依赖包的名称和版本范围。 - 读取依赖信息:如果找到
package.json,npm 会读取其中的dependencies和devDependencies字段,确定需要安装的依赖列表。
3. 检查 package-lock.json 文件
-
存在情况:
- 文件存在:如果项目根目录下存在
package-lock.json文件,npm 会将其内容与package.json进行对比。package-lock.json精确记录了每个依赖的具体版本、下载源、依赖的哈希值等信息。npm 会优先根据package-lock.json中的信息来安装依赖,以确保安装的依赖版本与之前记录的一致,保证项目的可重复性和一致性。 - 文件不存在:如果没有
package-lock.json文件,npm 会根据package.json中指定的版本范围去解析和确定要安装的依赖版本。
- 文件存在:如果项目根目录下存在
-
版本一致性检查:在对比
package-lock.json和package.json时,如果发现两者存在冲突(例如package.json中依赖的版本范围发生了变化),npm 会尝试解决这些冲突,必要时更新package-lock.json文件。
4. 检查本地缓存
- 缓存查找:npm 会检查本地的缓存目录(默认在
~/.npm),查看是否已经下载过所需的依赖包。如果缓存中存在且版本匹配,npm 会直接使用缓存中的包,而无需再次从网络下载,这样可以显著提高安装速度。 - 缓存验证:在使用缓存包之前,npm 会验证缓存包的完整性,通过比较哈希值等方式确保包没有被损坏。
5. 从注册表下载依赖包
- 选择注册表:npm 默认使用的注册表是
https://registry.npmjs.org/,但也可以通过配置文件或命令行参数指定其他注册表。 - 下载过程:如果本地缓存中没有找到所需的包,npm 会根据
package.json或package-lock.json中指定的信息,从注册表下载相应的包。在下载过程中,npm 会显示下载进度和相关信息。 - 处理依赖树:下载的包可能本身也有依赖项,npm 会递归地处理这些依赖关系,确保所有依赖的包都被正确下载和安装。它会分析每个包的
package.json文件,确定其依赖的其他包,并继续下载和安装这些间接依赖项。
6. 解决依赖冲突
- 版本冲突:在处理依赖关系时,可能会出现多个依赖对同一个包要求不同版本的情况。npm 会尝试通过语义化版本规则和依赖解析算法来解决这些冲突,选择一个兼容的版本进行安装。
- 更新
package-lock.json:如果在解决冲突的过程中对依赖的版本进行了调整,npm 会更新package-lock.json文件,以反映实际安装的依赖版本。
7. 执行安装脚本
- 脚本查找:有些包在安装过程中可能需要执行一些额外的脚本,例如构建过程、编译代码或生成配置文件等。这些脚本通常在包的
package.json文件的scripts字段中定义,常见的脚本有preinstall、install、postinstall等。 - 脚本执行:npm 会按照包的定义,在适当的时候执行这些脚本。
8. 链接全局模块(可选)
- 全局安装标志:如果使用了
-g或--global标志,npm 会将包安装到全局目录中,以便在系统的任何位置都可以访问。 - 创建符号链接:它会创建符号链接或复制文件到全局安装目录,具体取决于操作系统和 npm 的配置。
9. 更新 node_modules 目录
- 放置依赖包:npm 会将下载和安装好的包放置在项目目录下的
node_modules目录中。这个目录包含了项目所需的所有依赖项的文件和文件夹。 - 更新
package-lock.json:最后,npm 会再次检查并更新package-lock.json文件,确保它准确记录了实际安装的依赖结构和版本信息,以便后续安装时可以重现相同的依赖环境。
10. 完成安装
- 安装完成提示:当所有依赖都安装完成后,npm 会在终端输出安装完成的提示信息,显示安装的包的数量、安装时间等统计信息。此时,项目就可以使用这些安装好的依赖进行开发和运行了。
在执行npm install过程中,如果出现错误,NPM 会显示错误信息,指出问题所在,用户可以根据错误提示解决问题,例如网络问题、包名错误、版本不兼容等
npm install 的生命周期脚本
在执行 npm install 命令时,会按照特定顺序触发一系列的生命周期脚本
1. preinstall
- 触发时机:在开始实际安装依赖包之前触发。
- 用途:常用于执行一些准备工作,比如检查系统环境、创建必要的目录、初始化配置文件等。这可以确保在安装依赖之前,项目的运行环境满足要求。
- 示例:在
package.json里添加如下脚本:
{
"scripts": {
"preinstall": "echo 'Starting pre - install checks'; mkdir -p logs"
}
}
当运行 npm install 时,会先输出 Starting pre - install checks,然后创建一个名为 logs 的目录。
2. install
- 触发时机:在
preinstall脚本执行完成后,正式开始安装依赖包的过程中触发。 - 用途:虽然这个阶段通常不需要额外的自定义脚本,但如果有特殊需求,例如在安装过程中对依赖包进行一些预处理,可在此编写逻辑。不过要注意,此时依赖包尚未全部安装完成。
- 示例:
{
"scripts": {
"install": "echo 'Dependency installation in progress'"
}
}
3. postinstall
-
触发时机:所有依赖包都安装完成之后触发。
-
用途:这是最常用的生命周期脚本之一,常用于执行安装后的任务,如代码编译、生成静态资源、初始化数据库连接等。对于前端项目,可能会在这个阶段进行代码压缩、打包等操作。
-
示例:
{
"scripts": {
"postinstall": "webpack --config webpack.prod.js"
}
}
上述示例会在依赖安装完成后,使用 Webpack 按照生产环境的配置进行打包。
4. prepublish 和 prepare
- 触发时机:在 npm 发布包之前或者执行
npm install时触发(从 npm v4 开始,prepublish等同于prepare)。 - 用途:常用于在发布包或者安装依赖时进行一些准备工作,像编译 TypeScript 代码、生成文档、更新版本号等。
- 示例:
{
"scripts": {
"prepare": "tsc"
}
}
此示例会在发布包或安装依赖时,将 TypeScript 代码编译为 JavaScript 代码。
执行顺序
当执行 npm install 命令时,生命周期脚本的执行顺序通常为:preinstall -> install -> postinstall -> prepublish(等同于 prepare) 。
需要注意的是,不同版本的 npm 可能会对生命周期脚本的行为有细微的差异,在实际使用时建议参考 npm 的官方文档获取最新和最准确的信息。