vue-cli3.0原始碼分析@vue/cli-----add和invoke
上一篇已經講了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(`:package: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步驟,前面的載入省略了。