refactor: change render core export api

This commit is contained in:
1ncounter 2024-07-26 10:34:53 +08:00
parent c1356bfe70
commit 9d0f178a06
30 changed files with 480 additions and 154 deletions

View File

@ -0,0 +1,11 @@
export interface UriComponents {
path: string;
}
export class URI implements UriComponents {
readonly path: string;
constructor(path: string) {
this.path = path;
}
}

View File

@ -100,7 +100,9 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
*
*/
export interface IExtensionInfo {
name: string;
id: string;
displayName?: string;
version?: string;
}
export type ConfigurationDefaultValueSource = IExtensionInfo | Map<string, IExtensionInfo>;
@ -640,7 +642,7 @@ export class ConfigurationRegistry implements IConfigurationRegistry {
function isSameExtension(a?: IExtensionInfo, b?: IExtensionInfo): boolean {
if (!a || !b) return false;
return a.name === b.name;
return a.id === b.id && a.version === b.version;
}
Registry.add(Extensions.Configuration, new ConfigurationRegistry());

View File

@ -7,9 +7,10 @@ export type ExtensionInitializer = <Context = any>(ctx: Context) => IExtensionIn
*
*/
export interface IFunctionExtension extends ExtensionInitializer {
name: string;
id: string;
displayName?: string;
version: string;
meta?: IExtensionMetadata;
metadata?: IExtensionMetadata;
}
export interface IExtensionMetadata {

View File

@ -2,7 +2,7 @@ import { InstantiationService } from '@alilc/lowcode-shared';
import { IWorkbenchService } from './workbench';
import { IConfigurationService } from './configuration';
export class MainApplication {
class MainApplication {
constructor() {
console.log('main application');
}

View File

@ -0,0 +1,140 @@
import { URI } from '../../common/uri';
export enum FileType {
/**
* File is unknown (neither file, directory).
*/
Unknown = 0,
/**
* File is a normal file.
*/
File = 1,
/**
* File is a directory.
*/
Directory = 2,
}
export interface IStat {
/**
* The file type.
*/
readonly type: FileType;
/**
* The last modification date represented as millis from unix epoch.
*/
readonly mtime: number;
/**
* The creation date represented as millis from unix epoch.
*/
readonly ctime: number;
}
export interface IBaseFileStat {
/**
* The unified resource identifier of this file or folder.
*/
readonly resource: URI;
/**
* The name which is the last segment
* of the {{path}}.
*/
readonly name: string;
/**
* The size of the file.
*
* The value may or may not be resolved as
* it is optional.
*/
readonly size?: number;
/**
* The last modification date represented as millis from unix epoch.
*
* The value may or may not be resolved as
* it is optional.
*/
readonly mtime?: number;
/**
* The creation date represented as millis from unix epoch.
*
* The value may or may not be resolved as
* it is optional.
*/
readonly ctime?: number;
/**
* A unique identifier that represents the
* current state of the file or directory.
*
* The value may or may not be resolved as
* it is optional.
*/
readonly etag?: string;
/**
* File is readonly. Components like editors should not
* offer to edit the contents.
*/
readonly readonly?: boolean;
/**
* File is locked. Components like editors should offer
* to edit the contents and ask the user upon saving to
* remove the lock.
*/
readonly locked?: boolean;
}
/**
* A file resource with meta information and resolved children if any.
*/
export interface IFileStat extends IBaseFileStat {
/**
* The resource is a file.
*/
readonly isFile: boolean;
/**
* The resource is a directory.
*/
readonly isDirectory: boolean;
/**
* The children of the file stat or undefined if none.
*/
children: IFileStat[] | undefined;
}
export interface IFileStatWithMetadata extends Required<IFileStat> {
readonly children: IFileStatWithMetadata[];
}
export const enum FileOperation {
CREATE,
DELETE,
MOVE,
COPY,
WRITE,
}
export interface IFileOperationEvent {
readonly resource: URI;
readonly operation: FileOperation;
isOperation(operation: FileOperation.DELETE | FileOperation.WRITE): boolean;
isOperation(
operation: FileOperation.CREATE | FileOperation.MOVE | FileOperation.COPY,
): this is IFileOperationEventWithMetadata;
}
export interface IFileOperationEventWithMetadata extends IFileOperationEvent {
readonly target: IFileStatWithMetadata;
}

View File

@ -0,0 +1,6 @@
/**
* URI -> file content
* URI -> file Stat
*/
export interface IFileManagement {}

View File

@ -0,0 +1 @@
export interface IFileService {}

View File

@ -0,0 +1,26 @@
import { URI } from '../common/uri';
export interface IWorkspaceFolderData {
/**
* The associated URI for this workspace folder.
*/
readonly uri: URI;
/**
* The name of this workspace folder. Defaults to
* the basename of its [uri-path](#Uri.path)
*/
readonly name: string;
/**
* The ordinal number of this workspace folder.
*/
readonly index: number;
}
export interface IWorkspaceFolder extends IWorkspaceFolderData {
/**
* Given workspace folder relative path, returns the resource with the absolute path.
*/
toResource: (relativePath: string) => URI;
}

View File

@ -0,0 +1,82 @@
import { type Event } from '@alilc/lowcode-shared';
import { URI } from '../../common/uri';
export interface IEditWindow {
// readonly onWillLoad: Event<ILoadEvent>;
readonly onDidSignalReady: Event<void>;
readonly onDidDestroy: Event<void>;
readonly onDidClose: Event<void>;
readonly id: number;
readonly config: IWindowConfiguration | undefined;
readonly isReady: boolean;
ready(): Promise<IEditWindow>;
load(config: IWindowConfiguration, options?: { isReload?: boolean }): void;
reload(): void;
}
export interface IWindowConfiguration {
filesToOpenOrCreate?: IPath[];
}
export interface IPath<T = any> {
/**
* Optional editor options to apply in the file
*/
readonly options?: T;
/**
* The file path to open within the instance
*/
fileUri?: URI;
/**
* Specifies if the file should be only be opened
* if it exists.
*/
readonly openOnlyIfExists?: boolean;
}
export const enum WindowMode {
Maximized,
Normal,
Fullscreen,
Custom,
}
export interface IWindowState {
width?: number;
height?: number;
x?: number;
y?: number;
mode?: WindowMode;
zoomLevel?: number;
readonly display?: number;
}
export interface IOpenConfiguration {
readonly urisToOpen?: IWindowOpenable[];
readonly preferNewWindow?: boolean;
readonly forceNewWindow?: boolean;
readonly forceNewTabbedWindow?: boolean;
readonly forceReuseWindow?: boolean;
readonly forceEmpty?: boolean;
}
export interface IBaseWindowOpenable {
label?: string;
}
export interface IFolderToOpen extends IBaseWindowOpenable {
readonly folderUri: URI;
}
export interface IFileToOpen extends IBaseWindowOpenable {
readonly fileUri: URI;
}
export type IWindowOpenable = IFolderToOpen | IFileToOpen;

View File

@ -0,0 +1,43 @@
import { type Event } from '@alilc/lowcode-shared';
import { IEditWindow, IOpenConfiguration } from './window';
export interface IWindowService {
readonly onDidOpenWindow: Event<IEditWindow>;
readonly onDidSignalReadyWindow: Event<IEditWindow>;
readonly onDidChangeFullScreen: Event<{ window: IEditWindow; fullscreen: boolean }>;
readonly onDidDestroyWindow: Event<IEditWindow>;
open(openConfig: IOpenConfiguration): Promise<IEditWindow[]>;
sendToFocused(channel: string, ...args: any[]): void;
sendToOpeningWindow(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
getWindows(): IEditWindow[];
getWindowCount(): number;
getFocusedWindow(): IEditWindow | undefined;
getLastActiveWindow(): IEditWindow | undefined;
getWindowById(windowId: number): IEditWindow | undefined;
}
export class WindowService implements IWindowService {
private readonly windows = new Map<number, IEditWindow>();
getWindows(): IEditWindow[] {
return [...this.windows.values()];
}
getWindowCount(): number {
return this.windows.size;
}
getFocusedWindow(): IEditWindow | undefined {
return this.getWindows().find((w) => w.focused);
}
getLastActiveWindow(): IEditWindow | undefined {
return this.getWindows().find((w) => w.lastActive);
}
}

View File

@ -1,4 +1,31 @@
import { IWorkspaceFolder } from './folder';
/**
*
* workspace -> one or more folders -> virtual files
* file -> editWindow
* editorView -> component tree schema
*
* project = (one or muti folders -> files) + some configs
*/
export interface Workspace {}
export interface IWorkspace {
readonly id: string;
/**
* Folders in the workspace.
*/
readonly folders: IWorkspaceFolder[];
}
export class Workspace implements IWorkspace {
private _folders: IWorkspaceFolder[] = [];
constructor(private _id: string) {}
get id() {
return this._id;
}
get folders() {
return this._folders;
}
}

View File

@ -1,12 +1,8 @@
import { createDecorator, Provide } from '@alilc/lowcode-shared';
export interface IWorkspaceService {
mount(container: HTMLElement): void;
}
export interface IWorkspaceService {}
export const IWorkspaceService = createDecorator<IWorkspaceService>('workspaceService');
@Provide(IWorkspaceService)
export class WorkspaceService implements IWorkspaceService {
mount(container: HTMLElement): void {}
}
export class WorkspaceService implements IWorkspaceService {}

View File

@ -1,14 +1,14 @@
import { createRenderer } from '@alilc/lowcode-renderer-core';
import { type Root, createRoot } from 'react-dom/client';
import { RendererContext } from './context';
import { RendererContext, getRenderInstancesByAccessor } from './context';
import { ApplicationView, boosts } from '../app';
import { type ReactAppOptions } from './types';
export const createApp = async (options: ReactAppOptions) => {
return createRenderer(async (context) => {
const { schema, boostsManager } = context;
return createRenderer(async (accessor) => {
const instances = getRenderInstancesByAccessor(accessor);
boostsManager.extend(boosts.toExpose());
instances.boostsManager.extend(boosts.toExpose());
let root: Root | undefined;
@ -16,9 +16,9 @@ export const createApp = async (options: ReactAppOptions) => {
async mount(containerOrId) {
if (root) return;
const defaultId = schema.get('config')?.targetRootID ?? 'app';
const defaultId = instances.schema.get('config')?.targetRootID ?? 'app';
const rootElement = normalizeContainer(containerOrId, defaultId);
const contextValue = { ...context, options };
const contextValue = { ...instances, options };
root = createRoot(rootElement);
root.render(

View File

@ -4,7 +4,7 @@ import {
type LowCodeComponentProps,
createComponent as createSchemaComponent,
} from '../runtime/createComponent';
import { RendererContext } from './context';
import { RendererContext, getRenderInstancesByAccessor } from './context';
import { type ReactAppOptions } from './types';
interface Render {
@ -12,16 +12,16 @@ interface Render {
}
export async function createComponent(options: ReactAppOptions) {
const creator = createRenderer<Render>((context) => {
const { schema } = context;
const componentsTree = schema.get('componentsTree')[0];
const creator = createRenderer<Render>((accessor) => {
const instances = getRenderInstancesByAccessor(accessor);
const componentsTree = instances.schema.get('componentsTree')[0];
const LowCodeComponent = createSchemaComponent(componentsTree, {
displayName: componentsTree.componentName,
...options.component,
});
const contextValue = { ...context, options };
const contextValue = { ...instances, options };
function Component(props: LowCodeComponentProps) {
return (

View File

@ -1,10 +1,39 @@
import {
IBoostsService,
IComponentTreeModelService,
ILifeCycleService,
IPackageManagementService,
ISchemaService,
} from '@alilc/lowcode-renderer-core';
import { InstanceAccessor } from '@alilc/lowcode-shared';
import { createContext, useContext } from 'react';
import { type RenderContext } from '@alilc/lowcode-renderer-core';
import { type ReactAppOptions } from './types';
export const RendererContext = createContext<RenderContext & { options: ReactAppOptions }>(
undefined!,
);
export interface IRendererContext {
readonly options: ReactAppOptions;
readonly schema: Omit<ISchemaService, 'initialize'>;
readonly packageManager: IPackageManagementService;
readonly boostsManager: IBoostsService;
readonly componentTreeModel: IComponentTreeModelService;
readonly lifeCycle: ILifeCycleService;
}
export const getRenderInstancesByAccessor = (accessor: InstanceAccessor) => {
return {
schema: accessor.get(ISchemaService),
packageManager: accessor.get(IPackageManagementService),
boostsManager: accessor.get(IBoostsService),
componentTreeModel: accessor.get(IComponentTreeModelService),
lifeCycle: accessor.get(ILifeCycleService),
};
};
export const RendererContext = createContext<IRendererContext>(undefined!);
RendererContext.displayName = 'RendererContext';

View File

@ -5,7 +5,7 @@ export { defineRendererPlugin } from './app';
export * from './router';
export { LifecyclePhase } from '@alilc/lowcode-renderer-core';
export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
export type { Package, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
export type {
PackageLoader,
CodeScope,

View File

@ -1,3 +1,10 @@
export * from './context';
export * from './plugin';
export type * from '@alilc/lowcode-renderer-router';
export type {
RouteLocation,
RawLocation,
Router,
RouterOptions,
RouteLocationNormalized,
RouterHistory,
} from '@alilc/lowcode-renderer-router';

View File

@ -1,13 +1,12 @@
import { invariant, specTypes, type ComponentTreeRoot } from '@alilc/lowcode-shared';
import { forwardRef, useRef, useEffect } from 'react';
import { isValidElementType } from 'react-is';
import { useRendererContext } from '../api/context';
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 {
RenderContext,
IComponentTreeModel,
CreateComponentTreeModelOptions,
} from '@alilc/lowcode-renderer-core';
@ -36,7 +35,7 @@ const lowCodeComponentsCache = new Map<string, ReactComponent>();
export function getComponentByName(
name: string,
{ packageManager }: RenderContext,
{ packageManager }: IRendererContext,
componentOptions: ComponentOptions = {},
): ReactComponent {
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);

View File

@ -1,23 +1,22 @@
/* --------------- api -------------------- */
export { createRenderer } from './main';
export { definePackageLoader } from './services/package';
export { LifecyclePhase } from './services/lifeCycleService';
export { Widget } from './services/widget';
export * from '../../shared/src/utils/node';
export { IBoostsService, IExtensionHostService } from './services/extension';
export { definePackageLoader, IPackageManagementService } from './services/package';
export { LifecyclePhase, ILifeCycleService } from './services/lifeCycleService';
export { IComponentTreeModelService } from './services/model';
export { ICodeRuntimeService } from './services/code-runtime';
export { IRuntimeIntlService } from './services/runtimeIntlService';
export { IRuntimeUtilService } from './services/runtimeUtilService';
export { ISchemaService } from './services/schema';
export { Widget } from './widget';
export * from './utils/value';
/* --------------- types ---------------- */
export type * from './types';
export type {
Plugin,
IRenderObject,
PluginContext,
RenderAdapter,
RenderContext,
} from './services/extension';
export type * from './services/extension';
export type * from './services/code-runtime';
export type * from './services/model';
export type * from './services/package';
export type * from './services/schema';
export type * from './services/widget';
export type * from './services/extension';
export type * from './widget';

View File

@ -1,7 +1,6 @@
import { invariant, InstantiationService } from '@alilc/lowcode-shared';
import { ICodeRuntimeService } from './services/code-runtime';
import {
IBoostsService,
IExtensionHostService,
type RenderAdapter,
type IRenderObject,
@ -9,80 +8,8 @@ import {
import { IPackageManagementService } from './services/package';
import { ISchemaService } from './services/schema';
import { ILifeCycleService, LifecyclePhase } from './services/lifeCycleService';
import { IComponentTreeModelService } from './services/model';
import type { AppOptions, RendererApplication } from './types';
export class RendererMain<RenderObject> {
private mode: 'development' | 'production' = 'production';
private initOptions: AppOptions;
private renderObject: RenderObject;
private adapter: RenderAdapter<RenderObject>;
constructor(
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
@IPackageManagementService private packageManagementService: IPackageManagementService,
@ISchemaService private schemaService: ISchemaService,
@IExtensionHostService private extensionHostService: IExtensionHostService,
@IComponentTreeModelService private componentTreeModelService: IComponentTreeModelService,
@IBoostsService private boostsService: IBoostsService,
@ILifeCycleService private lifeCycleService: ILifeCycleService,
) {}
async main(options: AppOptions, adapter: RenderAdapter<RenderObject>) {
const { schema, mode, plugins = [] } = options;
if (mode) this.mode = mode;
this.initOptions = { ...options };
this.adapter = adapter;
// valid schema
this.schemaService.initialize(schema);
this.codeRuntimeService.initialize(options.codeRuntime ?? {});
await this.lifeCycleService.setPhase(LifecyclePhase.OptionsResolved);
const renderContext = {
schema: this.schemaService,
packageManager: this.packageManagementService,
boostsManager: this.boostsService,
componentTreeModel: this.componentTreeModelService,
lifeCycle: this.lifeCycleService,
};
this.renderObject = await this.adapter(renderContext);
await this.extensionHostService.registerPlugin(plugins);
// 先加载插件提供 package loader
await this.packageManagementService.loadPackages(this.initOptions.packages ?? []);
await this.lifeCycleService.setPhase(LifecyclePhase.Ready);
}
getApp(): RendererApplication<RenderObject> {
// construct application
return Object.freeze<RendererApplication<RenderObject>>({
// develop use
__options: this.initOptions,
mode: this.mode,
schema: this.schemaService,
packageManager: this.packageManagementService,
...this.renderObject,
use: (plugin) => {
return this.extensionHostService.registerPlugin(plugin);
},
destroy: async () => {
return this.lifeCycleService.setPhase(LifecyclePhase.Destroying);
},
});
}
}
/**
* createRenderer
* @param schema
@ -94,13 +21,51 @@ export function createRenderer<RenderObject = IRenderObject>(
): (options: AppOptions) => Promise<RendererApplication<RenderObject>> {
invariant(typeof renderAdapter === 'function', 'The first parameter must be a function.');
const instantiationService = new InstantiationService({ defaultScope: 'Singleton' });
const rendererMain = instantiationService.createInstance(
RendererMain,
) as RendererMain<RenderObject>;
const accessor = new InstantiationService({ defaultScope: 'Singleton' });
let mode: 'development' | 'production' = 'production';
const schemaService = accessor.get(ISchemaService);
const packageManagementService = accessor.get(IPackageManagementService);
const codeRuntimeService = accessor.get(ICodeRuntimeService);
const lifeCycleService = accessor.get(ILifeCycleService);
const extensionHostService = accessor.get(IExtensionHostService);
return async (options) => {
await rendererMain.main(options, renderAdapter);
return rendererMain.getApp();
if (options.mode) mode = options.mode;
// valid schema
schemaService.initialize(options.schema);
codeRuntimeService.initialize(options.codeRuntime ?? {});
await lifeCycleService.setPhase(LifecyclePhase.OptionsResolved);
const renderObject = await renderAdapter(accessor);
await extensionHostService.registerPlugin(options.plugins ?? []);
// 先加载插件提供 package loader
await packageManagementService.loadPackages(options.packages ?? []);
await lifeCycleService.setPhase(LifecyclePhase.Ready);
const app: RendererApplication<RenderObject> = {
get mode() {
return mode;
},
schema: schemaService,
packageManager: packageManagementService,
...renderObject,
use: (plugin) => {
return extensionHostService.registerPlugin(plugin);
},
destroy: async () => {
return lifeCycleService.setPhase(LifecyclePhase.Destroying);
},
};
if (mode === 'development') {
Object.defineProperty(app, '__options', { get: () => options });
}
return app;
};
}

View File

@ -1,8 +1,9 @@
import { type EventEmitter, type IStore, type StringDictionary } from '@alilc/lowcode-shared';
import { type EventEmitter, type StringDictionary } from '@alilc/lowcode-shared';
import { type IBoosts } from './boosts';
import { ILifeCycleService } from '../lifeCycleService';
import { type ISchemaService } from '../schema';
import { type IPackageManagementService } from '../package';
import { type IStore } from '../../utils/store';
export interface PluginContext<BoostsExtends = object> {
eventEmitter: EventEmitter;

View File

@ -1,26 +1,10 @@
import { IPackageManagementService } from '../package';
import { IBoostsService } from './boosts';
import { ISchemaService } from '../schema';
import { IComponentTreeModelService } from '../model';
import { ILifeCycleService } from '../lifeCycleService';
import { type InstanceAccessor } from '@alilc/lowcode-shared';
export interface IRenderObject {
mount: (containerOrId?: string | HTMLElement) => void | Promise<void>;
unmount: () => void | Promise<void>;
}
export interface RenderContext {
readonly schema: Omit<ISchemaService, 'initialize'>;
readonly packageManager: IPackageManagementService;
readonly boostsManager: IBoostsService;
readonly componentTreeModel: IComponentTreeModelService;
readonly lifeCycle: ILifeCycleService;
}
export interface RenderAdapter<Render> {
(context: RenderContext): Render | Promise<Render>;
(accessor: InstanceAccessor): Render | Promise<Render>;
}

View File

@ -14,7 +14,7 @@ import {
uniqueId,
} from '@alilc/lowcode-shared';
import { type ICodeRuntime } from '../code-runtime';
import { IWidget, Widget } from '../widget';
import { IWidget, Widget } from '../../widget';
export interface NormalizedComponentNode extends ComponentNode {
loopArgs: [string, string];

View File

@ -7,7 +7,7 @@ import {
Provide,
specTypes,
exportByReference,
mapPackageToId,
mapPackageToUniqueId,
type Reference,
} from '@alilc/lowcode-shared';
import { get as lodashGet } from 'lodash-es';
@ -91,7 +91,7 @@ export class PackageManagementService implements IPackageManagementService {
}
getModuleByReference<T = any>(reference: Reference): T | undefined {
const id = mapPackageToId(reference);
const id = mapPackageToUniqueId(reference);
if (this.packageStore.has(id)) {
const library = this.packageStore.get(id);
const result = exportByReference(library, reference);
@ -138,7 +138,7 @@ export class PackageManagementService implements IPackageManagementService {
const normalized: NormalizedPackage = {
package: packageInfo.package,
id: mapPackageToId(packageInfo),
id: mapPackageToUniqueId(packageInfo),
library: packageInfo.library,
raw: packageInfo,
};

View File

@ -1,5 +1,5 @@
import { type NodeType, uniqueId, type ComponentNode } from '@alilc/lowcode-shared';
import { IComponentTreeModel } from '../model';
import { IComponentTreeModel } from '../services/model';
export interface IWidget<Component, ComponentInstance = unknown> {
readonly key: string;

View File

@ -1,7 +1,10 @@
import * as Platform from './platform';
export { Platform };
export * from './event';
export * from './logger';
export * from './intl';
export * from './instantiation';
export * from './signals';
export * as Platform from './platform';
export * from './linkedList';

View File

@ -1,8 +1,10 @@
import * as Iterable from './iterable';
export { Iterable };
export * from './invariant';
export * from './unique-id';
export * from './types';
export * from './async';
export * from './node';
export * from './resource';
export * as Iterable from './iterable';

View File

@ -1,5 +1,7 @@
export * as specTypes from './spec';
export * as jsonTypes from './json';
export * as types from './type';
import * as specTypes from './spec';
import * as jsonTypes from './json';
import * as types from './type';
export * from './constraint';
export { specTypes, jsonTypes, types };