NodeJs 第六章(npm install生命周期)

526 阅读6分钟

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 字段中定义,常见的脚本有 preinstallinstallpostinstall 等。
  • 脚本执行: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 的官方文档获取最新和最准确的信息。