forked from alibaba/lowcode-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin-manager.ts
More file actions
200 lines (174 loc) · 6.45 KB
/
plugin-manager.ts
File metadata and controls
200 lines (174 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { Editor, engineConfig } from '@alilc/lowcode-editor-core';
import { getLogger } from '@alilc/lowcode-utils';
import {
ILowCodePlugin,
ILowCodePluginConfig,
ILowCodePluginManager,
ILowCodePluginContext,
ILowCodeRegisterOptions,
IPluginContextOptions,
PreferenceValueType,
ILowCodePluginConfigMeta,
PluginPreference,
ILowCodePluginPreferenceDeclaration,
isLowCodeRegisterOptions,
} from './plugin-types';
import { filterValidOptions } from './plugin-utils';
import { LowCodePlugin } from './plugin';
import LowCodePluginContext from './plugin-context';
import { invariant } from '../utils';
import sequencify from './sequencify';
import semverSatisfies from 'semver/functions/satisfies';
const logger = getLogger({ level: 'warn', bizName: 'designer:pluginManager' });
export class LowCodePluginManager implements ILowCodePluginManager {
private plugins: ILowCodePlugin[] = [];
private pluginsMap: Map<string, ILowCodePlugin> = new Map();
private pluginPreference?: PluginPreference = new Map();
private editor: Editor;
constructor(editor: Editor) {
this.editor = editor;
}
private _getLowCodePluginContext(options: IPluginContextOptions) {
return new LowCodePluginContext(this, options);
}
isEngineVersionMatched(versionExp: string): boolean {
const engineVersion = engineConfig.get('ENGINE_VERSION');
return semverSatisfies(engineVersion, versionExp);
}
/**
* register a plugin
* @param pluginConfigCreator - a creator function which returns the plugin config
* @param options - the plugin options
* @param registerOptions - the plugin register options
*/
async register(
pluginConfigCreator: (ctx: ILowCodePluginContext, options: any) => ILowCodePluginConfig,
options?: any,
registerOptions?: ILowCodeRegisterOptions,
): Promise<void> {
// registerOptions maybe in the second place
if (isLowCodeRegisterOptions(options)) {
registerOptions = options;
options = {};
}
let { pluginName, meta = {} } = pluginConfigCreator as any;
const { preferenceDeclaration, engines } = meta as ILowCodePluginConfigMeta;
const ctx = this._getLowCodePluginContext({ pluginName });
const customFilterValidOptions = engineConfig.get('customPluginFilterOptions', filterValidOptions);
const config = pluginConfigCreator(ctx, customFilterValidOptions(options, preferenceDeclaration!));
// compat the legacy way to declare pluginName
// @ts-ignore
pluginName = pluginName || config.name;
invariant(
pluginName,
'pluginConfigCreator.pluginName required',
config,
);
ctx.setPreference(pluginName, (preferenceDeclaration as ILowCodePluginPreferenceDeclaration));
const allowOverride = registerOptions?.override === true;
if (this.pluginsMap.has(pluginName)) {
if (!allowOverride) {
throw new Error(`Plugin with name ${pluginName} exists`);
} else {
// clear existing plugin
const originalPlugin = this.pluginsMap.get(pluginName);
logger.log(
'plugin override, originalPlugin with name ',
pluginName,
' will be destroyed, config:',
originalPlugin?.config,
);
originalPlugin?.destroy();
this.pluginsMap.delete(pluginName);
}
}
const engineVersionExp = engines && engines.lowcodeEngine;
if (engineVersionExp && !this.isEngineVersionMatched(engineVersionExp)) {
throw new Error(`plugin ${pluginName} skipped, engine check failed, current engine version is ${engineConfig.get('ENGINE_VERSION')}, meta.engines.lowcodeEngine is ${engineVersionExp}`);
}
const plugin = new LowCodePlugin(pluginName, this, config, meta);
// support initialization of those plugins which registered after normal initialization by plugin-manager
if (registerOptions?.autoInit) {
await plugin.init();
}
this.plugins.push(plugin);
this.pluginsMap.set(pluginName, plugin);
logger.log(`plugin registered with pluginName: ${pluginName}, config: ${config}, meta: ${meta}`);
}
get(pluginName: string): ILowCodePlugin | undefined {
return this.pluginsMap.get(pluginName);
}
getAll(): ILowCodePlugin[] {
return this.plugins;
}
has(pluginName: string): boolean {
return this.pluginsMap.has(pluginName);
}
async delete(pluginName: string): Promise<boolean> {
const idx = this.plugins.findIndex((plugin) => plugin.name === pluginName);
if (idx === -1) return false;
const plugin = this.plugins[idx];
await plugin.destroy();
this.plugins.splice(idx, 1);
return this.pluginsMap.delete(pluginName);
}
async init(pluginPreference?: PluginPreference) {
const pluginNames: string[] = [];
const pluginObj: { [name: string]: ILowCodePlugin } = {};
this.pluginPreference = pluginPreference;
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) {
try {
await this.pluginsMap.get(pluginName)!.init();
} catch (e) {
logger.error(
`Failed to init plugin:${pluginName}, it maybe affect those plugins which depend on this.`,
);
logger.error(e);
}
}
}
async destroy() {
for (const plugin of this.plugins) {
await plugin.destroy();
}
}
get size() {
return this.pluginsMap.size;
}
getPluginPreference(pluginName: string): Record<string, PreferenceValueType> | null | undefined {
if (!this.pluginPreference) {
return null;
}
return this.pluginPreference.get(pluginName);
}
toProxy() {
return new Proxy(this, {
get(target, prop, receiver) {
if (target.pluginsMap.has(prop as string)) {
// 禁用态的插件,直接返回 undefined
if (target.pluginsMap.get(prop as string)!.disabled) {
return undefined;
}
return target.pluginsMap.get(prop as string)?.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();
}
}