【npm源码】npm install命令安装过程解析

上一篇文章讲解了如何找到源码以及npm config命令的源码解读,今天来看一下npm install命令。若是不知道怎么找源码能够参考上一篇文章【npm源码】config get命令大扒皮,一件衣服都不留node

首先在源码中找到Installer构造函数:react

/lib/install.js
function Installer (where, dryrun, args, opts) {
  validate('SBA|SBAO', arguments)
  if (!opts) opts = {}
  this.where = where
  this.dryrun = dryrun
  this.args = args
  // fakechildren are children created from the lockfile and lack relationship data
  // the only exist when the tree does not match the lockfile
  // this is fine when doing full tree installs/updates but not ok when modifying only
  // a few deps via `npm install` or `npm uninstall`.
  this.currentTree = null
  this.idealTree = null
  this.differences = []
  this.todo = []
  this.progress = {}
  this.noPackageJsonOk = !!args.length
  this.topLevelLifecycles = !args.length

  this.autoPrune = npm.config.get('package-lock')

  const dev = npm.config.get('dev')
  const only = npm.config.get('only')
  const onlyProd = /^prod(uction)?$/.test(only)
  const onlyDev = /^dev(elopment)?$/.test(only)
  const prod = npm.config.get('production')
  this.dev = opts.dev != null ? opts.dev : dev || (!onlyProd && !prod) || onlyDev
  this.prod = opts.prod != null ? opts.prod : !onlyDev

  this.packageLockOnly = opts.packageLockOnly != null
    ? opts.packageLockOnly : npm.config.get('package-lock-only')
  this.rollback = opts.rollback != null ? opts.rollback : npm.config.get('rollback')
  this.link = opts.link != null ? opts.link : npm.config.get('link')
  this.saveOnlyLock = opts.saveOnlyLock
  this.global = opts.global != null ? opts.global : this.where === path.resolve(npm.globalDir, '..')
  this.audit = npm.config.get('audit') && !this.global
  this.started = Date.now()
}
复制代码

参数

  • where 字符串 表示当前运行install命令的目录 D:\workspace\mine\npm彻底使用指南\test
  • dryrun 布尔值,若是为true代表你不须要让npm作出任何更新而且只报告完成了什么
  • args 数组,表示晕运行install命令传进来的参数 ['react']
  • opts 表示安装时的一些其参数,如当前在开发环境哈仍是测试环境,是否在全局环境等。

重要的属性

  • where : 在那个目录下
  • dryrun : 是否只打印信息
  • args :参数
  • opts: 选项
  • currentTree: 当前硬盘上node_moudules 构成的 tree
  • idealTree: 安装相应的模块后 理想的 tree
  • differences: 两棵树的差别队列
  • todo: 一系列要作的事情的队列
  • progress: 进程管理
  • noPackageJsonOk : 没有packageJson文件是否ok,这里要多说一句,若是install没有安装对象 也就是arg.length = 0 此时!!0 为false,就是说没有packageJson是不行的,由于这时候运行的是npm install ,要去拉取package.json里的依赖去安装,因此没有package.json是不行的。
  • topLevelLifecycles 顶层元素的生命周期
  • autoPrune 布尔值,读取配置文件 package-lock 判断是否自动生成package-lock.json文件
  • only 当是dev或者development时,不带任何参数运行局部npm install,只会有devDependencies(和他们的依赖)会被安装,当是prod或者production是,无参运行npm install,只有non-devDependencies(和他们的依赖)会被安装
  • packageLockOnly 若是为true,则不进行安装动做,只是将安装的内容写进package.json的dependencies里面

再继续找,找到安装过程:npm

/lib/install.js
Installer.prototype.run = function (_cb) {
  //进行一些校验
  ...
  //将安装的步骤放入队列里面
  var installSteps = []
  var postInstallSteps = []
  if (!this.dryrun) {
    installSteps.push(
      [this.newTracker(log, 'runTopLevelLifecycles', 2)],
      [this, this.runPreinstallTopLevelLifecycles])
  }
  installSteps.push(
    [this.newTracker(log, 'loadCurrentTree', 4)],
    [this, this.loadCurrentTree],
    [this, this.finishTracker, 'loadCurrentTree'],

    [this.newTracker(log, 'loadIdealTree', 12)],
    [this, this.loadIdealTree],
    [this, this.finishTracker, 'loadIdealTree'],

    [this, this.debugTree, 'currentTree', 'currentTree'],
    [this, this.debugTree, 'idealTree', 'idealTree'],

    [this.newTracker(log, 'generateActionsToTake')],
    [this, this.generateActionsToTake],
    [this, this.finishTracker, 'generateActionsToTake'],

    [this, this.debugActions, 'diffTrees', 'differences'],
    [this, this.debugActions, 'decomposeActions', 'todo'],
    [this, this.startAudit]
  )

  if (this.packageLockOnly) {
    postInstallSteps.push(
      [this, this.saveToDependencies])
  } else if (!this.dryrun) {
    installSteps.push(
      [this.newTracker(log, 'executeActions', 8)],
      [this, this.executeActions],
      [this, this.finishTracker, 'executeActions'])
    var node_modules = path.resolve(this.where, 'node_modules')
    var staging = path.resolve(node_modules, '.staging')
    postInstallSteps.push(
      [this.newTracker(log, 'rollbackFailedOptional', 1)],
      [this, this.rollbackFailedOptional, staging, this.todo],
      [this, this.finishTracker, 'rollbackFailedOptional'],
      [this, this.commit, staging, this.todo],

      [this, this.runPostinstallTopLevelLifecycles],
      [this, this.finishTracker, 'runTopLevelLifecycles']
    )
    if (getSaveType()) {
      postInstallSteps.push(
        [this, (next) => { computeMetadata(this.idealTree); next() }],
        [this, this.pruneIdealTree],
        [this, this.debugLogicalTree, 'saveTree', 'idealTree'],
        [this, this.saveToDependencies])
    }
  }
  postInstallSteps.push(
    [this, this.printWarnings],
    [this, this.printInstalled])

  var self = this
  //到这里才真正开始执行
  chain(installSteps, function (installEr) {
    if (installEr) self.failing = true
    chain(postInstallSteps, function (postInstallEr) {
      if (installEr && postInstallEr) {
        var msg = errorMessage(postInstallEr)
        msg.summary.forEach(function (logline) {
          log.warn.apply(log, logline)
        })
        msg.detail.forEach(function (logline) {
          log.verbose.apply(log, logline)
        })
      }
      cb(installEr || postInstallEr, self.getInstalledModules(), self.idealTree)
    })
  })
  return result
}
复制代码

这里就是安装模块的整个流程了,如今来分析一下。json

首先定义两个队列 :

installStep: 安装步骤数组

postInstallSteps: 安装完成以后的步骤bash

判断是不是干运行app

若是不是干运行,则运行 preinstall 钩子(若是工程定义了的话)ide

若是是干运行,则进入下一步函数

loadCurrentTree阶段

这一步会执行两个动做:post

  1. 判断是不是全局安装,(this.global 为true 表示是全局安装有 -g 参数),若是是则调用readGlobalPackageData读取全局目录下的node_modules文件树,若是不是则调用readLocalPackageData读取当前项目目录下的node_modules文件树 。
  2. 调用normalizeCurrentTree将上一步获得的tree序标准化。

loadIdealTree阶段

这一步会执行三个动做,

  1. 调用cloneCurrentTree函数 把currentTree复制给idealTree

  2. 调用loadShrinkwrap函数 ,该函数会依次读取npm-shrinkwrap.json、package-lock.json、package.json ,肯定完整的依赖树,注意这时候的依赖树只是逻辑上的依赖树

  3. 调用loadAllDepsIntoIdealTree函数 遍历上一步获得的idealTree,添加任何缺乏的依赖项,在不破坏原来的module的状况下,依赖项会尽量的放到顶层。

debugTree阶段

这一步会对上面生成的currentTree和idealTree进行一些处理

返回带有unicode管道字符的obj字符串表示形式 并打印在控制台,效果相似于npm ls

实际调用了 archy 方法 ,详细信息点这里

generateActionsToTake阶段

这阶段会有五个动做

  1. 调用validateTree方法 对idealTree进行校验,校验的内容有是否须要peer dependencies,node版本校验,要安装的包是否在dev和prod环境中都存在。

  2. 调用diffTrees方法找到由currentTree转为idelTree的不一样动做并放到 differences队列中

  3. 调用 computeLinked 方法计算局部包如何连接到合适的全局包

  4. 调用checkPermissions方法检查differnces里的anction操做是否有权限,好比是否有写文件的权限等。

  5. 调用decomposeActions方法,把diffrence队列里的action按照add、update、move、remove进行分解并放到todo队列中

debugActions阶段

这个阶段只作一件事就是打印每一个action的信息

packageLockOnly阶段

若是packageLockOnly为true

.npmrc中对应的配置项为 package-lock-only

packageLockOnly的意思就是只将要安装的模块写到package.json的dependences里面,而不执行任何其余动做

dryrun阶段

若是packageLockOnly为false 而且dryrun(干运行)为true

.npmrc中对应的配置项为 dry-run

这个时候不执行任何动做只是打印信息

executeActions阶段

若是packageLockOnly为false,而且dryrun(干运行)为false,这个时候开始了真正安装阶段。

这个阶段调用executeActions方法,去执行todo里面的内容

/lib/install.js
steps.push(
    [doSerialActions, 'global-install', staging, todo, trackLifecycle.newGroup('global-install')],
    [lock, node_modules, '.staging'],
    [rimraf, staging],
    [doParallelActions, 'extract', staging, todo, cg.newGroup('extract', 100)],
    [doReverseSerialActions, 'unbuild', staging, todo, cg.newGroup('unbuild')],
    [doSerialActions, 'remove', staging, todo, cg.newGroup('remove')],
    [doSerialActions, 'move', staging, todo, cg.newGroup('move')],
    [doSerialActions, 'finalize', staging, todo, cg.newGroup('finalize')],
    [doParallelActions, 'refresh-package-json', staging, todo, cg.newGroup('refresh-package-json')],
    [doParallelActions, 'preinstall', staging, todo, trackLifecycle.newGroup('preinstall')],
    [doSerialActions, 'build', staging, todo, trackLifecycle.newGroup('build')],
    [doSerialActions, 'global-link', staging, todo, trackLifecycle.newGroup('global-link')],
    [doParallelActions, 'update-linked', staging, todo, trackLifecycle.newGroup('update-linked')],
    [doSerialActions, 'install', staging, todo, trackLifecycle.newGroup('install')],
    [doSerialActions, 'postinstall', staging, todo, trackLifecycle.newGroup('postinstall')])
复制代码

postInstall 阶段

在这个阶段会有三个步骤

  1. 检测安装是否失败,若是失败就进行回滚操做

  2. 执行全部的生命周期函数(若是定义了的话)

  3. 在控制台打印警告信息和安装了那些模块信息

ヽ✿゜▽゜)ノ

相关文章
相关标签/搜索