前言
在packages/@vue/cli/bin/vue.js
中可以得知,执行add <plugin> [pluginOptions]
该命令,会执行../lib/add
文件。
主函数
../lib/add.js
文件中,主要导出了add
函数:
module.exports = (...args) => {
return add(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
接下来开始分析add.js
:
判断是否有未commit的情况
if (!(await confirmIfGitDirty(context))) {
return
}
在confirmIfGitDirty
这个方法中,会判断用户是否修改了代码但是未commit的情况,提醒用户是否要提交。
根据版本号处理router和vuex
const servicePkg = loadModule('@vue/cli-service/package.json', context)
if (servicePkg && semver.satisfies(servicePkg.version, '3.x')) {
// special internal "plugins"
if (/^(@vue\/)?router$/.test(pluginToAdd)) {
return addRouter(context)
}
if (/^(@vue\/)?vuex$/.test(pluginToAdd)) {
return addVuex(context)
}
}
使用loadModule
获得当前的package.json
从而获得vue的版本。再判断是安装router
或者vuex
模块,就不会通过包管理器安装,而是调用addRouter
或者addVuex
。以addRouter
为例子:
async function addRouter (context) {
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
})
}
循环用户使用vue-router
是使用hash
模式还是history
模式,用户选择完之后,调用@vue/cli-service/generator/router
,里面调用了@vue/cli-plugin-router/generator
。这个文件就是vue-router
插件(插件的开发流程详见上一文)。
解析安装的插件名
const pluginRe = /^(@?[^@]+)(?:@(.+))?$/
const [
// eslint-disable-next-line
_skip,
pluginName,
pluginVersion
] = pluginToAdd.match(pluginRe)
const packageName = resolvePluginId(pluginName)
log()
log(`📦 Installing ${chalk.cyan(packageName)}...`)
log()
安装
const pm = new PackageManager({ context })
if (pluginVersion) {
// 是否指定了版本
await pm.add(`${packageName}@${pluginVersion}`)
} else if (isOfficialPlugin(packageName)) {
// 是否是官方插件
const { latestMinor } = await getVersions()
await pm.add(`${packageName}@~${latestMinor}`)
} else {
await pm.add(packageName, { tilde: true })
}
log(`${chalk.green('✔')} Successfully installed plugin: ${chalk.cyan(packageName)}`)
log()
获得插件的generator.js路径
const generatorPath = resolveModule(`${packageName}/generator`, context)
if (generatorPath) {
invoke(pluginName, options, context)
} else {
log(`Plugin ${packageName} does not have a generator to invoke`)
}
如果插件的genrator.js或者genrator/index.js
文件路径之后,调用invoke
方法。
invoke方法
async function invoke (pluginName, options = {}, context = process.cwd()) {
if (!(await confirmIfGitDirty(context))) {
return
}
delete options._
const pkg = getPkg(context)
// 判断插件是否安装
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)
if (!id) {
throw new Error(
`Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
`Did you forget to install it?`
)
}
// 判断插件内部是否有generator方法
const pluginGenerator = loadModule(`${id}/generator`, context)
if (!pluginGenerator) {
throw new Error(`Plugin ${id} does not have a generator.`)
}
// 判断插件是否含有 prompt。如果有则调用 inquirer.prompt 获取插件的 option,并传给其 generator
let { registry, $inlineOptions, ...pluginOptions } = options
if ($inlineOptions) {
try {
pluginOptions = JSON.parse($inlineOptions)
} catch (e) {
throw new Error(`Couldn't parse inline options JSON: ${e.message}`)
}
} else if (!Object.keys(pluginOptions).length) {
let pluginPrompts = loadModule(`${id}/prompts`, context)
if (pluginPrompts) {
const prompt = inquirer.createPromptModule()
if (typeof pluginPrompts === 'function') {
pluginPrompts = pluginPrompts(pkg, prompt)
}
if (typeof pluginPrompts.getPrompts === 'function') {
pluginPrompts = pluginPrompts.getPrompts(pkg, prompt)
}
pluginOptions = await prompt(pluginPrompts)
}
}
const plugin = {
id,
apply: pluginGenerator,
options: {
registry,
...pluginOptions
}
}
await runGenerator(context, plugin, pkg)
}
runGenerator
方法中调用了generator.generate
,跟vue create
中的类似,就不赘述了。
总结
add <plugin> [pluginOptions]
命名调用了一个add
的主函数,在该函数执行的过程中会执行如下流程:
- 先判断用户当前代码是否有
git
且是否存在未提交代码的情况; - 获得当前的
vue cli
的版本号以及是否安装的是router
或者vuex
,是的话就不会通过包管理器安装,而是调用addRouter
或者addVuex
,去调用插件的generator
进行处理; - 解析插件的名;
- 调用包管理器安装插件;
- 获得插件的
generator
文件的路径; - 最后调用
invoke
方法,该函数的流程跟vue create
中插件的安装流程差不多。
由此可见:vue add
相当于只是 vue create
中的一部分,vue create
包含了插件的安装以及调用,vue add
命令只是将此功能分离了出来。
参考
如有错误,欢迎指出,感谢~