new Service

Serviceconstructor 方法如下:

constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
  ...
  this.webpackChainFns = [] // 每一项为 chainable webpack 配置函数
  this.webpackRawConfigFns = [] // 每一项为 raw webpack 配置函数
  this.devServerConfigFns = []
  this.commands = {}
  // Folder containing the target package.json for plugins
  // 项目中 package.json 目录
  this.pkgContext = context
  // package.json containing the plugins
  this.pkg = this.resolvePkg(pkg)
  // If there are inline plugins, they will be used instead of those
  // found in package.json.
  // When useBuiltIn === false, built-in plugins are disabled. This is mostly
  // for testing.
  // 如果有 inline plugins 的话,就不会去加载 package.json 里 devDependencies 和 dependencies 的插件
  this.plugins = this.resolvePlugins(plugins, useBuiltIn)
  // resolve the default mode to use for each command
  // this is provided by plugins as module.exports.defaultModes
  // so we can get the information without actually applying the plugin.
  // 为命令指定模式
  // 注册的插件可以通过 module.exports.defaultModes 指定特定的模式
  /*{
    serve: 'development',
    build: 'production',
    inspect: 'development',
    'test:unit': 'test'
  }*/
  this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
    return Object.assign(modes, defaultModes)
  }, {})
}

实例化的过程主要进行了插件的解析和为每一种 CLI 命令指定模式,先看一下插件的解析。

resolvePlugins

resolvePlugins (inlinePlugins, useBuiltIn) {
  const idToPlugin = id => ({
    id: id.replace(/^.\//, 'built-in:'),
    apply: require(id)
  })

  let plugins

  // 内置插件
  const builtInPlugins = [
    './commands/serve',
    './commands/build',
    './commands/inspect',
    './commands/help',
    // config plugins are order sensitive
    './config/base',
    './config/css',
    './config/dev',
    './config/prod',
    './config/app'
  ].map(idToPlugin)

  // builtInPlugins
  //  [{ id: 'built-in:commands/serve', apply:{ [Function] defaultModes: [Object] } },...]
  if (inlinePlugins) {
    plugins = useBuiltIn !== false
      ? builtInPlugins.concat(inlinePlugins)
      : inlinePlugins
  } else {
    //const pluginRE = /^(@vue\/|vue-|@[\w-]+\/vue-)cli-plugin-/
    // exports.isPlugin = id => pluginRE.test(id)
    const projectPlugins = Object.keys(this.pkg.devDependencies || {})
      .concat(Object.keys(this.pkg.dependencies || {}))
      .filter(isPlugin)
      .map(id => {
        if (
          this.pkg.optionalDependencies &&
          id in this.pkg.optionalDependencies
        ) {
          let apply = () => {}
          try {
            apply = require(id)
          } catch (e) {
            warn(`Optional dependency ${id} is not installed.`)
          }

          return { id, apply }
        } else {
          return idToPlugin(id)
        }
      })

    plugins = builtInPlugins.concat(projectPlugins) // 内置插件和项目中的插件
  }

  // Local plugins
  // 项目本地的插件,针对于只需要在项目里直接访问插件 API 而不需要创建一个完整的插件
  if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
    const files = this.pkg.vuePlugins.service
    if (!Array.isArray(files)) {
      throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
    }
    plugins = plugins.concat(files.map(file => ({
      id: `local:${file}`,
      apply: loadModule(file, this.pkgContext)
    })))
  }
  return plugins
}

可以将解析的插件分为4类:

  • 内置插件
  • inlinePlugins
  • package.json 插件
  • package.vuePlugins 插件

内置插件指的是 @vue/cli-service 内部提供的插件,又可以大致分为两类,serve,build,inspect,help这一类插件在内部动态注册新的 CLI 命令, 开发者即可通过 npm script 的形式去启动对应的 CLI 命令服务,base ,css, dev, prod, app 这一类插件主要是完成 webpack 本地编译构建时的各种相关的配置。 @vue/cli-servicewebpack 的开发构建功能收敛到内部来完成。

inlinePlugins 指的是直接在实例化 Service 时传入,执行 vue servevue build 命令时会创建一个Service 实例,并传入 inlinePlugins

package.json 插件 指的是 devDependenciesdependencies 中的 vue 插件,比如 @vue/cli-plugin-eslint

package.vuePlugins 也是在 package.json 中的插件,不过是在 vuePlugins 字段中,该类插件是针对于只需要在项目里直接访问插件 API 而不需要创建一个完整的插件。

CLI 模式

CLI 模式是 vue cli 中一个重要的概念,详细介绍可点击查看官方 文档,在解析完插件之后就为每种插件命令指定模式,插件命令的模式可以 通过 module.exports.defaultModes 以 { [commandName]: mode } 的形式来暴露:

module.exports = api => {
  api.registerCommand('build', () => {
    // ...
  })
}

module.exports.defaultModes = {
  build: 'production'
}

解析命令模式利用 js 内建函数 reduce 实现:

this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
  return Object.assign(modes, defaultModes)
}, {})

这一节大致介绍了 new Service 的过程,在下一节将介绍 Service 实例的 run 方法。