cli@3.0 插件系统简析(4)

// @vue/cli/lib/invoke.js async function invoke (pluginName, options = {}, context = process.cwd()) { const pkg = getPkg(context) ... // 从项目应用package.json中获取插件名 const id = findPlugin(pkg.devDependencies) || findPlugin(pkg.dependencies) ... // 加载对应插件提供的generator方法 const pluginGenerator = loadModule(`${id}/generator`, context) ... const plugin = { id, apply: pluginGenerator, options } // 开始执行generator方法 await runGenerator(context, plugin, pkg) } async function runGenerator (context, plugin, pkg = getPkg(context)) { ... // 实例化一个Generator实例 const generator = new Generator(context, { pkg plugins: [plugin], // 插件提供的generator方法 files: await readFiles(context), // 将项目当中的文件读取为字符串的形式保存到内存当中,被读取的文件规则具体见readFiles方法 completeCbs: createCompleteCbs, invoking: true }) ... // resolveFiles 将内存当中的所有缓存的 files 输出到文件当中 await generator.generate({ extractConfigFiles: true, checkExisting: true }) }

和 @vue/cli-service 类似,在 @vue/cli 内部也有一个核心的类 Generator ,每个 @vue/cli 的插件对应一个 Generator 的实例。在实例化 Generator 方法的过程当中,完成插件提供的 generator 的执行。

// @vue/cli/lib/Generator.js module.exports = class Generator { 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.invoking = invoking // for conflict resolution this.depSources = {} // virtual file tree this.files = files this.fileMiddlewares = [] this.postProcessFilesCbs = [] ... const cliService = plugins.find(p => p.id === '@vue/cli-service') const rootOptions = cliService ? cliService.options : inferRootOptions(pkg) // apply generators from plugins // 每个插件对应生成一个 GeneratorAPI 实例,并将实例 api 传入插件暴露出来的 generator 函数 plugins.forEach(({ id, apply, options }) => { const api = new GeneratorAPI(id, this, options, rootOptions) apply(api, options, rootOptions, invoking) }) } }

和 @vue/cli-service 所使用的插件类似,@vue/cli 插件所提供的 generator 也是向外暴露一个函数,接收的第一个参数 api,然后通过该 api 提供的方法去完成应用的拓展工作。

开发者利用这个 api 实例去完成项目应用的拓展工作,这个 api 实例提供了:

拓展 package.json 配置方法( api.extendPackage )
利用 ejs 渲染模板文件的方法( api.render )
内存中保存的文件字符串全部被写入文件后的回调函数( api.onCreateComplete )
向文件当中注入 import 语法的方法( api.injectImports )
...

例如 @vue/cli-plugin-eslint 插件的 generator 方法主要是完成了:vue-cli-service cli lint 服务命令的添加、相关 lint 标准库的依赖添加等工作:

module.exports = (api, { config, lintOn = [] }, _, invoking) => { if (typeof lintOn === 'string') { lintOn = lintOn.split(',') } const eslintConfig = require('./eslintOptions').config(api) const pkg = { scripts: { lint: 'vue-cli-service lint' }, eslintConfig, devDependencies: {} } if (config === 'airbnb') { eslintConfig.extends.push('@vue/airbnb') Object.assign(pkg.devDependencies, { '@vue/eslint-config-airbnb': '^3.0.0-rc.10' }) } else if (config === 'standard') { eslintConfig.extends.push('@vue/standard') Object.assign(pkg.devDependencies, { '@vue/eslint-config-standard': '^3.0.0-rc.10' }) } else if (config === 'prettier') { eslintConfig.extends.push('@vue/prettier') Object.assign(pkg.devDependencies, { '@vue/eslint-config-prettier': '^3.0.0-rc.10' }) } else { // default eslintConfig.extends.push('eslint:recommended') } ... api.extendPackage(pkg) ... // lint & fix after create to ensure files adhere to chosen config if (config && config !== 'base') { api.onCreateComplete(() => { require('./lint')({ silent: true }, api) }) } }

以上介绍了 @vue/cli 和插件系统相关的几个核心的模块,即:

add.js 提供了插件下载的 cli 命令服务和安装的功能;

invoke.js 完成插件所提供的 generator 方法的加载和执行,同时将项目当中的文件转化为字符串缓存到内存当中;

Generator.js 和插件进行桥接,@vue/cli 每次 add 一个插件时,都会实例化一个 Generator 实例与之对应;

GeneratorAPI.js 和插件一一对应,是 @vue/cli 暴露给插件的 api 对象,提供了很多项目应用的拓展工作。

总结

以上是对 Vue-cli@3.0 的插件系统当中两个主要部分:@vue/cli 和 @vue/cli-service 简析。

@vue/cli 提供 vue cli 命令,负责偏好设置,生成模板、安装插件依赖的工作,例如 vue create <projectName> 、 vue add <pluginName>

@vue/cli-service 作为 @vue/cli 整个插件系统当中的内部核心插件,提供了 webpack 配置更新,本地开发构建服务

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/a0f22b73d631940d731e2859e4e9925f.html