插件对象

PluginManager对象

PluginManager是整个San CLI UI插件系统的基础,该对象的方法按照功能可分为四类。

上文提到,在San CLI UI加载依赖时,会尝试读取依赖包内的ui.js文件,并将PluginManager对象的实例api注入其中,因此以下插件的使用均基于api来调用。

1. 插件加载及定义

插件加载

通过api.registerAddon函数,开发者可以为自定义的组件指定id及加载路径(在npm包内的ui.js中),San CLI UI在插件加载时,会尝试从开发者指定的路径下加载插件定义,从而集成到San CLI UI对应位置。

API 说明

registerAddon

参数说明类型默认值
id插件唯一idstring
url可选,开发模式下加载的urlstring
pathnpm包的路径string

API使用方式如下:

if (process.env.SAN_CLI_UI_DEV) { // 在开发模式下加载自定义端口文件
    api.registerAddon({
        id: 'san.widgets.client-addon.dev',
        url: 'http://localhost:8889/index.js'
    });
}
else {
    api.registerAddon({ // 在生产模式下加载npm包的路径
        id: 'san.widgets.client-addon',
        path: 'san-cli-ui-addon-widgets/dist'
    });
}

api.registerAddon仅实现了插件包的加载,而加载的插件显示在何处?插件的显示项以及数据操作逻辑则需要单独调用每个插件的api进行描述。

San CLI UI中可以注册的插件类型包括:widget插件、配置插件、任务插件、自定义视图插件。

widget插件

widget(部件)插件,指显示在「项目仪表盘」内的小部件,San CLI UI默认部件有:欢迎提示、运行任务、终止端口、新闻订阅。

通过api.registerWidget方法,开发者可实现自定义的部件,显示在仪表盘内。

API 说明

registerWidget:

参数说明类型默认值
id必选,部件唯一的 IDstring
title必选,部件的名称string
description必选,部件的描述string
icon必选,组件的icon,取值可选santd内的icon类型string
component必选,加载的动态组件id,会用 ClientAddonApi 进行注册string
defaultHeight组件的默认高度(与最大最小高度可以选填)number
defaultWidth组件的默认宽度(与最大最小宽度可以选填)number
minHeight组件的最小高度number
minWidth组件的最小宽度number
maxHeight组件的最大高度number
maxWidth组件的最大宽度number
openDetailsButton是否显示部件右上角展示详情按钮,默认不显示booleanfalse
needsUserConfig是否显示部件右上角配置按钮,默认不显示booleanfalse
onConfigOpen当点击右上角配置按钮时,返回此函数配置的表单格式function({context})
defaultConfigonConfigOpen配置的表单的默认值function()
onAdded将部件从列表添加到仪表盘页面时触发此函数function({widget, definition})
onRemoved将部件从仪表盘页面移除时触发此函数function({widget, definition})

API使用方式如下:

api.registerWidget({
    id: 'san.widgets.test', // 必选,唯一的 ID
    title: 'title', // 必选,组件的名称
    description: 'description',  // 必选,组件的描述
    icon: 'info-circle', // 必选,组件的icon,取值可选santd内的icon类型
    component: 'san.widgets.components.test-widget', // 必选,加载的动态组件,会用 ClientAddonApi 进行注册
    minWidth: 6, // 宽度
    minHeight: 1, // 高度
    maxWidth: 6,
    maxHeight: 6,
    defaultWidth: 5, // 必选
    defaultHeight: 2, // 必选
    openDetailsButton: false, // 可选
    defaultConfig: () => ({  // 可选,如果有prompt表单,返回默认配置
        hi: 'hello'
    }),
    async onConfigOpen() {  // 可选,返回表单配置
        return {
            prompts: [
                {
                    name: 'hi',
                    type: 'input',
                    message: '',
                    validate: input => !!input
                }
            ]
        };
    }
});

配置插件

配置插件主要用于在配置管理中,将项目中配置文件的修改变为可视化的表单操作,方便用户理解并修改配置项。目前San CLI UI内默认配置项包含san.config.jseslint的配置。

通过调用api.registerConfig可以更改项目的配置,此函数返回一个符合inquirer.prompts格式的对象,San CLI UI内支持的 inquirer 类型有:checkbox、confirm、input、list、string。通过该对象生成表单,可在项目配置中显示并修改具体项目的配置。

API 说明:

registerConfig

参数说明类型默认值
id必选,配置项的唯一idstring
name必选,配置项的展示名称string
description必选,配置项的展示描述string
icon配置项的图标,取值可选santd内的icon类型或静态图片链接string
link配置项更多的链接string
files提供配置项需要检测的配置文件,支持的类型有:json、yaml、js、packageobject
onRead配置详情页面显示的表单对象,对于每个配置项都包含一个描述,整体格式符合inquirer.prompts对象onRead: ({data}) => ({prompts: [...]})
onWrite数据写入配置文件触发的钩子,可在此执行node.js的代码onWrite: ({ prompts, answers, data, files, cwd, api }) => {...})

onRead

参数说明类型默认值
id必选,配置项的唯一idstring
name必选,配置项的展示名称string
description必选,配置项的展示描述string
icon配置项的图标,取值可选santd内的icon类型或静态图片链接string
link配置项更多的链接string
files提供配置项需要检测的配置文件,支持的类型有:json、yaml、js、packageobject
onRead配置详情页面显示的表单对象,对于每个配置项都包含一个描述,整体格式符合inquirer.prompts对象onRead: ({data}) => ({prompts: [...]})
onWrite数据写入配置文件触发的钩子,可在此执行node.js的代码onWrite: ({ prompts, answers, data, files, cwd, api }) =>

通过onRead函数返回的对象,符合inquirer.prompts格式,支持配置单个表单或带多个选项卡的表单

// 配置多个表单
api.registerConfig({
  /* ... */
  onRead: ({ data, cwd }) => ({
    tabs: [
      {
        id: 'tab1',
        label: 'My tab',
        // 可选的
        icon: 'application_settings',
        prompts: [
          // 表单对象
        ]
      },
      {
        id: 'tab2',
        label: 'My other tab',
        prompts: [
          // 表单对象
        ]
      }
    ]
  })
})

onWrite

参数说明类型默认值
prompts运行时表单对象string
answers用户输入的回答数据string
data从配置文件读取的只读的初始化数据string
files被找到的文件的描述器 ({ type: 'json', path: '...' })string
cwd当前工作目录string
api写入api辅助函数object

onWrite.api

参数说明类型默认值
assignData(fileId, newData)在写入前使用 Object.assign 来更新配置文件function
setData(fileId, newData)newData 的每个 key 在写入之前都将会被深设置在配置数据上 (或当值为 undefined 时被移除)。function
async getAnswer(id, mapper)为一个给定的表单 id 获取答案,并通过可能提供了的 mapper 函数 (例如 JSON.parse) 进行 map 处理。function

其中prompts对象格式:

{
    id: data.name,
    type: data.type,
    name: data.short || null,
    message: data.message,
    group: data.group || null,
    description: data.description || null,
    link: data.link || null,
    choices: null,
    visible: true,
    enabled: true,
    // 当前值 (未被过滤的)
    value: null,
    // 如果用户修改过了则为 true
    valueChanged: false,
    error: null,
    tabId: null,
    // 原始的 inquirer 提示符对象
    raw: data
}

API使用方式如下:

	// san.config.js的配置
    api.registerConfig({
        id: 'san.san-cli', // 配置项的id
        name: 'San CLI',
        description: 'configuration.san-cli.description',
        link: 'https://ecomfe.github.io/san-cli/#/config',
        files: {
            san: {
                js: ['san.config.js']
            }
        },
        icon: iconUrl,
        onRead: ({data}) => ({
            prompts: [
                {
                    name: 'publicPath',
                    type: 'input',
                    default: '/',
                    value: data.san && data.san.publicPath,
                    message: 'configuration.san-cli.publicPath.label',
                    description: 'configuration.san-cli.publicPath.description',
                    group: 'configuration.san-cli.groups.general',
                    link: 'https://ecomfe.github.io/san-cli/#/config'
                },
                ...
            ]
        }),
        onWrite: async ({api, prompts}) => { // 在写入时显示
            const sanData = {};
            for (const prompt of prompts) {
                sanData[prompt.id] = await api.getAnswer(prompt.id);
            }
            api.setData('san', sanData);
        }
    });
配置文件

registerConfig配置项files中可以声明多个配置文件,例如 .eslintrc 和 san.config.js,支持的类型有:json、yaml、js、package。配置时需要严格按照次顺序,如果这项配置不存在,则会创建列表中的第一个文件。例如:

    api.registerConfig({
        id: 'san.eslintrc',
        name: 'ESLint configuration',
        description: 'configuration.eslint.description',
        link: 'https://eslint.org',
        files: {
            eslint: {
                js: ['.eslintrc.js'],
                json: ['.eslintrc', '.eslintrc.json'],
                yaml: ['.eslintrc.yaml', '.eslintrc.yml'],
                // 会从 `package.json` 读取
                package: 'eslintConfig'
            }
        },
        ...
    )

配置文件的内容与读取数据的对应关系如下:

// ui.js
api.registerConfig({
        id: 'san.san-cli', // 配置项的id
        name: 'San CLI',
        description: 'configuration.san-cli.description',
        link: 'https://ecomfe.github.io/san-cli/#/config',
        files: {
            san: {
                js: ['san.config.js']
            }
        },
}
// san.config.js
{
    assetsDir: STATIC_PRO,
    publicPath: '/',
    outputDir: 'dist',
    filenameHashing: isProduction,
    css: {
        sourceMap: isProduction,
        cssPreprocessor: 'less',
        extract: true
    },


    pages: {
        index: {
            entry: './pages/index.js',
            filename: 'index.html',
            template: './assets/index.html',
            title: '项目管理器 - san ui',
            chunks: ['index', 'vendors']
        }
    },
    ...
}
// 读取到cli ui后
{
	san: {
		assetsDir: STATIC_PRO,
    	publicPath: '/',
    	outputDir: 'dist',
    	filenameHashing: isProduction,
    	css: {
        	sourceMap: isProduction,
        	cssPreprocessor: 'less',
        	extract: true
    	},
    	pages: {
        	index: {
            	entry: './pages/index.js',
            	filename: 'index.html',
            	template: './assets/index.html',
            	title: '项目管理器 - san ui',
            	chunks: ['index', 'vendors']
        	}
    	}
    ...
	}
}

任务插件

在项目任务中展示的任务项,生成自项目 package.json 文件的 scripts 字段。

San CLI UI默认内置了san servesan buildsan inspect三个命令的增强任务,包括:startbuildanalyzerbuild:moderninspect几个任务。

通过api.registerTask 方法,实现任务的“增强”,为任务增加额外的信息和显示,并能在对应的调用周期下实现附加功能。

API 说明

registerTask

参数说明类型默认值
match正则匹配对应的命令reg
description任务对应的描述string
icon任务的图标,取值可选santd内的icon类型或静态图片链接string
link任务说明的链接string
prompts返回任务可配置项的表单,整体格式符合inquirer.prompts对象object
onBeforeRun启动任务之前的钩子函数,可以修改任务参数onBeforeRun: async ({ answers, args }) => {})
onRun任务运行之后立即调用的钩子函数onRun: async ({ args, child, cwd }) => {}),child: Node 子进程,cwd: 进程所在目录
onExit任务退出后触发的钩子函数onExit: async ({ args, child, cwd, code, signal }) => {}) code:退出码
views额外的视图,默认情况下,这里是展示终端输出的 dashboard 视图array
defaultView展示任务详情时默认选择的视图 (默认是 dashboard)string

API使用方式如下:

api.registerTask({
    // 匹配san serve
    match: /san(-cli\/index\.js)? serve(\s+--\S+(\s+\S+)?)*$/,
    description: 'task.description.serve',
    link: 'https://ecomfe.github.io/san-cli',
    icon: sanIcon,
    prompts: [
        {
            name: 'open',
            type: 'confirm',
            default: false,
            message: 'task.serve.open'
        },
        ...
    ],
    onBeforeRun: ({answers, args}) => {
        ...
    },
    onRun: () => {
        ...
    },
    onExit: () => {
        ...
    },
    views: [
        {
            id: 'san.cli-ui.views.dashboard',
            label: 'addons.dashboard.title',
            component: 'san.cli-ui.components.dashboard'
        },
        ...
    ],
    defaultView: 'san.cli-ui.views.dashboard'
});

自定义视图插件与自定义路由插件

开发者可以使用api.registerView创建自定义视图,结合使用ClientAddonApi.addRoute创建自定义路由跳转该视图。

在ui.js通过api.registerView注册的视图,在服务端触发视图增加的subscription监听,将新增的页面路径及名称推送到客户端显示,而客户端组件加载时,已通过ClientAddonApi.addRoute将路由加载到san-router,当点击跳转时,就如处理San CLI UI默认路由一般,跳转至对应自定义组件页面。

API 说明

api.registerView

参数说明类型默认值
id视图id,使用 'ClientAddonApi.addRoutes' 方法中相同的idstring
name视图显示名称string
icon按钮图标名称(santd的图标类型)string

API使用方式如下:

api.registerView({
    id: 'san.myviews.views',
    name: '我的视图',
    // Santd的图标
    icon: 'smile'
});

api.registerView通常需要配合ClientAddon对象的ClientAddonApi.addRoute方法一同使用:

import Myview from './components/myview';

/* global ClientAddonApi */
if (window.ClientAddonApi) {
    // 注意这里第一个参数,应registerView的id参数相同,这里会创建一个'/addon/san.myviews.views' 路由
    ClientAddonApi.addRoutes('san.myviews.views', Myview);
}

2. 事件交互

prompts表单对象

prompts对象必须是合法的 inquirer 对象。基本的结构如下:

{
    id: data.name,
    type: data.type,
    visible: true,
    enabled: true,
    name: data.name || null,
    message: data.message,
    placeholder: data.placeholder || null,
    group: data.group || null,
    description: data.description || null,
    link: data.link || null,
    choices: null,
    value: null,
    valueChanged: false,
    error: null,
    tabId: data.tabId || null,
    formItemLayout: data.formItemLayout || {},
    raw: data
}

支持的 inquirer 类型有:checkbox、confirm、input、list、string。

confirm类型的组件会以一个switch按钮展示,使用例子如下:


{
    name: 'sourceMap',
    type: 'confirm',
    default: true,
    value: false,
    // 名称
    message: '在生产环境启用 Source Map',
    // 附加描述
    description: '如果你不需要生产环境下的 source map,禁用此项可以加速生产环境构建。',
    // 用来将提示符按章节分组
    group: '基础设置',
    // “More info”链接
    link: 'https://ecomfe.github.io/san-cli/#/config'
}

input类型的组件会以一个输入框展示,使用例子如下:

{
    name: 'publicPath',
    type: 'input',
    default: '/',
    value: '/',
    message: '在生产环境启用 Source Map',
    description: '如果你不需要生产环境下的 source map,禁用此项可以加速生产环境构建。',
    group: '基础设置',
    link: 'https://ecomfe.github.io/san-cli/#/config'
}

list类型的组件会以一个下拉列表框展示,使用例子如下:

{
    name: 'import/unambiguous',
    type: 'list',
    message: '代码质量和纠错',
    value: 1,
    choices: [
        {
            name: '关闭',
            value: 0
        },
        {
            name: '警告',
            value: 1
        },
        {
            name: '错误',
            value: 2
        }
    ]
}

插件action

插件的action是San CLI UI的插件在浏览器端和Node.js之间的事件调用监听机制,例如终止端口插件的终止按钮,在按下后,会利用此api向node端传递需要杀死的端口,进而调用kill函数完成功能。

API 说明

api.callActionw

参数说明类型默认值
id需要触发的action的唯一idstring
paramsaction的参数Object

api.onAction

参数说明类型默认值
id需要监听的action的idstring
callback监听到action后执行的回调函数function

使用方式如下

    // 调用一个 action
    api.callAction('san.widgets.actions.kill-port', {
        port: 8080
    }).then(results => {
        console.log(results)
	}).catch(errors => {
  		console.error(errors)
    })
    // 监听一个 action
    api.onAction('san.widgets.actions.kill-port', async params => {
        ...
        return {
            status: res
        };
    });

需要确保id唯一,建议使用命名空间调用: const {onAction, callAction} = api.namespace('myname.')

在浏览器端的组件内,可通过调用san-component扩展的方法,在插件action方法调用的不同时期执行逻辑:

  • $onPluginActionCalled:在action调用后执行
  • $onPluginActionResolved:在action返回后执行
  • $callPluginAction:在action调用时执行

例如:

export default {
    created () {
        this.$onPluginActionCalled(action => {
            // 当 action 被调用时 且在运行之前
            console.log('called', action)
        });
        this.$onPluginActionResolved(action => {
            // 当 action 运行完毕之后
            console.log('resolved', action)
        });
    },

    methods: {
        testAction () {
            // 调用一个插件的 action
            this.$callPluginAction('com.my-name.test-action', {
                meow: 'meow'
            });
        }
    }
}

插件事件钩子

在ui.js的配置中,提供了项目不同阶段的时间钩子:

api说明例子
onProjectOpen当插件在当前项目中第一次被加载时触发api.onProjectOpen((project, previousProject) => { // 重置数据 })
onPluginReload当插件被重新加载时触发api.onPluginReload((project) => { console.log('plugin reloaded') })

进程通讯ipc

IPC 就是进程间通信 (Inter-Process Communication) 的缩写。该系统允许你轻松的从子进程 (例如任务) 发送消息,并且轻量快速。在ui.js中使用api.getIpc()获取IPC的实例,进而实现进程的通讯,包含以下方法:

  • ipc.on(callback):添加listener监听
  • ipc.off(callback):移除listener监听
  • ipc.send(data):向连接的所有的IPC客户端发送消息

使用方式如下:

   ipc.on(({data}) => {
        if (data.sanCliServe) {
            sharedData.set('serve-url', data.sanCliServe.url);
        }
    });
    ipc.off(({data}) => {
        ...
    });
    ipc.send(data);

数据共享

San CLI UI为开发者提供一种简易的、自定义组件之间通过共享的数据互通信息的方式。在ui.js中使用const sharedData = api.getSharedData('my.com.')获取sharedData的实例,为保证唯一使用,需要在使用数据函数时,输入唯一id生成自己的命名空间。包含以下方法:

  • sharedData.get($id):获取sharedData中$id的数据
  • sharedData.set($id, value, {disk}):设置sharedData中$id的数据
  • sharedData.remove($id):清除sharedData中的$id的数据
  • sharedData.watch($id, handler):监听sharedData的$id的值变化
  • sharedData.unwatch($id, handler):清除sharedData的$id的监听

使用方法如下


// 设置或更新
api.setSharedData('com.my-name.my-variable', data)

// 获取
const sharedData = api.getSharedData('com.my-name.my-variable')


// 移除
api.removeSharedData('com.my-name.my-variable')

// 侦听变化
const watcher = (value, id) => {
  console.log(value, id)
}
api.watchSharedData('com.my-name.my-variable', watcher)
// 取消侦听
api.unwatchSharedData('com.my-name.my-variable', watcher)

// 带命名空间的版本
const {
  setSharedData,
  getSharedData,
  removeSharedData,
  watchSharedData,
  unwatchSharedData
} = api.namespace('com.my-name.')

setSharedData('my-variable', data);

在浏览器端的组件内,可通过调用san-component扩展的方法,调用sharedData:

  • $getSharedData($id):获取sharedData中$id的数据
  • $watchSharedData($id, handler):监听sharedData的$id的值变化
  • $setSharedData($id, data):设置sharedData中$id的数据

例如:


export default {

  async created () {
    const value = await this.$getSharedData('com.my-name.my-variable')

    this.$watchSharedData(`com.my-name.my-variable`, value => {
      console.log(value)
    })

    await this.$setSharedData('com.my-name.my-variable', 'new-value')
  }
}

3.持久存储db

San CLI UI为开发者提供对db操作的方法,数据存储的能力。在ui.js中通过调用api.getDB(namespace) 获取lowdb的实例对象,同样为隔离,需要输入唯一的命名空间。包含以下方法:

  • get(key):获取一个名为key的值
  • set(key, value):更新key的值为value

4.工具函数

  • api.hasPlugin('eslint') 如果项目使用了该插件则返回 true
  • api.getCwd()获取当前工作目录。
  • api.resolve(path) 在当前工程下解析一个文件:
  • api.getProject() 得到当前打开的工程。

ClientAddon对象

在插件包内,ClientAddon实例化的对象ClientAddonApi主要完成两件事:

  • 插件内对应组件的定义:defineComponent
  • 将插件内的组件语言包扩展至San CLI UIaddLocales

在插件内的使用方式如下:

import widgetdemo from './components/widget-demo';
import locales from './locales.json';

/* global ClientAddonApi */
if (window.ClientAddonApi) {
    // 扩展语言
    ClientAddonApi.addLocales(locales);
    // 推荐以类型前缀定义组件的唯一id:'san.widget'
    ClientAddonApi.defineComponent('san.widget.components.widget-demo', widgetdemo);
}

通过defineComponent将自定义组件加载到San CLI UI内,此时组件内可使用san-component增强的功能,如santd组件、$onPluginActionCalled等方法;通过addLocales将自定义组件的语言包加载到San CLI UI内,此时组件内可直接使用this.$t(key)的形式显示页面文案;通过ClientAddonApi.awaitComponent方法,在组件加载到后,将组件挂载到页面对应位置。