cli@3.0 插件系统简析

Vue-cli@3.0 是一个全新的 Vue 项目脚手架。不同于 1.x/2.x 基于模板的脚手架,Vue-cli@3.0 采用了一套基于插件的架构,它将部分核心功能收敛至 CLI 内部,同时对开发者暴露可拓展的 API 以供开发者对 CLI 的功能进行灵活的拓展和配置。接下来我们就通过 Vue-cli@3.0 的源码来看下这套插件架构是如何设计的。

整个插件系统当中包含2个重要的组成部分:

@vue/cli,提供 cli 命令服务,例如vue create创建一个新的项目;

@vue/cli-service,提供了本地开发构建服务。

@vue/cli-service

当你使用 vue create <project-name> 创建一个新的 Vue 项目,你会发现生成的项目相较于 1.x/2.x 初始化一个项目时从远程拉取的模板发生了很大的变化,其中关于 webpack 相关的配置以及 npm script 都没有在模板里面直接暴露出来,而是提供了新的 npm script:

// package.json "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }

前 2 个脚本命令是项目本地安装的 @vue/cli-service 所提供的基于 webpack 及相关的插件进行封装的本地开发/构建的服务。@vue/cli-service 将 webpack 及相关插件提供的功能都收敛到 @vue/cli-service 内部来实现。

这 2 个命令对应于 node_modules/@vue/cli-service/lib/commands 下的 serve.js 和 build/index.js。

在 serve.js 和 build/index.js 的内部分别暴露了一个函数及一个 defaultModes 属性供外部来使用。 事实上这两者都是作为 built-in(内置)插件来供 vue-cli-service 来使用的 。

说到这里那么就来看看 @vue/cli-service 内部是如何搭建整个插件系统的。就拿执行 npm run serve 启动本地开发服务来说,大概流程是这样的:

cli@3.0 插件系统简析


首先来看下 @vue/cli-service 提供的 cli 启动入口服务(@vue/cli-service/bin/vue-cli-service.js):

#!/usr/bin/env node const semver = require('semver') const { error } = require('@vue/cli-shared-utils') const Service = require('../lib/Service') // 引入 Service 基类 const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd()) // 实例化 service const rawArgv = process.argv.slice(2) const args = require('minimist')(rawArgv) const command = args._[0] service.run(command, args, rawArgv).catch(err => { // 开始执行对应的 service 服务 error(err) process.exit(1) })

看到这里你会发现在 bin 里面并未提供和本地开发 serve 相关的服务,事实上在项目当中本地安装的 @vue/cli-service 提供的不管是内置的还是插件提供的服务都是动态的去完成相关 CLI 服务的注册。

在 lib/Service.js 内部定义了一个核心的类 Service,它作为 @vue/cli 的运行时的服务而存在。在执行 npm run serve 后,首先完成 Service 的实例化工作:

class Service { constructor(context) { ... this.webpackChainFns = [] // 数组内部每项为一个fn this.webpackRawConfigFns = [] // 数组内部每项为一个 fn 或 webpack 对象字面量配置项 this.devServerConfigFns = [] this.commands = {} // 缓存动态注册 CLI 命令 ... this.plugins = this.resolvePlugins(plugins, useBuiltIn) // 完成插件的加载 this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { // 缓存不同 CLI 命令执行时所对应的mode值 return Object.assign(modes, defaultModes) }, {}) } }

在实例化 Service 的过程当中完成了两个比较重要的工作:

加载插件

将插件提供的不同命令服务所使用的 mode 进行缓存

当 Service 实例化完成后,调用实例上的 run 方法来启动对应的 CLI 命令所提供的服务。

async run (name, args = {}, rawArgv = []) { const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) // load env variables, load user config, apply plugins // 执行所有被加载进来的插件 this.init(mode) ... const { fn } = command return fn(args, rawArgv) // 开始执行对应的 cli 命令服务 } init (mode = process.env.VUE_CLI_MODE) { ... // 执行plugins // apply plugins. this.plugins.forEach(({ id, apply }) => { // 传入一个实例化的PluginAPI实例,插件名作为插件的id标识,在插件内部完成注册 cli 命令服务和 webpack 配置的更新的工作 apply(new PluginAPI(id, this), this.projectOptions) }) ... // apply webpack configs from project config file if (this.projectOptions.chainWebpack) { this.webpackChainFns.push(this.projectOptions.chainWebpack) } if (this.projectOptions.configureWebpack) { this.webpackRawConfigFns.push(this.projectOptions.configureWebpack) } }

接下来我们先看下 @vue/cli-service 当中的 Service 实例化的过程:通过 resolvePlugins 方法去完成插件的加载工作:

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

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