2022-12-05 11:47:04 +08:00
import { engineConfig } from '@alilc/lowcode-editor-core' ;
2022-01-25 15:30:17 +08:00
import { getLogger } from '@alilc/lowcode-utils' ;
import {
2022-12-22 12:10:00 +08:00
ILowCodePluginRuntime ,
2022-01-25 15:30:17 +08:00
ILowCodePluginManager ,
IPluginContextOptions ,
PluginPreference ,
2022-12-05 11:47:04 +08:00
ILowCodePluginContextApiAssembler ,
2022-01-25 15:30:17 +08:00
} from './plugin-types' ;
2022-12-22 12:10:00 +08:00
import { filterValidOptions , isLowCodeRegisterOptions } from './plugin-utils' ;
import { LowCodePluginRuntime } from './plugin' ;
2022-12-05 11:47:04 +08:00
// eslint-disable-next-line import/no-named-as-default
2022-01-25 15:30:17 +08:00
import LowCodePluginContext from './plugin-context' ;
import { invariant } from '../utils' ;
import sequencify from './sequencify' ;
import semverSatisfies from 'semver/functions/satisfies' ;
2022-12-22 12:10:00 +08:00
import {
2022-12-25 13:49:05 +08:00
IPublicTypePluginRegisterOptions ,
2022-12-22 12:10:00 +08:00
IPublicTypePreferenceValueType ,
IPublicTypePlugin ,
} from '@alilc/lowcode-types' ;
2022-01-25 15:30:17 +08:00
const logger = getLogger ( { level : 'warn' , bizName : 'designer:pluginManager' } ) ;
2022-12-22 12:10:00 +08:00
// 保留的事件前缀
2024-03-27 10:39:41 +08:00
const RESERVED_EVENT_PREFIX = [
'designer' ,
'editor' ,
'skeleton' ,
'renderer' ,
'render' ,
'utils' ,
'plugin' ,
'engine' ,
'editor-core' ,
'engine-core' ,
'plugins' ,
'event' ,
'events' ,
'log' ,
'logger' ,
'ctx' ,
'context' ,
] ;
2022-12-22 12:10:00 +08:00
2022-01-25 15:30:17 +08:00
export class LowCodePluginManager implements ILowCodePluginManager {
2022-12-22 12:10:00 +08:00
private plugins : ILowCodePluginRuntime [ ] = [ ] ;
2022-01-25 15:30:17 +08:00
2022-12-22 12:10:00 +08:00
pluginsMap : Map < string , ILowCodePluginRuntime > = new Map ( ) ;
pluginContextMap : Map < string , LowCodePluginContext > = new Map ( ) ;
2022-01-25 15:30:17 +08:00
private pluginPreference? : PluginPreference = new Map ( ) ;
2022-12-05 11:47:04 +08:00
contextApiAssembler : ILowCodePluginContextApiAssembler ;
2024-03-27 10:39:41 +08:00
constructor (
contextApiAssembler : ILowCodePluginContextApiAssembler ,
readonly viewName = 'global' ,
) {
2022-12-05 11:47:04 +08:00
this . contextApiAssembler = contextApiAssembler ;
2022-01-25 15:30:17 +08:00
}
2022-12-22 12:10:00 +08:00
_getLowCodePluginContext = ( options : IPluginContextOptions ) = > {
const { pluginName } = options ;
let context = this . pluginContextMap . get ( pluginName ) ;
if ( ! context ) {
context = new LowCodePluginContext ( options , this . contextApiAssembler ) ;
this . pluginContextMap . set ( pluginName , context ) ;
}
return context ;
} ;
2022-01-25 15:30:17 +08:00
isEngineVersionMatched ( versionExp : string ) : boolean {
const engineVersion = engineConfig . get ( 'ENGINE_VERSION' ) ;
2022-05-27 10:25:30 +08:00
// ref: https://github.com/npm/node-semver#functions
// 1.0.1-beta should match '^1.0.0'
return semverSatisfies ( engineVersion , versionExp , { includePrerelease : true } ) ;
2022-01-25 15:30:17 +08:00
}
2022-03-30 15:08:26 +08:00
/ * *
* register a plugin
* @param pluginConfigCreator - a creator function which returns the plugin config
* @param options - the plugin options
* @param registerOptions - the plugin register options
* /
2022-01-25 15:30:17 +08:00
async register (
2022-12-22 12:10:00 +08:00
pluginModel : IPublicTypePlugin ,
2022-03-30 15:08:26 +08:00
options? : any ,
2022-12-25 13:49:05 +08:00
registerOptions? : IPublicTypePluginRegisterOptions ,
2022-01-25 15:30:17 +08:00
) : Promise < void > {
2022-03-30 15:08:26 +08:00
// registerOptions maybe in the second place
if ( isLowCodeRegisterOptions ( options ) ) {
registerOptions = options ;
options = { } ;
}
2022-12-22 12:10:00 +08:00
let { pluginName , meta = { } } = pluginModel ;
const { preferenceDeclaration , engines } = meta ;
// filter invalid eventPrefix
const { eventPrefix } = meta ;
const isReservedPrefix = RESERVED_EVENT_PREFIX . find ( ( item ) = > item === eventPrefix ) ;
if ( isReservedPrefix ) {
meta . eventPrefix = undefined ;
2024-03-27 10:39:41 +08:00
logger . warn (
` plugin ${ pluginName } is trying to use ${ eventPrefix } as event prefix, which is a reserved event prefix, please use another one ` ,
) ;
2022-12-22 12:10:00 +08:00
}
const ctx = this . _getLowCodePluginContext ( { pluginName , meta } ) ;
2024-03-27 10:39:41 +08:00
const customFilterValidOptions = engineConfig . get (
'customPluginFilterOptions' ,
filterValidOptions ,
) ;
2023-06-07 17:20:28 +08:00
const pluginTransducer = engineConfig . get ( 'customPluginTransducer' , null ) ;
2024-03-27 10:39:41 +08:00
const newPluginModel = pluginTransducer
? await pluginTransducer ( pluginModel , ctx , options )
: pluginModel ;
const newOptions = customFilterValidOptions (
options ,
newPluginModel . meta ? . preferenceDeclaration ,
) ;
2023-06-07 17:20:28 +08:00
const config = newPluginModel ( ctx , newOptions ) ;
2022-03-30 15:08:26 +08:00
// compat the legacy way to declare pluginName
// @ts-ignore
pluginName = pluginName || config . name ;
2024-03-27 10:39:41 +08:00
invariant ( pluginName , 'pluginConfigCreator.pluginName required' , config ) ;
2022-01-25 15:30:17 +08:00
2024-03-27 10:39:41 +08:00
ctx . setPreference ( pluginName , preferenceDeclaration ! ) ;
2022-01-25 15:30:17 +08:00
2022-03-30 15:08:26 +08:00
const allowOverride = registerOptions ? . override === true ;
2022-01-25 15:30:17 +08:00
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 ) ) {
2024-03-27 10:39:41 +08:00
throw new Error (
` plugin ${ pluginName } skipped, engine check failed, current engine version is ${ engineConfig . get ( 'ENGINE_VERSION' ) } , meta.engines.lowcodeEngine is ${ engineVersionExp } ` ,
) ;
2022-01-25 15:30:17 +08:00
}
2022-12-22 12:10:00 +08:00
const plugin = new LowCodePluginRuntime ( pluginName , this , config , meta ) ;
// support initialization of those plugins which registered
// after normal initialization by plugin-manager
2022-03-30 15:08:26 +08:00
if ( registerOptions ? . autoInit ) {
2022-01-25 15:30:17 +08:00
await plugin . init ( ) ;
}
this . plugins . push ( plugin ) ;
this . pluginsMap . set ( pluginName , plugin ) ;
2022-12-22 12:10:00 +08:00
logger . log ( ` plugin registered with pluginName: ${ pluginName } , config: ` , config , 'meta:' , meta ) ;
2022-01-25 15:30:17 +08:00
}
2022-12-22 12:10:00 +08:00
get ( pluginName : string ) : ILowCodePluginRuntime | undefined {
2022-01-25 15:30:17 +08:00
return this . pluginsMap . get ( pluginName ) ;
}
2022-12-22 12:10:00 +08:00
getAll ( ) : ILowCodePluginRuntime [ ] {
2022-01-25 15:30:17 +08:00
return this . plugins ;
}
has ( pluginName : string ) : boolean {
return this . pluginsMap . has ( pluginName ) ;
}
async delete ( pluginName : string ) : Promise < boolean > {
2023-05-04 15:28:09 +08:00
const plugin = this . plugins . find ( ( { name } ) = > name === pluginName ) ;
if ( ! plugin ) return false ;
2022-01-25 15:30:17 +08:00
await plugin . destroy ( ) ;
2023-05-04 15:28:09 +08:00
const idx = this . plugins . indexOf ( plugin ) ;
2022-01-25 15:30:17 +08:00
this . plugins . splice ( idx , 1 ) ;
return this . pluginsMap . delete ( pluginName ) ;
}
async init ( pluginPreference? : PluginPreference ) {
const pluginNames : string [ ] = [ ] ;
2022-12-22 12:10:00 +08:00
const pluginObj : { [ name : string ] : ILowCodePluginRuntime } = { } ;
2022-01-25 15:30:17 +08:00
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 ( ) ;
2022-06-21 21:01:50 +08:00
} catch ( e ) /* istanbul ignore next */ {
2022-01-25 15:30:17 +08:00
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 ;
}
2024-03-27 10:39:41 +08:00
getPluginPreference (
pluginName : string ,
) : Record < string , IPublicTypePreferenceValueType > | null | undefined {
2022-01-25 15:30:17 +08:00
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 ) ;
} ,
} ) ;
}
2022-06-21 21:01:50 +08:00
/* istanbul ignore next */
2022-01-25 15:30:17 +08:00
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 ( ) ;
}
}