上一篇已经讲了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内部是怎么实现的,主要就是分为以下几步
- 信息验证
- 加载插件信息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,用来运行插件的一些内置代码
Generator
const generator = new Generator(context, {
pkg,
plugins: [plugin],
files: await readFiles(context),
completeCbs: createCompleteCbs,
invoking: true
})
这边的跟create中一样效果
最后
router和vuex是直接到Generator步骤,前面的加载省略了。