1

上一篇已经讲了create命令;
那么这一篇我们来看一下add和invoke这个命令。之所以放一起讲,是因为当add执行的时候,也会去执行invoke

add

vue add vue-cli-plugin-xxx 或 vue add @vue/xxx

通过这种形式就是vue-cli3.0内部能识别的插件了
首先来看一下入口

program
  .command('add <plugin> [pluginOptions]')
  .description('install a plugin and invoke its generator in an already created project')
  .option('--registry <url>', 'Use specified npm registry when installing dependencies (only for npm)') // 可以设置源
  .allowUnknownOption()
  .action((plugin) => {
    require('../lib/add')(plugin, minimist(process.argv.slice(3)))
  })

入口比较简单,接下来我们来看一下add.js文件

async function add (pluginName, options = {}, context = process.cwd()) {
  // special internal "plugins"
  // 这边对@vue/router和@vue/vuex这2个插件做特殊处理,直接从cli-service下拉模块
  if (/^(@vue\/)?router$/.test(pluginName)) {
    return addRouter(context)
  }
  if (/^(@vue\/)?vuex$/.test(pluginName)) {
    return addVuex(context)
  } 

  const packageName = resolvePluginId(pluginName) // 解析插件名

  log()
  log(`📦  Installing ${chalk.cyan(packageName)}...`)
  log()

  const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm')
  // 是用什么安装 npm、yarn
  await installPackage(context, packageManager, options.registry, packageName) // 开始安装插件

  log(`${chalk.green('✔')}  Successfully installed plugin: ${chalk.cyan(packageName)}`)
  log()

  const generatorPath = resolveModule(`${packageName}/generator`, context) // 解析路径
  // 开始加载插件下面的generator 
  if (generatorPath) {
    invoke(pluginName, options, context)
  } else {
    log(`Plugin ${packageName} does not have a generator to invoke`)
  }
}

这边也比较简单一目了然。

async function addRouter (context) {
  const inquirer = require('inquirer')
  const options = await inquirer.prompt([{
    name: 'routerHistoryMode',
    type: 'confirm',
    message: `Use history mode for router? ${chalk.yellow(`(Requires proper server setup for index fallback in production)`)}`
  }])
  invoke.runGenerator(context, {
    id: 'core:router',
    apply: loadModule('@vue/cli-service/generator/router', context),
    options
  })
}

async function addVuex (context) {
  invoke.runGenerator(context, {
    id: 'core:vuex',
    apply: loadModule('@vue/cli-service/generator/vuex', context)
  })
}

这2个就是单独添加router和vuex


exports.resolvePluginId = id => {
  // already full id
  // e.g. vue-cli-plugin-foo, @vue/cli-plugin-foo, @bar/vue-cli-plugin-foo
  if (pluginRE.test(id)) {
    return id
  }
  // scoped short
  // e.g. @vue/foo, @bar/foo
  if (id.charAt(0) === '@') {
    const scopeMatch = id.match(scopeRE)
    if (scopeMatch) {
      const scope = scopeMatch[0]
      const shortId = id.replace(scopeRE, '')
      return `${scope}${scope === '@vue/' ? `` : `vue-`}cli-plugin-${shortId}`
    }
  }
  // default short
  // e.g. foo
  return `vue-cli-plugin-${id}`
}

将@vue/xxx的形状解析为vue-cli-plugin-xxx

这边的主要流程就是安装插件并注入invoke

invoke

同样我们先来看一看入口

program
  .command('invoke <plugin> [pluginOptions]')
  .description('invoke the generator of a plugin in an already created project')
  .option('--registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
  .allowUnknownOption()
  .action((plugin) => {
    require('../lib/invoke')(plugin, minimist(process.argv.slice(3)))
  })

在add中的代码与入口调用是一样的,都是通过调用invoke.js

invoke(pluginName, options, context)

那么就来看看invoke.js内部是怎么实现的,主要就是分为以下几步

  1. 信息验证
  2. 加载插件信息generator/prompts
  3. 运行Generator

信息验证:

const pkg = getPkg(context) // package文件

  // attempt to locate the plugin in package.json
  const findPlugin = deps => {
    if (!deps) return
    let name
    // official
    if (deps[(name = `@vue/cli-plugin-${pluginName}`)]) {
      return name
    }
    // full id, scoped short, or default short
    if (deps[(name = resolvePluginId(pluginName))]) {
      return name
    }
  }

  const id = findPlugin(pkg.devDependencies) || findPlugin(pkg.dependencies)
  // 在devDependencies和dependencies依赖中寻找vue-cli插件
  if (!id) {
    throw new Error(
      `Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
        `Did you forget to install it?`
    )
  }

以上验证是否存在package.json文件,以及package文件内是否安装了vue-cli插件

加载插件

const pluginGenerator = loadModule(`${id}/generator`, context)
  // 加载插件下的generator文件
  if (!pluginGenerator) {
    throw new Error(`Plugin ${id} does not have a generator.`)
  }

  // resolve options if no command line options (other than --registry) are passed,
  // and the plugin contains a prompt module.
  // eslint-disable-next-line prefer-const
  let { registry, ...pluginOptions } = options
  if (!Object.keys(pluginOptions).length) {
    let pluginPrompts = loadModule(`${id}/prompts`, context)
    // 加载插件下的prompts,对话
    if (pluginPrompts) {
      if (typeof pluginPrompts === 'function') {
        pluginPrompts = pluginPrompts(pkg)
      }
      if (typeof pluginPrompts.getPrompts === 'function') {
        pluginPrompts = pluginPrompts.getPrompts(pkg)
      }
      pluginOptions = await inquirer.prompt(pluginPrompts)
    }
  }

以上就是加载了generator和prompts,用来运行插件的一些内置代码

Generator

const generator = new Generator(context, {
    pkg,
    plugins: [plugin],
    files: await readFiles(context),
    completeCbs: createCompleteCbs,
    invoking: true
  })

这边的跟create中一样效果

最后

router和vuex是直接到Generator步骤,前面的加载省略了。


DanielDemi
159 声望8 粉丝

开始前端之旅