前言
上文介绍了vue create的大致过程,下面要介绍vue add
和vue invoke
的执行过程,其实vue add
本质上还是执行vue invoke
,只不过多了插件安装的相关逻辑而已
vue create
如果你想在一个已经被创建好的项目中安装一个插件,可以使用 vue add 命令:
program
.command('add [pluginOptions]')
.description('install a plugin and invoke its generator in an already created project')
.option('--registry ', 'Use specified npm registry when installing dependencies (only for npm)')
.allowUnknownOption()
.action((plugin) => {
require('../lib/add')(plugin, minimist(process.argv.slice(3)))
})
- plugin: 安装的插件名称
- minimist(process.argv.slice(3)): pluginOptions对象
add.js
文件路径: ``
module.exports = (...args) => {
return add(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
add
func
/**
* @param pluginToAdd 待添加插件名称
* @param pluginToAdd 额外参数
* @param context 命令行执行路径
*/
async function add (pluginToAdd, options = {}, context = process.cwd()) {
if (!(await confirmIfGitDirty(context))) {
return
}
// for `vue add` command in 3.x projects
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)
}
}
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()
const generatorPath = resolveModule(`${packageName}/generator`, context)
if (generatorPath) {
invoke(pluginName, options, context)
} else {
log(`Plugin ${packageName} does not have a generator to invoke`)
}
}
首先执行confirmIfGitDirty
方法,它会判断是否为执行命令行路径是否为git仓库,不是则继续执行下面流程,如果是则判断是git仓库,则判断仓库状态是否已提交,如果没有提交则提示用户。
接下来则是判断@vue/cli-service
是否为3.x版本,如果是,则再判断是否为vue add router
或vue add vuex
去执行安装插件步骤,不过4.0版本开始,router
和vuex
这个2个cli插件被拆分成@vue/cli-plugin-router
与@vue/cli-plugin-vuex
,不作为内部的特殊插件。了解即可。
然后就是通过resolvePluginId
获取完整的插件名称。通过上文的提到的PackageManager
实例pm
来下载插件。接着通过resolveModule
方法动态创建require
语句来引入插件的generator
文件夹。若存在该路径,则直接调用invoke
方法执行插件的generator
逻辑。而invoke
正是vue invoke
命令调用的执行函数。
vue-cli插件相关文档
插件的 Generator 部分通常在你想要为项目扩展包依赖,创建新的文件或者编辑已经存在的文件时需要。
在 CLI 插件内部,generator 应该放在 generator.js 或者 generator/index.js 文件中。它将在以下两个场景被调用:
- 项目初始创建期间,CLI 插件被作为项目创建 preset 的一部分被安装时。
- 当插件在项目创建完成和通过 vue add 或者 vue invoke 单独调用被安装时。
具体更多请查看官网
vue invoke
module.exports = (...args) => {
return invoke(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
async function runGenerator (context, plugin, pkg = getPkg(context)) {
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
const afterInvokeCbs = []
const afterAnyInvokeCbs = []
const generator = new Generator(context, {
pkg,
plugins: [plugin],
files: await readFiles(context),
afterInvokeCbs,
afterAnyInvokeCbs,
invoking: true
})
log()
log(` Invoking generator for ${plugin.id}...`)
await generator.generate({
extractConfigFiles: true,
checkExisting: true
})
const newDeps = generator.pkg.dependencies
const newDevDeps = generator.pkg.devDependencies
const depsChanged =
JSON.stringify(newDeps) !== JSON.stringify(pkg.dependencies) ||
JSON.stringify(newDevDeps) !== JSON.stringify(pkg.devDependencies)
if (!isTestOrDebug && depsChanged) {
log(` Installing additional dependencies...`)
log()
const pm = new PackageManager({ context })
await pm.install()
}
if (afterInvokeCbs.length || afterAnyInvokeCbs.length) {
logWithSpinner('⚓', `Running completion hooks...`)
for (const cb of afterInvokeCbs) {
await cb()
}
for (const cb of afterAnyInvokeCbs) {
await cb()
}
stopSpinner()
log()
}
log(`${chalk.green('✔')} Successfully invoked generator for plugin: ${chalk.cyan(plugin.id)}`)
const changedFiles = getChangedFiles(context)
if (changedFiles.length) {
log(` The following files have been updated / added:\n`)
log(chalk.red(changedFiles.map(line => ` ${line}`).join('\n')))
log()
log(
` You should review these changes with ${chalk.cyan(
'git diff'
)} and commit them.`
)
log()
}
generator.printExitLogs()
}
本质上还是生成Generator
实例,并调用generator
来操作文件。