From 39be9506000b286e4f8871c9230e1831f998f40b Mon Sep 17 00:00:00 2001 From: 1ncounter <1ncounter.100@gmail.com> Date: Thu, 1 Aug 2024 20:28:50 +0800 Subject: [PATCH] refactor: renderer --- .../src/models/document/document-model.ts | 6 +- packages/engine-core/src/index.ts | 8 - .../src/lifecycle/lifeCycleService.ts | 82 +++++ packages/engine-core/src/main.ts | 19 +- packages/engine-core/src/theme/theme.ts | 5 + .../engine-core/src/theme/themeService.ts | 21 ++ packages/engine/src/main.ts | 2 +- packages/engine/src/themeService.ts | 27 -- packages/react-renderer/src/api/app.tsx | 61 +--- packages/react-renderer/src/api/component.tsx | 75 ++--- packages/react-renderer/src/api/context.ts | 41 --- packages/react-renderer/src/api/types.ts | 11 - packages/react-renderer/src/app/app.ts | 279 ++++++++++++++++++ packages/react-renderer/src/app/boosts.ts | 56 ++-- .../src/app/components/route.tsx | 51 ++++ .../src/app/components/routerView.tsx | 18 ++ .../src/app/components/view.tsx | 47 +++ .../src/{router => app}/context.ts | 7 + packages/react-renderer/src/app/index.ts | 4 +- packages/react-renderer/src/app/view.tsx | 34 --- packages/react-renderer/src/index.ts | 5 +- packages/react-renderer/src/router/index.ts | 10 - packages/react-renderer/src/router/plugin.ts | 39 --- packages/react-renderer/src/router/route.tsx | 42 --- .../react-renderer/src/router/routerView.tsx | 20 -- .../src/runtime/createComponent.tsx | 61 ++-- .../react-renderer/src/runtime/elements.tsx | 77 ++--- packages/react-renderer/src/runtime/index.ts | 1 + .../src/code-runtime/codeRuntimeService.ts | 15 +- .../componentTreeModel.ts | 0 .../src/component-tree-model/index.ts | 3 + .../widget.ts | 2 +- packages/renderer-core/src/createRenderer.ts | 123 -------- .../renderer-core/src/extension/boosts.ts | 4 +- .../src/extension/extensionHostService.ts | 9 +- packages/renderer-core/src/extension/index.ts | 1 - .../renderer-core/src/extension/plugin.ts | 12 +- .../renderer-core/src/extension/render.ts | 10 - packages/renderer-core/src/index.ts | 28 +- .../renderer-core/src/intl/intlService.ts | 46 ++- .../renderer-core/src/life-cycle/index.ts | 1 - .../src/life-cycle/lifeCycleService.ts | 99 ------- .../src/model/componentTreeModelService.ts | 73 ----- packages/renderer-core/src/model/index.ts | 2 - .../renderer-core/src/schema/schemaService.ts | 20 +- .../renderer-core/src/util/utilService.ts | 56 ++-- packages/renderer-core/src/widget/index.ts | 1 - packages/shared/src/common/disposable.ts | 2 +- .../instantiation/instantiationService.ts | 92 ++++-- packages/shared/src/common/intl.ts | 2 +- packages/shared/src/types/specs/asset-spec.ts | 4 +- 51 files changed, 829 insertions(+), 885 deletions(-) create mode 100644 packages/engine-core/src/theme/theme.ts create mode 100644 packages/engine-core/src/theme/themeService.ts delete mode 100644 packages/engine/src/themeService.ts delete mode 100644 packages/react-renderer/src/api/context.ts delete mode 100644 packages/react-renderer/src/api/types.ts create mode 100644 packages/react-renderer/src/app/app.ts create mode 100644 packages/react-renderer/src/app/components/route.tsx create mode 100644 packages/react-renderer/src/app/components/routerView.tsx create mode 100644 packages/react-renderer/src/app/components/view.tsx rename packages/react-renderer/src/{router => app}/context.ts (73%) delete mode 100644 packages/react-renderer/src/app/view.tsx delete mode 100644 packages/react-renderer/src/router/index.ts delete mode 100644 packages/react-renderer/src/router/plugin.ts delete mode 100644 packages/react-renderer/src/router/route.tsx delete mode 100644 packages/react-renderer/src/router/routerView.tsx rename packages/renderer-core/src/{model => component-tree-model}/componentTreeModel.ts (100%) create mode 100644 packages/renderer-core/src/component-tree-model/index.ts rename packages/renderer-core/src/{widget => component-tree-model}/widget.ts (93%) delete mode 100644 packages/renderer-core/src/createRenderer.ts delete mode 100644 packages/renderer-core/src/extension/render.ts delete mode 100644 packages/renderer-core/src/life-cycle/index.ts delete mode 100644 packages/renderer-core/src/life-cycle/lifeCycleService.ts delete mode 100644 packages/renderer-core/src/model/componentTreeModelService.ts delete mode 100644 packages/renderer-core/src/model/index.ts delete mode 100644 packages/renderer-core/src/widget/index.ts diff --git a/packages/designer/src/models/document/document-model.ts b/packages/designer/src/models/document/document-model.ts index bd1b7431a..57072818d 100644 --- a/packages/designer/src/models/document/document-model.ts +++ b/packages/designer/src/models/document/document-model.ts @@ -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({}); + const currentDocumentSchema = Signals.signal({}); const documentHistory = new History(currentDocumentSchema, () => {}); diff --git a/packages/engine-core/src/index.ts b/packages/engine-core/src/index.ts index a3c0f28b6..44a4836a4 100644 --- a/packages/engine-core/src/index.ts +++ b/packages/engine-core/src/index.ts @@ -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'; diff --git a/packages/engine-core/src/lifecycle/lifeCycleService.ts b/packages/engine-core/src/lifecycle/lifeCycleService.ts index e69de29bb..c90f1dadd 100644 --- a/packages/engine-core/src/lifecycle/lifeCycleService.ts +++ b/packages/engine-core/src/lifecycle/lifeCycleService.ts @@ -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; + + onWillDestory(): void; +} + +export const ILifeCycleService = createDecorator('lifeCycleService'); + +export class LifeCycleService implements ILifeCycleService { + private readonly phaseWhen = new Map(); + + 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 { + 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 {} +} diff --git a/packages/engine-core/src/main.ts b/packages/engine-core/src/main.ts index b54a5cf90..a070eb846 100644 --- a/packages/engine-core/src/main.ts +++ b/packages/engine-core/src/main.ts @@ -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); + } + + initServices() {} } -export async function createLowCodeEngineApp(): Promise { - const app = new MainApplication(); +export async function createLowCodeEngineApp() { + const app = new TestMainApplication(); await app.main(); diff --git a/packages/engine-core/src/theme/theme.ts b/packages/engine-core/src/theme/theme.ts new file mode 100644 index 000000000..7fc4a37ea --- /dev/null +++ b/packages/engine-core/src/theme/theme.ts @@ -0,0 +1,5 @@ +export interface ITheme { + type: string; + + value: string; +} diff --git a/packages/engine-core/src/theme/themeService.ts b/packages/engine-core/src/theme/themeService.ts new file mode 100644 index 000000000..b0f180d55 --- /dev/null +++ b/packages/engine-core/src/theme/themeService.ts @@ -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; +} + +export const IThemeService = createDecorator('themeService'); + +export class ThemeService extends Disposable implements IThemeService { + private _activeTheme: ITheme; + + private _onDidColorThemeChange = this._addDispose(new Events.Emitter()); + onDidColorThemeChange = this._onDidColorThemeChange.event; + + getTheme(): ITheme { + return this._activeTheme; + } +} diff --git a/packages/engine/src/main.ts b/packages/engine/src/main.ts index c5bc51437..350783d1c 100644 --- a/packages/engine/src/main.ts +++ b/packages/engine/src/main.ts @@ -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(); diff --git a/packages/engine/src/themeService.ts b/packages/engine/src/themeService.ts deleted file mode 100644 index ac7919d57..000000000 --- a/packages/engine/src/themeService.ts +++ /dev/null @@ -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('themeService'); - -export class ThemeService implements IThemeService { - private activeTheme: ITheme; - - getTheme(): ITheme { - return this.activeTheme; - } - - onDidColorThemeChange(listener: EventListener<[ITheme]>) { - return () => {}; - } -} diff --git a/packages/react-renderer/src/api/app.tsx b/packages/react-renderer/src/api/app.tsx index bf0aac153..0d5c6497a 100644 --- a/packages/react-renderer/src/api/app.tsx +++ b/packages/react-renderer/src/api/app.tsx @@ -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); - contextValue.boostsManager.extend(boosts.toExpose()); + await app.startup(); - let root: Root | undefined; - - return { - async mount(containerOrId) { - if (root) return; - - const defaultId = contextValue.schema.get('config.targetRootID', 'app'); - const rootElement = normalizeContainer(containerOrId, defaultId); - - root = createRoot(rootElement); - root.render( - - - , - ); - }, - unmount() { - if (root) { - root.unmount(); - root = undefined; - } - }, - }; - })(options); + return app; }; - -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; -} diff --git a/packages/react-renderer/src/api/component.tsx b/packages/react-renderer/src/api/component.tsx index 58e1fbdb1..f6607bf75 100644 --- a/packages/react-renderer/src/api/component.tsx +++ b/packages/react-renderer/src/api/component.tsx @@ -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'; - -interface Render { - toComponent(): FunctionComponent; + type ComponentOptions as SchemaComponentOptions, + reactiveStateFactory, +} from '../runtime'; +import { type ComponentsAccessor } from '../app'; + +export interface ComponentOptions extends SchemaComponentOptions { + schema: ComponentTree; + componentsRecord: Record; + codeScope?: StringDictionary; } -export async function createComponent(options: ReactAppOptions) { - const creator = createRenderer((service) => { - const contextValue: IRendererContext = service.invokeFunction((accessor) => { - return { - options, - ...getRenderInstancesByAccessor(accessor), - }; - }); - - const componentsTree = contextValue.schema.get('componentsTree.0'); - - if (!componentsTree) { - throw new Error('componentsTree is required'); - } - - const LowCodeComponent = createSchemaComponent(componentsTree, { - displayName: componentsTree.componentName, - ...options.component, - }); - - function Component(props: LowCodeComponentProps) { - return ( - - - - ); - } - - return { - toComponent() { - return Component; - }, - }; +export function createComponent( + options: ComponentOptions, +): FunctionComponent { + 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; } diff --git a/packages/react-renderer/src/api/context.ts b/packages/react-renderer/src/api/context.ts deleted file mode 100644 index 2f924f9ca..000000000 --- a/packages/react-renderer/src/api/context.ts +++ /dev/null @@ -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; - - 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(undefined!); - -RendererContext.displayName = 'RendererContext'; - -export const useRendererContext = () => useContext(RendererContext); diff --git a/packages/react-renderer/src/api/types.ts b/packages/react-renderer/src/api/types.ts deleted file mode 100644 index 2c73be30f..000000000 --- a/packages/react-renderer/src/api/types.ts +++ /dev/null @@ -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; -} diff --git a/packages/react-renderer/src/app/app.ts b/packages/react-renderer/src/app/app.ts new file mode 100644 index 000000000..8783df8a9 --- /dev/null +++ b/packages/react-renderer/src/app/app.ts @@ -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; + +export type SchemaAccessor = Pick; + +export interface IRendererApplication { + readonly options: AppOptions; + + readonly mode: 'development' | 'production'; + + mount: (containerOrId?: string | HTMLElement) => void | Promise; + + unmount: () => void | Promise; + + use(plugin: Plugin): Promise; + + destroy(): void; +} + +export interface AppOptions { + schema: Project; + packages?: Package[]; + plugins?: Plugin[]; + /** + * code runtime 设置选项 + */ + codeRuntime?: CodeRuntimeOptions; + + component?: { + stateCreator?: ModelStateCreator; + /** + * 数据源创建工厂函数 + */ + dataSourceCreator?: ModelDataSourceCreator; + } & Pick; + + faultComponent?: ComponentType; +} + +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('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('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) { + 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) { + return plugin; +} diff --git a/packages/react-renderer/src/app/boosts.ts b/packages/react-renderer/src/app/boosts.ts index d38798cf7..d748cfccc 100644 --- a/packages/react-renderer/src/app/boosts.ts +++ b/packages/react-renderer/src/app/boosts.ts @@ -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>; @@ -9,39 +10,58 @@ export interface OutletProps { export type Outlet = ComponentType; -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( + 'reactRendererBoostsService', +); + +export type ReactRendererBoostsApi = Pick< + IReactRendererBoostsService, + 'addAppWrapper' | 'setOutlet' +>; + +export class ReactRendererBoostsService implements IReactRendererBoostsService { + private _wrappers: WrapperComponent[] = []; - private outlet: Outlet | null = null; + private _outlet: Outlet | null = null; - getAppWrappers() { - return this.wrappers; + 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) { - return plugin; -} diff --git a/packages/react-renderer/src/app/components/route.tsx b/packages/react-renderer/src/app/components/route.tsx new file mode 100644 index 000000000..a4fed8b7c --- /dev/null +++ b/packages/react-renderer/src/app/components/route.tsx @@ -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('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('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 ; + } + + return null; +} diff --git a/packages/react-renderer/src/app/components/routerView.tsx b/packages/react-renderer/src/app/components/routerView.tsx new file mode 100644 index 000000000..d4158b5dc --- /dev/null +++ b/packages/react-renderer/src/app/components/routerView.tsx @@ -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 ( + + {children} + + ); +} diff --git a/packages/react-renderer/src/app/components/view.tsx b/packages/react-renderer/src/app/components/view.tsx new file mode 100644 index 000000000..ebfbef752 --- /dev/null +++ b/packages/react-renderer/src/app/components/view.tsx @@ -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 = ( + {Outlet ? : } + ); + + const layoutConfig = app.schema.get('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 = {element}; + } + } + + if (wrappers.length > 0) { + element = wrappers.reduce((preElement, CurrentWrapper) => { + return {preElement}; + }, element); + } + + return {element}; + }; +}; diff --git a/packages/react-renderer/src/router/context.ts b/packages/react-renderer/src/app/context.ts similarity index 73% rename from packages/react-renderer/src/router/context.ts rename to packages/react-renderer/src/app/context.ts index c6f6fa6ce..04f01d8c3 100644 --- a/packages/react-renderer/src/router/context.ts +++ b/packages/react-renderer/src/app/context.ts @@ -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(undefined!); + +AppContext.displayName = 'AppContext'; + +export const useAppContext = () => useContext(AppContext); export const RouterContext = createContext(undefined!); diff --git a/packages/react-renderer/src/app/index.ts b/packages/react-renderer/src/app/index.ts index c1e198137..c9bdc70de 100644 --- a/packages/react-renderer/src/app/index.ts +++ b/packages/react-renderer/src/app/index.ts @@ -1,2 +1,2 @@ -export * from './boosts'; -export * from './view'; +export * from './app'; +export * from './context'; diff --git a/packages/react-renderer/src/app/view.tsx b/packages/react-renderer/src/app/view.tsx deleted file mode 100644 index e8731b3c8..000000000 --- a/packages/react-renderer/src/app/view.tsx +++ /dev/null @@ -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 = ; - - 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 = {element}; - } - } - - if (appWrappers.length > 0) { - element = appWrappers.reduce((preElement, CurrentWrapper) => { - return {preElement}; - }, element); - } - - return element; -} diff --git a/packages/react-renderer/src/index.ts b/packages/react-renderer/src/index.ts index cc01820a6..b458f00c1 100644 --- a/packages/react-renderer/src/index.ts +++ b/packages/react-renderer/src/index.ts @@ -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'; diff --git a/packages/react-renderer/src/router/index.ts b/packages/react-renderer/src/router/index.ts deleted file mode 100644 index 0c2143dd8..000000000 --- a/packages/react-renderer/src/router/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './context'; -export * from './plugin'; -export type { - RouteLocation, - RawLocation, - Router, - RouterOptions, - RouteLocationNormalized, - RouterHistory, -} from '@alilc/lowcode-renderer-router'; diff --git a/packages/react-renderer/src/router/plugin.ts b/packages/react-renderer/src/router/plugin.ts deleted file mode 100644 index ba617ccd0..000000000 --- a/packages/react-renderer/src/router/plugin.ts +++ /dev/null @@ -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(); - }, -}); diff --git a/packages/react-renderer/src/router/route.tsx b/packages/react-renderer/src/router/route.tsx deleted file mode 100644 index abf345479..000000000 --- a/packages/react-renderer/src/router/route.tsx +++ /dev/null @@ -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 ; - } - - return null; -} diff --git a/packages/react-renderer/src/router/routerView.tsx b/packages/react-renderer/src/router/routerView.tsx deleted file mode 100644 index cf9013df8..000000000 --- a/packages/react-renderer/src/router/routerView.tsx +++ /dev/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 ( - - {children} - - ); - }; -}; diff --git a/packages/react-renderer/src/runtime/createComponent.tsx b/packages/react-renderer/src/runtime/createComponent.tsx index 6e9f430e4..9163e3c24 100644 --- a/packages/react-renderer/src/runtime/createComponent.tsx +++ b/packages/react-renderer/src/runtime/createComponent.tsx @@ -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; + modelOptions: ComponentTreeModelOptions; beforeElementCreate?(widget: ReactWidget): ReactWidget; elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode; @@ -33,20 +30,22 @@ export interface LowCodeComponentProps { const lowCodeComponentsCache = new Map(); -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, ) { - const context = useRendererContext(); - const { options: globalOptions, componentTreeModel } = context; - const modelRef = useRef>(); 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 (
- {model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))} + {model.widgets.map((w) => + createElementByWidget({ + widget: w, + codeRuntime: w.model.codeRuntime, + components, + options: componentOptions, + }), + )}
); }); diff --git a/packages/react-renderer/src/runtime/elements.tsx b/packages/react-renderer/src/runtime/elements.tsx index 98c21fd78..d8b56b344 100644 --- a/packages/react-renderer/src/runtime/elements.tsx +++ b/packages/react-renderer/src/runtime/elements.tsx @@ -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; export type ReactWidget = IWidget; @@ -29,16 +29,13 @@ export type ReactWidget = IWidget; 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 ; + return ; } if (specTypes.isJSI18nNode(rawNode)) { - return ; + return ; } 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 ( - - ); + return ; } - return ( - - ); + return ; }; - 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} /> ); }); diff --git a/packages/react-renderer/src/runtime/index.ts b/packages/react-renderer/src/runtime/index.ts index c49e6c9b2..f4cfd142d 100644 --- a/packages/react-renderer/src/runtime/index.ts +++ b/packages/react-renderer/src/runtime/index.ts @@ -1,2 +1,3 @@ export * from './createComponent'; +export * from './reactiveState'; export * from './elements'; diff --git a/packages/renderer-core/src/code-runtime/codeRuntimeService.ts b/packages/renderer-core/src/code-runtime/codeRuntimeService.ts index ab63e0aa0..cdffed19e 100644 --- a/packages/renderer-core/src/code-runtime/codeRuntimeService.ts +++ b/packages/renderer-core/src/code-runtime/codeRuntimeService.ts @@ -18,18 +18,17 @@ export interface ICodeRuntimeService extends IDisposable { export const ICodeRuntimeService = createDecorator('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( options: CodeRuntimeOptions = {}, ): ICodeRuntime { diff --git a/packages/renderer-core/src/model/componentTreeModel.ts b/packages/renderer-core/src/component-tree-model/componentTreeModel.ts similarity index 100% rename from packages/renderer-core/src/model/componentTreeModel.ts rename to packages/renderer-core/src/component-tree-model/componentTreeModel.ts diff --git a/packages/renderer-core/src/component-tree-model/index.ts b/packages/renderer-core/src/component-tree-model/index.ts new file mode 100644 index 000000000..478c1c9ec --- /dev/null +++ b/packages/renderer-core/src/component-tree-model/index.ts @@ -0,0 +1,3 @@ +export * from './componentTreeModel'; + +export type { IWidget } from './widget'; diff --git a/packages/renderer-core/src/widget/widget.ts b/packages/renderer-core/src/component-tree-model/widget.ts similarity index 93% rename from packages/renderer-core/src/widget/widget.ts rename to packages/renderer-core/src/component-tree-model/widget.ts index fc897929e..3738ab88e 100644 --- a/packages/renderer-core/src/widget/widget.ts +++ b/packages/renderer-core/src/component-tree-model/widget.ts @@ -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 { readonly key: string; diff --git a/packages/renderer-core/src/createRenderer.ts b/packages/renderer-core/src/createRenderer.ts deleted file mode 100644 index 5b9fb13aa..000000000 --- a/packages/renderer-core/src/createRenderer.ts +++ /dev/null @@ -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 = { - readonly mode: 'development' | 'production'; - - readonly schema: Omit; - - readonly packageManager: IPackageManagementService; - - use(plugin: Plugin): Promise; - - destroy(): void; -} & Render; - -export function createRenderer( - renderAdapter: RenderAdapter, -): (options: AppOptions) => Promise> { - 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 = { - 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; - }; -} diff --git a/packages/renderer-core/src/extension/boosts.ts b/packages/renderer-core/src/extension/boosts.ts index ddc2eeb3d..724fa12cd 100644 --- a/packages/renderer-core/src/extension/boosts.ts +++ b/packages/renderer-core/src/extension/boosts.ts @@ -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 = IBoostsApi & Extends & { [key: string]: any }; diff --git a/packages/renderer-core/src/extension/extensionHostService.ts b/packages/renderer-core/src/extension/extensionHostService.ts index c207424fb..4a81ab70d 100644 --- a/packages/renderer-core/src/extension/extensionHostService.ts +++ b/packages/renderer-core/src/extension/extensionHostService.ts @@ -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 { diff --git a/packages/renderer-core/src/extension/index.ts b/packages/renderer-core/src/extension/index.ts index e5c9d94ff..af79b6b04 100644 --- a/packages/renderer-core/src/extension/index.ts +++ b/packages/renderer-core/src/extension/index.ts @@ -1,4 +1,3 @@ export * from './extensionHostService'; export * from './plugin'; export * from './boosts'; -export * from './render'; diff --git a/packages/renderer-core/src/extension/plugin.ts b/packages/renderer-core/src/extension/plugin.ts index 863fa6fa3..46485290b 100644 --- a/packages/renderer-core/src/extension/plugin.ts +++ b/packages/renderer-core/src/extension/plugin.ts @@ -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 { - globalState: IStore; + globalState: Map; boosts: IBoosts; schema: Pick; packageManager: IPackageManagementService; - - whenLifeCylePhaseChange: ILifeCycleService['when']; } -export interface Plugin extends IDisposable { +export interface Plugin { /** * 插件的 name 作为唯一标识,并不可重复。 */ @@ -26,7 +22,7 @@ export interface Plugin extends IDisposable { * 插件启动函数 * @param context 插件能力上下文 */ - setup(context: PluginContext): void | Promise; + setup(context: PluginContext): IDisposable | Promise; /** * 插件的依赖插件 */ diff --git a/packages/renderer-core/src/extension/render.ts b/packages/renderer-core/src/extension/render.ts deleted file mode 100644 index a6364a6ca..000000000 --- a/packages/renderer-core/src/extension/render.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type IInstantiationService } from '@alilc/lowcode-shared'; - -export interface IRenderObject { - mount: (containerOrId?: string | HTMLElement) => void | Promise; - unmount: () => void | Promise; -} - -export interface RenderAdapter { - (instantiationService: IInstantiationService): Render | Promise; -} diff --git a/packages/renderer-core/src/index.ts b/packages/renderer-core/src/index.ts index cad42f4e9..70fdfc944 100644 --- a/packages/renderer-core/src/index.ts +++ b/packages/renderer-core/src/index.ts @@ -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'; diff --git a/packages/renderer-core/src/intl/intlService.ts b/packages/renderer-core/src/intl/intlService.ts index 8c0ad7cd6..6c702a6aa 100644 --- a/packages/renderer-core/src/intl/intlService.ts +++ b/packages/renderer-core/src/intl/intlService.ts @@ -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'); 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); - } } diff --git a/packages/renderer-core/src/life-cycle/index.ts b/packages/renderer-core/src/life-cycle/index.ts deleted file mode 100644 index c3ec2d63e..000000000 --- a/packages/renderer-core/src/life-cycle/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './lifeCycleService'; diff --git a/packages/renderer-core/src/life-cycle/lifeCycleService.ts b/packages/renderer-core/src/life-cycle/lifeCycleService.ts deleted file mode 100644 index 146b830d9..000000000 --- a/packages/renderer-core/src/life-cycle/lifeCycleService.ts +++ /dev/null @@ -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; - - 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('lifeCycleService'); - -export class LifeCycleService implements ILifeCycleService { - private readonly phaseWhen = new Map(); - - 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 { - 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 {} -} diff --git a/packages/renderer-core/src/model/componentTreeModelService.ts b/packages/renderer-core/src/model/componentTreeModelService.ts deleted file mode 100644 index 90b7048c3..000000000 --- a/packages/renderer-core/src/model/componentTreeModelService.ts +++ /dev/null @@ -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( - componentsTree: ComponentTree, - options?: CreateComponentTreeModelOptions, - ): IComponentTreeModel; - - createById( - id: string, - options?: CreateComponentTreeModelOptions, - ): IComponentTreeModel; -} - -export const IComponentTreeModelService = createDecorator( - 'componentTreeModelService', -); - -export class ComponentTreeModelService extends Disposable implements IComponentTreeModelService { - constructor( - @ISchemaService private schemaService: ISchemaService, - @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, - ) { - super(); - } - - create( - componentsTree: ComponentTree, - options: CreateComponentTreeModelOptions, - ): IComponentTreeModel { - this._throwIfDisposed(`ComponentTreeModelService has been disposed.`); - - return this._addDispose( - new ComponentTreeModel( - componentsTree, - this.codeRuntimeService.createCodeRuntime({ - initScopeValue: options?.codeScopeValue as any, - }), - options, - ), - ); - } - - createById( - id: string, - options: CreateComponentTreeModelOptions, - ): IComponentTreeModel { - const componentsTrees = this.schemaService.get('componentsTree', []); - const componentsTree = componentsTrees.find((item) => item.id === id); - - invariant(componentsTree, 'componentsTree not found'); - - return this.create(componentsTree, options); - } -} diff --git a/packages/renderer-core/src/model/index.ts b/packages/renderer-core/src/model/index.ts deleted file mode 100644 index d4c01d45a..000000000 --- a/packages/renderer-core/src/model/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './componentTreeModel'; -export * from './componentTreeModelService'; diff --git a/packages/renderer-core/src/schema/schemaService.ts b/packages/renderer-core/src/schema/schemaService.ts index e3bd5046a..51dea18dc 100644 --- a/packages/renderer-core/src/schema/schemaService.ts +++ b/packages/renderer-core/src/schema/schemaService.ts @@ -11,6 +11,8 @@ export type SchemaUpdateEvent = { key: string; previous: any; data: any }; export interface ISchemaService { readonly onSchemaUpdate: Events.Event; + initialize(schema: unknown): void; + get(key: string): T | undefined; get(key: string, defaultValue?: T): T; @@ -20,20 +22,22 @@ export interface ISchemaService { export const ISchemaService = createDecorator('schemaService'); export class SchemaService extends Disposable implements ISchemaService { - private store: NormalizedSchema; + private _schema: NormalizedSchema; - private _observer = this._addDispose(new Events.Emitter()); + private _onSchemaUpdate = this._addDispose(new Events.Emitter()); - 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(key: string, defaultValue?: T): T { - return (lodashGet(this.store, key) ?? defaultValue) as T; + return (lodashGet(this._schema, key) ?? defaultValue) as T; } } diff --git a/packages/renderer-core/src/util/utilService.ts b/packages/renderer-core/src/util/utilService.ts index 58ba3858d..79f189af2 100644 --- a/packages/renderer-core/src/util/utilService.ts +++ b/packages/renderer-core/src/util/utilService.ts @@ -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('rendererUtilService'); -export class RuntimeUtilService implements IRuntimeUtilService { +export class RuntimeUtilService extends Disposable implements IRuntimeUtilService { private _utilsMap: Map = 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); - } - this._injectScope(); + super(); + + this._addDispose( + this.schemaService.onSchemaUpdate(({ key, data }) => { + if (key === 'utils') { + for (const item of data) { + this.add(item); + } + } + }), + ); + } + + 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); - } } diff --git a/packages/renderer-core/src/widget/index.ts b/packages/renderer-core/src/widget/index.ts deleted file mode 100644 index 3171f7e1f..000000000 --- a/packages/renderer-core/src/widget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './widget'; diff --git a/packages/shared/src/common/disposable.ts b/packages/shared/src/common/disposable.ts index ae62db7a8..934403370 100644 --- a/packages/shared/src/common/disposable.ts +++ b/packages/shared/src/common/disposable.ts @@ -47,7 +47,7 @@ export abstract class Disposable implements IDisposable { /** * Adds `o` to the collection of disposables managed by this object. */ - protected addDispose(o: T): T { + protected _addDispose(o: T): T { this._throwIfDisposed(); if ((o as unknown as Disposable) === this) { throw new Error('Cannot register a disposable on itself!'); diff --git a/packages/shared/src/common/instantiation/instantiationService.ts b/packages/shared/src/common/instantiation/instantiationService.ts index 4c4ea9de9..99692eeca 100644 --- a/packages/shared/src/common/instantiation/instantiationService.ts +++ b/packages/shared/src/common/instantiation/instantiationService.ts @@ -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(Ctor: T, ...args: any[]): InstanceType; invokeFunction( @@ -23,6 +21,8 @@ export interface IInstantiationService { ...args: Args ): R; + createChild(container: BeanContainer): IInstantiationService; + dispose(): void; } @@ -31,11 +31,47 @@ export const IInstantiationService = createDecorator('ins export class InstantiationService implements IInstantiationService { private _activeInstantiations = new Set>(); + private _children = new Set(); + private _isDisposed = false; private readonly _beansToMaybeDispose = new Set(); - 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(id: BeanIdentifier): T { - const thing = this.container.get(id); + const thing = this._container.get(id); if (thing instanceof CtorDescriptor) { return this._safeCreateAndCacheInstance(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(id: BeanIdentifier, 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}`); + } + } + + private _setCreatedServiceInstance(id: BeanIdentifier, 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'); } - this._beansToMaybeDispose.clear(); - this._isDisposed = true; } private _throwIfDisposed(): void { diff --git a/packages/shared/src/common/intl.ts b/packages/shared/src/common/intl.ts index d9f034cd4..f2bf1c47a 100644 --- a/packages/shared/src/common/intl.ts +++ b/packages/shared/src/common/intl.ts @@ -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(); diff --git a/packages/shared/src/types/specs/asset-spec.ts b/packages/shared/src/types/specs/asset-spec.ts index a693916d4..0b4bb974c 100644 --- a/packages/shared/src/types/specs/asset-spec.ts +++ b/packages/shared/src/types/specs/asset-spec.ts @@ -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 列表 */