插件扩展
插件是 EasyEditor 的功能扩展机制,通过插件可以扩展编辑器的功能,增强核心能力。本指南将帮助你了解如何开发和集成自定义插件。
概述
插件是 EasyEditor 的扩展单元,用于增强编辑器的功能。插件可以:
- 添加新功能和物料组件
- 扩展现有类的方法和属性
- 监听和响应编辑器事件
- 与其他插件协作和通信
- 集成第三方库和服务
EasyEditor 采用了"微内核+插件"的架构设计,大部分功能都是通过插件实现的,这使得编辑器非常灵活和可扩展。
生命周期
插件遵循以下生命周期:
- 注册阶段:插件向编辑器注册,设置插件名称、依赖等基本信息
- 扩展阶段:如果存在
extend
方法,调用它来扩展核心类 - 初始化阶段:调用插件的
init
方法,设置事件监听,初始化资源 - 运行阶段:插件正常工作,响应事件,提供功能
- 销毁阶段:调用插件的
destroy
方法,清理资源,移除事件监听
编辑器会确保按照正确的依赖顺序初始化插件,并在适当的时候销毁插件。
目录结构
一个完整的插件通常包含以下文件结构:
my-plugin/
├── index.ts # 插件入口
└── types.ts # 类型定义
最简单的插件可以只有一个入口文件。
使用
基础插件
最基本的插件示例:
import type { PluginCreator } from '@easy-editor/core'
// 插件工厂函数,可接收配置参数
const MyPlugin: PluginCreator<Option> = (options = {}) => {
// 返回插件定义
return {
name: 'MyPlugin', // 插件名称,必须唯一
deps: [], // 依赖的其他插件
init(ctx) { // 初始化方法
ctx.logger.info('MyPlugin initialized')
// 注册全局变量,可以被其他插件访问
ctx.set('myPluginData', {
version: '1.0.0',
...options
})
// 注册事件监听
ctx.event.on('document.open', (doc) => {
ctx.logger.info('Document opened:', doc.id)
})
},
destroy(ctx) { // 销毁方法
ctx.logger.info('MyPlugin destroyed')
// 移除事件监听,避免内存泄漏
ctx.event.off('document.open')
}
}
}
export default MyPlugin
事件处理插件
处理编辑器事件的插件示例:
import { type PluginCreator, DESIGNER_EVENT } from '@easy-editor/core'
const EventHandlerPlugin: PluginCreator = () => {
return {
name: 'EventHandlerPlugin',
deps: [],
init(ctx) {
const { designer } = ctx
// 监听组件选择事件
designer.onEvent(DESIGNER_EVENT.SELECTION_CHANGE, (nodeIds) => {
const node = designer.currentDocument?.getNode(nodeIds[0])
if (node) {
ctx.logger.info('Selected component:', node.componentName)
}
})
// 监听属性变更事件
designer.onEvent(DESIGNER_EVENT.NODE_PROPS_CHANGE, ({ node, prop, newvalue }) => {
ctx.logger.info(`Property ${prop} of ${node.id} changed to:`, newvalue)
})
// 监听拖拽事件
designer.onEvent(DESIGNER_EVENT.DRAG_END, (e) => {
ctx.logger.info('Component dropped:', e)
})
}
}
}
export default EventHandlerPlugin
功能扩展插件
扩展编辑器核心类的插件示例:
import type { Plugin } from '@easy-editor/core'
const ExtendPlugin = (): Plugin => {
return {
name: 'ExtendPlugin',
deps: [],
init(ctx) {
ctx.logger.info('ExtendPlugin initialized')
},
// 扩展核心类
extend({ extendClass, extend }) {
const { Node, Document } = extendClass
// 扩展 Node 类
extend('Node', {
// 添加自定义方法
duplicate: {
value(this: Node) {
const parent = this.parent
if (!parent) return null
const index = parent.children.indexOf(this)
const schema = this.export()
// 创建副本
return parent.document.createNode({
...schema,
id: undefined, // 让系统生成新ID
props: {
...schema.props,
label: `${schema.props?.label || 'Component'} Copy`
}
}, parent, { index: index + 1 })
}
},
// 添加自定义属性
isContainer: {
get(this: Node) {
// 检查节点是否为容器类型
return this.componentMeta?.configure?.component?.isContainer || false
}
}
})
}
}
}
export default ExtendPlugin
注册插件
在引擎初始化时注册插件:
import {
init,
plugins,
} from '@easy-editor/core'
import MyPlugin from './plugins/my-plugin'
import EventHandlerPlugin from './plugins/event-handler-plugin'
import ExtendPlugin from './plugins/extend-plugin'
// 注册插件
plugins.registerPlugins([
MyPlugin({ debug: true }),
EventHandlerPlugin(),
ExtendPlugin()
])
// 初始化引擎
await init({
designMode: 'design',
})
// 也可以在初始化后动态注册插件
plugins.registerPlugins([
MyNewPlugin()
])
通信模式
插件之间可以通过以下方式进行通信:
事件通信
通过编辑器的事件系统进行通信:
// 插件 A
init(ctx) {
// 触发自定义事件
ctx.event.emit('pluginA.dataChanged', { someData: 'value' })
}
// 插件 B
init(ctx) {
// 监听插件 A 的事件
ctx.event.on('pluginA.dataChanged', (data) => {
console.log('Received data from Plugin A:', data)
})
}
共享上下文
通过插件上下文共享数据和方法:
// 插件 A
init(ctx) {
// 注册共享服务
ctx.set('dataService', {
getData: () => ({ value: 42 }),
setData: (data) => console.log('Data set:', data)
})
}
// 插件 B
init(ctx) {
// 获取插件 A 注册的服务
const dataService = ctx.get('dataService')
if (dataService) {
const data = dataService.getData()
console.log('Got data:', data)
dataService.setData({ newValue: 100 })
}
}
访问扩展方法
通过扩展方法进行通信:
// 插件 A 扩展了 Node 类
extend({ extend }) {
extend('Node', {
pluginAMethod: {
value(this: Node, param: string) {
return `Plugin A method: ${param}`
}
}
})
}
// 插件 B 使用插件 A 的扩展方法
init(ctx) {
ctx.event.on('node.select', (nodeId) => {
const node = ctx.designer.currentDocument?.getNode(nodeId)
if (node && typeof node['pluginAMethod'] === 'function') {
const result = node['pluginAMethod']('test')
console.log(result) // 输出: "Plugin A method: test"
}
})
}
配置项
核心属性
name
(必需)
定义插件的唯一名称,用于识别插件和处理依赖关系。
{
name: 'MyPlugin'
}
deps
(可选)
定义插件依赖的其他插件列表。EasyEditor 会确保依赖的插件在当前插件之前加载和初始化。
{
deps: ['CorePlugin', 'UIPlugin'] // 依赖 CorePlugin 和 UIPlugin
}
eventPrefix
(可选)
定义插件的事件前缀,用于区分不同插件的事件。如果不设置,则使用 'common' 作为前缀。
{
eventPrefix: 'my-plugin' // 事件名将变为 'my-plugin.eventName'
}
核心方法
init
(必需)
插件初始化方法,在编辑器启动时执行。可以是同步或异步函数。
{
init(ctx) {
// 初始化逻辑
ctx.logger.info('Plugin initialized')
// 订阅事件
ctx.event.on('document.open', (doc) => {
// 处理事件
})
// 异步初始化示例
// return new Promise((resolve) => {
// setTimeout(() => {
// // 异步操作完成
// resolve()
// }, 1000)
// })
}
}
destroy
(可选)
插件销毁方法,在插件被移除时执行,用于清理资源。
{
destroy(ctx) {
// 清理资源
ctx.logger.info('Plugin destroyed')
// 取消事件订阅
ctx.event.off('document.open')
// 清除定时器
clearInterval(this.timer)
// 销毁DOM元素
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element)
}
}
}
extend
(可选)
扩展编辑器核心类的方法,可以添加新的方法或属性,修改现有行为。
{
extend({ extendClass, extend }) {
const { Node, Document } = extendClass
// 扩展 Node 类
extend('Node', {
// 添加方法
customMethod: {
value(this: Node, param: string) {
return `Custom method with param: ${param}`
}
},
// 添加属性
customProperty: {
get(this: Node) {
return this.getExtraPropValue('customProp')
},
set(this: Node, value) {
this.setExtraPropValue('customProp', value)
}
}
})
// 扩展 Document 类
extend('Document', {
// 添加方法
customDocumentMethod: {
value(this: Document) {
return 'Custom document method'
}
}
})
}
}
上下文 (Context)
插件上下文 ctx
是一个包含编辑器核心功能的对象,提供了访问和操作编辑器各个部分的能力。主要包括:
核心模块访问
// 获取编辑器实例
const editor = ctx.editor
// 获取设计器实例
const designer = ctx.designer
// 获取项目管理实例
const project = ctx.project
// 获取模拟器实例
const simulator = ctx.simulator
// 获取设置器管理实例
const setterManager = ctx.setterManager
// 获取组件元数据管理实例
const componentMetaManager = ctx.componentMetaManager
// 获取热键管理
const hotkey = ctx.hotkey
日志系统
// 记录日志
ctx.logger.debug('Debug message')
ctx.logger.info('Info message')
ctx.logger.warn('Warning message')
ctx.logger.error('Error message')
事件系统
插件上下文提供了两个事件系统:全局事件系统 event
和插件专用事件系统 pluginEvent
。
// 全局事件系统
// 订阅事件
ctx.event.on('eventName', (data) => {
// 处理事件
})
// 取消事件订阅
ctx.event.off('eventName')
// 触发事件
ctx.event.emit('eventName', eventData)
// 插件专用事件系统
// 使用插件名作为事件前缀
ctx.pluginEvent.emit('dataChanged', { value: 100 })
ctx.pluginEvent.on('dataChanged', (data) => {
console.log('Plugin event received:', data)
})
数据共享
// 存储共享数据
ctx.set('key', value)
// 获取共享数据
const value = ctx.get('key')
// 删除共享数据
ctx.delete('key')
核心类扩展
extend
方法允许你扩展以下核心类:
设计器相关类
- Designer: 设计器主类
- Dragon: 拖拽管理
- Detecting: 检测管理
- Selection: 选区管理
- DropLocation: 放置位置
- OffsetObserver: 偏移观察器
模拟器相关类
- Simulator: 模拟器主类
- Viewport: 视口管理
项目相关类
- Project: 项目管理
- Document: 文档管理
- History: 历史记录
- Node: 节点
- NodeChildren: 节点子元素
- Props: 属性集合
- Prop: 单个属性
组件相关类
- ComponentMetaManager: 组件元数据管理器
- SetterManager: 设置器管理器
- ComponentMeta: 组件元数据
类型扩展
利用 declare
的类型扩展机制,扩展和自定义核心类型定义。以下是一个示例,展示如何扩展 NodeSchema 接口:
declare module '@easy-editor/core' {
interface NodeSchema {
/**
* 是否是根节点
*/
isRoot?: boolean
/**
* 是否是分组
*/
isGroup?: boolean
/**
* dashboard 额外信息
*/
$dashboard?: {
/**
* 位置信息
*/
rect?: DashboardRect
}
}
}
注册选项
在注册插件时,可以提供额外的选项:
autoInit
(可选)
是否自动初始化插件。如果设为 true
,则在注册插件后立即初始化,而不等待插件管理器统一初始化所有插件。
// 注册并立即初始化插件
await pluginManager.register(MyPlugin(), { autoInit: true })
override
(可选)
是否允许覆盖同名插件。如果设为 true
,则当注册的插件与已存在的插件同名时,会先销毁已存在的插件,然后注册新插件。
// 注册并覆盖同名插件
await pluginManager.register(MyPlugin(), { override: true })