本文主要学习vue-cli3.0的源码的记录。源码地址: https://github.com/vuejs/vue-cli 主要对packages里面的@vue进行学习。以下图
在图中咱们能够看到vue-cli中,不只仅有初始化工程,还有许多通用的工具、插件。接下来咱们就对这些插件进行学习。css
首先咱们来看cli的目录:
首先来看package.jsonhtml
{ "name": "@vue/cli", // 名称 "version": "3.5.5", // 版本号 "bin": { "vue": "bin/vue.js" }, // 这个是用于命令窗口执行的命令;若是是全局安装了,那么vue就是一个命令值 vue xxxx "engines": { "node": ">=8.9" } // 须要的node版本号 }
咱们如今咱们能够去看bin/vue.js文件,对该文件给出注释,方便阅读vue
#!/usr/bin/env node // 这边备注是node来解析, 固然若是不写也没事 // Check node version before requiring/doing anything else // The user may be on a very old node version const chalk = require('chalk') // 用于输出有色彩 const semver = require('semver') // 用于比较版本号 const requiredVersion = require('../package.json').engines.node // 获取node版本号要求 // 检测node的版本号,若是不符合要求就给提示 function checkNodeVersion (wanted, id) { if (!semver.satisfies(process.version, wanted)) { // process.version表示当前node版本 console.log(chalk.red( 'You are using Node ' + process.version + ', but this version of ' + id + ' requires Node ' + wanted + '.\nPlease upgrade your Node version.' )) // 给出当前vue-cli须要的版本为多少 process.exit(1) } } checkNodeVersion(requiredVersion, 'vue-cli') if (semver.satisfies(process.version, '9.x')) { console.log(chalk.red( `You are using Node ${process.version}.\n` + `Node.js 9.x has already reached end-of-life and will not be supported in future major releases.\n` + `It's strongly recommended to use an active LTS version instead.` )) } // 若是node为9.x那么给出相应的提示 const fs = require('fs') // 文件 const path = require('path') // 路径 const slash = require('slash') // 用于转换 Windows 反斜杠路径转换为正斜杠路径 \ => / const minimist = require('minimist') // 用来解析从参数 // enter debug mode when creating test repo if ( slash(process.cwd()).indexOf('/packages/test') > 0 && ( // process.cwd()为当前绝对路径,如F:\packages\@vue\cli\bin fs.existsSync(path.resolve(process.cwd(), '../@vue')) || fs.existsSync(path.resolve(process.cwd(), '../../@vue')) ) ) { process.env.VUE_CLI_DEBUG = true } const program = require('commander') // node对话,输入 const loadCommand = require('../lib/util/loadCommand') // 用于查找模块 program .version(require('../package').version) .usage('<command> [options]')
上述是一些检测的代码。
以后就要开始交互式的命令了。node
咱们能够看到program.command就是建立的一个命令,后面会有不少的命令create,add,invoke等等,这一节主要来说解creategit
program .command('create <app-name>') .description('create a new project powered by vue-cli-service') .option('-p, --preset <presetName>', 'Skip prompts and use saved or remote preset') .option('-d, --default', 'Skip prompts and use default preset') .option('-i, --inlinePreset <json>', 'Skip prompts and use inline JSON string as preset') .option('-m, --packageManager <command>', 'Use specified npm client when installing dependencies') .option('-r, --registry <url>', 'Use specified npm registry when installing dependencies (only for npm)') .option('-g, --git [message]', 'Force git initialization with initial commit message') .option('-n, --no-git', 'Skip git initialization') .option('-f, --force', 'Overwrite target directory if it exists') .option('-c, --clone', 'Use git clone when fetching remote preset') .option('-x, --proxy', 'Use specified proxy when creating project') .option('-b, --bare', 'Scaffold project without beginner instructions') .option('--skipGetStarted', 'Skip displaying "Get started" instructions') .action((name, cmd) => { const options = cleanArgs(cmd) if (minimist(process.argv.slice(3))._.length > 1) { console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.')) } // --git makes commander to default git to true if (process.argv.includes('-g') || process.argv.includes('--git')) { options.forceGit = true } require('../lib/create')(name, options) })
这边建立了一个command('create <app-name>'),若是是全局安装了@vue-cli3.0,那么就可使用github
vue create xxxx yyy
xxx yyy为文件名称和option这些参数配置项。
咱们来看一下分别有哪些配置项:vuex
-p, --preset <presetName> 忽略提示符并使用已保存的或远程的预设选项 -d, --default 忽略提示符并使用默认预设选项 -i, --inlinePreset <json> 忽略提示符并使用内联的 JSON 字符串预设选项 -m, --packageManager <command> 在安装依赖时使用指定的 npm 客户端 -r, --registry <url> 在安装依赖时使用指定的 npm registry -g, --git [message] 强制 / 跳过 git 初始化,并可选的指定初始化提交信息 -n, --no-git 跳过 git 初始化 -f, --force 覆写目标目录可能存在的配置 -c, --clone 使用 git clone 获取远程预设选项 -x, --proxy 使用指定的代理建立项目 -b, --bare 建立项目时省略默认组件中的新手指导信息 -h, --help 输出使用帮助信息
在action中就是进入命令后的执行代码,如下部分主要就是提取-g命令,vue-cli
const options = cleanArgs(cmd) if (minimist(process.argv.slice(3))._.length > 1) { console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.')) } // --git makes commander to default git to true if (process.argv.includes('-g') || process.argv.includes('--git')) { options.forceGit = true }
接下来就是进入create.js文件typescript
async function create (projectName, options) { // 代理使用 -x 或--proxy参数配置 if (options.proxy) { process.env.HTTP_PROXY = options.proxy } const cwd = options.cwd || process.cwd() // 当前目录 const inCurrent = projectName === '.' // 是否存在当前目录 const name = inCurrent ? path.relative('../', cwd) : projectName // 项目名称 const targetDir = path.resolve(cwd, projectName || '.') // 生成项目目录 const result = validateProjectName(name) // 验证名称是否符合规范 if (!result.validForNewPackages) { console.error(chalk.red(`Invalid project name: "${name}"`)) result.errors && result.errors.forEach(err => { console.error(chalk.red.dim('Error: ' + err)) }) result.warnings && result.warnings.forEach(warn => { console.error(chalk.red.dim('Warning: ' + warn)) }) exit(1) } // 检测文件是否存在, if (fs.existsSync(targetDir)) { if (options.force) { await fs.remove(targetDir) // 这边强制覆盖 } else { await clearConsole() if (inCurrent) { // 这边提示是否在当前文件建立? const { ok } = await inquirer.prompt([ { name: 'ok', type: 'confirm', message: `Generate project in current directory?` } ]) if (!ok) { return } } else { // 文件已重复 const { action } = await inquirer.prompt([ { name: 'action', type: 'list', message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`, choices: [ { name: 'Overwrite', value: 'overwrite' }, { name: 'Merge', value: 'merge' }, { name: 'Cancel', value: false } ] } ]) if (!action) { return } else if (action === 'overwrite') { console.log(`\nRemoving ${chalk.cyan(targetDir)}...`) await fs.remove(targetDir) } } } } // 新建构造器 const creator = new Creator(name, targetDir, getPromptModules()) // getPromptModules()为内置插件对话对象 await creator.create(options) }
以上大部分都是定义文件,目录和一名称效验,文件效验,比较简单易懂,接下来就是建立Creator构造器了npm
这一节的内容会比较绕一点。
首先咱们先来了解一下vue-cli-preset,这是一个包含建立新项目所需预约义选项和插件的 JSON 对象,让用户无需在命令提示中选择它们。
vue create 过程当中保存的 preset 会被放在你的用户目录下的一个配置文件中 (~/.vuerc)。你能够经过直接编辑这个文件来调整、添加、删除保存好的 preset。这里有一个 preset 的示例:
{ "useConfigFiles": true, "router": true, "vuex": true, "cssPreprocessor": "sass", "plugins": { "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-eslint": { "config": "airbnb", "lintOn": ["save", "commit"] } } }
Preset 的数据会被插件生成器用来生成相应的项目文件。除了上述这些字段,你也能够为集成工具添加配置:
{ "useConfigFiles": true, "plugins": {...}, "configs": { "vue": {...}, "postcss": {...}, "eslintConfig": {...}, "jest": {...} } }
这些额外的配置将会根据 useConfigFiles 的值被合并到 package.json 或相应的配置文件中。例如,当 "useConfigFiles": true 的时候,configs 的值将会被合并到 vue.config.js 中。
更多关于 preset 能够前往 vue-cli 官网 插件和 Preset https://cli.vuejs.org/zh/guid...。
在基础验证完后会建立一个Creator实例
const creator = new Creator(name, targetDir, getPromptModules())
在分析Creator以前,咱们先来看一下getPromptModules是什么。getPromptModules源码
exports.getPromptModules = () => { return [ 'babel', 'typescript', 'pwa', 'router', 'vuex', 'cssPreprocessors', 'linter', 'unit', 'e2e' ].map(file => require(`../promptModules/${file}`)) }
咱们能够在promptModules中分别看到
其中好比unit.js:
module.exports = cli => { cli.injectFeature({ name: 'Unit Testing', value: 'unit', short: 'Unit', description: 'Add a Unit Testing solution like Jest or Mocha', link: 'https://cli.vuejs.org/config/#unit-testing', plugins: ['unit-jest', 'unit-mocha'] }) cli.injectPrompt({ name: 'unit', when: answers => answers.features.includes('unit'), type: 'list', message: 'Pick a unit testing solution:', choices: [ { name: 'Mocha + Chai', value: 'mocha', short: 'Mocha' }, { name: 'Jest', value: 'jest', short: 'Jest' } ] }) cli.onPromptComplete((answers, options) => { if (answers.unit === 'mocha') { options.plugins['@vue/cli-plugin-unit-mocha'] = {} } else if (answers.unit === 'jest') { options.plugins['@vue/cli-plugin-unit-jest'] = {} } }) }
咱们能够看到这部其实就是对一些内置插件的一些配置项,用于对话后来进行安装,
cli.injectFeature:是用来注入featurePrompt,即初始化项目时,选择的babel、typescript等 cli.injectPrompt:是根据选择的 featurePrompt 而后注入对应的 prompt,当选择了 unit,接下来会有如下的 prompt,选择 Mocha + Chai 仍是 Jest cli.onPromptComplete: 就是一个回调,会根据选择来添加对应的插件, 当选择了 mocha ,那么就会添加 @vue/cli-plugin-unit-mocha 插件
接下来咱们来看一下其构造函数
constructor (name, context, promptModules) { super() this.name = name // 目录名称 this.context = process.env.VUE_CLI_CONTEXT = context // 当前目录 const { presetPrompt, featurePrompt } = this.resolveIntroPrompts() // 以前预制的插件,和项目的一些feature this.presetPrompt = presetPrompt this.featurePrompt = featurePrompt this.outroPrompts = this.resolveOutroPrompts() // 其余的插件 this.injectedPrompts = [] // 当选择featurePrompt时,注入的prompts this.promptCompleteCbs = [] this.createCompleteCbs = [] this.run = this.run.bind(this) const promptAPI = new PromptModuleAPI(this) promptModules.forEach(m => m(promptAPI)) }
上述代码咱们主要来看一下PromptModuleAPI,其余都是一些变量初始化的定义
module.exports = class PromptModuleAPI { constructor (creator) { this.creator = creator } injectFeature (feature) { this.creator.featurePrompt.choices.push(feature) } injectPrompt (prompt) { this.creator.injectedPrompts.push(prompt) } injectOptionForPrompt (name, option) { this.creator.injectedPrompts.find(f => { return f.name === name }).choices.push(option) } onPromptComplete (cb) { this.creator.promptCompleteCbs.push(cb) } }
这边是建立一个PromptModuleAPI实例,并经过promptModules.forEach(m => m(promptAPI)),将预设的内置插件加入到
this.creator.featurePrompt,this.creator.injectedPrompts和this.creator.promptCompleteCbs中
在建立了Creator实例后,而后调用了create方法
await creator.create(options)
create方法源码,这段代码比较简单,主要是判断是否有-p,-d,-i的配置项来直接安装,若是没有的话,就进入对话模式this.promptAndResolvePreset,来选择性的安装
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG const { run, name, context, createCompleteCbs } = this if (!preset) { // 是否存在 -p 或--preset if (cliOptions.preset) { // vue create foo --preset bar preset = await this.resolvePreset(cliOptions.preset, cliOptions.clone) } else if (cliOptions.default) { // 是否有-d或--default的命令,若是有则,默认直接安装 // vue create foo --default preset = defaults.presets.default } else if (cliOptions.inlinePreset) { // 是否有--inlinePreset或-i来注入插件 // vue create foo --inlinePreset {...} try { preset = JSON.parse(cliOptions.inlinePreset) } catch (e) { error(`CLI inline preset is not valid JSON: ${cliOptions.inlinePreset}`) exit(1) } } else { preset = await this.promptAndResolvePreset() } }
先判断 vue create 命令是否带有 -p 选项,若是有的话会调用 resolvePreset 去解析 preset。resolvePreset 函数会先获取 ~/.vuerc 中保存的 preset, 而后进行遍历,若是里面包含了 -p 中的 <presetName>,则返回~/.vuerc 中的 preset。若是没有则判断是不是采用内联的 JSON 字符串预设选项,若是是就会解析 .json 文件,并返回 preset,还有一种状况就是从远程获取 preset(利用 download-git-repo 下载远程的 preset.json)并返回。
上面的状况是当 vue create 命令带有 -p 选项的时候才会执行,若是没有就会调用 promptAndResolvePreset 函数利用 inquirer.prompt 以命令后交互的形式来获取 preset,下面看下 promptAndResolvePreset 函数的源码:
async promptAndResolvePreset (answers = null) { // prompt if (!answers) { await clearConsole(true) answers = await inquirer.prompt(this.resolveFinalPrompts()) // 交互式命令对话,安装defalut和Manually select features } debug('vue-cli:answers')(answers) if (answers.packageManager) { saveOptions({ packageManager: answers.packageManager }) } let preset if (answers.preset && answers.preset !== '__manual__') { // 若是是选择本地保存的preset(.vuerc) preset = await this.resolvePreset(answers.preset) } else { // manual preset = { useConfigFiles: answers.useConfigFiles === 'files', plugins: {} } answers.features = answers.features || [] // run cb registered by prompt modules to finalize the preset this.promptCompleteCbs.forEach(cb => cb(answers, preset)) } // validate validatePreset(preset) // save preset if (answers.save && answers.saveName) { savePreset(answers.saveName, preset) } debug('vue-cli:preset')(preset) return preset }
看到这里会比较乱的,preset会比较多,咱们再来看一下resolveFinalPrompts源码
resolveFinalPrompts () { // patch generator-injected prompts to only show in manual mode this.injectedPrompts.forEach(prompt => { const originalWhen = prompt.when || (() => true) prompt.when = answers => { return isManualMode(answers) && originalWhen(answers) } }) const prompts = [ this.presetPrompt, this.featurePrompt, ...this.injectedPrompts, ...this.outroPrompts ] debug('vue-cli:prompts')(prompts) console.log(1, prompts) return prompts }
这里咱们能够看到presetPrompt, featurePrompt, injectedPrompts, outroPrompts 合并成一个数组进行返回,
presetPrompt是预设的,当上一次选择manually模式进行了预设,并保存到.vuerc中,那么初始化的时候会列出已保存的插件
featurePrompt就是内置的一些插件
injectedPrompts是经过-i命令来手动注入的插件
outroPrompts是一些其余的插件。
这边对话完以后,就要开始依赖的安装了。
咱们继把create中的代码往下走
const packageManager = ( cliOptions.packageManager || loadOptions().packageManager || (hasYarn() ? 'yarn' : 'npm') ) await clearConsole() // 清空控制台 logWithSpinner(`✨`, `Creating project in ${chalk.yellow(context)}.`) this.emit('creation', { event: 'creating' }) // get latest CLI version const { latest } = await getVersions() const latestMinor = `${semver.major(latest)}.${semver.minor(latest)}.0` // generate package.json with plugin dependencies const pkg = { name, version: '0.1.0', private: true, devDependencies: {} } const deps = Object.keys(preset.plugins) deps.forEach(dep => { if (preset.plugins[dep]._isPreset) { return } // Note: the default creator includes no more than `@vue/cli-*` & `@vue/babel-preset-env`, // so it is fine to only test `@vue` prefix. // Other `@vue/*` packages' version may not be in sync with the cli itself. pkg.devDependencies[dep] = ( preset.plugins[dep].version || ((/^@vue/.test(dep)) ? `^${latestMinor}` : `latest`) ) }) // write package.json await writeFileTree(context, { 'package.json': JSON.stringify(pkg, null, 2) })
这边主要就是获取cli的版本和生产package.json,其中主要是获取版本号
module.exports = async function getVersions () { if (sessionCached) { return sessionCached } let latest const local = require(`../../package.json`).version if (process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG) { return (sessionCached = { current: local, latest: local }) } const { latestVersion = local, lastChecked = 0 } = loadOptions() const cached = latestVersion const daysPassed = (Date.now() - lastChecked) / (60 * 60 * 1000 * 24) if (daysPassed > 1) { // 距离上次检查更新超过一天 // if we haven't check for a new version in a day, wait for the check // before proceeding latest = await getAndCacheLatestVersion(cached) } else { // Otherwise, do a check in the background. If the result was updated, // it will be used for the next 24 hours. getAndCacheLatestVersion(cached) // 后台更新 latest = cached } return (sessionCached = { current: local, latest }) } // fetch the latest version and save it on disk // so that it is available immediately next time async function getAndCacheLatestVersion (cached) { const getPackageVersion = require('./getPackageVersion') const res = await getPackageVersion('vue-cli-version-marker', 'latest') if (res.statusCode === 200) { const { version } = res.body if (semver.valid(version) && version !== cached) { saveOptions({ latestVersion: version, lastChecked: Date.now() }) return version } } return cached }
这边主要是有2个版本变量,一个是local本地cli版本。另外一个laset远程cli版本
另外getAndCacheLatestVersion而是经过 vue-cli-version-marker npm 包获取的 CLI 版本。
生产package.json以后,咱们在继续看后面代码
const shouldInitGit = this.shouldInitGit(cliOptions) if (shouldInitGit) { logWithSpinner(`🗃`, `Initializing git repository...`) this.emit('creation', { event: 'git-init' }) await run('git init') } // install plugins stopSpinner() log(`⚙ Installing CLI plugins. This might take a while...`) log() this.emit('creation', { event: 'plugins-install' }) if (isTestOrDebug) { // in development, avoid installation process await require('./util/setupDevProject')(context) } else { await installDeps(context, packageManager, cliOptions.registry) }
这段代码首先会调用shouldInitGit来判断是否须要git初始化,判断的情景是:
是否安装了git;命令中是否有-g或--git,或--no-git或-n;生成的目录是否包含了git
判断完以后须要git初始化项目后,接下来就会调用installDeps来安装依赖
exports.installDeps = async function installDeps (targetDir, command, cliRegistry) { const args = [] if (command === 'npm') { args.push('install', '--loglevel', 'error') } else if (command === 'yarn') { // do nothing } else { throw new Error(`Unknown package manager: ${command}`) } await addRegistryToArgs(command, args, cliRegistry) debug(`command: `, command) // DEBUG=vue-cli:install vue create demo debug(`args: `, args) await executeCommand(command, args, targetDir) }
在下载完依赖以后就会resolvePlugins,其做用就是加载每一个插件的generator,而且若是插件须要进行命令式交互的话,会执行inquirer.prompt获取option。
async resolvePlugins (rawPlugins) { // ensure cli-service is invoked first rawPlugins = sortObject(rawPlugins, ['@vue/cli-service'], true) const plugins = [] for (const id of Object.keys(rawPlugins)) { const apply = loadModule(`${id}/generator`, this.context) || (() => {}) let options = rawPlugins[id] || {} if (options.prompts) { const prompts = loadModule(`${id}/prompts`, this.context) if (prompts) { log() log(`${chalk.cyan(options._isPreset ? `Preset options:` : id)}`) options = await inquirer.prompt(prompts) } } plugins.push({ id, apply, options }) } return plugins }
根据vue-cli插件规范,差价内部是须要配置generator, prompts选填,index.js必填。具体查看官网插件的开发文档。
因此resolvePlugins这边就是对这些插件进行解析。若是不是按照官网的规范,那么这边就不能解析正确了。这一步中全部的交互式对话都会完成。
以后就开始实例化Generator,把解析的插件传入
const generator = new Generator(context, { pkg, plugins, completeCbs: createCompleteCbs })
在来看一下其构造函数:
constructor (context, { pkg = {}, plugins = [], completeCbs = [], files = {}, invoking = false } = {}) { this.context = context this.plugins = plugins this.originalPkg = pkg this.pkg = Object.assign({}, pkg) this.imports = {} this.rootOptions = {} this.completeCbs = completeCbs this.configTransforms = {} // 插件经过 GeneratorAPI 暴露的 addConfigTransform 方法添加如何提取配置文件 this.defaultConfigTransforms = defaultConfigTransforms // 默认的配置文件 this.reservedConfigTransforms = reservedConfigTransforms // 保留的配置文件 this.invoking = invoking // for conflict resolution this.depSources = {} // virtual file tree this.files = files this.fileMiddlewares = [] this.postProcessFilesCbs = [] // exit messages this.exitLogs = [] const cliService = plugins.find(p => p.id === '@vue/cli-service') const rootOptions = cliService ? cliService.options : inferRootOptions(pkg) // apply generators from plugins plugins.forEach(({ id, apply, options }) => { // 每一个插件对应生成一个 GeneratorAPI 实例,并将实例 api 传入插件暴露出来的 generator 函数 const api = new GeneratorAPI(id, this, options, rootOptions) apply(api, options, rootOptions, invoking) }) }
这边主要就是声明一些变量,并建立每一个插件的GeneratorAPI 。咱们来看一下GeneratorAPI,GeneratorAPI 是比较重要的模块,若是插件须要自定义项目模板、修改模块该怎么处理?都是这个GeneratorAPI来实现的。@vue/cli插件所提供的generator向外暴露一个函数,接收第一个参数api,而后经过该api提供的一些方法来完成应用的拓展工做。咱们来看一下具体提供了哪些方法:
hasPlugin: 判断项目中是否有某个插件 extendPackage: 拓展package.json配置 render: 利用ejs渲染模板文件 onCreateComplete: 内存中保留的文件字符串所有被写入文件后的回调函数 exitLog: 当generator退出时候的信息 genJsConfig: 将json文件生成为js配置文件 injectImports: 向文件当中注入import语法方法 injectRootoptions: 向vue实例中添加选项 ....等等 看看官网文档
GeneratorAPI方法能够具体在根据须要在详细看看。
const api = new GeneratorAPI(id, this, options, rootOptions) apply(api, options, rootOptions, invoking)
这一段代码就是运行了各个插件内部的generator方法。
再回首看看resolvePlugins 就明白了
resolvePlugins () { .... const apply = loadModule(`${id}/generator`, this.context) || (() => {}) ... }
在Generator实例化时候,还能够分红3步走:extractConfigFiles, resolveFiles和writeFileTree
async generate ({ extractConfigFiles = false, checkExisting = false } = {}) { // save the file system before applying plugin for comparison const initialFiles = Object.assign({}, this.files) // extract configs from package.json into dedicated files. this.extractConfigFiles(extractConfigFiles, checkExisting) //提取配置文件 // wait for file resolve await this.resolveFiles() // 模板渲染 // set package.json this.sortPkg() this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n' // write/update file tree to disk await writeFileTree(this.context, this.files, initialFiles) // 在磁盘上生成文件 }
extractConfigFiles
提取配置文件指的是将一些插件(好比 eslint,babel)的配置从 package.json 的字段中提取到专属的配置文件中。
resolveFiles主要分为如下三个部分执行
fileMiddlewares
injectImportsAndOptions
postProcessFilesCbs
fileMiddlewares 里面包含了 ejs render 函数,全部插件调用 api.render 时候只是把对应的渲染函数 push 到了 fileMiddlewares 中,等全部的 插件执行完之后才会遍历执行 fileMiddlewares 里面的全部函数,即在内存中生成模板文件字符串。
injectImportsAndOptions 就是将 generator 注入的 import 和 rootOption 解析到对应的文件中,好比选择了 vuex, 会在 src/main.js 中添加 import store from './store',以及在 vue 根实例中添加 router 选项。
postProcessFilesCbs 是在全部普通文件在内存中渲染成字符串完成以后要执行的遍历回调。例如将 @vue/cli-service/generator/index.js 中的 render 是放在了 fileMiddlewares 里面,而将 @vue/cli-service/generator/router/index.js 中将替换 src/App.vue 文件的方法放在了 postProcessFiles 里面,缘由是对 src/App.vue 文件的一些替换必定是发生在 render 函数以后,若是在以前,修改后的 src/App.vue 在以后 render 函数执行时又会被覆盖,这样显然不合理。
writeFileTree在提取了配置文件和模板渲染以后调用了 sortPkg 对 package.json 的字段进行了排序并将 package.json 转化为 json 字符串添加到项目的 files 中。 此时整个项目的文件已经在内存中生成好了(在源码中就是对应的 this.files),接下来就调用 writeFileTree 方法将内存中的字符串模板文件生成在磁盘中。