mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 11:20:11 +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 { History } from './history';
|
||||
|
||||
export interface DocumentSchema extends ComponentTreeRoot {
|
||||
export interface DocumentSchema extends ComponentTree {
|
||||
id: string;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export interface DocumentModel {
|
||||
|
||||
export function createDocumentModel(project: Project) {
|
||||
const uid = uniqueId('doc');
|
||||
const currentDocumentSchema = signal<DocumentSchema>({});
|
||||
const currentDocumentSchema = Signals.signal<DocumentSchema>({});
|
||||
|
||||
const documentHistory = new History(currentDocumentSchema, () => {});
|
||||
|
||||
|
||||
@ -2,11 +2,3 @@ export * from './configuration';
|
||||
export * from './extension/extension';
|
||||
export * from './resource';
|
||||
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 { IWorkbenchService } from './workbench';
|
||||
import { IConfigurationService } from './configuration';
|
||||
import { ConfigurationService, IConfigurationService } from './configuration';
|
||||
|
||||
class MainApplication {
|
||||
class TestMainApplication {
|
||||
constructor() {
|
||||
console.log('main application');
|
||||
}
|
||||
|
||||
async main() {
|
||||
const instantiationService = new InstantiationService();
|
||||
const configurationService = instantiationService.get(IConfigurationService);
|
||||
const workbench = instantiationService.get(IWorkbenchService);
|
||||
|
||||
await configurationService.initialize();
|
||||
workbench.initialize();
|
||||
}
|
||||
|
||||
createServices() {
|
||||
const instantiationService = new InstantiationService();
|
||||
|
||||
const configurationService = new ConfigurationService();
|
||||
instantiationService.container.set(IConfigurationService, configurationService);
|
||||
}
|
||||
|
||||
export async function createLowCodeEngineApp(): Promise<MainApplication> {
|
||||
const app = new MainApplication();
|
||||
initServices() {}
|
||||
}
|
||||
|
||||
export async function createLowCodeEngineApp() {
|
||||
const app = new TestMainApplication();
|
||||
|
||||
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 { IConfigurationService, IWorkspaceService } from '@alilc/lowcode-engine-core';
|
||||
import { IConfigurationService } from '@alilc/lowcode-engine-core';
|
||||
|
||||
export class MainEngineApplication {
|
||||
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 { type Root, createRoot } from 'react-dom/client';
|
||||
import { type IRendererContext, RendererContext, getRenderInstancesByAccessor } from './context';
|
||||
import { ApplicationView, boosts } from '../app';
|
||||
import { type ReactAppOptions } from './types';
|
||||
import { App, type AppOptions } from '../app';
|
||||
|
||||
export const createApp = async (options: ReactAppOptions) => {
|
||||
return createRenderer(async (service) => {
|
||||
const contextValue: IRendererContext = service.invokeFunction((accessor) => {
|
||||
return {
|
||||
options,
|
||||
...getRenderInstancesByAccessor(accessor),
|
||||
export const createApp = async (options: AppOptions) => {
|
||||
const app = new App(options);
|
||||
|
||||
await app.startup();
|
||||
|
||||
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 ComponentTreeRoot } from '@alilc/lowcode-shared';
|
||||
import { type FunctionComponent } from 'react';
|
||||
import { type StringDictionary, type ComponentTree } from '@alilc/lowcode-shared';
|
||||
import { CodeRuntime } from '@alilc/lowcode-renderer-core';
|
||||
import { FunctionComponent, ComponentType } from 'react';
|
||||
import {
|
||||
type LowCodeComponentProps,
|
||||
createComponent as createSchemaComponent,
|
||||
} from '../runtime/createComponent';
|
||||
import { type IRendererContext, RendererContext, getRenderInstancesByAccessor } from './context';
|
||||
import { type ReactAppOptions } from './types';
|
||||
type ComponentOptions as SchemaComponentOptions,
|
||||
reactiveStateFactory,
|
||||
} from '../runtime';
|
||||
import { type ComponentsAccessor } from '../app';
|
||||
|
||||
interface Render {
|
||||
toComponent(): FunctionComponent<LowCodeComponentProps>;
|
||||
export interface ComponentOptions extends SchemaComponentOptions {
|
||||
schema: ComponentTree;
|
||||
componentsRecord: Record<string, ComponentType>;
|
||||
codeScope?: StringDictionary;
|
||||
}
|
||||
|
||||
export async function createComponent(options: ReactAppOptions) {
|
||||
const creator = createRenderer<Render>((service) => {
|
||||
const contextValue: IRendererContext = service.invokeFunction((accessor) => {
|
||||
return {
|
||||
options,
|
||||
...getRenderInstancesByAccessor(accessor),
|
||||
};
|
||||
});
|
||||
|
||||
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;
|
||||
export function createComponent(
|
||||
options: ComponentOptions,
|
||||
): FunctionComponent<LowCodeComponentProps> {
|
||||
const { schema, componentsRecord, modelOptions, codeScope, ...componentOptions } = options;
|
||||
const codeRuntime = new CodeRuntime(codeScope);
|
||||
const components: ComponentsAccessor = {
|
||||
getComponent(componentName) {
|
||||
return componentsRecord[componentName] as any;
|
||||
},
|
||||
};
|
||||
|
||||
const LowCodeComponent = createSchemaComponent(schema, codeRuntime, components, {
|
||||
...componentOptions,
|
||||
modelOptions: {
|
||||
...modelOptions,
|
||||
stateCreator: modelOptions.stateCreator ?? reactiveStateFactory,
|
||||
},
|
||||
});
|
||||
|
||||
const render = await creator(options);
|
||||
|
||||
return render.toComponent();
|
||||
return LowCodeComponent;
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
export type WrapperComponent = ComponentType<PropsWithChildren<any>>;
|
||||
@ -9,39 +10,58 @@ export interface OutletProps {
|
||||
|
||||
export type Outlet = ComponentType<OutletProps>;
|
||||
|
||||
export interface ReactRendererBoostsApi {
|
||||
export interface IReactRendererBoostsService {
|
||||
addAppWrapper(appWrapper: WrapperComponent): void;
|
||||
|
||||
getAppWrappers(): WrapperComponent[];
|
||||
|
||||
setOutlet(outlet: Outlet): void;
|
||||
|
||||
getOutlet(): Outlet | null;
|
||||
}
|
||||
|
||||
class ReactRendererBoosts {
|
||||
private wrappers: WrapperComponent[] = [];
|
||||
export const IReactRendererBoostsService = createDecorator<IReactRendererBoostsService>(
|
||||
'reactRendererBoostsService',
|
||||
);
|
||||
|
||||
private outlet: Outlet | null = null;
|
||||
export type ReactRendererBoostsApi = Pick<
|
||||
IReactRendererBoostsService,
|
||||
'addAppWrapper' | 'setOutlet'
|
||||
>;
|
||||
|
||||
getAppWrappers() {
|
||||
return this.wrappers;
|
||||
export class ReactRendererBoostsService implements IReactRendererBoostsService {
|
||||
private _wrappers: WrapperComponent[] = [];
|
||||
|
||||
private _outlet: Outlet | null = null;
|
||||
|
||||
constructor(@IExtensionHostService private extensionHostService: IExtensionHostService) {
|
||||
this.extensionHostService.boostsManager.extend(this._toExpose());
|
||||
}
|
||||
|
||||
getOutlet() {
|
||||
return this.outlet;
|
||||
getAppWrappers(): WrapperComponent[] {
|
||||
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 {
|
||||
addAppWrapper: (appWrapper) => {
|
||||
if (appWrapper) this.wrappers.push(appWrapper);
|
||||
this.addAppWrapper(appWrapper);
|
||||
},
|
||||
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 { 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!);
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './boosts';
|
||||
export * from './view';
|
||||
export * from './app';
|
||||
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/component';
|
||||
export * from './api/context';
|
||||
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 {
|
||||
@ -13,4 +10,4 @@ export type {
|
||||
ModelDataSourceCreator,
|
||||
ModelStateCreator,
|
||||
} 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 { isValidElementType } from 'react-is';
|
||||
import { useRendererContext, IRendererContext } from '../api/context';
|
||||
import { reactiveStateFactory } from './reactiveState';
|
||||
import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
|
||||
import { appendExternalStyle } from '../utils/element';
|
||||
import { type ComponentsAccessor } from '../app';
|
||||
|
||||
import type {
|
||||
IComponentTreeModel,
|
||||
CreateComponentTreeModelOptions,
|
||||
} from '@alilc/lowcode-renderer-core';
|
||||
import type { ICodeRuntime, IComponentTreeModel } from '@alilc/lowcode-renderer-core';
|
||||
import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
|
||||
|
||||
export interface ComponentOptions {
|
||||
displayName?: string;
|
||||
modelOptions?: Pick<CreateComponentTreeModelOptions, 'id' | 'metadata'>;
|
||||
modelOptions: ComponentTreeModelOptions;
|
||||
|
||||
beforeElementCreate?(widget: ReactWidget): ReactWidget;
|
||||
elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
|
||||
@ -33,20 +30,22 @@ export interface LowCodeComponentProps {
|
||||
|
||||
const lowCodeComponentsCache = new Map<string, ReactComponent>();
|
||||
|
||||
export function getComponentByName(
|
||||
export function getOrCreateComponent(
|
||||
name: string,
|
||||
{ packageManager }: IRendererContext,
|
||||
componentOptions: ComponentOptions = {},
|
||||
codeRuntime: ICodeRuntime,
|
||||
components: ComponentsAccessor,
|
||||
componentOptions: ComponentOptions,
|
||||
): ReactComponent {
|
||||
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);
|
||||
const result = lowCodeComponentsCache.get(name) || components.getComponent(name);
|
||||
|
||||
if (specTypes.isLowCodeComponentPackage(result)) {
|
||||
const { schema, ...metadata } = result;
|
||||
|
||||
const lowCodeComponent = createComponent(schema, {
|
||||
const lowCodeComponent = createComponent(schema, codeRuntime, components, {
|
||||
...componentOptions,
|
||||
displayName: name,
|
||||
modelOptions: {
|
||||
...componentOptions.modelOptions,
|
||||
id: metadata.id,
|
||||
metadata,
|
||||
},
|
||||
@ -63,8 +62,10 @@ export function getComponentByName(
|
||||
}
|
||||
|
||||
export function createComponent(
|
||||
schema: string | ComponentTreeRoot,
|
||||
componentOptions: ComponentOptions = {},
|
||||
schema: ComponentTree,
|
||||
codeRuntime: ICodeRuntime,
|
||||
components: ComponentsAccessor,
|
||||
componentOptions: ComponentOptions,
|
||||
) {
|
||||
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
|
||||
|
||||
@ -72,26 +73,17 @@ export function createComponent(
|
||||
props: LowCodeComponentProps,
|
||||
ref: ForwardedRef<any>,
|
||||
) {
|
||||
const context = useRendererContext();
|
||||
const { options: globalOptions, componentTreeModel } = context;
|
||||
|
||||
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
|
||||
|
||||
if (!modelRef.current) {
|
||||
const finalOptions: CreateComponentTreeModelOptions = {
|
||||
...modelOptions,
|
||||
codeScopeValue: {
|
||||
modelRef.current = new ComponentTreeModel(
|
||||
schema,
|
||||
codeRuntime.createChild({
|
||||
props,
|
||||
},
|
||||
stateCreator: reactiveStateFactory,
|
||||
dataSourceCreator: globalOptions.dataSourceCreator,
|
||||
};
|
||||
} as any),
|
||||
modelOptions,
|
||||
);
|
||||
|
||||
if (typeof schema === 'string') {
|
||||
modelRef.current = componentTreeModel.createById(schema, finalOptions);
|
||||
} else {
|
||||
modelRef.current = componentTreeModel.create(schema, finalOptions);
|
||||
}
|
||||
console.log(
|
||||
'%c [ model ]-103',
|
||||
'font-size:13px; background:pink; color:#bf2c9f;',
|
||||
@ -137,7 +129,14 @@ export function createComponent(
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
});
|
||||
|
||||
@ -19,9 +19,9 @@ import {
|
||||
createElement,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { ComponentsAccessor } from '../app';
|
||||
import { useReactiveStore } from './hooks/useReactiveStore';
|
||||
import { getComponentByName, type ComponentOptions } from './createComponent';
|
||||
import { getOrCreateComponent, type ComponentOptions } from './createComponent';
|
||||
|
||||
export type ReactComponent = ComponentType<any>;
|
||||
export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
||||
@ -29,16 +29,13 @@ export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
||||
interface WidgetRendererProps {
|
||||
widget: ReactWidget;
|
||||
codeRuntime: ICodeRuntime;
|
||||
components: ComponentsAccessor;
|
||||
options: ComponentOptions;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function createElementByWidget(
|
||||
widget: ReactWidget,
|
||||
codeRuntime: ICodeRuntime,
|
||||
options: ComponentOptions,
|
||||
): ReactNode {
|
||||
export function createElementByWidget(props: WidgetRendererProps): ReactNode {
|
||||
const getElement = (widget: ReactWidget) => {
|
||||
const { key, rawNode } = widget;
|
||||
|
||||
@ -47,11 +44,11 @@ export function createElementByWidget(
|
||||
}
|
||||
|
||||
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)) {
|
||||
return <I18nText key={key} i18n={rawNode} codeRuntime={codeRuntime} />;
|
||||
return <I18nText key={key} i18n={rawNode} codeRuntime={props.codeRuntime} />;
|
||||
}
|
||||
|
||||
const { condition, loop } = widget.rawNode as NormalizedComponentNode;
|
||||
@ -62,44 +59,32 @@ export function createElementByWidget(
|
||||
if (Array.isArray(loop) && loop.length === 0) return null;
|
||||
|
||||
if (specTypes.isJSExpression(loop)) {
|
||||
return (
|
||||
<LoopWidgetRenderer
|
||||
key={key}
|
||||
loop={loop}
|
||||
widget={widget}
|
||||
codeRuntime={codeRuntime}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
return <LoopWidgetRenderer key={key} loop={loop} {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<WidgetComponent key={key} widget={widget} codeRuntime={codeRuntime} options={options} />
|
||||
);
|
||||
return <WidgetComponent key={key} {...props} />;
|
||||
};
|
||||
|
||||
if (options.beforeElementCreate) {
|
||||
widget = options.beforeElementCreate(widget);
|
||||
if (props.options.beforeElementCreate) {
|
||||
props.widget = props.options.beforeElementCreate(props.widget);
|
||||
}
|
||||
|
||||
const element = getElement(widget);
|
||||
const element = getElement(props.widget);
|
||||
|
||||
if (options.elementCreated) {
|
||||
return options.elementCreated(widget, element);
|
||||
if (props.options.elementCreated) {
|
||||
return props.options.elementCreated(props.widget, element);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
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 { ref, ...componentProps } = componentNode.props;
|
||||
|
||||
const context = useRendererContext();
|
||||
|
||||
const Component = useMemo(
|
||||
() => getComponentByName(componentNode.componentName, context, options),
|
||||
() => getOrCreateComponent(componentNode.componentName, codeRuntime, components, options),
|
||||
[widget],
|
||||
);
|
||||
|
||||
@ -123,15 +108,15 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
}, {} as StringDictionary);
|
||||
|
||||
return widgets.map((n) =>
|
||||
createElementByWidget(
|
||||
n,
|
||||
codeRuntime.createChild({ initScopeValue: params }),
|
||||
options,
|
||||
),
|
||||
createElementByWidget({
|
||||
...props,
|
||||
widget: n,
|
||||
codeRuntime: codeRuntime.createChild({ initScopeValue: params }),
|
||||
}),
|
||||
);
|
||||
};
|
||||
} else {
|
||||
return widgets.map((n) => createElementByWidget(n, codeRuntime, options));
|
||||
return widgets.map((n) => createElementByWidget({ ...props, widget: n }));
|
||||
}
|
||||
}
|
||||
} else if (specTypes.isJSFunction(node)) {
|
||||
@ -183,7 +168,7 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
key: widget.key,
|
||||
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';
|
||||
|
||||
function LoopWidgetRenderer({
|
||||
loop,
|
||||
widget,
|
||||
codeRuntime,
|
||||
options,
|
||||
...otherProps
|
||||
}: {
|
||||
interface LoopWidgetRendererProps extends WidgetRendererProps {
|
||||
loop: JSExpression;
|
||||
widget: ReactWidget;
|
||||
codeRuntime: ICodeRuntime;
|
||||
options: ComponentOptions;
|
||||
[key: string]: any;
|
||||
}) {
|
||||
}
|
||||
|
||||
function LoopWidgetRenderer(props: LoopWidgetRendererProps) {
|
||||
const { loop, widget, codeRuntime, ...otherProps } = props;
|
||||
const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode;
|
||||
const state = useReactiveStore({
|
||||
target: {
|
||||
@ -252,7 +230,6 @@ function LoopWidgetRenderer({
|
||||
key={`loop-${widget.key}-${idx}`}
|
||||
widget={widget}
|
||||
codeRuntime={childRuntime}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './createComponent';
|
||||
export * from './reactiveState';
|
||||
export * from './elements';
|
||||
|
||||
@ -18,18 +18,17 @@ export interface ICodeRuntimeService extends IDisposable {
|
||||
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
||||
|
||||
export class CodeRuntimeService extends Disposable implements ICodeRuntimeService {
|
||||
private _rootRuntime: ICodeRuntime;
|
||||
private _rootRuntime?: ICodeRuntime;
|
||||
get rootRuntime() {
|
||||
if (!this._rootRuntime) {
|
||||
this._rootRuntime = this._addDispose(new CodeRuntime());
|
||||
}
|
||||
return this._rootRuntime;
|
||||
}
|
||||
|
||||
constructor(
|
||||
options: CodeRuntimeOptions = {},
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
constructor(@ISchemaService private schemaService: ISchemaService) {
|
||||
super();
|
||||
|
||||
this._rootRuntime = this._addDispose(new CodeRuntime(options));
|
||||
this._addDispose(
|
||||
this.schemaService.onSchemaUpdate(({ key, data }) => {
|
||||
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>(
|
||||
options: CodeRuntimeOptions<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 { IComponentTreeModel } from '../services/model';
|
||||
import { IComponentTreeModel } from './componentTreeModel';
|
||||
|
||||
export interface IWidget<Component, ComponentInstance = unknown> {
|
||||
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 { isObject } from 'lodash-es';
|
||||
import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime';
|
||||
import { IRuntimeUtilService } from '../util/utilService';
|
||||
import { IRuntimeIntlService } from '../intlService';
|
||||
import { IRuntimeUtilService } from '../util';
|
||||
import { IRuntimeIntlService } from '../intl';
|
||||
|
||||
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 { IPackageManagementService } from '../package';
|
||||
import { ISchemaService } from '../schema';
|
||||
import { ILifeCycleService } from '../life-cycle/lifeCycleService';
|
||||
import { ICodeRuntimeService } from '../code-runtime';
|
||||
import { IRuntimeIntlService } from '../intl';
|
||||
import { IRuntimeUtilService } from '../util';
|
||||
@ -28,7 +27,6 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
||||
private _pluginSetupContext: PluginContext;
|
||||
|
||||
constructor(
|
||||
@ILifeCycleService lifeCycleService: ILifeCycleService,
|
||||
@IPackageManagementService packageManagementService: IPackageManagementService,
|
||||
@ISchemaService schemaService: ISchemaService,
|
||||
@ICodeRuntimeService codeRuntimeService: ICodeRuntimeService,
|
||||
@ -48,10 +46,6 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
||||
boosts: this.boostsManager.toExpose(),
|
||||
schema: schemaService,
|
||||
packageManager: packageManagementService,
|
||||
|
||||
whenLifeCylePhaseChange: (phase) => {
|
||||
return lifeCycleService.when(phase);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,9 +95,8 @@ export class ExtensionHostService extends Disposable implements IExtensionHostSe
|
||||
private async _doSetupPlugin(plugin: Plugin) {
|
||||
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._addDispose(plugin);
|
||||
}
|
||||
|
||||
getPlugin(name: string): Plugin | undefined {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from './extensionHostService';
|
||||
export * from './plugin';
|
||||
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 { ILifeCycleService } from '../life-cycle/lifeCycleService';
|
||||
import { type ISchemaService } from '../schema';
|
||||
import { type IPackageManagementService } from '../package';
|
||||
import { type IStore } from '../../utils/store';
|
||||
|
||||
export interface PluginContext<BoostsExtends = object> {
|
||||
globalState: IStore<StringDictionary, string>;
|
||||
globalState: Map<string, any>;
|
||||
|
||||
boosts: IBoosts<BoostsExtends>;
|
||||
|
||||
schema: Pick<ISchemaService, 'get' | 'set'>;
|
||||
|
||||
packageManager: IPackageManagementService;
|
||||
|
||||
whenLifeCylePhaseChange: ILifeCycleService['when'];
|
||||
}
|
||||
|
||||
export interface Plugin<BoostsExtends = object> extends IDisposable {
|
||||
export interface Plugin<BoostsExtends = object> {
|
||||
/**
|
||||
* 插件的 name 作为唯一标识,并不可重复。
|
||||
*/
|
||||
@ -26,7 +22,7 @@ export interface Plugin<BoostsExtends = object> extends IDisposable {
|
||||
* 插件启动函数
|
||||
* @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 './createRenderer';
|
||||
export { IExtensionHostService } from './extension';
|
||||
export { definePackageLoader, IPackageManagementService } from './package';
|
||||
export { LifecyclePhase, ILifeCycleService } from './life-cycle';
|
||||
export { IComponentTreeModelService } from './model';
|
||||
export { ICodeRuntimeService, mapValue, someValue } from './code-runtime';
|
||||
export { IRuntimeIntlService } from './intl';
|
||||
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';
|
||||
export * from './extension';
|
||||
export * from './code-runtime';
|
||||
export * from './component-tree-model';
|
||||
export * from './package';
|
||||
export * from './schema';
|
||||
export * from './extension';
|
||||
export * from './intl';
|
||||
export * from './util';
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
type LocaleTranslationsMap,
|
||||
Disposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { ICodeRuntimeService } from '../code-runtime';
|
||||
|
||||
export interface MessageDescriptor {
|
||||
key: string;
|
||||
@ -16,6 +15,8 @@ export interface MessageDescriptor {
|
||||
}
|
||||
|
||||
export interface IRuntimeIntlService {
|
||||
initialize(locale: string | undefined, i18nTranslations: LocaleTranslationsMap): IntlApi;
|
||||
|
||||
localize(descriptor: MessageDescriptor): string;
|
||||
|
||||
setLocale(locale: Locale): void;
|
||||
@ -28,20 +29,31 @@ export interface IRuntimeIntlService {
|
||||
export const IRuntimeIntlService = createDecorator<IRuntimeIntlService>('IRuntimeIntlService');
|
||||
|
||||
export class RuntimeIntlService extends Disposable implements IRuntimeIntlService {
|
||||
private _intl: Intl;
|
||||
private _intl: Intl = this._addDispose(new Intl());
|
||||
|
||||
constructor(
|
||||
defaultLocale: string | undefined,
|
||||
i18nTranslations: LocaleTranslationsMap,
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
) {
|
||||
constructor() {
|
||||
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)) {
|
||||
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 {
|
||||
@ -65,20 +77,4 @@ export class RuntimeIntlService extends Disposable implements IRuntimeIntlServic
|
||||
addTranslations(locale: Locale, translations: 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 {
|
||||
readonly onSchemaUpdate: Events.Event<SchemaUpdateEvent>;
|
||||
|
||||
initialize(schema: unknown): void;
|
||||
|
||||
get<T>(key: string): T | undefined;
|
||||
get<T>(key: string, defaultValue?: T): T;
|
||||
|
||||
@ -20,20 +22,22 @@ export interface ISchemaService {
|
||||
export const ISchemaService = createDecorator<ISchemaService>('schemaService');
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
initialize(schema: unknown) {
|
||||
if (!isObject(schema)) {
|
||||
throw Error('schema must a object');
|
||||
}
|
||||
|
||||
this.store = {} as any;
|
||||
this._schema = {} as any;
|
||||
for (const key of Object.keys(schema)) {
|
||||
const value = (schema as any)[key];
|
||||
|
||||
@ -52,12 +56,12 @@ export class SchemaService extends Disposable implements ISchemaService {
|
||||
set(key: string, value: any): void {
|
||||
const previous = this.get(key);
|
||||
if (!isEqual(previous, value)) {
|
||||
lodashSet(this.store, key, value);
|
||||
this._observer.notify({ key, previous, data: value });
|
||||
lodashSet(this._schema, key, value);
|
||||
this._onSchemaUpdate.notify({ key, previous, data: value });
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
createDecorator,
|
||||
type StringDictionary,
|
||||
Disposable,
|
||||
type UtilsApi,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { isPlainObject } from 'lodash-es';
|
||||
import { IPackageManagementService } from '../package';
|
||||
import { ICodeRuntimeService } from '../code-runtime';
|
||||
import { ISchemaService } from '../schema';
|
||||
|
||||
export interface IRuntimeUtilService {
|
||||
initialize(): UtilsApi;
|
||||
|
||||
add(utilItem: UtilDescription, 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 class RuntimeUtilService implements IRuntimeUtilService {
|
||||
export class RuntimeUtilService extends Disposable implements IRuntimeUtilService {
|
||||
private _utilsMap: Map<string, any> = new Map();
|
||||
|
||||
constructor(
|
||||
utils: UtilDescription[] = [],
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@IPackageManagementService private packageManagementService: IPackageManagementService,
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
for (const util of utils) {
|
||||
this.add(util);
|
||||
super();
|
||||
|
||||
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;
|
||||
@ -93,20 +121,4 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
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.
|
||||
*/
|
||||
protected addDispose<T extends IDisposable>(o: T): T {
|
||||
protected _addDispose<T extends IDisposable>(o: T): T {
|
||||
this._throwIfDisposed();
|
||||
if ((o as unknown as Disposable) === this) {
|
||||
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 {
|
||||
type BeanIdentifier,
|
||||
@ -14,8 +14,6 @@ export interface InstanceAccessor {
|
||||
}
|
||||
|
||||
export interface IInstantiationService {
|
||||
readonly container: BeanContainer;
|
||||
|
||||
createInstance<T extends Constructor>(Ctor: T, ...args: any[]): InstanceType<T>;
|
||||
|
||||
invokeFunction<R, Args extends any[] = []>(
|
||||
@ -23,6 +21,8 @@ export interface IInstantiationService {
|
||||
...args: Args
|
||||
): R;
|
||||
|
||||
createChild(container: BeanContainer): IInstantiationService;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@ -31,11 +31,47 @@ export const IInstantiationService = createDecorator<IInstantiationService>('ins
|
||||
export class InstantiationService implements IInstantiationService {
|
||||
private _activeInstantiations = new Set<BeanIdentifier<any>>();
|
||||
|
||||
private _children = new Set<InstantiationService>();
|
||||
|
||||
private _isDisposed = false;
|
||||
private readonly _beansToMaybeDispose = new Set<any>();
|
||||
|
||||
constructor(public readonly container: BeanContainer = new BeanContainer()) {
|
||||
this.container.set(IInstantiationService, this);
|
||||
constructor(
|
||||
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 {
|
||||
const thing = this.container.get(id);
|
||||
const thing = this._container.get(id);
|
||||
if (thing instanceof CtorDescriptor) {
|
||||
return this._safeCreateAndCacheInstance<T>(id, thing);
|
||||
} else {
|
||||
@ -138,7 +174,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
|
||||
// check all dependencies for existence and if they need to be created first
|
||||
for (const dependency of getBeanDependecies(item.desc.ctor)) {
|
||||
const instanceOrDesc = this.container.get(dependency.id);
|
||||
const instanceOrDesc = this._container.get(dependency.id);
|
||||
if (!instanceOrDesc) {
|
||||
throw new Error(
|
||||
`[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
|
||||
// instantiating a dependency might have side-effect and recursively trigger instantiation
|
||||
// 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) {
|
||||
// create instance and overwrite the service collections
|
||||
const instance = this.createInstance(
|
||||
instanceOrDesc.ctor,
|
||||
instanceOrDesc.staticArguments,
|
||||
const instance = this._createServiceInstanceWithOwner(
|
||||
data.id,
|
||||
data.desc.ctor,
|
||||
data.desc.staticArguments,
|
||||
);
|
||||
this._beansToMaybeDispose.add(instance);
|
||||
this.container.set(data.id, instance);
|
||||
this._setCreatedServiceInstance(data.id, instance);
|
||||
}
|
||||
graph.removeNode(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.container.get(id) as T;
|
||||
return this._container.get(id) as T;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._isDisposed) return;
|
||||
private _createServiceInstanceWithOwner<T>(id: BeanIdentifier<T>, ctor: any, args: any[]): T {
|
||||
if (this._container.get(id) instanceof CtorDescriptor) {
|
||||
const instance = this.createInstance(ctor, args);
|
||||
this._beansToMaybeDispose.add(instance);
|
||||
|
||||
// dispose all services created by this service
|
||||
for (const candidate of this._beansToMaybeDispose) {
|
||||
if (isDisposable(candidate)) {
|
||||
candidate.dispose();
|
||||
return instance;
|
||||
} else if (this._parent) {
|
||||
return this._parent._createServiceInstanceWithOwner(id, ctor, args);
|
||||
} 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 {
|
||||
|
||||
@ -29,7 +29,7 @@ export class Intl extends Disposable {
|
||||
return this._messageStore.value[this._locale.value] ?? {};
|
||||
});
|
||||
|
||||
this.addDispose(
|
||||
this._addDispose(
|
||||
toDisposable(
|
||||
effect(() => {
|
||||
const cache = createIntlCache();
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* 低代码引擎资产包协议规范
|
||||
*/
|
||||
import { StringDictionary } from '..';
|
||||
import { ComponentTreeRoot } from './lowcode-spec';
|
||||
import { ComponentTree } from './lowcode-spec';
|
||||
import { ComponentMetaData, Reference } from './material-spec';
|
||||
|
||||
/**
|
||||
@ -85,7 +85,7 @@ export interface Package {
|
||||
/**
|
||||
* 低代码组件的 schema 内容
|
||||
*/
|
||||
schema?: ComponentTreeRoot;
|
||||
schema?: ComponentTree;
|
||||
/**
|
||||
* 当前资源所依赖的其他资源包的 id 列表
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user