mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-13 04:03:07 +00:00
refactor: renderer
This commit is contained in:
parent
ddc2473f98
commit
39be950600
@ -1,8 +1,8 @@
|
|||||||
import { signal, uniqueId, ComponentTreeRoot } from '@alilc/lowcode-shared';
|
import { Signals, uniqueId, ComponentTree } from '@alilc/lowcode-shared';
|
||||||
import { type Project } from '../project';
|
import { type Project } from '../project';
|
||||||
import { History } from './history';
|
import { History } from './history';
|
||||||
|
|
||||||
export interface DocumentSchema extends ComponentTreeRoot {
|
export interface DocumentSchema extends ComponentTree {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export interface DocumentModel {
|
|||||||
|
|
||||||
export function createDocumentModel(project: Project) {
|
export function createDocumentModel(project: Project) {
|
||||||
const uid = uniqueId('doc');
|
const uid = uniqueId('doc');
|
||||||
const currentDocumentSchema = signal<DocumentSchema>({});
|
const currentDocumentSchema = Signals.signal<DocumentSchema>({});
|
||||||
|
|
||||||
const documentHistory = new History(currentDocumentSchema, () => {});
|
const documentHistory = new History(currentDocumentSchema, () => {});
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,3 @@ export * from './configuration';
|
|||||||
export * from './extension/extension';
|
export * from './extension/extension';
|
||||||
export * from './resource';
|
export * from './resource';
|
||||||
export * from './command';
|
export * from './command';
|
||||||
|
|
||||||
// test
|
|
||||||
export * from './extension/registry';
|
|
||||||
export * from './main';
|
|
||||||
export * from './keybinding/keybindingRegistry';
|
|
||||||
export * from './keybinding/keybindingParser';
|
|
||||||
export * from './keybinding/keybindingResolver';
|
|
||||||
export * from './keybinding/keybindings';
|
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
import { createDecorator, Barrier } from '@alilc/lowcode-shared';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期阶段
|
||||||
|
*/
|
||||||
|
export const enum LifecyclePhase {
|
||||||
|
/**
|
||||||
|
* 开始
|
||||||
|
*/
|
||||||
|
Starting = 1,
|
||||||
|
/**
|
||||||
|
* 已就绪
|
||||||
|
*/
|
||||||
|
Ready = 2,
|
||||||
|
/**
|
||||||
|
* 销毁中
|
||||||
|
*/
|
||||||
|
Destroying = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILifeCycleService {
|
||||||
|
/**
|
||||||
|
* A flag indicating in what phase of the lifecycle we currently are.
|
||||||
|
*/
|
||||||
|
phase: LifecyclePhase;
|
||||||
|
|
||||||
|
setPhase(phase: LifecyclePhase): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that resolves when a certain lifecycle phase
|
||||||
|
* has started.
|
||||||
|
*/
|
||||||
|
when(phase: LifecyclePhase): Promise<void>;
|
||||||
|
|
||||||
|
onWillDestory(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ILifeCycleService = createDecorator<ILifeCycleService>('lifeCycleService');
|
||||||
|
|
||||||
|
export class LifeCycleService implements ILifeCycleService {
|
||||||
|
private readonly phaseWhen = new Map<LifecyclePhase, Barrier>();
|
||||||
|
|
||||||
|
private _phase = LifecyclePhase.Starting;
|
||||||
|
|
||||||
|
get phase(): LifecyclePhase {
|
||||||
|
return this._phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPhase(value: LifecyclePhase) {
|
||||||
|
if (value < this._phase) {
|
||||||
|
throw new Error('Lifecycle cannot go backwards');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._phase === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._phase = value;
|
||||||
|
|
||||||
|
const barrier = this.phaseWhen.get(this._phase);
|
||||||
|
if (barrier) {
|
||||||
|
barrier.open();
|
||||||
|
this.phaseWhen.delete(this._phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async when(phase: LifecyclePhase): Promise<void> {
|
||||||
|
if (phase <= this._phase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let barrier = this.phaseWhen.get(phase);
|
||||||
|
if (!barrier) {
|
||||||
|
barrier = new Barrier();
|
||||||
|
this.phaseWhen.set(phase, barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
await barrier.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
onWillDestory(): void {}
|
||||||
|
}
|
||||||
@ -1,24 +1,31 @@
|
|||||||
import { InstantiationService } from '@alilc/lowcode-shared';
|
import { InstantiationService } from '@alilc/lowcode-shared';
|
||||||
import { IWorkbenchService } from './workbench';
|
import { IWorkbenchService } from './workbench';
|
||||||
import { IConfigurationService } from './configuration';
|
import { ConfigurationService, IConfigurationService } from './configuration';
|
||||||
|
|
||||||
class MainApplication {
|
class TestMainApplication {
|
||||||
constructor() {
|
constructor() {
|
||||||
console.log('main application');
|
console.log('main application');
|
||||||
}
|
}
|
||||||
|
|
||||||
async main() {
|
async main() {
|
||||||
const instantiationService = new InstantiationService();
|
|
||||||
const configurationService = instantiationService.get(IConfigurationService);
|
|
||||||
const workbench = instantiationService.get(IWorkbenchService);
|
const workbench = instantiationService.get(IWorkbenchService);
|
||||||
|
|
||||||
await configurationService.initialize();
|
await configurationService.initialize();
|
||||||
workbench.initialize();
|
workbench.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createServices() {
|
||||||
|
const instantiationService = new InstantiationService();
|
||||||
|
|
||||||
|
const configurationService = new ConfigurationService();
|
||||||
|
instantiationService.container.set(IConfigurationService, configurationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createLowCodeEngineApp(): Promise<MainApplication> {
|
initServices() {}
|
||||||
const app = new MainApplication();
|
}
|
||||||
|
|
||||||
|
export async function createLowCodeEngineApp() {
|
||||||
|
const app = new TestMainApplication();
|
||||||
|
|
||||||
await app.main();
|
await app.main();
|
||||||
|
|
||||||
|
|||||||
5
packages/engine-core/src/theme/theme.ts
Normal file
5
packages/engine-core/src/theme/theme.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface ITheme {
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
21
packages/engine-core/src/theme/themeService.ts
Normal file
21
packages/engine-core/src/theme/themeService.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Disposable, Events, type IDisposable, createDecorator } from '@alilc/lowcode-shared';
|
||||||
|
import { type ITheme } from './theme';
|
||||||
|
|
||||||
|
export interface IThemeService extends IDisposable {
|
||||||
|
getTheme(): ITheme;
|
||||||
|
|
||||||
|
onDidColorThemeChange: Events.Event<ITheme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IThemeService = createDecorator<IThemeService>('themeService');
|
||||||
|
|
||||||
|
export class ThemeService extends Disposable implements IThemeService {
|
||||||
|
private _activeTheme: ITheme;
|
||||||
|
|
||||||
|
private _onDidColorThemeChange = this._addDispose(new Events.Emitter<ITheme>());
|
||||||
|
onDidColorThemeChange = this._onDidColorThemeChange.event;
|
||||||
|
|
||||||
|
getTheme(): ITheme {
|
||||||
|
return this._activeTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { InstantiationService } from '@alilc/lowcode-shared';
|
import { InstantiationService } from '@alilc/lowcode-shared';
|
||||||
import { IConfigurationService, IWorkspaceService } from '@alilc/lowcode-engine-core';
|
import { IConfigurationService } from '@alilc/lowcode-engine-core';
|
||||||
|
|
||||||
export class MainEngineApplication {
|
export class MainEngineApplication {
|
||||||
instantiationService = new InstantiationService();
|
instantiationService = new InstantiationService();
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { type Event, type EventListener, createDecorator } from '@alilc/lowcode-shared';
|
|
||||||
|
|
||||||
export interface ITheme {
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IThemeService {
|
|
||||||
getTheme(): ITheme;
|
|
||||||
|
|
||||||
onDidColorThemeChange: Event<[ITheme]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IThemeService = createDecorator<IThemeService>('themeService');
|
|
||||||
|
|
||||||
export class ThemeService implements IThemeService {
|
|
||||||
private activeTheme: ITheme;
|
|
||||||
|
|
||||||
getTheme(): ITheme {
|
|
||||||
return this.activeTheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
onDidColorThemeChange(listener: EventListener<[ITheme]>) {
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,60 +1,9 @@
|
|||||||
import { createRenderer } from '@alilc/lowcode-renderer-core';
|
import { App, type AppOptions } from '../app';
|
||||||
import { type Root, createRoot } from 'react-dom/client';
|
|
||||||
import { type IRendererContext, RendererContext, getRenderInstancesByAccessor } from './context';
|
|
||||||
import { ApplicationView, boosts } from '../app';
|
|
||||||
import { type ReactAppOptions } from './types';
|
|
||||||
|
|
||||||
export const createApp = async (options: ReactAppOptions) => {
|
export const createApp = async (options: AppOptions) => {
|
||||||
return createRenderer(async (service) => {
|
const app = new App(options);
|
||||||
const contextValue: IRendererContext = service.invokeFunction((accessor) => {
|
|
||||||
return {
|
await app.startup();
|
||||||
options,
|
|
||||||
...getRenderInstancesByAccessor(accessor),
|
return app;
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
contextValue.boostsManager.extend(boosts.toExpose());
|
|
||||||
|
|
||||||
let root: Root | undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
async mount(containerOrId) {
|
|
||||||
if (root) return;
|
|
||||||
|
|
||||||
const defaultId = contextValue.schema.get<string>('config.targetRootID', 'app');
|
|
||||||
const rootElement = normalizeContainer(containerOrId, defaultId);
|
|
||||||
|
|
||||||
root = createRoot(rootElement);
|
|
||||||
root.render(
|
|
||||||
<RendererContext.Provider value={contextValue}>
|
|
||||||
<ApplicationView />
|
|
||||||
</RendererContext.Provider>,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
unmount() {
|
|
||||||
if (root) {
|
|
||||||
root.unmount();
|
|
||||||
root = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeContainer(container: Element | string | undefined, defaultId: string): Element {
|
|
||||||
let result: Element | undefined = undefined;
|
|
||||||
|
|
||||||
if (typeof container === 'string') {
|
|
||||||
const el = document.getElementById(container);
|
|
||||||
if (el) result = el;
|
|
||||||
} else if (container instanceof window.Element) {
|
|
||||||
result = container;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
result = document.createElement('div');
|
|
||||||
result.id = defaultId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,53 +1,38 @@
|
|||||||
import { createRenderer } from '@alilc/lowcode-renderer-core';
|
import { type StringDictionary, type ComponentTree } from '@alilc/lowcode-shared';
|
||||||
import { type ComponentTreeRoot } from '@alilc/lowcode-shared';
|
import { CodeRuntime } from '@alilc/lowcode-renderer-core';
|
||||||
import { type FunctionComponent } from 'react';
|
import { FunctionComponent, ComponentType } from 'react';
|
||||||
import {
|
import {
|
||||||
type LowCodeComponentProps,
|
type LowCodeComponentProps,
|
||||||
createComponent as createSchemaComponent,
|
createComponent as createSchemaComponent,
|
||||||
} from '../runtime/createComponent';
|
type ComponentOptions as SchemaComponentOptions,
|
||||||
import { type IRendererContext, RendererContext, getRenderInstancesByAccessor } from './context';
|
reactiveStateFactory,
|
||||||
import { type ReactAppOptions } from './types';
|
} from '../runtime';
|
||||||
|
import { type ComponentsAccessor } from '../app';
|
||||||
|
|
||||||
interface Render {
|
export interface ComponentOptions extends SchemaComponentOptions {
|
||||||
toComponent(): FunctionComponent<LowCodeComponentProps>;
|
schema: ComponentTree;
|
||||||
|
componentsRecord: Record<string, ComponentType>;
|
||||||
|
codeScope?: StringDictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createComponent(options: ReactAppOptions) {
|
export function createComponent(
|
||||||
const creator = createRenderer<Render>((service) => {
|
options: ComponentOptions,
|
||||||
const contextValue: IRendererContext = service.invokeFunction((accessor) => {
|
): FunctionComponent<LowCodeComponentProps> {
|
||||||
return {
|
const { schema, componentsRecord, modelOptions, codeScope, ...componentOptions } = options;
|
||||||
options,
|
const codeRuntime = new CodeRuntime(codeScope);
|
||||||
...getRenderInstancesByAccessor(accessor),
|
const components: ComponentsAccessor = {
|
||||||
};
|
getComponent(componentName) {
|
||||||
});
|
return componentsRecord[componentName] as any;
|
||||||
|
|
||||||
const componentsTree = contextValue.schema.get<ComponentTreeRoot>('componentsTree.0');
|
|
||||||
|
|
||||||
if (!componentsTree) {
|
|
||||||
throw new Error('componentsTree is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const LowCodeComponent = createSchemaComponent(componentsTree, {
|
|
||||||
displayName: componentsTree.componentName,
|
|
||||||
...options.component,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Component(props: LowCodeComponentProps) {
|
|
||||||
return (
|
|
||||||
<RendererContext.Provider value={contextValue}>
|
|
||||||
<LowCodeComponent {...props} />
|
|
||||||
</RendererContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
toComponent() {
|
|
||||||
return Component;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LowCodeComponent = createSchemaComponent(schema, codeRuntime, components, {
|
||||||
|
...componentOptions,
|
||||||
|
modelOptions: {
|
||||||
|
...modelOptions,
|
||||||
|
stateCreator: modelOptions.stateCreator ?? reactiveStateFactory,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const render = await creator(options);
|
return LowCodeComponent;
|
||||||
|
|
||||||
return render.toComponent();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import {
|
|
||||||
IBoostsManager,
|
|
||||||
IComponentTreeModelService,
|
|
||||||
ILifeCycleService,
|
|
||||||
IPackageManagementService,
|
|
||||||
ISchemaService,
|
|
||||||
IExtensionHostService,
|
|
||||||
} from '@alilc/lowcode-renderer-core';
|
|
||||||
import { InstanceAccessor } from '@alilc/lowcode-shared';
|
|
||||||
import { createContext, useContext } from 'react';
|
|
||||||
import { type ReactAppOptions } from './types';
|
|
||||||
|
|
||||||
export interface IRendererContext {
|
|
||||||
readonly options: ReactAppOptions;
|
|
||||||
|
|
||||||
readonly schema: Omit<ISchemaService, 'initialize'>;
|
|
||||||
|
|
||||||
readonly packageManager: IPackageManagementService;
|
|
||||||
|
|
||||||
readonly boostsManager: IBoostsManager;
|
|
||||||
|
|
||||||
readonly componentTreeModel: IComponentTreeModelService;
|
|
||||||
|
|
||||||
readonly lifeCycle: ILifeCycleService;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getRenderInstancesByAccessor = (accessor: InstanceAccessor) => {
|
|
||||||
return {
|
|
||||||
schema: accessor.get(ISchemaService),
|
|
||||||
packageManager: accessor.get(IPackageManagementService),
|
|
||||||
boostsManager: accessor.get(IExtensionHostService).boostsManager,
|
|
||||||
componentTreeModel: accessor.get(IComponentTreeModelService),
|
|
||||||
lifeCycle: accessor.get(ILifeCycleService),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RendererContext = createContext<IRendererContext>(undefined!);
|
|
||||||
|
|
||||||
RendererContext.displayName = 'RendererContext';
|
|
||||||
|
|
||||||
export const useRendererContext = () => useContext(RendererContext);
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { type AppOptions } from '@alilc/lowcode-renderer-core';
|
|
||||||
import { type ComponentType } from 'react';
|
|
||||||
import { type ComponentOptions } from '../runtime/createComponent';
|
|
||||||
|
|
||||||
export interface ReactAppOptions extends AppOptions {
|
|
||||||
component?: Pick<
|
|
||||||
ComponentOptions,
|
|
||||||
'beforeElementCreate' | 'elementCreated' | 'componentRefAttached'
|
|
||||||
>;
|
|
||||||
faultComponent?: ComponentType<any>;
|
|
||||||
}
|
|
||||||
279
packages/react-renderer/src/app/app.ts
Normal file
279
packages/react-renderer/src/app/app.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
import {
|
||||||
|
InstantiationService,
|
||||||
|
BeanContainer,
|
||||||
|
CtorDescriptor,
|
||||||
|
Disposable,
|
||||||
|
} from '@alilc/lowcode-shared';
|
||||||
|
import {
|
||||||
|
CodeRuntimeService,
|
||||||
|
ICodeRuntimeService,
|
||||||
|
IExtensionHostService,
|
||||||
|
ExtensionHostService,
|
||||||
|
IPackageManagementService,
|
||||||
|
PackageManagementService,
|
||||||
|
ISchemaService,
|
||||||
|
SchemaService,
|
||||||
|
IRuntimeIntlService,
|
||||||
|
RuntimeIntlService,
|
||||||
|
IRuntimeUtilService,
|
||||||
|
RuntimeUtilService,
|
||||||
|
} from '@alilc/lowcode-renderer-core';
|
||||||
|
import { createRouter, type RouterOptions, type Router } from '@alilc/lowcode-renderer-router';
|
||||||
|
import { type ComponentType, createElement } from 'react';
|
||||||
|
import { type Root, createRoot } from 'react-dom/client';
|
||||||
|
import {
|
||||||
|
IReactRendererBoostsService,
|
||||||
|
ReactRendererBoostsService,
|
||||||
|
type ReactRendererBoostsApi,
|
||||||
|
} from './boosts';
|
||||||
|
import { createAppView } from './components/view';
|
||||||
|
import { ComponentOptions } from '../runtime';
|
||||||
|
|
||||||
|
import type { Project, Package } from '@alilc/lowcode-shared';
|
||||||
|
import type {
|
||||||
|
Plugin,
|
||||||
|
CodeRuntimeOptions,
|
||||||
|
ModelDataSourceCreator,
|
||||||
|
ModelStateCreator,
|
||||||
|
ICodeRuntime,
|
||||||
|
} from '@alilc/lowcode-renderer-core';
|
||||||
|
|
||||||
|
export type ComponentsAccessor = Pick<IPackageManagementService, 'getComponent'>;
|
||||||
|
|
||||||
|
export type SchemaAccessor = Pick<ISchemaService, 'get'>;
|
||||||
|
|
||||||
|
export interface IRendererApplication {
|
||||||
|
readonly options: AppOptions;
|
||||||
|
|
||||||
|
readonly mode: 'development' | 'production';
|
||||||
|
|
||||||
|
mount: (containerOrId?: string | HTMLElement) => void | Promise<void>;
|
||||||
|
|
||||||
|
unmount: () => void | Promise<void>;
|
||||||
|
|
||||||
|
use(plugin: Plugin<ReactRendererBoostsApi>): Promise<void>;
|
||||||
|
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppOptions {
|
||||||
|
schema: Project;
|
||||||
|
packages?: Package[];
|
||||||
|
plugins?: Plugin[];
|
||||||
|
/**
|
||||||
|
* code runtime 设置选项
|
||||||
|
*/
|
||||||
|
codeRuntime?: CodeRuntimeOptions;
|
||||||
|
|
||||||
|
component?: {
|
||||||
|
stateCreator?: ModelStateCreator;
|
||||||
|
/**
|
||||||
|
* 数据源创建工厂函数
|
||||||
|
*/
|
||||||
|
dataSourceCreator?: ModelDataSourceCreator;
|
||||||
|
} & Pick<ComponentOptions, 'beforeElementCreate' | 'elementCreated' | 'componentRefAttached'>;
|
||||||
|
|
||||||
|
faultComponent?: ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class App extends Disposable implements IRendererApplication {
|
||||||
|
private _root?: Root;
|
||||||
|
|
||||||
|
private instantiationService: InstantiationService;
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return __DEV__ ? 'development' : 'production';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _router: Router;
|
||||||
|
get router() {
|
||||||
|
return this._router;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _components?: ComponentsAccessor;
|
||||||
|
get components() {
|
||||||
|
if (!this._components) {
|
||||||
|
this._components = this.instantiationService.invokeFunction((accessor) => {
|
||||||
|
const packageManager = accessor.get(IPackageManagementService);
|
||||||
|
return {
|
||||||
|
getComponent: packageManager.getComponent.bind(packageManager),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._components;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _schema?: SchemaAccessor;
|
||||||
|
get schema() {
|
||||||
|
if (!this._schema) {
|
||||||
|
this._schema = this.instantiationService.invokeFunction((accessor) => {
|
||||||
|
const schemaService = accessor.get(ISchemaService);
|
||||||
|
return {
|
||||||
|
get: schemaService.get.bind(schemaService),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _codeRuntime?: ICodeRuntime;
|
||||||
|
get codeRuntime() {
|
||||||
|
if (!this._codeRuntime) {
|
||||||
|
return this.instantiationService.invokeFunction((accessor) => {
|
||||||
|
return accessor.get(ICodeRuntimeService).rootRuntime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._codeRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(public readonly options: AppOptions) {
|
||||||
|
super();
|
||||||
|
this._createServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
async startup() {
|
||||||
|
await this._initServices();
|
||||||
|
await this._createRouter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createServices() {
|
||||||
|
const container = new BeanContainer();
|
||||||
|
|
||||||
|
// create services
|
||||||
|
container.set(ISchemaService, new SchemaService());
|
||||||
|
container.set(IRuntimeIntlService, new RuntimeIntlService());
|
||||||
|
|
||||||
|
container.set(ICodeRuntimeService, new CtorDescriptor(CodeRuntimeService));
|
||||||
|
container.set(IPackageManagementService, new CtorDescriptor(PackageManagementService));
|
||||||
|
container.set(IExtensionHostService, new CtorDescriptor(ExtensionHostService));
|
||||||
|
container.set(IRuntimeUtilService, new CtorDescriptor(RuntimeUtilService));
|
||||||
|
|
||||||
|
container.set(IReactRendererBoostsService, new CtorDescriptor(ReactRendererBoostsService));
|
||||||
|
|
||||||
|
this.instantiationService = this._addDispose(new InstantiationService(container));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _initServices() {
|
||||||
|
const [
|
||||||
|
schemaService,
|
||||||
|
extensionHostService,
|
||||||
|
packageManagementService,
|
||||||
|
utilService,
|
||||||
|
intlService,
|
||||||
|
] = this.instantiationService.invokeFunction((accessor) => [
|
||||||
|
accessor.get(ISchemaService),
|
||||||
|
accessor.get(IExtensionHostService),
|
||||||
|
accessor.get(IPackageManagementService),
|
||||||
|
accessor.get(IRuntimeUtilService),
|
||||||
|
accessor.get(IRuntimeIntlService),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// init services
|
||||||
|
schemaService.initialize(this.options.schema);
|
||||||
|
|
||||||
|
const defaultLocale = schemaService.get<string>('config.defaultLocale');
|
||||||
|
const i18ns = schemaService.get('i18n', {});
|
||||||
|
|
||||||
|
const [intlApi, utilApi] = [
|
||||||
|
intlService.initialize(defaultLocale, i18ns),
|
||||||
|
utilService.initialize(),
|
||||||
|
];
|
||||||
|
this.codeRuntime.getScope().set('intl', intlApi);
|
||||||
|
this.codeRuntime.getScope().set('utils', utilApi);
|
||||||
|
|
||||||
|
await extensionHostService.registerPlugin(this.options.plugins ?? []);
|
||||||
|
|
||||||
|
await packageManagementService.loadPackages(this.options.packages ?? []);
|
||||||
|
|
||||||
|
lifeCycleService.setPhase(LifecyclePhase.Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createRouter() {
|
||||||
|
const defaultRouterOptions: RouterOptions = {
|
||||||
|
historyMode: 'browser',
|
||||||
|
baseName: '/',
|
||||||
|
routes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let routerConfig = defaultRouterOptions;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const routerSchema = this.schema.get('router');
|
||||||
|
if (routerSchema) {
|
||||||
|
routerConfig = this.codeRuntime.resolve(routerSchema);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`schema's router config is resolve error: `, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._router = createRouter(routerConfig);
|
||||||
|
|
||||||
|
this.codeRuntime.getScope().set('router', this._router);
|
||||||
|
|
||||||
|
await this._router.isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
async mount(containerOrId?: string | HTMLElement) {
|
||||||
|
this._throwIfDisposed(`this app has been destroyed`);
|
||||||
|
|
||||||
|
if (this._root) return;
|
||||||
|
|
||||||
|
const reactBoosts = this.instantiationService.invokeFunction((accessor) => {
|
||||||
|
return accessor.get(IReactRendererBoostsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultId = this.schema.get<string>('config.targetRootID', 'app');
|
||||||
|
const rootElement = normalizeContainer(containerOrId, defaultId);
|
||||||
|
|
||||||
|
const AppView = createAppView(this, reactBoosts.getAppWrappers(), reactBoosts.getOutlet());
|
||||||
|
|
||||||
|
this._root = createRoot(rootElement);
|
||||||
|
this._root.render(createElement(AppView));
|
||||||
|
}
|
||||||
|
|
||||||
|
unmount() {
|
||||||
|
this._throwIfDisposed(`this app has been destroyed`);
|
||||||
|
|
||||||
|
if (this._root) {
|
||||||
|
this._root.unmount();
|
||||||
|
this._root = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use(plugin: Plugin<ReactRendererBoostsApi>) {
|
||||||
|
this._throwIfDisposed(`this app has been destroyed`);
|
||||||
|
|
||||||
|
const extensionHostService = this.instantiationService.invokeFunction((accessor) => {
|
||||||
|
return accessor.get(IExtensionHostService);
|
||||||
|
});
|
||||||
|
|
||||||
|
return extensionHostService.registerPlugin(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeContainer(container: Element | string | undefined, defaultId: string): Element {
|
||||||
|
let result: Element | undefined = undefined;
|
||||||
|
|
||||||
|
if (typeof container === 'string') {
|
||||||
|
const el = document.getElementById(container);
|
||||||
|
if (el) result = el;
|
||||||
|
} else if (container instanceof window.Element) {
|
||||||
|
result = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = document.createElement('div');
|
||||||
|
result.id = defaultId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineRendererPlugin(plugin: Plugin<ReactRendererBoostsApi>) {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { type Plugin } from '@alilc/lowcode-renderer-core';
|
import { IExtensionHostService } from '@alilc/lowcode-renderer-core';
|
||||||
|
import { createDecorator } from '@alilc/lowcode-shared';
|
||||||
import { type ComponentType, type PropsWithChildren } from 'react';
|
import { type ComponentType, type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
export type WrapperComponent = ComponentType<PropsWithChildren<any>>;
|
export type WrapperComponent = ComponentType<PropsWithChildren<any>>;
|
||||||
@ -9,39 +10,58 @@ export interface OutletProps {
|
|||||||
|
|
||||||
export type Outlet = ComponentType<OutletProps>;
|
export type Outlet = ComponentType<OutletProps>;
|
||||||
|
|
||||||
export interface ReactRendererBoostsApi {
|
export interface IReactRendererBoostsService {
|
||||||
addAppWrapper(appWrapper: WrapperComponent): void;
|
addAppWrapper(appWrapper: WrapperComponent): void;
|
||||||
|
|
||||||
|
getAppWrappers(): WrapperComponent[];
|
||||||
|
|
||||||
setOutlet(outlet: Outlet): void;
|
setOutlet(outlet: Outlet): void;
|
||||||
|
|
||||||
|
getOutlet(): Outlet | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReactRendererBoosts {
|
export const IReactRendererBoostsService = createDecorator<IReactRendererBoostsService>(
|
||||||
private wrappers: WrapperComponent[] = [];
|
'reactRendererBoostsService',
|
||||||
|
);
|
||||||
|
|
||||||
private outlet: Outlet | null = null;
|
export type ReactRendererBoostsApi = Pick<
|
||||||
|
IReactRendererBoostsService,
|
||||||
|
'addAppWrapper' | 'setOutlet'
|
||||||
|
>;
|
||||||
|
|
||||||
getAppWrappers() {
|
export class ReactRendererBoostsService implements IReactRendererBoostsService {
|
||||||
return this.wrappers;
|
private _wrappers: WrapperComponent[] = [];
|
||||||
|
|
||||||
|
private _outlet: Outlet | null = null;
|
||||||
|
|
||||||
|
constructor(@IExtensionHostService private extensionHostService: IExtensionHostService) {
|
||||||
|
this.extensionHostService.boostsManager.extend(this._toExpose());
|
||||||
}
|
}
|
||||||
|
|
||||||
getOutlet() {
|
getAppWrappers(): WrapperComponent[] {
|
||||||
return this.outlet;
|
return this._wrappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
toExpose(): ReactRendererBoostsApi {
|
addAppWrapper(appWrapper: WrapperComponent): void {
|
||||||
|
if (appWrapper) this._wrappers.push(appWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutlet(outletComponent: Outlet): void {
|
||||||
|
if (outletComponent) this._outlet = outletComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutlet(): Outlet | null {
|
||||||
|
return this._outlet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toExpose(): ReactRendererBoostsApi {
|
||||||
return {
|
return {
|
||||||
addAppWrapper: (appWrapper) => {
|
addAppWrapper: (appWrapper) => {
|
||||||
if (appWrapper) this.wrappers.push(appWrapper);
|
this.addAppWrapper(appWrapper);
|
||||||
},
|
},
|
||||||
setOutlet: (outletComponent) => {
|
setOutlet: (outletComponent) => {
|
||||||
if (outletComponent) this.outlet = outletComponent;
|
this.setOutlet(outletComponent);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const boosts = new ReactRendererBoosts();
|
|
||||||
|
|
||||||
export function defineRendererPlugin(plugin: Plugin<ReactRendererBoostsApi>) {
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|||||||
51
packages/react-renderer/src/app/components/route.tsx
Normal file
51
packages/react-renderer/src/app/components/route.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { type ComponentTree, type PageConfig } from '@alilc/lowcode-shared';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useAppContext } from '../context';
|
||||||
|
import { OutletProps } from '../boosts';
|
||||||
|
import { useRouteLocation } from '../context';
|
||||||
|
import { createComponent } from '../../runtime/createComponent';
|
||||||
|
import { reactiveStateFactory } from '../../runtime/reactiveState';
|
||||||
|
|
||||||
|
export function RouteOutlet(props: OutletProps) {
|
||||||
|
const app = useAppContext();
|
||||||
|
const location = useRouteLocation();
|
||||||
|
const { schema, options } = app;
|
||||||
|
|
||||||
|
const pageConfig = useMemo(() => {
|
||||||
|
const pages = schema.get<PageConfig[]>('pages', []);
|
||||||
|
const matched = location.matched[location.matched.length - 1];
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
const page = pages.find((item) => matched.page === item.mappingId);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
if (pageConfig?.type === 'lowCode') {
|
||||||
|
const componentsTrees = schema.get<ComponentTree[]>('componentsTree', []);
|
||||||
|
const target = componentsTrees.find((item) => item.id === pageConfig.mappingId);
|
||||||
|
|
||||||
|
if (!target) return null;
|
||||||
|
|
||||||
|
const {
|
||||||
|
stateCreator = reactiveStateFactory,
|
||||||
|
dataSourceCreator,
|
||||||
|
...otherOptions
|
||||||
|
} = options.component ?? {};
|
||||||
|
const LowCodeComponent = createComponent(target, app.codeRuntime, app.components, {
|
||||||
|
displayName: pageConfig.id,
|
||||||
|
modelOptions: {
|
||||||
|
metadata: pageConfig,
|
||||||
|
stateCreator,
|
||||||
|
dataSourceCreator,
|
||||||
|
},
|
||||||
|
...otherOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <LowCodeComponent {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
18
packages/react-renderer/src/app/components/routerView.tsx
Normal file
18
packages/react-renderer/src/app/components/routerView.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { type Router } from '@alilc/lowcode-renderer-router';
|
||||||
|
import { useState, useEffect, type ReactNode } from 'react';
|
||||||
|
import { RouterContext, RouteLocationContext } from '../context';
|
||||||
|
|
||||||
|
export function RouterView({ router, children }: { router: Router; children?: ReactNode }) {
|
||||||
|
const [location, setCurrentLocation] = useState(router.getCurrentLocation());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const remove = router.afterRouteChange((to) => setCurrentLocation(to));
|
||||||
|
return () => remove();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouterContext.Provider value={router}>
|
||||||
|
<RouteLocationContext.Provider value={location}>{children}</RouteLocationContext.Provider>
|
||||||
|
</RouterContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
packages/react-renderer/src/app/components/view.tsx
Normal file
47
packages/react-renderer/src/app/components/view.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { AppContext } from '../context';
|
||||||
|
import { type App } from '../app';
|
||||||
|
import { getOrCreateComponent, reactiveStateFactory } from '../../runtime';
|
||||||
|
import { RouterView } from './routerView';
|
||||||
|
import { RouteOutlet } from './route';
|
||||||
|
import { type WrapperComponent, type Outlet } from '../boosts';
|
||||||
|
|
||||||
|
export const createAppView = (app: App, wrappers: WrapperComponent[], Outlet: Outlet | null) => {
|
||||||
|
return function ApplicationView() {
|
||||||
|
let element = (
|
||||||
|
<RouterView router={app.router}>{Outlet ? <Outlet /> : <RouteOutlet />}</RouterView>
|
||||||
|
);
|
||||||
|
|
||||||
|
const layoutConfig = app.schema.get<any>('config.layout');
|
||||||
|
|
||||||
|
if (layoutConfig) {
|
||||||
|
const componentName = layoutConfig.componentName;
|
||||||
|
const {
|
||||||
|
stateCreator = reactiveStateFactory,
|
||||||
|
dataSourceCreator,
|
||||||
|
...otherOptions
|
||||||
|
} = app.options.component ?? {};
|
||||||
|
|
||||||
|
const Layout = getOrCreateComponent(componentName, app.codeRuntime, app.components, {
|
||||||
|
displayName: componentName,
|
||||||
|
modelOptions: {
|
||||||
|
stateCreator,
|
||||||
|
dataSourceCreator,
|
||||||
|
},
|
||||||
|
...otherOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Layout) {
|
||||||
|
const layoutProps: any = layoutConfig.props ?? {};
|
||||||
|
element = <Layout {...layoutProps}>{element}</Layout>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrappers.length > 0) {
|
||||||
|
element = wrappers.reduce((preElement, CurrentWrapper) => {
|
||||||
|
return <CurrentWrapper>{preElement}</CurrentWrapper>;
|
||||||
|
}, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AppContext.Provider value={app}>{element}</AppContext.Provider>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,5 +1,12 @@
|
|||||||
import { type Router, type RouteLocationNormalized } from '@alilc/lowcode-renderer-router';
|
import { type Router, type RouteLocationNormalized } from '@alilc/lowcode-renderer-router';
|
||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
import { type App } from './app';
|
||||||
|
|
||||||
|
export const AppContext = createContext<App>(undefined!);
|
||||||
|
|
||||||
|
AppContext.displayName = 'AppContext';
|
||||||
|
|
||||||
|
export const useAppContext = () => useContext(AppContext);
|
||||||
|
|
||||||
export const RouterContext = createContext<Router>(undefined!);
|
export const RouterContext = createContext<Router>(undefined!);
|
||||||
|
|
||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './boosts';
|
export * from './app';
|
||||||
export * from './view';
|
export * from './context';
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
import { useRendererContext } from '../api/context';
|
|
||||||
import { getComponentByName } from '../runtime/createComponent';
|
|
||||||
import { boosts } from './boosts';
|
|
||||||
|
|
||||||
export function ApplicationView() {
|
|
||||||
const rendererContext = useRendererContext();
|
|
||||||
const { schema, options } = rendererContext;
|
|
||||||
const appWrappers = boosts.getAppWrappers();
|
|
||||||
const Outlet = boosts.getOutlet();
|
|
||||||
|
|
||||||
if (!Outlet) return null;
|
|
||||||
|
|
||||||
let element = <Outlet />;
|
|
||||||
|
|
||||||
const layoutConfig = schema.get('config')?.layout;
|
|
||||||
|
|
||||||
if (layoutConfig) {
|
|
||||||
const componentName = layoutConfig.componentName;
|
|
||||||
const Layout = getComponentByName(componentName, rendererContext, options.component);
|
|
||||||
|
|
||||||
if (Layout) {
|
|
||||||
const layoutProps: any = layoutConfig.props ?? {};
|
|
||||||
element = <Layout {...layoutProps}>{element}</Layout>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appWrappers.length > 0) {
|
|
||||||
element = appWrappers.reduce((preElement, CurrentWrapper) => {
|
|
||||||
return <CurrentWrapper>{preElement}</CurrentWrapper>;
|
|
||||||
}, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
@ -1,9 +1,6 @@
|
|||||||
export * from './api/app';
|
export * from './api/app';
|
||||||
export * from './api/component';
|
export * from './api/component';
|
||||||
export * from './api/context';
|
|
||||||
export { defineRendererPlugin } from './app';
|
export { defineRendererPlugin } from './app';
|
||||||
export * from './router';
|
|
||||||
export { LifecyclePhase } from '@alilc/lowcode-renderer-core';
|
|
||||||
|
|
||||||
export type { Package, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
|
export type { Package, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
|
||||||
export type {
|
export type {
|
||||||
@ -13,4 +10,4 @@ export type {
|
|||||||
ModelDataSourceCreator,
|
ModelDataSourceCreator,
|
||||||
ModelStateCreator,
|
ModelStateCreator,
|
||||||
} from '@alilc/lowcode-renderer-core';
|
} from '@alilc/lowcode-renderer-core';
|
||||||
export type { ReactRendererBoostsApi } from './app/boosts';
|
export type * from './app';
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
export * from './context';
|
|
||||||
export * from './plugin';
|
|
||||||
export type {
|
|
||||||
RouteLocation,
|
|
||||||
RawLocation,
|
|
||||||
Router,
|
|
||||||
RouterOptions,
|
|
||||||
RouteLocationNormalized,
|
|
||||||
RouterHistory,
|
|
||||||
} from '@alilc/lowcode-renderer-router';
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { defineRendererPlugin } from '../app/boosts';
|
|
||||||
import { createRouter, type RouterOptions } from '@alilc/lowcode-renderer-router';
|
|
||||||
import { createRouterView } from './routerView';
|
|
||||||
import { RouteOutlet } from './route';
|
|
||||||
|
|
||||||
const defaultRouterOptions: RouterOptions = {
|
|
||||||
historyMode: 'browser',
|
|
||||||
baseName: '/',
|
|
||||||
routes: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const routerPlugin = defineRendererPlugin({
|
|
||||||
name: 'rendererRouter',
|
|
||||||
async setup(context) {
|
|
||||||
const { schema, boosts } = context;
|
|
||||||
|
|
||||||
let routerConfig = defaultRouterOptions;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const routerSchema = schema.get('router');
|
|
||||||
if (routerSchema) {
|
|
||||||
routerConfig = boosts.codeRuntime.resolve(routerSchema);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`schema's router config is resolve error: `, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = createRouter(routerConfig);
|
|
||||||
const RouterView = createRouterView(router);
|
|
||||||
|
|
||||||
boosts.addAppWrapper(RouterView);
|
|
||||||
boosts.setOutlet(RouteOutlet);
|
|
||||||
|
|
||||||
boosts.codeRuntime.getScope().set('router', router);
|
|
||||||
boosts.temporaryUse('router', router);
|
|
||||||
|
|
||||||
await router.isReady();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { useRendererContext } from '../api/context';
|
|
||||||
import { OutletProps } from '../app/boosts';
|
|
||||||
import { useRouteLocation } from './context';
|
|
||||||
import { createComponent } from '../runtime/createComponent';
|
|
||||||
|
|
||||||
export function RouteOutlet(props: OutletProps) {
|
|
||||||
const context = useRendererContext();
|
|
||||||
const location = useRouteLocation();
|
|
||||||
const { schema, packageManager, options } = context;
|
|
||||||
|
|
||||||
const pageConfig = useMemo(() => {
|
|
||||||
const pages = schema.get('pages') ?? [];
|
|
||||||
const matched = location.matched[location.matched.length - 1];
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
const page = pages.find((item) => matched.page === item.mappingId);
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}, [location]);
|
|
||||||
|
|
||||||
if (pageConfig?.type === 'lowCode') {
|
|
||||||
// 在页面渲染时重新获取 componentsMap
|
|
||||||
// 因为 componentsMap 可能在路由跳转之前懒加载新的页面 schema
|
|
||||||
const componentsMap = schema.get('componentsMap');
|
|
||||||
packageManager.resolveComponentMaps(componentsMap);
|
|
||||||
|
|
||||||
const LowCodeComponent = createComponent(pageConfig.mappingId, {
|
|
||||||
displayName: pageConfig.id,
|
|
||||||
modelOptions: {
|
|
||||||
metadata: pageConfig,
|
|
||||||
},
|
|
||||||
...options.component,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <LowCodeComponent {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { type Router } from '@alilc/lowcode-renderer-router';
|
|
||||||
import { useState, useEffect, type ReactNode } from 'react';
|
|
||||||
import { RouterContext, RouteLocationContext } from './context';
|
|
||||||
|
|
||||||
export const createRouterView = (router: Router) => {
|
|
||||||
return function RouterView({ children }: { children?: ReactNode }) {
|
|
||||||
const [location, setCurrentLocation] = useState(router.getCurrentLocation());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const remove = router.afterRouteChange((to) => setCurrentLocation(to));
|
|
||||||
return () => remove();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouterContext.Provider value={router}>
|
|
||||||
<RouteLocationContext.Provider value={location}>{children}</RouteLocationContext.Provider>
|
|
||||||
</RouterContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,20 +1,17 @@
|
|||||||
import { invariant, specTypes, type ComponentTreeRoot } from '@alilc/lowcode-shared';
|
import { invariant, specTypes, type ComponentTree } from '@alilc/lowcode-shared';
|
||||||
|
import { type ComponentTreeModelOptions, ComponentTreeModel } from '@alilc/lowcode-renderer-core';
|
||||||
import { forwardRef, useRef, useEffect } from 'react';
|
import { forwardRef, useRef, useEffect } from 'react';
|
||||||
import { isValidElementType } from 'react-is';
|
import { isValidElementType } from 'react-is';
|
||||||
import { useRendererContext, IRendererContext } from '../api/context';
|
|
||||||
import { reactiveStateFactory } from './reactiveState';
|
|
||||||
import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
|
import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
|
||||||
import { appendExternalStyle } from '../utils/element';
|
import { appendExternalStyle } from '../utils/element';
|
||||||
|
import { type ComponentsAccessor } from '../app';
|
||||||
|
|
||||||
import type {
|
import type { ICodeRuntime, IComponentTreeModel } from '@alilc/lowcode-renderer-core';
|
||||||
IComponentTreeModel,
|
|
||||||
CreateComponentTreeModelOptions,
|
|
||||||
} from '@alilc/lowcode-renderer-core';
|
|
||||||
import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
|
import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
|
||||||
|
|
||||||
export interface ComponentOptions {
|
export interface ComponentOptions {
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
modelOptions?: Pick<CreateComponentTreeModelOptions, 'id' | 'metadata'>;
|
modelOptions: ComponentTreeModelOptions;
|
||||||
|
|
||||||
beforeElementCreate?(widget: ReactWidget): ReactWidget;
|
beforeElementCreate?(widget: ReactWidget): ReactWidget;
|
||||||
elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
|
elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
|
||||||
@ -33,20 +30,22 @@ export interface LowCodeComponentProps {
|
|||||||
|
|
||||||
const lowCodeComponentsCache = new Map<string, ReactComponent>();
|
const lowCodeComponentsCache = new Map<string, ReactComponent>();
|
||||||
|
|
||||||
export function getComponentByName(
|
export function getOrCreateComponent(
|
||||||
name: string,
|
name: string,
|
||||||
{ packageManager }: IRendererContext,
|
codeRuntime: ICodeRuntime,
|
||||||
componentOptions: ComponentOptions = {},
|
components: ComponentsAccessor,
|
||||||
|
componentOptions: ComponentOptions,
|
||||||
): ReactComponent {
|
): ReactComponent {
|
||||||
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);
|
const result = lowCodeComponentsCache.get(name) || components.getComponent(name);
|
||||||
|
|
||||||
if (specTypes.isLowCodeComponentPackage(result)) {
|
if (specTypes.isLowCodeComponentPackage(result)) {
|
||||||
const { schema, ...metadata } = result;
|
const { schema, ...metadata } = result;
|
||||||
|
|
||||||
const lowCodeComponent = createComponent(schema, {
|
const lowCodeComponent = createComponent(schema, codeRuntime, components, {
|
||||||
...componentOptions,
|
...componentOptions,
|
||||||
displayName: name,
|
displayName: name,
|
||||||
modelOptions: {
|
modelOptions: {
|
||||||
|
...componentOptions.modelOptions,
|
||||||
id: metadata.id,
|
id: metadata.id,
|
||||||
metadata,
|
metadata,
|
||||||
},
|
},
|
||||||
@ -63,8 +62,10 @@ export function getComponentByName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
schema: string | ComponentTreeRoot,
|
schema: ComponentTree,
|
||||||
componentOptions: ComponentOptions = {},
|
codeRuntime: ICodeRuntime,
|
||||||
|
components: ComponentsAccessor,
|
||||||
|
componentOptions: ComponentOptions,
|
||||||
) {
|
) {
|
||||||
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
|
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
|
||||||
|
|
||||||
@ -72,26 +73,17 @@ export function createComponent(
|
|||||||
props: LowCodeComponentProps,
|
props: LowCodeComponentProps,
|
||||||
ref: ForwardedRef<any>,
|
ref: ForwardedRef<any>,
|
||||||
) {
|
) {
|
||||||
const context = useRendererContext();
|
|
||||||
const { options: globalOptions, componentTreeModel } = context;
|
|
||||||
|
|
||||||
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
|
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
|
||||||
|
|
||||||
if (!modelRef.current) {
|
if (!modelRef.current) {
|
||||||
const finalOptions: CreateComponentTreeModelOptions = {
|
modelRef.current = new ComponentTreeModel(
|
||||||
...modelOptions,
|
schema,
|
||||||
codeScopeValue: {
|
codeRuntime.createChild({
|
||||||
props,
|
props,
|
||||||
},
|
} as any),
|
||||||
stateCreator: reactiveStateFactory,
|
modelOptions,
|
||||||
dataSourceCreator: globalOptions.dataSourceCreator,
|
);
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof schema === 'string') {
|
|
||||||
modelRef.current = componentTreeModel.createById(schema, finalOptions);
|
|
||||||
} else {
|
|
||||||
modelRef.current = componentTreeModel.create(schema, finalOptions);
|
|
||||||
}
|
|
||||||
console.log(
|
console.log(
|
||||||
'%c [ model ]-103',
|
'%c [ model ]-103',
|
||||||
'font-size:13px; background:pink; color:#bf2c9f;',
|
'font-size:13px; background:pink; color:#bf2c9f;',
|
||||||
@ -137,7 +129,14 @@ export function createComponent(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={props.id} className={props.className} style={props.style} ref={ref}>
|
<div id={props.id} className={props.className} style={props.style} ref={ref}>
|
||||||
{model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))}
|
{model.widgets.map((w) =>
|
||||||
|
createElementByWidget({
|
||||||
|
widget: w,
|
||||||
|
codeRuntime: w.model.codeRuntime,
|
||||||
|
components,
|
||||||
|
options: componentOptions,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import {
|
|||||||
createElement,
|
createElement,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useRendererContext } from '../api/context';
|
import { ComponentsAccessor } from '../app';
|
||||||
import { useReactiveStore } from './hooks/useReactiveStore';
|
import { useReactiveStore } from './hooks/useReactiveStore';
|
||||||
import { getComponentByName, type ComponentOptions } from './createComponent';
|
import { getOrCreateComponent, type ComponentOptions } from './createComponent';
|
||||||
|
|
||||||
export type ReactComponent = ComponentType<any>;
|
export type ReactComponent = ComponentType<any>;
|
||||||
export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
||||||
@ -29,16 +29,13 @@ export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
|||||||
interface WidgetRendererProps {
|
interface WidgetRendererProps {
|
||||||
widget: ReactWidget;
|
widget: ReactWidget;
|
||||||
codeRuntime: ICodeRuntime;
|
codeRuntime: ICodeRuntime;
|
||||||
|
components: ComponentsAccessor;
|
||||||
options: ComponentOptions;
|
options: ComponentOptions;
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createElementByWidget(
|
export function createElementByWidget(props: WidgetRendererProps): ReactNode {
|
||||||
widget: ReactWidget,
|
|
||||||
codeRuntime: ICodeRuntime,
|
|
||||||
options: ComponentOptions,
|
|
||||||
): ReactNode {
|
|
||||||
const getElement = (widget: ReactWidget) => {
|
const getElement = (widget: ReactWidget) => {
|
||||||
const { key, rawNode } = widget;
|
const { key, rawNode } = widget;
|
||||||
|
|
||||||
@ -47,11 +44,11 @@ export function createElementByWidget(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (specTypes.isJSExpression(rawNode)) {
|
if (specTypes.isJSExpression(rawNode)) {
|
||||||
return <Text key={key} expr={rawNode} codeRuntime={codeRuntime} />;
|
return <Text key={key} expr={rawNode} codeRuntime={props.codeRuntime} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specTypes.isJSI18nNode(rawNode)) {
|
if (specTypes.isJSI18nNode(rawNode)) {
|
||||||
return <I18nText key={key} i18n={rawNode} codeRuntime={codeRuntime} />;
|
return <I18nText key={key} i18n={rawNode} codeRuntime={props.codeRuntime} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { condition, loop } = widget.rawNode as NormalizedComponentNode;
|
const { condition, loop } = widget.rawNode as NormalizedComponentNode;
|
||||||
@ -62,44 +59,32 @@ export function createElementByWidget(
|
|||||||
if (Array.isArray(loop) && loop.length === 0) return null;
|
if (Array.isArray(loop) && loop.length === 0) return null;
|
||||||
|
|
||||||
if (specTypes.isJSExpression(loop)) {
|
if (specTypes.isJSExpression(loop)) {
|
||||||
return (
|
return <LoopWidgetRenderer key={key} loop={loop} {...props} />;
|
||||||
<LoopWidgetRenderer
|
|
||||||
key={key}
|
|
||||||
loop={loop}
|
|
||||||
widget={widget}
|
|
||||||
codeRuntime={codeRuntime}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <WidgetComponent key={key} {...props} />;
|
||||||
<WidgetComponent key={key} widget={widget} codeRuntime={codeRuntime} options={options} />
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.beforeElementCreate) {
|
if (props.options.beforeElementCreate) {
|
||||||
widget = options.beforeElementCreate(widget);
|
props.widget = props.options.beforeElementCreate(props.widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = getElement(widget);
|
const element = getElement(props.widget);
|
||||||
|
|
||||||
if (options.elementCreated) {
|
if (props.options.elementCreated) {
|
||||||
return options.elementCreated(widget, element);
|
return props.options.elementCreated(props.widget, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WidgetComponent(props: WidgetRendererProps) {
|
export function WidgetComponent(props: WidgetRendererProps) {
|
||||||
const { widget, codeRuntime, options, ...otherProps } = props;
|
const { widget, codeRuntime, components, options, ...otherProps } = props;
|
||||||
const componentNode = widget.rawNode as NormalizedComponentNode;
|
const componentNode = widget.rawNode as NormalizedComponentNode;
|
||||||
const { ref, ...componentProps } = componentNode.props;
|
const { ref, ...componentProps } = componentNode.props;
|
||||||
|
|
||||||
const context = useRendererContext();
|
|
||||||
|
|
||||||
const Component = useMemo(
|
const Component = useMemo(
|
||||||
() => getComponentByName(componentNode.componentName, context, options),
|
() => getOrCreateComponent(componentNode.componentName, codeRuntime, components, options),
|
||||||
[widget],
|
[widget],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -123,15 +108,15 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
|||||||
}, {} as StringDictionary);
|
}, {} as StringDictionary);
|
||||||
|
|
||||||
return widgets.map((n) =>
|
return widgets.map((n) =>
|
||||||
createElementByWidget(
|
createElementByWidget({
|
||||||
n,
|
...props,
|
||||||
codeRuntime.createChild({ initScopeValue: params }),
|
widget: n,
|
||||||
options,
|
codeRuntime: codeRuntime.createChild({ initScopeValue: params }),
|
||||||
),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return widgets.map((n) => createElementByWidget(n, codeRuntime, options));
|
return widgets.map((n) => createElementByWidget({ ...props, widget: n }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (specTypes.isJSFunction(node)) {
|
} else if (specTypes.isJSFunction(node)) {
|
||||||
@ -183,7 +168,7 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
|||||||
key: widget.key,
|
key: widget.key,
|
||||||
ref: attachRef,
|
ref: attachRef,
|
||||||
},
|
},
|
||||||
widget.children?.map((item) => createElementByWidget(item, codeRuntime, options)) ?? [],
|
widget.children?.map((item) => createElementByWidget({ ...props, widget: item })) ?? [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,19 +198,12 @@ function I18nText(props: { i18n: JSI18n; codeRuntime: ICodeRuntime }) {
|
|||||||
|
|
||||||
I18nText.displayName = 'I18nText';
|
I18nText.displayName = 'I18nText';
|
||||||
|
|
||||||
function LoopWidgetRenderer({
|
interface LoopWidgetRendererProps extends WidgetRendererProps {
|
||||||
loop,
|
|
||||||
widget,
|
|
||||||
codeRuntime,
|
|
||||||
options,
|
|
||||||
...otherProps
|
|
||||||
}: {
|
|
||||||
loop: JSExpression;
|
loop: JSExpression;
|
||||||
widget: ReactWidget;
|
}
|
||||||
codeRuntime: ICodeRuntime;
|
|
||||||
options: ComponentOptions;
|
function LoopWidgetRenderer(props: LoopWidgetRendererProps) {
|
||||||
[key: string]: any;
|
const { loop, widget, codeRuntime, ...otherProps } = props;
|
||||||
}) {
|
|
||||||
const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode;
|
const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode;
|
||||||
const state = useReactiveStore({
|
const state = useReactiveStore({
|
||||||
target: {
|
target: {
|
||||||
@ -252,7 +230,6 @@ function LoopWidgetRenderer({
|
|||||||
key={`loop-${widget.key}-${idx}`}
|
key={`loop-${widget.key}-${idx}`}
|
||||||
widget={widget}
|
widget={widget}
|
||||||
codeRuntime={childRuntime}
|
codeRuntime={childRuntime}
|
||||||
options={options}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './createComponent';
|
export * from './createComponent';
|
||||||
|
export * from './reactiveState';
|
||||||
export * from './elements';
|
export * from './elements';
|
||||||
|
|||||||
@ -18,18 +18,17 @@ export interface ICodeRuntimeService extends IDisposable {
|
|||||||
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
||||||
|
|
||||||
export class CodeRuntimeService extends Disposable implements ICodeRuntimeService {
|
export class CodeRuntimeService extends Disposable implements ICodeRuntimeService {
|
||||||
private _rootRuntime: ICodeRuntime;
|
private _rootRuntime?: ICodeRuntime;
|
||||||
get rootRuntime() {
|
get rootRuntime() {
|
||||||
|
if (!this._rootRuntime) {
|
||||||
|
this._rootRuntime = this._addDispose(new CodeRuntime());
|
||||||
|
}
|
||||||
return this._rootRuntime;
|
return this._rootRuntime;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(@ISchemaService private schemaService: ISchemaService) {
|
||||||
options: CodeRuntimeOptions = {},
|
|
||||||
@ISchemaService private schemaService: ISchemaService,
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._rootRuntime = this._addDispose(new CodeRuntime(options));
|
|
||||||
this._addDispose(
|
this._addDispose(
|
||||||
this.schemaService.onSchemaUpdate(({ key, data }) => {
|
this.schemaService.onSchemaUpdate(({ key, data }) => {
|
||||||
if (key === 'constants') {
|
if (key === 'constants') {
|
||||||
@ -39,6 +38,10 @@ export class CodeRuntimeService extends Disposable implements ICodeRuntimeServic
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialize(options: CodeRuntimeOptions): void {
|
||||||
|
this._rootRuntime = this._addDispose(new CodeRuntime(options));
|
||||||
|
}
|
||||||
|
|
||||||
createCodeRuntime<T extends StringDictionary = StringDictionary>(
|
createCodeRuntime<T extends StringDictionary = StringDictionary>(
|
||||||
options: CodeRuntimeOptions<T> = {},
|
options: CodeRuntimeOptions<T> = {},
|
||||||
): ICodeRuntime<T> {
|
): ICodeRuntime<T> {
|
||||||
|
|||||||
3
packages/renderer-core/src/component-tree-model/index.ts
Normal file
3
packages/renderer-core/src/component-tree-model/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './componentTreeModel';
|
||||||
|
|
||||||
|
export type { IWidget } from './widget';
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type NodeType, uniqueId, type ComponentNode } from '@alilc/lowcode-shared';
|
import { type NodeType, uniqueId, type ComponentNode } from '@alilc/lowcode-shared';
|
||||||
import { IComponentTreeModel } from '../services/model';
|
import { IComponentTreeModel } from './componentTreeModel';
|
||||||
|
|
||||||
export interface IWidget<Component, ComponentInstance = unknown> {
|
export interface IWidget<Component, ComponentInstance = unknown> {
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
@ -1,123 +0,0 @@
|
|||||||
import {
|
|
||||||
invariant,
|
|
||||||
InstantiationService,
|
|
||||||
BeanContainer,
|
|
||||||
CtorDescriptor,
|
|
||||||
type Project,
|
|
||||||
type Package,
|
|
||||||
} from '@alilc/lowcode-shared';
|
|
||||||
import { CodeRuntimeService, ICodeRuntimeService, type CodeRuntimeOptions } from './code-runtime';
|
|
||||||
import {
|
|
||||||
IExtensionHostService,
|
|
||||||
type RenderAdapter,
|
|
||||||
type IRenderObject,
|
|
||||||
ExtensionHostService,
|
|
||||||
type Plugin,
|
|
||||||
} from './extension';
|
|
||||||
import { IPackageManagementService, PackageManagementService } from './package';
|
|
||||||
import { ISchemaService, SchemaService } from './schema';
|
|
||||||
import { ILifeCycleService, LifecyclePhase, LifeCycleService } from './life-cycle';
|
|
||||||
import { IRuntimeIntlService, RuntimeIntlService } from './intl';
|
|
||||||
import { IRuntimeUtilService, RuntimeUtilService } from './util';
|
|
||||||
import { type ModelDataSourceCreator } from './model';
|
|
||||||
|
|
||||||
export interface AppOptions {
|
|
||||||
schema: Project;
|
|
||||||
packages?: Package[];
|
|
||||||
plugins?: Plugin[];
|
|
||||||
/**
|
|
||||||
* code runtime 设置选项
|
|
||||||
*/
|
|
||||||
codeRuntime?: CodeRuntimeOptions;
|
|
||||||
/**
|
|
||||||
* 数据源创建工厂函数
|
|
||||||
*/
|
|
||||||
dataSourceCreator?: ModelDataSourceCreator;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RendererApplication<Render = unknown> = {
|
|
||||||
readonly mode: 'development' | 'production';
|
|
||||||
|
|
||||||
readonly schema: Omit<ISchemaService, 'initialize'>;
|
|
||||||
|
|
||||||
readonly packageManager: IPackageManagementService;
|
|
||||||
|
|
||||||
use(plugin: Plugin): Promise<void>;
|
|
||||||
|
|
||||||
destroy(): void;
|
|
||||||
} & Render;
|
|
||||||
|
|
||||||
export function createRenderer<RenderObject = IRenderObject>(
|
|
||||||
renderAdapter: RenderAdapter<RenderObject>,
|
|
||||||
): (options: AppOptions) => Promise<RendererApplication<RenderObject>> {
|
|
||||||
invariant(typeof renderAdapter === 'function', 'The first parameter must be a function.');
|
|
||||||
|
|
||||||
return async (options) => {
|
|
||||||
// create services
|
|
||||||
const container = new BeanContainer();
|
|
||||||
const lifeCycleService = new LifeCycleService();
|
|
||||||
container.set(ILifeCycleService, lifeCycleService);
|
|
||||||
|
|
||||||
const schemaService = new SchemaService(options.schema);
|
|
||||||
container.set(ISchemaService, schemaService);
|
|
||||||
|
|
||||||
container.set(
|
|
||||||
ICodeRuntimeService,
|
|
||||||
new CtorDescriptor(CodeRuntimeService, [options.codeRuntime]),
|
|
||||||
);
|
|
||||||
container.set(IPackageManagementService, new CtorDescriptor(PackageManagementService));
|
|
||||||
|
|
||||||
const utils = schemaService.get('utils');
|
|
||||||
container.set(IRuntimeUtilService, new CtorDescriptor(RuntimeUtilService, [utils]));
|
|
||||||
|
|
||||||
const defaultLocale = schemaService.get('config.defaultLocale');
|
|
||||||
const i18ns = schemaService.get('i18n', {});
|
|
||||||
container.set(
|
|
||||||
IRuntimeIntlService,
|
|
||||||
new CtorDescriptor(RuntimeIntlService, [defaultLocale, i18ns]),
|
|
||||||
);
|
|
||||||
|
|
||||||
container.set(IExtensionHostService, new CtorDescriptor(ExtensionHostService));
|
|
||||||
|
|
||||||
const instantiationService = new InstantiationService(container);
|
|
||||||
|
|
||||||
lifeCycleService.setPhase(LifecyclePhase.OptionsResolved);
|
|
||||||
|
|
||||||
const [extensionHostService, packageManagementService] = instantiationService.invokeFunction(
|
|
||||||
(accessor) => {
|
|
||||||
return [accessor.get(IExtensionHostService), accessor.get(IPackageManagementService)];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderObject = await renderAdapter(instantiationService);
|
|
||||||
|
|
||||||
await extensionHostService.registerPlugin(options.plugins ?? []);
|
|
||||||
|
|
||||||
await packageManagementService.loadPackages(options.packages ?? []);
|
|
||||||
|
|
||||||
lifeCycleService.setPhase(LifecyclePhase.Ready);
|
|
||||||
|
|
||||||
const app: RendererApplication<RenderObject> = {
|
|
||||||
get mode() {
|
|
||||||
return __DEV__ ? 'development' : 'production';
|
|
||||||
},
|
|
||||||
schema: schemaService,
|
|
||||||
packageManager: packageManagementService,
|
|
||||||
...renderObject,
|
|
||||||
|
|
||||||
use: (plugin) => {
|
|
||||||
return extensionHostService.registerPlugin(plugin);
|
|
||||||
},
|
|
||||||
destroy: () => {
|
|
||||||
lifeCycleService.setPhase(LifecyclePhase.Destroying);
|
|
||||||
instantiationService.dispose();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (__DEV__) {
|
|
||||||
Object.defineProperty(app, '__options', { get: () => options });
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { type StringDictionary } from '@alilc/lowcode-shared';
|
import { type StringDictionary } from '@alilc/lowcode-shared';
|
||||||
import { isObject } from 'lodash-es';
|
import { isObject } from 'lodash-es';
|
||||||
import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime';
|
import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime';
|
||||||
import { IRuntimeUtilService } from '../util/utilService';
|
import { IRuntimeUtilService } from '../util';
|
||||||
import { IRuntimeIntlService } from '../intlService';
|
import { IRuntimeIntlService } from '../intl';
|
||||||
|
|
||||||
export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any };
|
export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any };
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { type Plugin, type PluginContext } from './plugin';
|
|||||||
import { BoostsManager } from './boosts';
|
import { BoostsManager } from './boosts';
|
||||||
import { IPackageManagementService } from '../package';
|
import { IPackageManagementService } from '../package';
|
||||||
import { ISchemaService } from '../schema';
|
import { ISchemaService } from '../schema';
|
||||||
import { ILifeCycleService } from '../life-cycle/lifeCycleService';
|
|
||||||
import { ICodeRuntimeService } from '../code-runtime';
|
import { ICodeRuntimeService } from '../code-runtime';
|
||||||
import { IRuntimeIntlService } from '../intl';
|
import { IRuntimeIntlService } from '../intl';
|
||||||
import { IRuntimeUtilService } from '../util';
|
import { IRuntimeUtilService } from '../util';
|
||||||
@ -28,7 +27,6 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
|||||||
private _pluginSetupContext: PluginContext;
|
private _pluginSetupContext: PluginContext;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ILifeCycleService lifeCycleService: ILifeCycleService,
|
|
||||||
@IPackageManagementService packageManagementService: IPackageManagementService,
|
@IPackageManagementService packageManagementService: IPackageManagementService,
|
||||||
@ISchemaService schemaService: ISchemaService,
|
@ISchemaService schemaService: ISchemaService,
|
||||||
@ICodeRuntimeService codeRuntimeService: ICodeRuntimeService,
|
@ICodeRuntimeService codeRuntimeService: ICodeRuntimeService,
|
||||||
@ -48,10 +46,6 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
|||||||
boosts: this.boostsManager.toExpose(),
|
boosts: this.boostsManager.toExpose(),
|
||||||
schema: schemaService,
|
schema: schemaService,
|
||||||
packageManager: packageManagementService,
|
packageManager: packageManagementService,
|
||||||
|
|
||||||
whenLifeCylePhaseChange: (phase) => {
|
|
||||||
return lifeCycleService.when(phase);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,9 +95,8 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
|||||||
private async _doSetupPlugin(plugin: Plugin) {
|
private async _doSetupPlugin(plugin: Plugin) {
|
||||||
if (this._activePlugins.has(plugin.name)) return;
|
if (this._activePlugins.has(plugin.name)) return;
|
||||||
|
|
||||||
await plugin.setup(this._pluginSetupContext);
|
this._addDispose(await plugin.setup(this._pluginSetupContext));
|
||||||
this._activePlugins.add(plugin.name);
|
this._activePlugins.add(plugin.name);
|
||||||
this._addDispose(plugin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlugin(name: string): Plugin | undefined {
|
getPlugin(name: string): Plugin | undefined {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export * from './extensionHostService';
|
export * from './extensionHostService';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
export * from './boosts';
|
export * from './boosts';
|
||||||
export * from './render';
|
|
||||||
|
|||||||
@ -1,23 +1,19 @@
|
|||||||
import { type StringDictionary, type IDisposable } from '@alilc/lowcode-shared';
|
import { type IDisposable } from '@alilc/lowcode-shared';
|
||||||
import { type IBoosts } from './boosts';
|
import { type IBoosts } from './boosts';
|
||||||
import { ILifeCycleService } from '../life-cycle/lifeCycleService';
|
|
||||||
import { type ISchemaService } from '../schema';
|
import { type ISchemaService } from '../schema';
|
||||||
import { type IPackageManagementService } from '../package';
|
import { type IPackageManagementService } from '../package';
|
||||||
import { type IStore } from '../../utils/store';
|
|
||||||
|
|
||||||
export interface PluginContext<BoostsExtends = object> {
|
export interface PluginContext<BoostsExtends = object> {
|
||||||
globalState: IStore<StringDictionary, string>;
|
globalState: Map<string, any>;
|
||||||
|
|
||||||
boosts: IBoosts<BoostsExtends>;
|
boosts: IBoosts<BoostsExtends>;
|
||||||
|
|
||||||
schema: Pick<ISchemaService, 'get' | 'set'>;
|
schema: Pick<ISchemaService, 'get' | 'set'>;
|
||||||
|
|
||||||
packageManager: IPackageManagementService;
|
packageManager: IPackageManagementService;
|
||||||
|
|
||||||
whenLifeCylePhaseChange: ILifeCycleService['when'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Plugin<BoostsExtends = object> extends IDisposable {
|
export interface Plugin<BoostsExtends = object> {
|
||||||
/**
|
/**
|
||||||
* 插件的 name 作为唯一标识,并不可重复。
|
* 插件的 name 作为唯一标识,并不可重复。
|
||||||
*/
|
*/
|
||||||
@ -26,7 +22,7 @@ export interface Plugin<BoostsExtends = object> extends IDisposable {
|
|||||||
* 插件启动函数
|
* 插件启动函数
|
||||||
* @param context 插件能力上下文
|
* @param context 插件能力上下文
|
||||||
*/
|
*/
|
||||||
setup(context: PluginContext<BoostsExtends>): void | Promise<void>;
|
setup(context: PluginContext<BoostsExtends>): IDisposable | Promise<IDisposable>;
|
||||||
/**
|
/**
|
||||||
* 插件的依赖插件
|
* 插件的依赖插件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { type IInstantiationService } from '@alilc/lowcode-shared';
|
|
||||||
|
|
||||||
export interface IRenderObject {
|
|
||||||
mount: (containerOrId?: string | HTMLElement) => void | Promise<void>;
|
|
||||||
unmount: () => void | Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RenderAdapter<Render> {
|
|
||||||
(instantiationService: IInstantiationService): Render | Promise<Render>;
|
|
||||||
}
|
|
||||||
@ -1,20 +1,8 @@
|
|||||||
/* --------------- api -------------------- */
|
export * from './extension';
|
||||||
export * from './createRenderer';
|
export * from './code-runtime';
|
||||||
export { IExtensionHostService } from './extension';
|
export * from './component-tree-model';
|
||||||
export { definePackageLoader, IPackageManagementService } from './package';
|
export * from './package';
|
||||||
export { LifecyclePhase, ILifeCycleService } from './life-cycle';
|
export * from './schema';
|
||||||
export { IComponentTreeModelService } from './model';
|
export * from './extension';
|
||||||
export { ICodeRuntimeService, mapValue, someValue } from './code-runtime';
|
export * from './intl';
|
||||||
export { IRuntimeIntlService } from './intl';
|
export * from './util';
|
||||||
export { IRuntimeUtilService } from './util';
|
|
||||||
export { ISchemaService } from './schema';
|
|
||||||
export { Widget } from './widget';
|
|
||||||
|
|
||||||
/* --------------- types ---------------- */
|
|
||||||
export type * from './extension';
|
|
||||||
export type * from './code-runtime';
|
|
||||||
export type * from './model';
|
|
||||||
export type * from './package';
|
|
||||||
export type * from './schema';
|
|
||||||
export type * from './extension';
|
|
||||||
export type * from './widget';
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
type LocaleTranslationsMap,
|
type LocaleTranslationsMap,
|
||||||
Disposable,
|
Disposable,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { ICodeRuntimeService } from '../code-runtime';
|
|
||||||
|
|
||||||
export interface MessageDescriptor {
|
export interface MessageDescriptor {
|
||||||
key: string;
|
key: string;
|
||||||
@ -16,6 +15,8 @@ export interface MessageDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRuntimeIntlService {
|
export interface IRuntimeIntlService {
|
||||||
|
initialize(locale: string | undefined, i18nTranslations: LocaleTranslationsMap): IntlApi;
|
||||||
|
|
||||||
localize(descriptor: MessageDescriptor): string;
|
localize(descriptor: MessageDescriptor): string;
|
||||||
|
|
||||||
setLocale(locale: Locale): void;
|
setLocale(locale: Locale): void;
|
||||||
@ -28,20 +29,31 @@ export interface IRuntimeIntlService {
|
|||||||
export const IRuntimeIntlService = createDecorator<IRuntimeIntlService>('IRuntimeIntlService');
|
export const IRuntimeIntlService = createDecorator<IRuntimeIntlService>('IRuntimeIntlService');
|
||||||
|
|
||||||
export class RuntimeIntlService extends Disposable implements IRuntimeIntlService {
|
export class RuntimeIntlService extends Disposable implements IRuntimeIntlService {
|
||||||
private _intl: Intl;
|
private _intl: Intl = this._addDispose(new Intl());
|
||||||
|
|
||||||
constructor(
|
constructor() {
|
||||||
defaultLocale: string | undefined,
|
|
||||||
i18nTranslations: LocaleTranslationsMap,
|
|
||||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
this._intl = this._addDispose(new Intl(defaultLocale));
|
initialize(locale: string | undefined, i18nTranslations: LocaleTranslationsMap): IntlApi {
|
||||||
|
if (locale) this._intl.setLocale(locale);
|
||||||
for (const key of Object.keys(i18nTranslations)) {
|
for (const key of Object.keys(i18nTranslations)) {
|
||||||
this._intl.addTranslations(key, i18nTranslations[key]);
|
this._intl.addTranslations(key, i18nTranslations[key]);
|
||||||
}
|
}
|
||||||
this._injectScope();
|
|
||||||
|
const exposed: IntlApi = {
|
||||||
|
i18n: (key, params) => {
|
||||||
|
return this.localize({ key, params });
|
||||||
|
},
|
||||||
|
getLocale: () => {
|
||||||
|
return this.getLocale();
|
||||||
|
},
|
||||||
|
setLocale: (locale) => {
|
||||||
|
this.setLocale(locale);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return exposed;
|
||||||
}
|
}
|
||||||
|
|
||||||
localize(descriptor: MessageDescriptor): string {
|
localize(descriptor: MessageDescriptor): string {
|
||||||
@ -65,20 +77,4 @@ export class RuntimeIntlService extends Disposable implements IRuntimeIntlServic
|
|||||||
addTranslations(locale: Locale, translations: Translations) {
|
addTranslations(locale: Locale, translations: Translations) {
|
||||||
this._intl.addTranslations(locale, translations);
|
this._intl.addTranslations(locale, translations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _injectScope(): void {
|
|
||||||
const exposed: IntlApi = {
|
|
||||||
i18n: (key, params) => {
|
|
||||||
return this.localize({ key, params });
|
|
||||||
},
|
|
||||||
getLocale: () => {
|
|
||||||
return this.getLocale();
|
|
||||||
},
|
|
||||||
setLocale: (locale) => {
|
|
||||||
this.setLocale(locale);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.codeRuntimeService.rootRuntime.getScope().setValue(exposed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export * from './lifeCycleService';
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import { createDecorator, Barrier } from '@alilc/lowcode-shared';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期阶段
|
|
||||||
*/
|
|
||||||
export const enum LifecyclePhase {
|
|
||||||
/**
|
|
||||||
* 开始
|
|
||||||
*/
|
|
||||||
Starting = 1,
|
|
||||||
/**
|
|
||||||
* 配置解析完成
|
|
||||||
*/
|
|
||||||
OptionsResolved = 2,
|
|
||||||
/**
|
|
||||||
* 已就绪
|
|
||||||
*/
|
|
||||||
Ready = 3,
|
|
||||||
/**
|
|
||||||
* 销毁中
|
|
||||||
*/
|
|
||||||
Destroying = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ILifeCycleService {
|
|
||||||
/**
|
|
||||||
* A flag indicating in what phase of the lifecycle we currently are.
|
|
||||||
*/
|
|
||||||
phase: LifecyclePhase;
|
|
||||||
|
|
||||||
setPhase(phase: LifecyclePhase): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a promise that resolves when a certain lifecycle phase
|
|
||||||
* has started.
|
|
||||||
*/
|
|
||||||
when(phase: LifecyclePhase): Promise<void>;
|
|
||||||
|
|
||||||
onWillDestory(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LifecyclePhaseToString(phase: LifecyclePhase): string {
|
|
||||||
switch (phase) {
|
|
||||||
case LifecyclePhase.Starting:
|
|
||||||
return 'Starting';
|
|
||||||
case LifecyclePhase.OptionsResolved:
|
|
||||||
return 'OptionsResolved';
|
|
||||||
case LifecyclePhase.Ready:
|
|
||||||
return 'Ready';
|
|
||||||
case LifecyclePhase.Destroying:
|
|
||||||
return 'Destroying';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ILifeCycleService = createDecorator<ILifeCycleService>('lifeCycleService');
|
|
||||||
|
|
||||||
export class LifeCycleService implements ILifeCycleService {
|
|
||||||
private readonly phaseWhen = new Map<LifecyclePhase, Barrier>();
|
|
||||||
|
|
||||||
private _phase = LifecyclePhase.Starting;
|
|
||||||
|
|
||||||
get phase(): LifecyclePhase {
|
|
||||||
return this._phase;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPhase(value: LifecyclePhase) {
|
|
||||||
if (value < this._phase) {
|
|
||||||
throw new Error('Lifecycle cannot go backwards');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._phase === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._phase = value;
|
|
||||||
|
|
||||||
const barrier = this.phaseWhen.get(this._phase);
|
|
||||||
if (barrier) {
|
|
||||||
barrier.open();
|
|
||||||
this.phaseWhen.delete(this._phase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async when(phase: LifecyclePhase): Promise<void> {
|
|
||||||
if (phase <= this._phase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let barrier = this.phaseWhen.get(phase);
|
|
||||||
if (!barrier) {
|
|
||||||
barrier = new Barrier();
|
|
||||||
this.phaseWhen.set(phase, barrier);
|
|
||||||
}
|
|
||||||
|
|
||||||
await barrier.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
onWillDestory(): void {}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import {
|
|
||||||
createDecorator,
|
|
||||||
type IDisposable,
|
|
||||||
Disposable,
|
|
||||||
invariant,
|
|
||||||
type ComponentTree,
|
|
||||||
type StringDictionary,
|
|
||||||
} from '@alilc/lowcode-shared';
|
|
||||||
import { ICodeRuntimeService } from '../code-runtime';
|
|
||||||
import {
|
|
||||||
type IComponentTreeModel,
|
|
||||||
ComponentTreeModel,
|
|
||||||
type ComponentTreeModelOptions,
|
|
||||||
} from './componentTreeModel';
|
|
||||||
import { ISchemaService } from '../schema';
|
|
||||||
|
|
||||||
export interface CreateComponentTreeModelOptions extends ComponentTreeModelOptions {
|
|
||||||
codeScopeValue?: StringDictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IComponentTreeModelService extends IDisposable {
|
|
||||||
create<Component>(
|
|
||||||
componentsTree: ComponentTree,
|
|
||||||
options?: CreateComponentTreeModelOptions,
|
|
||||||
): IComponentTreeModel<Component>;
|
|
||||||
|
|
||||||
createById<Component>(
|
|
||||||
id: string,
|
|
||||||
options?: CreateComponentTreeModelOptions,
|
|
||||||
): IComponentTreeModel<Component>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IComponentTreeModelService = createDecorator<IComponentTreeModelService>(
|
|
||||||
'componentTreeModelService',
|
|
||||||
);
|
|
||||||
|
|
||||||
export class ComponentTreeModelService extends Disposable implements IComponentTreeModelService {
|
|
||||||
constructor(
|
|
||||||
@ISchemaService private schemaService: ISchemaService,
|
|
||||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
create<Component>(
|
|
||||||
componentsTree: ComponentTree,
|
|
||||||
options: CreateComponentTreeModelOptions,
|
|
||||||
): IComponentTreeModel<Component> {
|
|
||||||
this._throwIfDisposed(`ComponentTreeModelService has been disposed.`);
|
|
||||||
|
|
||||||
return this._addDispose(
|
|
||||||
new ComponentTreeModel(
|
|
||||||
componentsTree,
|
|
||||||
this.codeRuntimeService.createCodeRuntime({
|
|
||||||
initScopeValue: options?.codeScopeValue as any,
|
|
||||||
}),
|
|
||||||
options,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
createById<Component>(
|
|
||||||
id: string,
|
|
||||||
options: CreateComponentTreeModelOptions,
|
|
||||||
): IComponentTreeModel<Component> {
|
|
||||||
const componentsTrees = this.schemaService.get<ComponentTree[]>('componentsTree', []);
|
|
||||||
const componentsTree = componentsTrees.find((item) => item.id === id);
|
|
||||||
|
|
||||||
invariant(componentsTree, 'componentsTree not found');
|
|
||||||
|
|
||||||
return this.create(componentsTree, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from './componentTreeModel';
|
|
||||||
export * from './componentTreeModelService';
|
|
||||||
@ -11,6 +11,8 @@ export type SchemaUpdateEvent = { key: string; previous: any; data: any };
|
|||||||
export interface ISchemaService {
|
export interface ISchemaService {
|
||||||
readonly onSchemaUpdate: Events.Event<SchemaUpdateEvent>;
|
readonly onSchemaUpdate: Events.Event<SchemaUpdateEvent>;
|
||||||
|
|
||||||
|
initialize(schema: unknown): void;
|
||||||
|
|
||||||
get<T>(key: string): T | undefined;
|
get<T>(key: string): T | undefined;
|
||||||
get<T>(key: string, defaultValue?: T): T;
|
get<T>(key: string, defaultValue?: T): T;
|
||||||
|
|
||||||
@ -20,20 +22,22 @@ export interface ISchemaService {
|
|||||||
export const ISchemaService = createDecorator<ISchemaService>('schemaService');
|
export const ISchemaService = createDecorator<ISchemaService>('schemaService');
|
||||||
|
|
||||||
export class SchemaService extends Disposable implements ISchemaService {
|
export class SchemaService extends Disposable implements ISchemaService {
|
||||||
private store: NormalizedSchema;
|
private _schema: NormalizedSchema;
|
||||||
|
|
||||||
private _observer = this._addDispose(new Events.Emitter<SchemaUpdateEvent>());
|
private _onSchemaUpdate = this._addDispose(new Events.Emitter<SchemaUpdateEvent>());
|
||||||
|
|
||||||
readonly onSchemaUpdate = this._observer.event;
|
readonly onSchemaUpdate = this._onSchemaUpdate.event;
|
||||||
|
|
||||||
constructor(schema: unknown) {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(schema: unknown) {
|
||||||
if (!isObject(schema)) {
|
if (!isObject(schema)) {
|
||||||
throw Error('schema must a object');
|
throw Error('schema must a object');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store = {} as any;
|
this._schema = {} as any;
|
||||||
for (const key of Object.keys(schema)) {
|
for (const key of Object.keys(schema)) {
|
||||||
const value = (schema as any)[key];
|
const value = (schema as any)[key];
|
||||||
|
|
||||||
@ -52,12 +56,12 @@ export class SchemaService extends Disposable implements ISchemaService {
|
|||||||
set(key: string, value: any): void {
|
set(key: string, value: any): void {
|
||||||
const previous = this.get(key);
|
const previous = this.get(key);
|
||||||
if (!isEqual(previous, value)) {
|
if (!isEqual(previous, value)) {
|
||||||
lodashSet(this.store, key, value);
|
lodashSet(this._schema, key, value);
|
||||||
this._observer.notify({ key, previous, data: value });
|
this._onSchemaUpdate.notify({ key, previous, data: value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T>(key: string, defaultValue?: T): T {
|
get<T>(key: string, defaultValue?: T): T {
|
||||||
return (lodashGet(this.store, key) ?? defaultValue) as T;
|
return (lodashGet(this._schema, key) ?? defaultValue) as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,17 @@ import {
|
|||||||
type UtilDescription,
|
type UtilDescription,
|
||||||
createDecorator,
|
createDecorator,
|
||||||
type StringDictionary,
|
type StringDictionary,
|
||||||
|
Disposable,
|
||||||
|
type UtilsApi,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { isPlainObject } from 'lodash-es';
|
import { isPlainObject } from 'lodash-es';
|
||||||
import { IPackageManagementService } from '../package';
|
import { IPackageManagementService } from '../package';
|
||||||
import { ICodeRuntimeService } from '../code-runtime';
|
import { ICodeRuntimeService } from '../code-runtime';
|
||||||
|
import { ISchemaService } from '../schema';
|
||||||
|
|
||||||
export interface IRuntimeUtilService {
|
export interface IRuntimeUtilService {
|
||||||
|
initialize(): UtilsApi;
|
||||||
|
|
||||||
add(utilItem: UtilDescription, force?: boolean): void;
|
add(utilItem: UtilDescription, force?: boolean): void;
|
||||||
add(name: string, target: AnyFunction | StringDictionary, force?: boolean): void;
|
add(name: string, target: AnyFunction | StringDictionary, force?: boolean): void;
|
||||||
|
|
||||||
@ -17,18 +22,41 @@ export interface IRuntimeUtilService {
|
|||||||
|
|
||||||
export const IRuntimeUtilService = createDecorator<IRuntimeUtilService>('rendererUtilService');
|
export const IRuntimeUtilService = createDecorator<IRuntimeUtilService>('rendererUtilService');
|
||||||
|
|
||||||
export class RuntimeUtilService implements IRuntimeUtilService {
|
export class RuntimeUtilService extends Disposable implements IRuntimeUtilService {
|
||||||
private _utilsMap: Map<string, any> = new Map();
|
private _utilsMap: Map<string, any> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
utils: UtilDescription[] = [],
|
|
||||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||||
@IPackageManagementService private packageManagementService: IPackageManagementService,
|
@IPackageManagementService private packageManagementService: IPackageManagementService,
|
||||||
|
@ISchemaService private schemaService: ISchemaService,
|
||||||
) {
|
) {
|
||||||
for (const util of utils) {
|
super();
|
||||||
this.add(util);
|
|
||||||
|
this._addDispose(
|
||||||
|
this.schemaService.onSchemaUpdate(({ key, data }) => {
|
||||||
|
if (key === 'utils') {
|
||||||
|
for (const item of data) {
|
||||||
|
this.add(item);
|
||||||
}
|
}
|
||||||
this._injectScope();
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(): UtilsApi {
|
||||||
|
const exposed: UtilsApi = new Proxy(Object.create(null), {
|
||||||
|
get: (_, p: string) => {
|
||||||
|
return this._utilsMap.get(p);
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
has: (_, p: string) => {
|
||||||
|
return this._utilsMap.has(p);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return exposed;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(utilItem: UtilDescription, force?: boolean): void;
|
add(utilItem: UtilDescription, force?: boolean): void;
|
||||||
@ -93,20 +121,4 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
|||||||
return this.packageManagementService.getModuleByReference(utilItem.content);
|
return this.packageManagementService.getModuleByReference(utilItem.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _injectScope(): void {
|
|
||||||
const exposed = new Proxy(Object.create(null), {
|
|
||||||
get: (_, p: string) => {
|
|
||||||
return this._utilsMap.get(p);
|
|
||||||
},
|
|
||||||
set() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
has: (_, p: string) => {
|
|
||||||
return this._utilsMap.has(p);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.codeRuntimeService.rootRuntime.getScope().set('utils', exposed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export * from './widget';
|
|
||||||
@ -47,7 +47,7 @@ export abstract class Disposable implements IDisposable {
|
|||||||
/**
|
/**
|
||||||
* Adds `o` to the collection of disposables managed by this object.
|
* Adds `o` to the collection of disposables managed by this object.
|
||||||
*/
|
*/
|
||||||
protected addDispose<T extends IDisposable>(o: T): T {
|
protected _addDispose<T extends IDisposable>(o: T): T {
|
||||||
this._throwIfDisposed();
|
this._throwIfDisposed();
|
||||||
if ((o as unknown as Disposable) === this) {
|
if ((o as unknown as Disposable) === this) {
|
||||||
throw new Error('Cannot register a disposable on itself!');
|
throw new Error('Cannot register a disposable on itself!');
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isDisposable } from '../disposable';
|
import { dispose, isDisposable } from '../disposable';
|
||||||
import { Graph, CyclicDependencyError } from '../graph';
|
import { Graph, CyclicDependencyError } from '../graph';
|
||||||
import {
|
import {
|
||||||
type BeanIdentifier,
|
type BeanIdentifier,
|
||||||
@ -14,8 +14,6 @@ export interface InstanceAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IInstantiationService {
|
export interface IInstantiationService {
|
||||||
readonly container: BeanContainer;
|
|
||||||
|
|
||||||
createInstance<T extends Constructor>(Ctor: T, ...args: any[]): InstanceType<T>;
|
createInstance<T extends Constructor>(Ctor: T, ...args: any[]): InstanceType<T>;
|
||||||
|
|
||||||
invokeFunction<R, Args extends any[] = []>(
|
invokeFunction<R, Args extends any[] = []>(
|
||||||
@ -23,6 +21,8 @@ export interface IInstantiationService {
|
|||||||
...args: Args
|
...args: Args
|
||||||
): R;
|
): R;
|
||||||
|
|
||||||
|
createChild(container: BeanContainer): IInstantiationService;
|
||||||
|
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,11 +31,47 @@ export const IInstantiationService = createDecorator<IInstantiationService>('ins
|
|||||||
export class InstantiationService implements IInstantiationService {
|
export class InstantiationService implements IInstantiationService {
|
||||||
private _activeInstantiations = new Set<BeanIdentifier<any>>();
|
private _activeInstantiations = new Set<BeanIdentifier<any>>();
|
||||||
|
|
||||||
|
private _children = new Set<InstantiationService>();
|
||||||
|
|
||||||
private _isDisposed = false;
|
private _isDisposed = false;
|
||||||
private readonly _beansToMaybeDispose = new Set<any>();
|
private readonly _beansToMaybeDispose = new Set<any>();
|
||||||
|
|
||||||
constructor(public readonly container: BeanContainer = new BeanContainer()) {
|
constructor(
|
||||||
this.container.set(IInstantiationService, this);
|
private readonly _container: BeanContainer = new BeanContainer(),
|
||||||
|
private readonly _parent?: InstantiationService,
|
||||||
|
) {
|
||||||
|
this._container.set(IInstantiationService, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
createChild(container: BeanContainer): IInstantiationService {
|
||||||
|
this._throwIfDisposed();
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
const result = new (class extends InstantiationService {
|
||||||
|
override dispose(): void {
|
||||||
|
that._children.delete(result);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
})(container, this);
|
||||||
|
this._children.add(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
if (this._isDisposed) return;
|
||||||
|
// dispose all child services
|
||||||
|
dispose(this._children);
|
||||||
|
this._children.clear();
|
||||||
|
|
||||||
|
// dispose all services created by this service
|
||||||
|
for (const candidate of this._beansToMaybeDispose) {
|
||||||
|
if (isDisposable(candidate)) {
|
||||||
|
candidate.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._beansToMaybeDispose.clear();
|
||||||
|
this._isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +128,7 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getOrCreateInstance<T>(id: BeanIdentifier<T>): T {
|
private _getOrCreateInstance<T>(id: BeanIdentifier<T>): T {
|
||||||
const thing = this.container.get(id);
|
const thing = this._container.get(id);
|
||||||
if (thing instanceof CtorDescriptor) {
|
if (thing instanceof CtorDescriptor) {
|
||||||
return this._safeCreateAndCacheInstance<T>(id, thing);
|
return this._safeCreateAndCacheInstance<T>(id, thing);
|
||||||
} else {
|
} else {
|
||||||
@ -138,7 +174,7 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
|
|
||||||
// check all dependencies for existence and if they need to be created first
|
// check all dependencies for existence and if they need to be created first
|
||||||
for (const dependency of getBeanDependecies(item.desc.ctor)) {
|
for (const dependency of getBeanDependecies(item.desc.ctor)) {
|
||||||
const instanceOrDesc = this.container.get(dependency.id);
|
const instanceOrDesc = this._container.get(dependency.id);
|
||||||
if (!instanceOrDesc) {
|
if (!instanceOrDesc) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`,
|
`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`,
|
||||||
@ -171,35 +207,45 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
// Repeat the check for this still being a service sync descriptor. That's because
|
// Repeat the check for this still being a service sync descriptor. That's because
|
||||||
// instantiating a dependency might have side-effect and recursively trigger instantiation
|
// instantiating a dependency might have side-effect and recursively trigger instantiation
|
||||||
// so that some dependencies are now fullfilled already.
|
// so that some dependencies are now fullfilled already.
|
||||||
const instanceOrDesc = this.container.get(data.id);
|
const instanceOrDesc = this._container.get(data.id);
|
||||||
if (instanceOrDesc instanceof CtorDescriptor) {
|
if (instanceOrDesc instanceof CtorDescriptor) {
|
||||||
// create instance and overwrite the service collections
|
// create instance and overwrite the service collections
|
||||||
const instance = this.createInstance(
|
const instance = this._createServiceInstanceWithOwner(
|
||||||
instanceOrDesc.ctor,
|
data.id,
|
||||||
instanceOrDesc.staticArguments,
|
data.desc.ctor,
|
||||||
|
data.desc.staticArguments,
|
||||||
);
|
);
|
||||||
this._beansToMaybeDispose.add(instance);
|
this._setCreatedServiceInstance(data.id, instance);
|
||||||
this.container.set(data.id, instance);
|
|
||||||
}
|
}
|
||||||
graph.removeNode(data);
|
graph.removeNode(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.container.get(id) as T;
|
return this._container.get(id) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
private _createServiceInstanceWithOwner<T>(id: BeanIdentifier<T>, ctor: any, args: any[]): T {
|
||||||
if (this._isDisposed) return;
|
if (this._container.get(id) instanceof CtorDescriptor) {
|
||||||
|
const instance = this.createInstance(ctor, args);
|
||||||
|
this._beansToMaybeDispose.add(instance);
|
||||||
|
|
||||||
// dispose all services created by this service
|
return instance;
|
||||||
for (const candidate of this._beansToMaybeDispose) {
|
} else if (this._parent) {
|
||||||
if (isDisposable(candidate)) {
|
return this._parent._createServiceInstanceWithOwner(id, ctor, args);
|
||||||
candidate.dispose();
|
} else {
|
||||||
|
throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._beansToMaybeDispose.clear();
|
|
||||||
this._isDisposed = true;
|
private _setCreatedServiceInstance<T>(id: BeanIdentifier<T>, instance: T): void {
|
||||||
|
if (this._container.get(id) instanceof CtorDescriptor) {
|
||||||
|
this._container.set(id, instance);
|
||||||
|
} else if (this._parent) {
|
||||||
|
this._parent._setCreatedServiceInstance(id, instance);
|
||||||
|
} else {
|
||||||
|
throw new Error('illegalState - setting UNKNOWN service instance');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _throwIfDisposed(): void {
|
private _throwIfDisposed(): void {
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class Intl extends Disposable {
|
|||||||
return this._messageStore.value[this._locale.value] ?? {};
|
return this._messageStore.value[this._locale.value] ?? {};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addDispose(
|
this._addDispose(
|
||||||
toDisposable(
|
toDisposable(
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const cache = createIntlCache();
|
const cache = createIntlCache();
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* 低代码引擎资产包协议规范
|
* 低代码引擎资产包协议规范
|
||||||
*/
|
*/
|
||||||
import { StringDictionary } from '..';
|
import { StringDictionary } from '..';
|
||||||
import { ComponentTreeRoot } from './lowcode-spec';
|
import { ComponentTree } from './lowcode-spec';
|
||||||
import { ComponentMetaData, Reference } from './material-spec';
|
import { ComponentMetaData, Reference } from './material-spec';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,7 +85,7 @@ export interface Package {
|
|||||||
/**
|
/**
|
||||||
* 低代码组件的 schema 内容
|
* 低代码组件的 schema 内容
|
||||||
*/
|
*/
|
||||||
schema?: ComponentTreeRoot;
|
schema?: ComponentTree;
|
||||||
/**
|
/**
|
||||||
* 当前资源所依赖的其他资源包的 id 列表
|
* 当前资源所依赖的其他资源包的 id 列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user