# plugin-core

# 简介

插件核心SDK包,对 @modern-js/core@modern-js/plugin 提供的插件化API进行的二次封装。以提供插件的 管理加载 等功能,支持以 function组件的形式载入插件。开发时除了可以使用自定义的hook(推荐)外,也可以使用默认提供的 createAsyncWorkflow类型的 hook 有: headerComponentsbodyComponentsfooterComponentspageComponentslogicFunctions

# 插件开发必读文档

# 说明

需与 @esign/ignore-webpack-plugin 一起使用,以实现构建主应用时 剔除 主应用中的插件源码

plugin-core架构图

# 安装

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}/${扩展点名称}`)
       */
    
上次更新: 10/31/2023, 2:15:53 AM