上一篇已经讲了 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 步骤,前面的加载省略了。