feat: 支持新版的 plugin 机制

fix: 兼容 vision 版本 select / radiogroup setter
chore: 优化工程化
This commit is contained in:
力皓 2020-12-29 10:50:19 +08:00
parent 55a9e26d1a
commit 1e8fc63321
18 changed files with 406 additions and 12 deletions

View File

@ -29,6 +29,7 @@
"**/*.md",
"**/test/**"
],
"message": "chore(release): publish %v",
"conventionalCommits": true
}
}

View File

@ -14,9 +14,9 @@
"clean": "rm -rf ./packages/*/lib ./packages/*/es ./packages/*/dist ./packages/*/build",
"lint": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet",
"lint:fix": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet --fix",
"pub": "lerna publish --force-publish --cd-version patch --message \"chore(release): publish %v\"",
"pub:prepatch": "lerna publish --force-publish --cd-version prepatch --npm-tag beta --preid beta --message \"chore(release): publish %v\"",
"pub:prerelease": "lerna publish --force-publish --cd-version prerelease --npm-tag beta --preid beta --message \"chore(release): publish %v\"",
"pub": "lerna publish --force-publish --cd-version patch",
"pub:prepatch": "lerna publish --force-publish --cd-version prepatch --npm-tag beta --preid beta",
"pub:prerelease": "lerna publish --force-publish --cd-version prerelease --npm-tag beta --preid beta",
"setup": "./scripts/setup.sh",
"start": "./scripts/start.sh",
"start:server": "./scripts/start-server.sh",

View File

@ -14,6 +14,8 @@ module.exports = {
'no-useless-constructor': 1,
'no-empty-function': 1,
'@typescript-eslint/member-ordering': 0,
'lines-between-class-members': 0
'lines-between-class-members': 0,
'no-await-in-loop': 0,
'no-plusplus': 0,
}
}

View File

@ -22,7 +22,8 @@
"enzyme-adapter-react-16": "^1.15.5",
"event": "^1.0.0",
"react": "^16",
"react-dom": "^16.7.0"
"react-dom": "^16.7.0",
"zen-logger": "^1.1.0"
},
"devDependencies": {
"@ali/lowcode-test-mate": "^1.0.1",

View File

@ -4,3 +4,4 @@ export * from './designer';
export * from './document';
export * from './project';
export * from './builtin-simulator';
export * from './plugin';

View File

@ -0,0 +1,3 @@
export * from './plugin-context';
export * from './plugin-manager';
export * from './plugin';

View File

@ -0,0 +1,62 @@
import { Editor, Hotkey, hotkey } from '@ali/lowcode-editor-core';
import { Skeleton } from '@ali/lowcode-editor-skeleton';
import { ComponentAction, ILowCodePluginConfig } from '@ali/lowcode-types';
import { getLogger, Logger } from '../utils';
import {
registerMetadataTransducer,
addBuiltinComponentAction,
removeBuiltinComponentAction,
MetadataTransducer,
} from '../component-meta';
import { Designer } from '../designer';
export interface IDesignerHelper {
registerMetadataTransducer: (transducer: MetadataTransducer, level = 100, id?: string) => void;
addBuiltinComponentAction: (action: ComponentAction) => void;
removeBuiltinComponentAction: (actionName: string) => void;
}
export interface ILowCodePluginContext {
skeleton: Skeleton;
designer: Designer;
editor: Editor;
hotkey: Hotkey;
logger: Logger;
plugins: LowCodePluginManager;
designerHelper: IDesignerHelper;
/**
*/
}
export default class PluginContext implements ILowCodePluginContext {
editor: Editor;
skeleton: Skeleton;
designer: Designer;
hotkey: Hotkey;
logger: Logger;
plugins: LowCodePluginManager;
designerHelper: IDesignerHelper;
constructor(editor: Editor, plugins: LowCodePluginManager) {
this.editor = editor;
this.designer = editor.get('designer');
this.skeleton = editor.get('skeleton');
this.hotkey = hotkey;
this.plugins = plugins;
this.designerHelper = this.createDesignerHelper();
}
private createDesignerHelper(): () => IDesignerHelper {
return {
registerMetadataTransducer,
addBuiltinComponentAction,
removeBuiltinComponentAction,
};
}
setLogger(config: ILowCodePluginConfig): (config: ILowCodePluginConfig) => void {
this.logger = getLogger({ level: 'log', bizName: `designer:plugin:${config.name}` });
}
}

View File

@ -0,0 +1,113 @@
import { Editor } from '@ali/lowcode-editor-core';
import { CompositeObject, ILowCodePlugin, ILowCodePluginConfig, ILowCodePluginManager } from '@ali/lowcode-types';
import { LowCodePlugin } from './plugin';
import LowCodePluginContext from './plugin-context';
import { getLogger, invariant } from '../utils';
import sequencify from './sequencify';
const logger = getLogger({ level: 'warn', bizName: 'designer:pluginManager' });
export class LowCodePluginManager implements ILowCodePluginManager {
private plugins: ILowCodePlugin[] = [];
private pluginsMap: Map<string, ILowCodePlugin> = new Map();
private editor: Editor;
constructor(editor: Editor) {
this.editor = editor;
}
private _getLowCodePluginContext() {
return new LowCodePluginContext(this.editor, this);
}
register(
pluginConfig: (ctx: ILowCodePluginContext, options: CompositeObject) => ILowCodePluginConfig,
options: CompositeObject,
): void {
const ctx = this._getLowCodePluginContext();
const config = pluginConfig(ctx, options);
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);
this.plugins.push(plugin);
this.pluginsMap.set(plugin.name, plugin);
logger.log('plugin registered with config:', config, ', options:', options);
}
get(pluginName: string): ILowCodePlugin {
return this.pluginsMap.get(pluginName);
}
getAll(): ILowCodePlugin[] {
return this.plugins;
}
has(pluginName: string): boolean {
return this.pluginsMap.has(pluginName);
}
async delete(pluginName: string): boolean {
const idx = this.plugins.findIndex(plugin => plugin.name === pluginName);
if (idx < -1) return;
const plugin = this.plugins[idx];
await plugin.destroy();
this.plugins.splice(idx, 1);
return this.pluginsMap.delete(pluginName);
}
async init() {
const pluginNames = [];
const pluginObj = {};
this.plugins.forEach(plugin => {
pluginNames.push(plugin.name);
pluginObj[plugin.name] = plugin;
});
const { missingTasks, sequence } = sequencify(pluginObj, pluginNames);
invariant(!missingTasks.length, 'plugin dependency missing', missingTasks);
logger.log('load plugin sequence:', sequence);
for (const pluginName of sequence) {
await this.pluginsMap.get(pluginName).init();
}
}
async destroy() {
for (const plugin of this.plugins) {
await plugin.destroy();
}
}
get size() {
return this.pluginsMap.size;
}
toProxy() {
return new Proxy(this, {
get(target, prop, receiver) {
if (target.pluginsMap.has(prop)) {
// 禁用态的插件,直接返回 undefined
if (target.pluginsMap.get(prop).disabled) {
return undefined;
}
return target.pluginsMap.get(prop)?.toProxy();
}
return Reflect.get(target, prop, receiver);
},
});
}
setDisabled(pluginName: string, flag = true) {
logger.warn(`plugin:${pluginName} has been set disable:${flag}`);
this.pluginsMap.get(pluginName)?.setDisabled(flag);
}
async dispose() {
await this.destroy();
this.plugins = [];
this.pluginsMap.clear();
}
}

View File

@ -0,0 +1,91 @@
import {
ILowCodePlugin,
ILowCodePluginConfig,
ILowCodePluginManager,
CompositeObject,
} from '@ali/lowcode-types';
import { EventEmitter } from 'events';
import { getLogger, Logger, invariant } from '../utils';
export class LowCodePlugin implements ILowCodePlugin {
config: ILowCodePluginConfig;
logger: Logger;
private manager: ILowCodePluginManager;
private options?: CompositeObject;
private emiter: EventEmitter;
private _inited: boolean;
/**
* disabled
*/
private _disabled: boolean;
constructor(
manager: ILowCodePluginManager,
config: ILowCodePluginConfig = {},
options: CompositeObject = {},
) {
this.manager = manager;
this.config = config;
this.options = options;
this.emiter = new EventEmitter();
this.logger = getLogger({ level: 'log', bizName: `designer:plugin:${config.name}` });
}
get name() {
return this.config.name;
}
get dep() {
return this.config.dep || [];
}
get disabled() {
return this._disabled;
}
on(...args) {
return this.emiter.on(...args);
}
emit(...args) {
return this.emiter.emit(...args);
}
async init() {
this.logger.log('method init called');
await this.config.init?.call();
this._inited = true;
}
async destroy() {
this.logger.log('method destroy called');
await this.config.destroy?.call();
}
private setDisabled(flag = true) {
this._disabled = flag;
}
toProxy() {
invariant(this._inited, 'Could not call toProxy before init');
const exports = this.config.exports?.();
return new Proxy(this, {
get(target, prop, receiver) {
if (hasOwnProperty.call(exports, prop)) {
return exports[prop];
}
return Reflect.get(target, prop, receiver);
},
});
}
dispose() {
return this.manager.delete(this.name);
}
}

View File

@ -0,0 +1,40 @@
function sequence(tasks, names, results, missing, recursive, nest) {
names.forEach((name) => {
if (results.indexOf(name) !== -1) {
return; // de-dup results
}
const node = tasks[name];
if (!node) {
missing.push(name);
} else if (nest.indexOf(name) > -1) {
nest.push(name);
recursive.push(nest.slice(0));
nest.pop(name);
} else if (node.dep.length) {
nest.push(name);
sequence(tasks, node.dep, results, missing, recursive, nest); // recurse
nest.pop(name);
}
results.push(name);
});
}
// tasks: object with keys as task names
// names: array of task names
export default function (tasks, names) {
let results = []; // the final sequence
const missing = []; // missing tasks
const recursive = []; // recursive task dependencies
sequence(tasks, names, results, missing, recursive, []);
if (missing.length || recursive.length) {
results = []; // results are incomplete at best, completely wrong at worst, remove them to avoid confusion
}
return {
sequence: results,
missingTasks: missing,
recursiveDependencies: recursive,
};
}

View File

@ -0,0 +1,4 @@
export * from './invariant';
export * from './slot';
export * from './tree';
export * from './logger';

View File

@ -0,0 +1,5 @@
export function invariant(check: any, message: string, thing?: any) {
if (!check) {
throw new Error('[designer] Invariant failed: ' + message + (thing ? ` in '${thing}'` : ''));
}
}

View File

@ -0,0 +1,7 @@
import Logger, { Level } from 'zen-logger';
export { Logger };
export function getLogger(config: { level: Level, bizName: string }): Logger {
return new Logger(config);
}

View File

@ -1,7 +1,7 @@
import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
import { isPlainObject, hasOwnProperty, cloneDeep, isI18NObject, isUseI18NSetter, convertToI18NObject, isString } from '@ali/lowcode-utils';
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, LiveEditing, TransformStage, Node, getConvertedExtraKey } from '@ali/lowcode-designer';
import { Designer, LiveEditing, TransformStage, Node, getConvertedExtraKey, LowCodePluginManager } from '@ali/lowcode-designer';
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
import bus from './bus';
import { VE_EVENTS } from './base/const';
@ -35,6 +35,9 @@ export const designer = new Designer({ editor });
editor.set(Designer, designer);
editor.set('designer', designer);
export const plugins = (new LowCodePluginManager(editor)).toProxy();
editor.set('plugins', plugins);
designer.project.onCurrentDocumentChange((doc) => {
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
editor.set('currentDocument', doc);

View File

@ -9,12 +9,13 @@ import {
registerMetadataTransducer,
addBuiltinComponentAction,
removeBuiltinComponentAction,
modifyBuiltinComponentAction,
ILowCodePluginContext,
// modifyBuiltinComponentAction,
} from '@ali/lowcode-designer';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
import Bus from './bus';
import { skeleton, designer, editor } from './editor';
import { skeleton, designer, editor, plugins } from './editor';
import { Workbench } from '@ali/lowcode-editor-skeleton';
import Panes from './panes';
import Exchange from './exchange';
@ -37,13 +38,14 @@ import '@ali/lowcode-editor-setters';
import './vision.less';
function init(container?: Element) {
async function init(container?: Element) {
if (!container) {
container = document.createElement('div');
document.body.appendChild(container);
}
container.id = 'engine';
await plugins.init();
render(
createElement(Workbench, {
skeleton,
@ -125,6 +127,7 @@ const VisualEngine = {
logger,
Symbols,
registerMetadataTransducer,
plugins,
// Flags,
};
@ -177,12 +180,13 @@ export {
logger,
Symbols,
registerMetadataTransducer,
plugins,
};
const version = '6.0.0 (LowcodeEngine 0.9.32)';
const version = '1.0.28';
console.log(
`%c VisionEngine %c v${version} `,
`%c AliLowCodeEngine %c v${version} `,
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;',
'padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e; font-weight: bold;',
);

View File

@ -69,7 +69,7 @@ function propTypeToSetter(propType: PropType): SetterType {
const componentName = dataSource.length >= 4 ? 'SelectSetter' : 'RadioGroupSetter';
return {
componentName,
props: { dataSource },
props: { dataSource, options: dataSource },
isRequired,
initialValue: dataSource[0] ? dataSource[0].value : null,
};
@ -148,6 +148,8 @@ function propTypeToSetter(propType: PropType): SetterType {
},
isRequired,
};
default:
// do nothing
}
return {
componentName: 'MixedSetter',

View File

@ -17,3 +17,4 @@ export * from './node';
export * from './transform-stage';
export * from './code-intermediate';
export * from './code-result';
export * from './plugin';

View File

@ -0,0 +1,54 @@
import { CompositeObject } from '@ali/lowcode-types';
import Logger from 'zen-logger';
import { Skeleton } from '@ali/lowcode-editor-skeleton';
import { Editor, Hotkey } from '@ali/lowcode-editor-core';
export interface ILowCodePluginConfig {
manager: ILowCodePluginManager;
name: string;
dep: string[]; // 依赖插件名
init(): void;
destroy(): void;
exports(): CompositeObject;
}
export interface ILowCodePlugin {
name: string;
dep: string[];
disabled: boolean;
config: ILowCodePluginConfig;
logger: Logger;
emit(): void;
on(): void;
init(): void;
destroy(): void;
toProxy(): any;
setDisabled(flag: boolean): void;
}
export interface ILowCodePluginContext {
skeleton: Skeleton;
editor: Editor;
plugins: ILowCodePluginManager;
hotkey: Hotkey;
logger: Logger;
/**
*/
}
export interface ILowCodePluginManager {
register(
pluginConfig: (ctx: ILowCodePluginContext, options: CompositeObject) => ILowCodePluginConfig,
options: CompositeObject,
): void;
get(pluginName: string): ILowCodePlugin;
getAll(): ILowCodePlugin[];
has(pluginName: string): boolean;
delete(pluginName: string): boolean;
setDisabled(pluginName: string, flag: boolean): void;
dispose(): void;
/**
disable / enable
*/
}