vue-cli3.0 source code analysis @ vue/cli-----add and invoke

Keywords: Javascript Vue npm JSON

The create command was mentioned in the previous article;
Let's take a look at the command add and invoke in this article. The reason why we put it together is that when add executes, it will also execute invoke

add

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

In this form, it is a plug-in that can be recognized inside vue-cli 3.0
Let's first look at the entrance

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)') // Can set source
  .allowUnknownOption()
  .action((plugin) => {
    require('../lib/add')(plugin, minimist(process.argv.slice(3)))
  })

The entry is relatively simple. Let's take a look at the add.js file

async function add (pluginName, options = {}, context = process.cwd()) {
  // special internal "plugins"
  // Here, we do special processing for the two plug-ins @ vue/router and @ vue/vuex, directly from the CLI service drop-down module
  if (/^(@vue\/)?router$/.test(pluginName)) {
    return addRouter(context)
  }
  if (/^(@vue\/)?vuex$/.test(pluginName)) {
    return addVuex(context)
  } 

  const packageName = resolvePluginId(pluginName) // Resolve plug-in name

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

  const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm')
  // What is npm and yarn installed with
  await installPackage(context, packageManager, options.registry, packageName) // Start installing plug-ins

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

  const generatorPath = resolveModule(`${packageName}/generator`, context) // Analytic path
  // Start loading the generator under the plug-in 
  if (generatorPath) {
    invoke(pluginName, options, context)
  } else {
    log(`Plugin ${packageName} does not have a generator to invoke`)
  }
}

This side is also relatively simple and clear at a glance.

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)
  })
}

These two are to add router and vuex separately


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}`
}

Resolve the shape of @ vue/xxx to Vue cli plugin XXX

The main process here is to install the plug-in and inject the invoke

invoke

Let's also take a look at the entrance

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)))
  })

The code in add is the same as the entry call, both by calling invoke.js

invoke(pluginName, options, context)

So let's see how the internal implementation of invoke.js is implemented. It is mainly divided into the following steps

  1. Information validation
  2. Load plug-in information generator/prompts
  3. Run Generator

Information validation:

const pkg = getPkg(context) // package file

  // 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)
  // Looking for Vue cli plug-ins in devdpendencies and dependencies dependencies
  if (!id) {
    throw new Error(
      `Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
        `Did you forget to install it?`
    )
  }

Verify whether the package.json file exists and whether the Vue cli plug-in is installed in the package file

Add plug-ins

const pluginGenerator = loadModule(`${id}/generator`, context)
  // Load the generator file under the plug-in
  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)
    // Load the prompts under the plug-in, dialog
    if (pluginPrompts) {
      if (typeof pluginPrompts === 'function') {
        pluginPrompts = pluginPrompts(pkg)
      }
      if (typeof pluginPrompts.getPrompts === 'function') {
        pluginPrompts = pluginPrompts.getPrompts(pkg)
      }
      pluginOptions = await inquirer.prompt(pluginPrompts)
    }
  }

The above is the built-in code loaded with generator and prompts to run the plug-in

Generator

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

This one is the same as the one in create

Last

router and vuex go directly to the Generator step, and the previous loading is omitted.

Posted by steveswt on Thu, 28 Nov 2019 08:14:56 -0800