前言
vue-cli 3.x带来了ui控制台的体验,让不熟悉cli命令的开发者能够更快的上手,同时提供了强大的插件拓展机制,可以以项目纬度定制自己的ui命令。近期也准备为架构方案提供ui的可视化配置,对vue-cli中ui的设计逻辑进行了一定的了解,特此记录,欢迎大家进行斧正
整体架构
整体架构图搬运自vue-cl官网
node端用的apollo-graphql,前端毫无疑问是vue(- -)运行vue ui
可以启动本地server,server相关参数和服务端启动细节,不是本文讨论范围,就不作赘述了,启动代码详见:
github.com/vuejs/vue-c… github.com/Akryum/vue-…
plugin机制
上述架构中承担至关重要的部分就是plugin,就是因为它暴露的API允许了开发者可以通过增强项目的配置和任务,也可以分享数据和进程间的通信,下面将从一个进入项目开始解析,插件是如何加载及应用。
open project
源码目录:cli-ui/apollo-server/connectors/project.js
vue-cli ui是以项目为纬度进行管理的,在创建/导入项目并进行相应项目后,将获取项目相关的信息(path),并存储项目信息用作下次默认打开
// load plugins
...
// Save for next time
context.db.set('config.lastOpenProject', id).write()
复制代码
load plugins
源码目录:cli-ui/apollo-server/connectors/plugins.js
获取一个project的path信息后,将获取项目相关的plugins
// Load plugins
await plugins.list(project.path, context)
复制代码
ui插件主要从三个地方获取:
- 从项目package.json里获取插件信息
这里可以通过vuePlugins.resolveForm指定到其他目录
let pkgContext = cwd.get()
// Custom package.json location
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
pkgContext = path.resolve(cwd.get(), pkg.vuePlugins.resolveFrom)
pkg = folders.readPackage(pkgContext, context)
}
pkgStore.set(file, { pkgContext, pkg })
let plugins = []
plugins = plugins.concat(findPlugins(pkg.devDependencies || {}, file))
plugins = plugins.concat(findPlugins(pkg.dependencies || {}, file))
复制代码
findPlugins通过正则规则/^(@vue/|vue-|@[\w-]+/vue-)cli-plugin-/来过滤dependencies和devDependencies中的插件@vue/cli-service作为特殊插件优先加载
- 内置默认ui,位置在cli-ui/apollo-server/ui-default
- package信息中指定的自定义ui插件
// load custom ui pulgins
const { pkg, pkgContext } = pkgStore.get(file)
if (pkg.vuePlugins && pkg.vuePlugins.ui) {
const files = pkg.vuePlugins.ui
if (Array.isArray(files)) {
for (const file of files) {
runPluginApi(pkgContext, pluginApi, context, file)
}
}
}
复制代码
拿到plugins后,将开始逐个允许插件,这里会涉及到一个重要的api PluginApi
,它是整个插件机制运行的核心,提供hooks供插件添加配置、任务、视图等,源码位于cli-ui/apollo-server/api/PluginAPI.js
// run plugin api
function runPluginApi(id, pluginApi, context, filename = 'ui') {
...
try{
// 核心逻辑将pluginApi作为参数传给各个模块插件
module(pluginApi)
} catch(e) {
}
}
复制代码
通过运行三类插件,使得插件可以通过pluginApi进行拓展
add client addons
客户端addons通过pluginApi的addClientAddon添加
//插件中注册
module.exports = api => {
api.addClientAddon({
id: 'org.vue.webpack.client-addon',
path: '@vue/cli-ui-addon-webpack/dist'
})
}
复制代码
对应客户端则会加载/_addon/org.vue.webpack.client-addon/index.js生产环境下
,而完成这一逻辑的主要依赖,以下几部分的设置
// 服务端保存添加的addon
pluginApi.clientAddons.forEach(options => {
clientAddons.add(options, context) //保存addons信息 并监听变化响应接口
})
// 服务端设置,express插件cli-ui/cli-ui/apollo-server/server.js
module.exports = app => {
...
app.use('/_addon/:id/*', clientAddons.serve)
...
}
// 插件包打包设置,配置vue.config.js
module.exports = {
...clientAddonConfig({ // clientAddonConfig为默认开发设置,源码在cli-ui/index.js
id: 'org.vue.webpack.client-addon', //id作为文件加载标识
port: 8096 // port用作开发模式下服务的端口
})
}
复制代码
最终客户端通过接口加载对应的addon脚本文件,现在你可以在视图中使用addon
api.describeTask({
/* ... */
// 额外的视图 (例如 webpack dashboard)
// 默认情况下,这是展示终端输出的 'output' 视图
views: [
{
// 唯一的 ID
id: 'org.vue.webpack.views.dashboard',
// 按钮文字
label: 'Dashboard',
// 按钮图标 (material-icons)
icon: 'dashboard',
// 加载的动态组件,会用 ClientAddonApi 进行注册
component: 'org.vue.webpack.components.dashboard'
}
],
// 展示任务详情时默认选择的视图 (默认情况下就是 output)
defaultView: 'org.vue.webpack.views.dashboard'
})
复制代码
而这里的org.vue.webpack.components.dashboard其实就是对应我们加载的addon里面注册的vue组件
...
ClientAddonApi.component('org.vue.webpack.components.dashboard', WebpackDashboard) //ClientAddonApi通过注入到windows作为全局变量使用,用于组件注册和加载
...
复制代码
add views
// Add views
for (const view of pluginApi.views) {
await views.add({ view, project }, context)
}
复制代码
加载自定义视图,这里是指通过api.addView来添加的视图,具体方式可以参见:cli.vuejs.org/zh/dev-guid…
这里需要注意addView里面的id和name均需要通过addClientAddon注册过
register widget
// Register widgets
for (const definition of pluginApi.widgetDefs) {
await widgets.registerDefinition({ definition, project }, context)
}
复制代码
注册widget,可以为项目dashboard页面添加自定义的ui插件,通过registerWidget来进行注册
module.exports = api => {
const { registerWidget, onAction, setSharedData } = api.namespace('org.vue.widgets.')
registerWidget({
id: 'welcome',
title: 'org.vue.widgets.welcome.title',
description: 'org.vue.widgets.welcome.description',
icon: 'mood',
component: 'org.vue.widgets.components.welcome', //这里的component也必须是已经在addon里面注册过的组件
minWidth: 3,
minHeight: 4,
maxWidth: 3,
maxHeight: 4,
maxCount: 1
})
}
复制代码
总结
通过各种视图、wigdet的初始化定义,在客户端请求信息接口后,根据获取到的信息渲染对应的内容。除了插件机制外,cli-ui还有许多地方值得借鉴,比如通过node-ipc实现进程间通信、通过ShareData的设计实现不同server数据和客户端数据的实时同步,后续有机会将会继续分享
最后献上官方cli-ui插件开发链接:cli.vuejs.org/zh/dev-guid…