【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. 在控制檯打印警告信息和安裝了那些模塊信息

ヽ✿゜▽゜)ノ

相關文章
相關標籤/搜索