上一篇已经讲了create命令;那么这一篇我们来看一下add和invoke这个命令。之所以放一起讲,是因为当add执行的时候,也会去执行invokeaddvue 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和vuexexports.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这边的主要流程就是安装插件并注入invokeinvoke同样我们先来看一看入口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.jsinvoke(pluginName, options, context)那么就来看看invoke.js内部是怎么实现的,主要就是分为以下几步信息验证加载插件信息generator/prompts运行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,用来运行插件的一些内置代码Generatorconst generator = new Generator(context, { pkg, plugins: [plugin], files: await readFiles(context), completeCbs: createCompleteCbs, invoking: true })这边的跟create中一样效果最后router和vuex是直接到Generator步骤,前面的加载省略了。