From 4f9be73b61e8a6fd13ab35b3a253f895468aca48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=9B=E7=9A=93?= Date: Fri, 22 Jan 2021 16:30:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20plugin=20=E7=9A=84?= =?UTF-8?q?=20autoInit=20=E6=B3=A8=E5=86=8C=E6=96=B9=E5=BC=8F=20chore(test?= =?UTF-8?q?):=20=E5=A2=9E=E5=8A=A0=20plugin=20=E7=9A=84=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/designer/jest.config.js | 1 + .../live-editing/live-editing.ts | 2 +- .../designer/src/plugin/plugin-manager.ts | 22 ++- packages/designer/src/plugin/plugin-types.ts | 17 +- packages/designer/src/plugin/plugin.ts | 26 ++- .../tests/plugin/plugin-manager.test.ts | 172 ++++++++++++++++++ packages/editor-preset-vision/package.json | 2 +- packages/engine/package.json | 1 + packages/types/src/index.ts | 1 - packages/types/src/metadata.ts | 4 +- 10 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 packages/designer/tests/plugin/plugin-manager.test.ts diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index faaacb0f5..efaf9181e 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -20,6 +20,7 @@ module.exports = { '!src/icons/**', '!src/locale/**', '!src/builtin-simulator/utils/**', + '!src/plugin/sequencify.ts', '!src/document/node/exclusive-group.ts', '!**/node_modules/**', '!**/vendor/**', diff --git a/packages/designer/src/builtin-simulator/live-editing/live-editing.ts b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts index a5601b592..5cf78090d 100644 --- a/packages/designer/src/builtin-simulator/live-editing/live-editing.ts +++ b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts @@ -48,7 +48,7 @@ export class LiveEditing { const npm = node?.componentMeta?.npm; const selected = [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || ''; - editor?.emit('designer.builinSimulator.LiveEditing', { + editor?.emit('designer.builtinSimulator.liveEditing', { selected, }); diff --git a/packages/designer/src/plugin/plugin-manager.ts b/packages/designer/src/plugin/plugin-manager.ts index 33f278405..e3b62b72a 100644 --- a/packages/designer/src/plugin/plugin-manager.ts +++ b/packages/designer/src/plugin/plugin-manager.ts @@ -1,5 +1,5 @@ import { Editor } from '@ali/lowcode-editor-core'; -import { CompositeObject, ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager, ILowCodePluginContext } from './plugin-types'; +import { ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager, ILowCodePluginContext, LowCodeRegisterOptions } from './plugin-types'; import { LowCodePlugin } from './plugin'; import LowCodePluginContext from './plugin-context'; import { getLogger, invariant } from '../utils'; @@ -22,19 +22,23 @@ export class LowCodePluginManager implements ILowCodePluginManager { return new LowCodePluginContext(this.editor, this); } - register( - pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig, - options?: CompositeObject, - ): void { + async register( + pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig, + pluginOptions?: any, + options?: LowCodeRegisterOptions, + ): Promise { const ctx = this._getLowCodePluginContext(); - const config = pluginConfig(ctx, options); + const config = pluginConfigCreator(ctx, pluginOptions); invariant(config.name, `${config.name} required`, config); ctx.setLogger(config); invariant(!this.pluginsMap.has(config.name), `${config.name} already exists`, this.pluginsMap.get(config.name)); - const plugin = new LowCodePlugin(this, config, options); + const plugin = new LowCodePlugin(this, config, pluginOptions); + if (options?.autoInit) { + await plugin.init(); + } this.plugins.push(plugin); this.pluginsMap.set(plugin.name, plugin); - logger.log('plugin registered with config:', config, ', options:', options); + logger.log('plugin registered with config:', config, ', options:', pluginOptions); } get(pluginName: string): ILowCodePlugin | undefined { @@ -51,7 +55,7 @@ export class LowCodePluginManager implements ILowCodePluginManager { async delete(pluginName: string): Promise { const idx = this.plugins.findIndex(plugin => plugin.name === pluginName); - if (idx < -1) return false; + if (idx === -1) return false; const plugin = this.plugins[idx]; await plugin.destroy(); diff --git a/packages/designer/src/plugin/plugin-types.ts b/packages/designer/src/plugin/plugin-types.ts index 6d4dd7600..96ad3c1c2 100644 --- a/packages/designer/src/plugin/plugin-types.ts +++ b/packages/designer/src/plugin/plugin-types.ts @@ -10,9 +10,9 @@ import { export interface ILowCodePluginConfig { name: string; dep?: string[]; // 依赖插件名 - init(): void; + init?(): void; destroy?(): void; - exports?(): CompositeObject; + exports?(): any; } export interface ILowCodePluginCore { @@ -22,10 +22,10 @@ export interface ILowCodePluginCore { config: ILowCodePluginConfig; logger: Logger; on(event: string | symbol, listener: (...args: any[]) => void): any; - off(event: string | symbol, listener: (...args: any[]) => void): any; emit(event: string | symbol, ...args: any[]): boolean; removeAllListeners(event?: string | symbol): this; - init(): void; + init(forceInit?: boolean): void; + isInited(): boolean; destroy(): void; toProxy(): any; setDisabled(flag: boolean): void; @@ -62,9 +62,10 @@ interface ILowCodePluginManagerPluginAccessor { export interface ILowCodePluginManagerCore { register( - pluginConfig: (ctx: ILowCodePluginContext, options?: CompositeObject) => ILowCodePluginConfig, + pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig, + pluginOptions?: any, options?: CompositeObject, - ): void; + ): Promise; get(pluginName: string): ILowCodePlugin | undefined; getAll(): ILowCodePlugin[]; has(pluginName: string): boolean; @@ -74,3 +75,7 @@ export interface ILowCodePluginManagerCore { } export type ILowCodePluginManager = ILowCodePluginManagerCore & ILowCodePluginManagerPluginAccessor; + +export type LowCodeRegisterOptions = { + autoInit?: boolean; +}; diff --git a/packages/designer/src/plugin/plugin.ts b/packages/designer/src/plugin/plugin.ts index 7ad0c1982..88c48e63c 100644 --- a/packages/designer/src/plugin/plugin.ts +++ b/packages/designer/src/plugin/plugin.ts @@ -2,8 +2,8 @@ import { ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager, - CompositeObject, -} from '@ali/lowcode-types'; +} from './plugin-types'; +import { CompositeObject } from '@ali/lowcode-types'; import { EventEmitter } from 'events'; import { getLogger, Logger, invariant } from '../utils'; @@ -50,30 +50,36 @@ export class LowCodePlugin implements ILowCodePlugin { } on(event: string | symbol, listener: (...args: any[]) => void): any { - return this.emitter.on(event, listener); + this.emitter.on(event, listener); + return () => { + this.emitter.off(event, listener); + }; } emit(event: string | symbol, ...args: any[]) { return this.emitter.emit(event, ...args); } - off(event: string | symbol, listener: (...args: any[]) => void): any { - return this.emitter.off(event, listener); - } - removeAllListeners(event: string | symbol): any { return this.emitter.removeAllListeners(event); } - async init() { + isInited() { + return this._inited; + } + + async init(forceInit?: boolean) { + if (this._inited && !forceInit) return; this.logger.log('method init called'); await this.config.init?.call(undefined); this._inited = true; } async destroy() { + if (!this._inited) return; this.logger.log('method destroy called'); await this.config?.destroy?.call(undefined); + this._inited = false; } setDisabled(flag = true) { @@ -93,7 +99,7 @@ export class LowCodePlugin implements ILowCodePlugin { }); } - dispose() { - return this.manager.delete(this.name); + async dispose() { + await this.manager.delete(this.name); } } diff --git a/packages/designer/tests/plugin/plugin-manager.test.ts b/packages/designer/tests/plugin/plugin-manager.test.ts new file mode 100644 index 000000000..85457075a --- /dev/null +++ b/packages/designer/tests/plugin/plugin-manager.test.ts @@ -0,0 +1,172 @@ +import '../fixtures/window'; +import { Editor } from '@ali/lowcode-editor-core'; +import { LowCodePluginManager } from '../../src/plugin/plugin-manager'; +import { ILowCodePluginContext, ILowCodePluginManager } from '../../src/plugin/plugin-types'; + +const editor = new Editor(); + +describe('plugin 测试', () => { + let pluginManager: ILowCodePluginManager; + beforeEach(() => { + pluginManager = new LowCodePluginManager(editor).toProxy(); + }); + afterEach(() => { + pluginManager.dispose(); + }); + + it('注册插件,插件参数生成函数能被调用,且能拿到正确的 ctx / options', () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + mockFn(ctx, options); + return { + name: 'demo1', + }; + }, { test: 1 }); + + const [expectedCtx, expectedOptions] = mockFn.mock.calls[0]; + expect(expectedCtx).toHaveProperty('designer'); + expect(expectedCtx).toHaveProperty('designerCabin'); + expect(expectedCtx).toHaveProperty('editor'); + expect(expectedCtx).toHaveProperty('hotkey'); + expect(expectedCtx).toHaveProperty('plugins'); + expect(expectedCtx).toHaveProperty('skeleton'); + expect(expectedCtx).toHaveProperty('logger'); + expect(expectedOptions).toEqual({ test: 1 }); + }); + + it('注册插件,调用插件 init 方法', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + init: mockFn, + exports() { + return { + x: 1, + y: 2, + } + } + }; + }, { test: 1 }); + await pluginManager.init(); + expect(pluginManager.size).toBe(1); + expect(pluginManager.has('demo1')).toBeTruthy(); + expect(pluginManager.get('demo1')!.isInited()).toBeTruthy(); + expect(pluginManager.demo1).toBeTruthy(); + expect(pluginManager.demo1.x).toBe(1); + expect(pluginManager.demo1.y).toBe(2); + expect(mockFn).toHaveBeenCalled(); + }); + + it('注册插件,调用 setDisabled 方法', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + init: mockFn, + }; + }, { test: 1 }); + await pluginManager.init(); + expect(pluginManager.demo1).toBeTruthy(); + pluginManager.setDisabled('demo1', true); + expect(pluginManager.demo1).toBeUndefined(); + }); + + it('删除插件,调用插件 destory 方法', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + destroy: mockFn, + }; + }, { test: 1 }); + await pluginManager.init(); + await pluginManager.delete('demo1'); + expect(mockFn).toHaveBeenCalled(); + await pluginManager.delete('non-existing'); + }); + + it('dep 依赖', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + dep: ['demo2'], + init: () => mockFn('demo1'), + }; + }); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo2', + init: () => mockFn('demo2'), + }; + }); + + await pluginManager.init(); + expect(mockFn).toHaveBeenNthCalledWith(1, 'demo2'); + expect(mockFn).toHaveBeenNthCalledWith(2, 'demo1'); + }); + + it('autoInit 功能', async () => { + const mockFn = jest.fn(); + await pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + init: mockFn, + }; + }, { test: 1 }, { autoInit: true }); + expect(mockFn).toHaveBeenCalled(); + }); + + it('插件不会重复 init,除非强制重新 init', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + init: mockFn, + }; + }, { test: 1 }); + await pluginManager.init(); + expect(mockFn).toHaveBeenCalledTimes(1); + + pluginManager.get('demo1')!.init(); + expect(mockFn).toHaveBeenCalledTimes(1); + + pluginManager.get('demo1')!.init(true); + expect(mockFn).toHaveBeenCalledTimes(2); + }); + + it('内部事件机制', async () => { + const mockFn = jest.fn(); + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + }; + }, { test: 1 }); + await pluginManager.init(); + const plugin = pluginManager.get('demo1')!; + + plugin.on('haha', mockFn); + plugin.emit('haha', 1, 2, 3); + + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toHaveBeenCalledWith(1, 2, 3); + + plugin.removeAllListeners('haha'); + plugin.emit('haha', 1, 2, 3); + expect(mockFn).toHaveBeenCalledTimes(1); + }); + + it('dispose 方法', async () => { + pluginManager.register((ctx: ILowCodePluginContext, options: any) => { + return { + name: 'demo1', + }; + }, { test: 1 }); + await pluginManager.init(); + const plugin = pluginManager.get('demo1')!; + await plugin.dispose(); + + expect(pluginManager.has('demo1')).toBeFalsy(); + }) +}); \ No newline at end of file diff --git a/packages/editor-preset-vision/package.json b/packages/editor-preset-vision/package.json index 14c973703..7dc168bc3 100644 --- a/packages/editor-preset-vision/package.json +++ b/packages/editor-preset-vision/package.json @@ -20,7 +20,7 @@ "dependencies": { "@ali/lowcode-designer": "^1.0.31", "@ali/lowcode-editor-core": "^1.0.31", - "@ali/lowcode-editor-setters": "^1.0.22", + "@ali/lowcode-editor-setters": "1.0.29", "@ali/lowcode-editor-skeleton": "^1.0.31", "@ali/lowcode-plugin-designer": "^1.0.31", "@ali/lowcode-plugin-outline-pane": "^1.0.31", diff --git a/packages/engine/package.json b/packages/engine/package.json index cb1ab489e..e21bf530f 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -24,6 +24,7 @@ "@ali/lowcode-plugin-designer": "^1.0.31", "@ali/lowcode-plugin-outline-pane": "^1.0.31", "@ali/lowcode-utils": "^1.0.31", + "@ali/lowcode-editor-setters": "1.0.29", "@ali/ve-i18n-util": "^2.0.0", "@ali/ve-icons": "^4.1.9", "@ali/ve-less-variables": "2.0.3", diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 927879331..64db05263 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -17,4 +17,3 @@ export * from './node'; export * from './transform-stage'; export * from './code-intermediate'; export * from './code-result'; -export * from './plugin'; diff --git a/packages/types/src/metadata.ts b/packages/types/src/metadata.ts index 4fdefa653..96299d66c 100644 --- a/packages/types/src/metadata.ts +++ b/packages/types/src/metadata.ts @@ -180,8 +180,8 @@ export interface Callbacks { onMouseDownHook?: (e: MouseEvent, currentNode: any) => any; onDblClickHook?: (e: MouseEvent, currentNode: any) => any; onClickHook?: (e: MouseEvent, currentNode: any) => any; - onLocateHook?: (e: any, currentNode: any) => any; - onAcceptHook?: (currentNode: any, locationData: any) => any; + // onLocateHook?: (e: any, currentNode: any) => any; + // onAcceptHook?: (currentNode: any, locationData: any) => any; onMoveHook?: (currentNode: any) => boolean; // thinkof 限制性拖拽 onChildMoveHook?: (childNode: any, currentNode: any) => boolean;