# plugin-core
# 简介
插件核心SDK包,对 @modern-js/core
、@modern-js/plugin
提供的插件化API进行的二次封装。以提供插件的 管理
、加载
等功能,支持以 function
或组件
的形式载入插件。开发时除了可以使用自定义的hook(推荐)
外,也可以使用默认提供的 createAsyncWorkflow
类型的 hook
有: headerComponents
、bodyComponents
、footerComponents
、pageComponents
、logicFunctions
# 插件开发必读文档
# 说明
需与 @esign/ignore-webpack-plugin 一起使用,以实现构建主应用时 剔除
主应用中的插件源码
# 安装
yarn add @esign/plugin-core -S
# 使用
import
pluginManager,
{
setGlobalOptions,
setAppContext,
useAppContext,
clearAppContext,
beforeEnterHook,
createExtensionPointHook,
EsExtensionPoint
}
from '@esign/plugin-core'
# API
- pluginManager({ path: string, env?: string, useEventEmitter?: boolean, usePrivateScope?: boolean, extractCss?: boolean, context: Record<string, any> }) 插件管理器
参考 案例二
@param
path: 必选, 插件地址(绝对路径
)@param
env: 可选, 当前项目运行环境,compile
: 本地编译编译时;runtime
: 生产运行时运行时@param
useEventEmitter: 可选, 是否启用事件中心(eventBus), 默认为false
@param
usePrivateScope: 可选, 是否启用manager
私有空间(开启后,会为每个pluginRunner
提供一个manager
,并创建一个context
, 默认为true
@param
extractCss: 可选, 该插件是否引入单独的css(若在打包时插件提取css,可设置true,否则为false), 默认为false
@param
context: 可选, 上下文参数, 默认为{}
@returns
Promise<any>:返回Promise- 注意事项
- 参数
extractCss
只有在production
且有单独css
时有效 develpment
时,通过import()
方式动态引入;值为runtime
时,通过动态注入script
标签实现引入;- 当
develpment
时,path
值必须为绝对路径,但路径不能包含index.ts
,详细见案例。原因为:因webpack
编译时必须知道具体路径,import()
不支持变量,但可使用魔法字符串
;
- 参数
- setGlobalOptions({ env?: string, extractCss?: boolean, useEventEmitter?: boolean, usePrivateScope?: boolean }) 设置全局变量
@param
env: 可选, 当前项目运行环境,compile
: 本地编译编译时;runtime
: 生产运行时运行时@param
useEventEmitter: 可选, 是否启用事件中心(eventBus), 默认为false
@param
usePrivateScope: 可选, 是否启用manager
私有空间(开启后,会为每个pluginRunner
提供一个manager
,并创建一个context
, 默认为true
@param
extractCss: 可选, 该插件是否引入单独的css(若在打包时插件提取css,可设置true,否则为false), 默认为false
@returns
void
- useAppContext(key: string) 获取当前 manager 上下文
@param
key: 可选, 当前数据的key
键@returns
查询的数据
- setAppContext(value: Record<string, any>) 设置当前 manager 上下文
@param
value: 要存储的键值对@returns
void
- clearAppContext() 清空当前 manager 上下文
@returns
void
- beforeEnterHook(routerOpts: BeforeEnterHookRouteUtilOptions, hookOpts: BeforeEnterHookConfigOptions, configs?: Record<string, any>, baseConfigs?: Record<string, any>) 动态路由钩子处理函数
@param
routerOpts 项目路由配置项@param
routerOpts.to 目标路由的链接@param
routerOpts.next next 方法@param
routerOpts.router 路由实例
@param
hookOpts 项目路由配置项@param
hookOpts.baseName 插件路由的基路径@param
hookOpts.defaultPath 默认值
@param
configs 插件配置项,不传则取localStorage
中配置的@param
baseConfigs 插件基线配置项,用于插件兜底使用@returns
void
- EsExtensionPoint 扩展点组件
参考 案例一
jsx/tsx
下,EsExtensionPoint
组件的调用方式为es-extension-point
- esExtensionPointLogic 逻辑插件方法,会随着
EsExtensionPoint
一起注册到Vue
下,使用方式可参考 案例一
- 调用方式:
this.$esExtensionPointLogic(option: string | object,
region?: string
)
@param
option: 必选,- 当类型为
string
时,即为扩展点的name
, 与EsExtensionPoint
组件name
含义相同 - 当类型为
object
时,即为逻辑插件配置项option.name
: 必选, 与EsExtensionPoint
组件name
含义相同option.region
: 可选, 功能域名称, 与EsExtensionPoint
组件region
含义相同option.type
: 可选, 自定义hooks
类型, 与EsExtensionPoint
组件type
含义相同option.options
: 可选, 插件配置项, 与EsExtensionPoint
组件options
含义相同option.hookParams
: 可选, 插件配置项, 与EsExtensionPoint
组件hookParams
含义相同
- 当类型为
@param
region: 可选, 功能域名称, 后续会废弃掉!!!@returns
插件内容
- 调用方式:
# EsExtensionPoint 组件
# Install 配置
参数 | 说明 | 类型 | 必填 | 默认值 |
---|---|---|---|---|
configs | 插件的配置项 | object | 是 | - |
baseConfigs | 接入管理平台后,用于兜底的插件的 config.json 配置项 | object | 否 | - |
loadingOption | 插件 loading 全局配置项, { open?: 是否开启, spinner?: 加载图标或类名, text?: 加载文案, background?: 遮罩背景色, customClass?: 自定义类名, width?: 宽, height?: 高 } | object | 否 | {} |
# API
参数 | 说明 | 类型 | 必填 | 默认值 |
---|---|---|---|---|
name | 扩展点名称。若当前插件为扩展点类插件,则 name 值与 config.json 中的插件 key 、插件的 hook 名称保持一致;若当前插件为功能域类插件,则 name 值与插件的 hook 名称保持一致,config.json 中的插件 key 为功能域名称; | string | 是 | - |
region | 功能域名称 | string | 否 | - |
options | 配置项 { env: 运行环境(compile : 本地编译编译时; runtime : 生产运行时运行时), useEventEmitter: 是否使用内置eventBus, usePrivateScope: 是否使用私有空间 extractCss: 是否有额外css, context: 上下文内容 } | object | 否 | {} |
loadingOption | 插件 loading 配置项, { open?: 是否开启, spinner?: 加载图标或类名, text?: 加载文案, background?: 遮罩背景色, customClass?: 自定义类名, width?: 宽, height?: 高 } | object | 否 | {} |
defaultHookParams | 默认向当前插件的 扩展点 Hook 传递的参数 | [Object, Array, String, Number, Boolean, Function] | 否 | null |
# Methods
方法名 | 说明 | 返回值 |
---|---|---|
on-load-success | 插件加载成功后触发的函数 | - |
on-load-error | 插件加载失败后触发的函数 | error |
on-load-finish | 插件加载后触发的函数(无论成功还是失败) | - |
# Events
方法名 | 说明 | 回调参数 |
---|---|---|
refreshExtensionPoint | 刷新当前扩展点的函数 | 向当前插件的 扩展点 Hook 传递的参数 |
EsExtensionPoint组件注意事项
- 在
jsx/tsx
下,EsExtensionPoint
组件的调用方式为es-extension-point
。
# 案例
提示
:更多更详细demo,esign-plugin-demo 插件存放地址为: /plugins/plugin-demo/src/index.ts
插件文件名必须为inedx.ts
||index.js
# config.json 配置文件
{
"name": "",
"plugins": {
"events-ep": "/plugins/base/events-plugin/src/index",
"store-epu": "/plugins/base/store-event-plugin/src/index",
"logics-epl": "/plugins/base/logic-plugin/src/index",
}
}
# 案例一(以组件的方式实现功能)
若以组件方式实现功能,需保证 插件资源的
key
值,EsExtensionPoint
组件的name
参数 和 插件的hook
必须保持一致
# main.js
import { EsExtensionPoint } from '@esign/plugin-core'
Vue.use(EsExtensionPoint, {
configs,
loadingOption: {}
})
# store-event-plugin 插件案例
import Component from './components/index.tsx'
import type { Plugin, PluginAPI } from '@esign/plugin-core'
export default (): Plugin => ({
name: 'base-store-event-plugin',
setup({ setAppContext, useAppContext }: PluginAPI) {
return {
// 方式一,通过 bodyComponents 钩子返回组件
// 备注,同一个manager下,插件的hook是通用的
bodyComponents: () => {
return Component
},
// 方式二,通过自定义hook返回组件
'store-epu': () => {
return Component
},
}
}
})
# 调用插件demo StoreView.vue
<template>
<div>
<h1>{{transformTxt}}</h1>
<!-- 扩展点插件 -->
<EsExtensionPoint name="store-epu" />
<!-- 功能域 -->
<EsExtensionPoint name="events-header-epu" region="events-ep" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({})
export default class TestView extends Vue {
async mounted() {
// 逻辑插件调用方式
const logic = await this.$esExtensionPointLogic('logics-epl');
}
}
</script>
# 案例二(以方法形式实现功能)
# store-event-plugin 插件案例
import Component from './components/index.tsx'
import type { Plugin, PluginAPI } from '@esign/plugin-core'
export default (): Plugin => ({
name: 'base-store-event-plugin',
setup({ setAppContext, useAppContext }: PluginAPI) {
return {
// 方式一,通过 bodyComponents 钩子返回组件
// 备注,同一个manager下,插件的hook是通用的
bodyComponents: () => {
return Component
},
// 方式二,通过自定义hook返回组件
'store-epu': () => {
return Component
}
}
}
})
# 调用插件demo StoreView.vue
<template>
<div>
<h1>{{transformTxt}}</h1>
<div v-for="(item, index) in storeExtendPoint" :key="index" :is="item" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
// pluginManager 文件夹,与src、plugins同级,作用为维护插件与扩展点的对应关系
import { testEsExtensionPoint } from '../../pluginManager'
@Component({})
export default class TestView extends Vue {
testEsExtensionPoint = []
transformTxt = ''
async mounted() {
// 此为插件引入方法,项目一经发布,不可删除
this.storeExtendPoint = await testEsExtensionPoint('store-epu');
}
}
</script>
# pluginManager/index.js
import pluginManager, { createExtensionPointHook } from '@esign/plugin-core'
// 首页插件调用方法
// 方法名称一旦确定,不再更改
export async function testEsExtensionPoint(name: string) {
const path = getPluginPath(name) // 获取插件路径
// 插件所提供的hooks,只可新增,不可修改及删除
const hooks = await pluginManager({ path, hooks: { [name]: createExtensionPointHook() } })
const [ Component ] = await hooks[name](null)
Vue.component(Component.name, Component)
// 返回内容格式可自定义
return Component.name
}
# 案例三(动态路由插件案例)
注意事项
- 路由组成: 动态路由基础路径 + 扩展点名称 + 插件路由路径
- 3.5.0 以上vue-router版本支持指定路由下添加路由(全局只有一个拦截,无需在对应的路由children下添加拦截方法)
- 如果报router上某些方法没找到请联系云天帮忙处理router sdk版本问题
- 更多动态路由的demo,可参考esign-plugin-demo
# 路由匹配流程图
# 插件案例
import TestView from '../pages/test/index.tsx' // 测试页组件
import HelloWorld from '../pages/HelloWorld.vue' // hello页组件
import Index from '../pages/index/index.tsx' // 首页
// eslint-disable-next-line
import { Plugin, PluginAPI } from '@esign/plugin-core'
import { RouteConfig } from 'vue-router'
export default (): Plugin => ({
name: 'dynamic-routes-plugin', // 插件的名称,唯一标识
// 插件初始化函数,只会执行一次;返回一个 Hook 对象
setup(api: PluginAPI) {
return {
/**
* 一级路由扩展点
* 方法名称必须为扩展点名称,这里的形参basename为动态路由基础路径 + 扩展点名称:/third/dynamic-epu
*/
'dynamic-epu'({ basename = '' }): RouteConfig[] |
{
// 指定路由name挂载路由 vue-router 2.5.0 开始支持
parentName: string,
routes: RouteConfig[]
} {
return [
{
path: `${basename}`,
name: 'pluginMain',
component: Index,
children: [
{
path: `${basename}/helloworld`,
name: 'pluginHello',
component: HelloWorld,
meta: {
title: 'hello world',
basename,
},
},
{
path: `${basename}/`,
redirect: `${basename}/helloworld`,
}
],
},
] as RouteConfig[]
},
/**
* 二级路由扩展点
* vue-router 必须在 3.5.0+
* 方法名称必须为扩展点名称,这里的形参basename为动态路由基础路径 + 扩展点名称:/logic/locality-dynamic-epu
*/
'locality-dynamic-epu'({ basename = '' }) {
return {
parentName: 'logic', // 要挂载的二级路由的name
routes: [
{
path: `${basename}`,
name: 'pluginMain',
component: Index,
children: [
{
path: `${basename}/helloworld`,
name: 'pluginHello',
component: HelloWorld,
meta: {
title: 'hello world',
basename,
},
},
{
path: `${basename}/`,
redirect: `${basename}/helloworld`,
}
],
},
] as RouteConfig[]
}
}
pageComponents() {
// 返回 TestView 组件
return TestView
},
}
},
})
# 调用插件Demo (router.js)
import { beforeEnterHook } from '@esign/plugin-core'
import VueRouter from 'vue-router'
const routes = [
// 重定向配置
{ path: '!/third/*', redirect: '/' },
]
var router = new VueRouter({
base: '/',
mode: 'history',
routes,
})
router.beforeEach((from, to, next) => {
// 判断当前路径是否为现有应用路由
if (router.match(from).matched.length) {
next()
return
}
beforeEnterHook(
{
from,
next,
router,
},
{
// 基础路径
baseName: 'third', // 若为二级路由,baseName值则为二级路由的 name
// 默认页面
defaultPath: '/',
},
undefined
)
})
# 动态路由跳转方式
- 跳转动态路由根路径(third)+扩展点名称,例如:
this.$router.push(`/third/${扩展点名称}`) /** * 二级路由的跳转路径则为 * this.$router.push(`/${二级路由的name}/${扩展点名称}`) */