mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 03:01:16 +00:00
fix: renderer bugs fix
This commit is contained in:
parent
a855c05d67
commit
450d08e500
@ -1,7 +1,8 @@
|
||||
import { createRenderer } from '@alilc/lowcode-renderer-core';
|
||||
import { type Root, createRoot } from 'react-dom/client';
|
||||
import { type ReactAppOptions, RendererContext } from './context';
|
||||
import { RendererContext } from './context';
|
||||
import { ApplicationView, boosts } from '../app';
|
||||
import { type ReactAppOptions } from './types';
|
||||
|
||||
export const createApp = async (options: ReactAppOptions) => {
|
||||
return createRenderer(async (context) => {
|
||||
|
||||
@ -1,17 +1,26 @@
|
||||
import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core';
|
||||
import { createRenderer } from '@alilc/lowcode-renderer-core';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/schema';
|
||||
import { RendererContext } from '../api/context';
|
||||
import {
|
||||
type LowCodeComponentProps,
|
||||
createComponent as createSchemaComponent,
|
||||
} from '../runtime/createComponent';
|
||||
import { RendererContext } from './context';
|
||||
import { type ReactAppOptions } from './types';
|
||||
|
||||
interface Render {
|
||||
toComponent(): FunctionComponent<LowCodeComponentProps>;
|
||||
}
|
||||
|
||||
export async function createComponent(options: AppOptions) {
|
||||
export async function createComponent(options: ReactAppOptions) {
|
||||
const creator = createRenderer<Render>((context) => {
|
||||
const { schema } = context;
|
||||
const componentsTree = schema.get('componentsTree')[0];
|
||||
|
||||
const LowCodeComponent = createSchemaComponent(componentsTree, {
|
||||
displayName: componentsTree.componentName,
|
||||
...options.component,
|
||||
});
|
||||
|
||||
const LowCodeComponent = createComponentBySchema(schema.get('componentsTree')[0]);
|
||||
const contextValue = { ...context, options };
|
||||
|
||||
function Component(props: LowCodeComponentProps) {
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import { type ComponentType, createContext, useContext } from 'react';
|
||||
import { type AppOptions, type RenderContext } from '@alilc/lowcode-renderer-core';
|
||||
|
||||
export interface ReactAppOptions extends AppOptions {
|
||||
faultComponent?: ComponentType<any>;
|
||||
}
|
||||
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!,
|
||||
|
||||
11
packages/react-renderer/src/api/types.ts
Normal file
11
packages/react-renderer/src/api/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { type AppOptions } from '@alilc/lowcode-renderer-core';
|
||||
import { type ComponentType } from 'react';
|
||||
import { type ComponentOptions } from '../runtime/createComponent';
|
||||
|
||||
export interface ReactAppOptions extends AppOptions {
|
||||
component?: Pick<
|
||||
ComponentOptions,
|
||||
'beforeElementCreate' | 'elementCreated' | 'componentRefAttached'
|
||||
>;
|
||||
faultComponent?: ComponentType<any>;
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { getComponentByName } from '../runtime/schema';
|
||||
import { getComponentByName } from '../runtime/createComponent';
|
||||
import { boosts } from './boosts';
|
||||
|
||||
export function ApplicationView() {
|
||||
const rendererContext = useRendererContext();
|
||||
const { schema } = rendererContext;
|
||||
const { schema, options } = rendererContext;
|
||||
const appWrappers = boosts.getAppWrappers();
|
||||
const Outlet = boosts.getOutlet();
|
||||
|
||||
@ -16,7 +16,7 @@ export function ApplicationView() {
|
||||
|
||||
if (layoutConfig) {
|
||||
const componentName = layoutConfig.componentName;
|
||||
const Layout = getComponentByName(componentName, rendererContext);
|
||||
const Layout = getComponentByName(componentName, rendererContext, options.component);
|
||||
|
||||
if (Layout) {
|
||||
const layoutProps: any = layoutConfig.props ?? {};
|
||||
|
||||
@ -6,5 +6,11 @@ export * from './router';
|
||||
export { LifecyclePhase } from '@alilc/lowcode-renderer-core';
|
||||
|
||||
export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
|
||||
export type { PackageLoader, CodeScope, Plugin } from '@alilc/lowcode-renderer-core';
|
||||
export type {
|
||||
PackageLoader,
|
||||
CodeScope,
|
||||
Plugin,
|
||||
ModelDataSourceCreator,
|
||||
ModelStateCreator,
|
||||
} from '@alilc/lowcode-renderer-core';
|
||||
export type { ReactRendererBoostsApi } from './app/boosts';
|
||||
|
||||
@ -2,12 +2,12 @@ import { useMemo } from 'react';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { OutletProps } from '../app/boosts';
|
||||
import { useRouteLocation } from './context';
|
||||
import { createComponentBySchema } from '../runtime/schema';
|
||||
import { createComponent } from '../runtime/createComponent';
|
||||
|
||||
export function RouteOutlet(props: OutletProps) {
|
||||
const context = useRendererContext();
|
||||
const location = useRouteLocation();
|
||||
const { schema, packageManager } = context;
|
||||
const { schema, packageManager, options } = context;
|
||||
|
||||
const pageConfig = useMemo(() => {
|
||||
const pages = schema.get('pages') ?? [];
|
||||
@ -27,11 +27,12 @@ export function RouteOutlet(props: OutletProps) {
|
||||
const componentsMap = schema.get('componentsMap');
|
||||
packageManager.resolveComponentMaps(componentsMap);
|
||||
|
||||
const LowCodeComponent = createComponentBySchema(pageConfig.mappingId, {
|
||||
const LowCodeComponent = createComponent(pageConfig.mappingId, {
|
||||
displayName: pageConfig.id,
|
||||
modelOptions: {
|
||||
metadata: pageConfig,
|
||||
},
|
||||
...options.component,
|
||||
});
|
||||
|
||||
return <LowCodeComponent {...props} />;
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { IComponentTreeModel } from '@alilc/lowcode-renderer-core';
|
||||
import { createContext, useContext, type ReactInstance } from 'react';
|
||||
import { type ReactComponent } from './components';
|
||||
|
||||
export const ModelContext = createContext<IComponentTreeModel<ReactComponent, ReactInstance>>(
|
||||
undefined!,
|
||||
);
|
||||
|
||||
export const useModel = () => useContext(ModelContext);
|
||||
|
||||
export const ModelContextProvider = ModelContext.Provider;
|
||||
@ -3,23 +3,23 @@ import { forwardRef, useRef, useEffect } from 'react';
|
||||
import { isValidElementType } from 'react-is';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { reactiveStateFactory } from './reactiveState';
|
||||
import { type ReactComponent, type ReactWidget, createElementByWidget } from './components';
|
||||
import { ModelContextProvider } from './context';
|
||||
import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
|
||||
import { appendExternalStyle } from '../utils/element';
|
||||
|
||||
import type {
|
||||
RenderContext,
|
||||
IComponentTreeModel,
|
||||
ComponentTreeModelOptions,
|
||||
CreateComponentTreeModelOptions,
|
||||
} from '@alilc/lowcode-renderer-core';
|
||||
import type { ReactInstance, CSSProperties, ForwardedRef } from 'react';
|
||||
import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
|
||||
|
||||
export interface ComponentOptions {
|
||||
displayName?: string;
|
||||
modelOptions?: ComponentTreeModelOptions;
|
||||
modelOptions?: Pick<CreateComponentTreeModelOptions, 'id' | 'metadata'>;
|
||||
|
||||
widgetCreated?(widget: ReactWidget): void;
|
||||
componentRefAttached?(widget: ReactWidget, instance: ReactInstance): void;
|
||||
beforeElementCreate?(widget: ReactWidget): ReactWidget;
|
||||
elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
|
||||
componentRefAttached?(widget: ReactWidget, instance: ReactInstance | null): void;
|
||||
}
|
||||
|
||||
export interface LowCodeComponentProps {
|
||||
@ -37,6 +37,7 @@ const lowCodeComponentsCache = new Map<string, ReactComponent>();
|
||||
export function getComponentByName(
|
||||
name: string,
|
||||
{ packageManager, boostsManager }: RenderContext,
|
||||
componentOptions: ComponentOptions = {},
|
||||
): ReactComponent {
|
||||
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);
|
||||
|
||||
@ -58,7 +59,8 @@ export function getComponentByName(
|
||||
});
|
||||
}
|
||||
|
||||
const lowCodeComponent = createComponentBySchema(componentsTree[0], {
|
||||
const lowCodeComponent = createComponent(componentsTree[0], {
|
||||
...componentOptions,
|
||||
displayName: name,
|
||||
modelOptions: {
|
||||
id: metadata.id,
|
||||
@ -76,40 +78,49 @@ export function getComponentByName(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createComponentBySchema(
|
||||
export function createComponent(
|
||||
schema: string | Spec.ComponentTreeRoot,
|
||||
{ displayName = '__LowCodeComponent__', modelOptions }: ComponentOptions = {},
|
||||
componentOptions: ComponentOptions = {},
|
||||
) {
|
||||
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
|
||||
|
||||
const LowCodeComponent = forwardRef(function (
|
||||
props: LowCodeComponentProps,
|
||||
ref: ForwardedRef<any>,
|
||||
) {
|
||||
const renderContext = useRendererContext();
|
||||
const { options, componentTreeModel } = renderContext;
|
||||
const context = useRendererContext();
|
||||
const { options: globalOptions, componentTreeModel } = context;
|
||||
|
||||
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
|
||||
|
||||
if (!modelRef.current) {
|
||||
const finalOptions: CreateComponentTreeModelOptions = {
|
||||
...modelOptions,
|
||||
codeScopeValue: {
|
||||
props,
|
||||
},
|
||||
stateCreator: reactiveStateFactory,
|
||||
dataSourceCreator: globalOptions.dataSourceCreator,
|
||||
};
|
||||
|
||||
if (typeof schema === 'string') {
|
||||
modelRef.current = componentTreeModel.createById(schema, modelOptions);
|
||||
modelRef.current = componentTreeModel.createById(schema, finalOptions);
|
||||
} else {
|
||||
modelRef.current = componentTreeModel.create(schema, modelOptions);
|
||||
modelRef.current = componentTreeModel.create(schema, finalOptions);
|
||||
}
|
||||
console.log(
|
||||
'%c [ model ]-103',
|
||||
'font-size:13px; background:pink; color:#bf2c9f;',
|
||||
modelRef.current,
|
||||
);
|
||||
}
|
||||
|
||||
const model = modelRef.current!;
|
||||
console.log('%c [ model ]-103', 'font-size:13px; background:pink; color:#bf2c9f;', model);
|
||||
|
||||
const isConstructed = useRef(false);
|
||||
const isMounted = useRef(false);
|
||||
|
||||
if (!isConstructed.current) {
|
||||
model.initialize({
|
||||
defaultProps: props,
|
||||
stateCreator: reactiveStateFactory,
|
||||
dataSourceCreator: options.dataSourceCreator,
|
||||
});
|
||||
|
||||
model.triggerLifeCycle('constructor');
|
||||
isConstructed.current = true;
|
||||
|
||||
@ -142,11 +153,9 @@ export function createComponentBySchema(
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModelContextProvider value={model}>
|
||||
<div id={props.id} className={props.className} style={props.style} ref={ref}>
|
||||
{model.widgets.map((w) => createElementByWidget(w, model.codeScope))}
|
||||
</div>
|
||||
</ModelContextProvider>
|
||||
<div id={props.id} className={props.className} style={props.style} ref={ref}>
|
||||
{model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {
|
||||
type IWidget,
|
||||
type ICodeScope,
|
||||
type ICodeRuntime,
|
||||
type NormalizedComponentNode,
|
||||
mapValue,
|
||||
} from '@alilc/lowcode-renderer-core';
|
||||
@ -15,70 +15,139 @@ import {
|
||||
import { type ComponentType, type ReactInstance, useMemo, createElement } from 'react';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { useReactiveStore } from './hooks/useReactiveStore';
|
||||
import { useModel } from './context';
|
||||
import { getComponentByName } from './schema';
|
||||
import { getComponentByName, type ComponentOptions } from './createComponent';
|
||||
|
||||
export type ReactComponent = ComponentType<any>;
|
||||
export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
|
||||
|
||||
interface WidgetRendererProps {
|
||||
widget: ReactWidget;
|
||||
codeScope: ICodeScope;
|
||||
codeRuntime: ICodeRuntime;
|
||||
options: ComponentOptions;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function createElementByWidget(
|
||||
widget: IWidget<ReactComponent, ReactInstance>,
|
||||
codeScope: ICodeScope,
|
||||
widget: ReactWidget,
|
||||
codeRuntime: ICodeRuntime,
|
||||
options: ComponentOptions,
|
||||
) {
|
||||
const { key, node } = widget;
|
||||
const getElement = (widget: ReactWidget) => {
|
||||
const { key, rawNode } = widget;
|
||||
|
||||
if (typeof node === 'string') {
|
||||
return node;
|
||||
if (typeof rawNode === 'string') {
|
||||
return rawNode;
|
||||
}
|
||||
|
||||
if (isJSExpression(rawNode)) {
|
||||
return <Text key={key} expr={rawNode} codeRuntime={codeRuntime} />;
|
||||
}
|
||||
|
||||
if (isJSI18nNode(rawNode)) {
|
||||
return <I18nText key={key} i18n={rawNode} codeRuntime={codeRuntime} />;
|
||||
}
|
||||
|
||||
const { condition, loop } = widget.rawNode as NormalizedComponentNode;
|
||||
|
||||
// condition为 Falsy 的情况下 不渲染
|
||||
if (!condition) return null;
|
||||
// loop 为数组且为空的情况下 不渲染
|
||||
if (Array.isArray(loop) && loop.length === 0) return null;
|
||||
|
||||
if (isJSExpression(loop)) {
|
||||
return (
|
||||
<LoopWidgetRenderer
|
||||
key={key}
|
||||
loop={loop}
|
||||
widget={widget}
|
||||
codeRuntime={codeRuntime}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<WidgetComponent key={key} widget={widget} codeRuntime={codeRuntime} options={options} />
|
||||
);
|
||||
};
|
||||
|
||||
if (options.beforeElementCreate) {
|
||||
widget = options.beforeElementCreate(widget);
|
||||
}
|
||||
|
||||
if (isJSExpression(node)) {
|
||||
return <Text key={key} expr={node} codeScope={codeScope} />;
|
||||
const element = getElement(widget);
|
||||
|
||||
if (options.elementCreated) {
|
||||
return options.elementCreated(widget, element);
|
||||
}
|
||||
|
||||
if (isJSI18nNode(node)) {
|
||||
return <I18nText key={key} i18n={node} codeScope={codeScope} />;
|
||||
}
|
||||
|
||||
const { condition, loop } = widget.node as NormalizedComponentNode;
|
||||
|
||||
// condition为 Falsy 的情况下 不渲染
|
||||
if (!condition) return null;
|
||||
// loop 为数组且为空的情况下 不渲染
|
||||
if (Array.isArray(loop) && loop.length === 0) return null;
|
||||
|
||||
if (isJSExpression(loop)) {
|
||||
return <LoopWidgetRenderer key={key} loop={loop} widget={widget} codeScope={codeScope} />;
|
||||
}
|
||||
|
||||
return <WidgetComponent key={key} widget={widget} codeScope={codeScope} />;
|
||||
return element;
|
||||
}
|
||||
|
||||
export function WidgetComponent(props: WidgetRendererProps) {
|
||||
const { widget, codeScope, ...otherProps } = props;
|
||||
const componentNode = widget.node as NormalizedComponentNode;
|
||||
const { widget, codeRuntime, options, ...otherProps } = props;
|
||||
const componentNode = widget.rawNode as NormalizedComponentNode;
|
||||
const { ref, ...componentProps } = componentNode.props;
|
||||
|
||||
const rendererContext = useRendererContext();
|
||||
const context = useRendererContext();
|
||||
|
||||
const Component = useMemo(
|
||||
() => getComponentByName(componentNode.componentName, rendererContext),
|
||||
() => getComponentByName(componentNode.componentName, context, options),
|
||||
[widget],
|
||||
);
|
||||
|
||||
// 先将 jsslot, jsFunction 对象转换
|
||||
const processedProps = mapValue(
|
||||
componentProps,
|
||||
(node) => isJSFunction(node) || isJSSlot(node),
|
||||
(node: Spec.JSSlot | Spec.JSFunction) => {
|
||||
if (isJSSlot(node)) {
|
||||
const slot = node as Spec.JSSlot;
|
||||
|
||||
if (slot.value) {
|
||||
const widgets = widget.model.buildWidgets(
|
||||
Array.isArray(node.value) ? node.value : [node.value],
|
||||
);
|
||||
|
||||
if (slot.params?.length) {
|
||||
return (...args: any[]) => {
|
||||
const params = slot.params!.reduce((prev, cur, idx) => {
|
||||
return (prev[cur] = args[idx]);
|
||||
}, {} as PlainObject);
|
||||
|
||||
return widgets.map((n) =>
|
||||
createElementByWidget(
|
||||
n,
|
||||
codeRuntime.createChild({ initScopeValue: params }),
|
||||
options,
|
||||
),
|
||||
);
|
||||
};
|
||||
} else {
|
||||
return widgets.map((n) => createElementByWidget(n, codeRuntime, options));
|
||||
}
|
||||
}
|
||||
} else if (isJSFunction(node)) {
|
||||
return widget.model.codeRuntime.resolve(node);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// development 模式下 把 widget 的内容作为 prop ,便于排查问题
|
||||
processedProps.widget = widget;
|
||||
}
|
||||
|
||||
const state = useReactiveStore({
|
||||
target: {
|
||||
condition: componentNode.condition,
|
||||
props: preprocessProps(componentProps, widget, codeScope),
|
||||
props: processedProps,
|
||||
},
|
||||
valueGetter(expr) {
|
||||
return widget.model.codeRuntime.resolve(expr, { scope: codeScope });
|
||||
return codeRuntime.resolve(expr);
|
||||
},
|
||||
});
|
||||
|
||||
@ -88,6 +157,8 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
} else {
|
||||
if (ref) widget.model.removeComponentRef(ref);
|
||||
}
|
||||
|
||||
options.componentRefAttached?.(widget, ins);
|
||||
};
|
||||
|
||||
if (!state.condition) {
|
||||
@ -107,58 +178,15 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
key: widget.key,
|
||||
ref: attachRef,
|
||||
},
|
||||
widget.children?.map((item) => createElementByWidget(item, codeScope)) ?? [],
|
||||
widget.children?.map((item) => createElementByWidget(item, codeRuntime, options)) ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
function preprocessProps(props: PlainObject, widget: ReactWidget, codeScope: ICodeScope) {
|
||||
// 先将 jsslot, jsFunction 对象转换
|
||||
const finalProps = mapValue(
|
||||
props,
|
||||
(node) => isJSFunction(node) || isJSSlot(node),
|
||||
(node: Spec.JSSlot | Spec.JSFunction) => {
|
||||
if (isJSSlot(node)) {
|
||||
const slot = node as Spec.JSSlot;
|
||||
|
||||
if (slot.value) {
|
||||
const widgets = widget.model.buildWidgets(
|
||||
Array.isArray(node.value) ? node.value : [node.value],
|
||||
);
|
||||
|
||||
if (slot.params?.length) {
|
||||
return (...args: any[]) => {
|
||||
const params = slot.params!.reduce((prev, cur, idx) => {
|
||||
return (prev[cur] = args[idx]);
|
||||
}, {} as PlainObject);
|
||||
|
||||
return widgets.map((n) => createElementByWidget(n, codeScope.createChild(params)));
|
||||
};
|
||||
} else {
|
||||
return widgets.map((n) => createElementByWidget(n, codeScope));
|
||||
}
|
||||
}
|
||||
} else if (isJSFunction(node)) {
|
||||
return widget.model.codeRuntime.resolve(node, { scope: codeScope });
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// development 模式下 把 widget 的内容作为 prop ,便于排查问题
|
||||
finalProps.widget = widget;
|
||||
}
|
||||
|
||||
return finalProps;
|
||||
}
|
||||
|
||||
function Text(props: { expr: Spec.JSExpression; codeScope: ICodeScope }) {
|
||||
const model = useModel();
|
||||
function Text(props: { expr: Spec.JSExpression; codeRuntime: ICodeRuntime }) {
|
||||
const text: string = useReactiveStore({
|
||||
target: props.expr,
|
||||
getter: (obj) => {
|
||||
return model.codeRuntime.resolve(obj, { scope: props.codeScope });
|
||||
return props.codeRuntime.resolve(obj);
|
||||
},
|
||||
});
|
||||
|
||||
@ -167,12 +195,11 @@ function Text(props: { expr: Spec.JSExpression; codeScope: ICodeScope }) {
|
||||
|
||||
Text.displayName = 'Text';
|
||||
|
||||
function I18nText(props: { i18n: Spec.JSI18n; codeScope: ICodeScope }) {
|
||||
const model = useModel();
|
||||
function I18nText(props: { i18n: Spec.JSI18n; codeRuntime: ICodeRuntime }) {
|
||||
const text: string = useReactiveStore({
|
||||
target: props.i18n,
|
||||
getter: (obj) => {
|
||||
return model.codeRuntime.resolve(obj, { scope: props.codeScope });
|
||||
return props.codeRuntime.resolve(obj);
|
||||
},
|
||||
});
|
||||
|
||||
@ -184,32 +211,34 @@ I18nText.displayName = 'I18nText';
|
||||
function LoopWidgetRenderer({
|
||||
loop,
|
||||
widget,
|
||||
codeScope,
|
||||
|
||||
codeRuntime,
|
||||
options,
|
||||
...otherProps
|
||||
}: {
|
||||
loop: Spec.JSExpression;
|
||||
widget: ReactWidget;
|
||||
codeScope: ICodeScope;
|
||||
|
||||
codeRuntime: ICodeRuntime;
|
||||
options: ComponentOptions;
|
||||
[key: string]: any;
|
||||
}) {
|
||||
const { condition, loopArgs } = widget.node as NormalizedComponentNode;
|
||||
const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode;
|
||||
const state = useReactiveStore({
|
||||
target: {
|
||||
loop,
|
||||
condition,
|
||||
},
|
||||
valueGetter(expr) {
|
||||
return widget.model.codeRuntime.resolve(expr, { scope: codeScope });
|
||||
return codeRuntime.resolve(expr);
|
||||
},
|
||||
});
|
||||
|
||||
if (state.condition && Array.isArray(state.loop) && state.loop.length > 0) {
|
||||
return state.loop.map((item: any, idx: number) => {
|
||||
const childScope = codeScope.createChild({
|
||||
[loopArgs[0]]: item,
|
||||
[loopArgs[1]]: idx,
|
||||
const childRuntime = codeRuntime.createChild({
|
||||
initScopeValue: {
|
||||
[loopArgs[0]]: item,
|
||||
[loopArgs[1]]: idx,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
@ -217,7 +246,8 @@ function LoopWidgetRenderer({
|
||||
{...otherProps}
|
||||
key={`loop-${widget.key}-${idx}`}
|
||||
widget={widget}
|
||||
codeScope={childScope}
|
||||
codeRuntime={childRuntime}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './schema';
|
||||
export * from './components';
|
||||
export * from './createComponent';
|
||||
export * from './elements';
|
||||
|
||||
128
packages/renderer-core/src/services/code-runtime/codeRuntime.ts
Normal file
128
packages/renderer-core/src/services/code-runtime/codeRuntime.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import {
|
||||
type PlainObject,
|
||||
type Spec,
|
||||
type EventDisposable,
|
||||
isJSExpression,
|
||||
isJSFunction,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeScope, CodeScope } from './codeScope';
|
||||
import { isNode } from '../../utils/node';
|
||||
import { mapValue } from '../../utils/value';
|
||||
import { evaluate } from './evaluate';
|
||||
|
||||
export interface CodeRuntimeOptions<T extends PlainObject = PlainObject> {
|
||||
initScopeValue?: Partial<T>;
|
||||
parentScope?: ICodeScope;
|
||||
|
||||
evalCodeFunction?: EvalCodeFunction;
|
||||
}
|
||||
|
||||
export interface ICodeRuntime<T extends PlainObject = PlainObject> {
|
||||
getScope(): ICodeScope<T>;
|
||||
|
||||
run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;
|
||||
|
||||
resolve(value: PlainObject): any;
|
||||
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable;
|
||||
|
||||
createChild<V extends PlainObject = PlainObject>(
|
||||
options: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
|
||||
): ICodeRuntime<V>;
|
||||
}
|
||||
|
||||
export type NodeResolverHandler = (node: Spec.JSNode) => Spec.JSNode | false | undefined;
|
||||
|
||||
let onResolveHandlers: NodeResolverHandler[] = [];
|
||||
|
||||
export type EvalCodeFunction = (code: string, scope: any) => any;
|
||||
|
||||
export class CodeRuntime<T extends PlainObject = PlainObject> implements ICodeRuntime<T> {
|
||||
private codeScope: ICodeScope<T>;
|
||||
|
||||
private evalCodeFunction: EvalCodeFunction = evaluate;
|
||||
|
||||
constructor(options: CodeRuntimeOptions<T> = {}) {
|
||||
if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction;
|
||||
|
||||
if (options.parentScope) {
|
||||
this.codeScope = options.parentScope.createChild<T>(options.initScopeValue ?? {});
|
||||
} else {
|
||||
this.codeScope = new CodeScope(options.initScopeValue ?? {});
|
||||
}
|
||||
}
|
||||
|
||||
getScope() {
|
||||
return this.codeScope;
|
||||
}
|
||||
|
||||
run<R = unknown>(code: string): R | undefined {
|
||||
if (!code) return undefined;
|
||||
|
||||
try {
|
||||
const result = this.evalCodeFunction(code, this.codeScope.value);
|
||||
|
||||
return result as R;
|
||||
} catch (err) {
|
||||
// todo replace logger
|
||||
console.error('eval error', code, this.codeScope.value, err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data: PlainObject): any {
|
||||
if (onResolveHandlers.length > 0) {
|
||||
data = mapValue(data, isNode, (node: Spec.JSNode) => {
|
||||
let newNode: Spec.JSNode | false | undefined = node;
|
||||
|
||||
for (const handler of onResolveHandlers) {
|
||||
newNode = handler(newNode as Spec.JSNode);
|
||||
if (newNode === false || typeof newNode === 'undefined') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
|
||||
return mapValue(
|
||||
data,
|
||||
(data) => {
|
||||
return isJSExpression(data) || isJSFunction(data);
|
||||
},
|
||||
(node: Spec.JSExpression | Spec.JSFunction) => {
|
||||
return this.resolveExprOrFunction(node);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private resolveExprOrFunction(node: Spec.JSExpression | Spec.JSFunction) {
|
||||
const v = this.run(node.value) as any;
|
||||
|
||||
if (typeof v === 'undefined' && node.mock) {
|
||||
return this.resolve(node.mock);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顺序执行 handler
|
||||
*/
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable {
|
||||
onResolveHandlers.push(handler);
|
||||
return () => {
|
||||
onResolveHandlers = onResolveHandlers.filter((h) => h !== handler);
|
||||
};
|
||||
}
|
||||
|
||||
createChild<V extends PlainObject = PlainObject>(
|
||||
options?: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
|
||||
): ICodeRuntime<V> {
|
||||
return new CodeRuntime({
|
||||
initScopeValue: options?.initScopeValue,
|
||||
parentScope: this.codeScope,
|
||||
evalCodeFunction: options?.evalCodeFunction ?? this.evalCodeFunction,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,126 +1,33 @@
|
||||
import {
|
||||
type PlainObject,
|
||||
type Spec,
|
||||
type EventDisposable,
|
||||
createDecorator,
|
||||
Provide,
|
||||
isJSExpression,
|
||||
isJSFunction,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeScope, CodeScope } from './codeScope';
|
||||
import { evaluate } from '../../utils/evaluate';
|
||||
import { isNode } from '../../utils/node';
|
||||
import { mapValue } from '../../utils/value';
|
||||
|
||||
export interface ResolveOptions {
|
||||
scope?: ICodeScope;
|
||||
}
|
||||
|
||||
export type NodeResolverHandler = (node: Spec.JSNode) => Spec.JSNode | false | undefined;
|
||||
import { createDecorator, invariant, Provide, type PlainObject } from '@alilc/lowcode-shared';
|
||||
import { type ICodeRuntime, type CodeRuntimeOptions, CodeRuntime } from './codeRuntime';
|
||||
|
||||
export interface ICodeRuntimeService {
|
||||
initialize(options: CodeRuntimeInitializeOptions): void;
|
||||
readonly rootRuntime: ICodeRuntime;
|
||||
|
||||
getScope(): ICodeScope;
|
||||
initialize(options: CodeRuntimeOptions): void;
|
||||
|
||||
run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;
|
||||
|
||||
resolve(value: PlainObject, options?: ResolveOptions): any;
|
||||
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable;
|
||||
|
||||
createChildScope(value: PlainObject): ICodeScope;
|
||||
createCodeRuntime<T extends PlainObject = PlainObject>(
|
||||
options: CodeRuntimeOptions<T>,
|
||||
): ICodeRuntime<T>;
|
||||
}
|
||||
|
||||
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
|
||||
|
||||
export interface CodeRuntimeInitializeOptions {
|
||||
evalCodeFunction?: (code: string, scope: any) => any;
|
||||
}
|
||||
|
||||
@Provide(ICodeRuntimeService)
|
||||
export class CodeRuntimeService implements ICodeRuntimeService {
|
||||
private codeScope: ICodeScope = new CodeScope({});
|
||||
rootRuntime: ICodeRuntime;
|
||||
|
||||
private evalCodeFunction = evaluate;
|
||||
|
||||
private onResolveHandlers: NodeResolverHandler[] = [];
|
||||
|
||||
initialize(options: CodeRuntimeInitializeOptions) {
|
||||
if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction;
|
||||
initialize(options?: CodeRuntimeOptions) {
|
||||
this.rootRuntime = new CodeRuntime(options);
|
||||
}
|
||||
|
||||
getScope() {
|
||||
return this.codeScope;
|
||||
}
|
||||
createCodeRuntime<T extends PlainObject = PlainObject>(
|
||||
options: CodeRuntimeOptions<T> = {},
|
||||
): ICodeRuntime<T> {
|
||||
invariant(this.rootRuntime, `please initialize codeRuntimeService on renderer starting!`);
|
||||
|
||||
run<R = unknown>(code: string, scope: ICodeScope = this.codeScope): R | undefined {
|
||||
if (!code) return undefined;
|
||||
|
||||
try {
|
||||
const result = this.evalCodeFunction(code, scope.value);
|
||||
|
||||
return result as R;
|
||||
} catch (err) {
|
||||
// todo replace logger
|
||||
console.error('eval error', code, scope.value, err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data: PlainObject, options: ResolveOptions = {}): any {
|
||||
const handlers = this.onResolveHandlers;
|
||||
|
||||
if (handlers.length > 0) {
|
||||
data = mapValue(data, isNode, (node: Spec.JSNode) => {
|
||||
let newNode: Spec.JSNode | false | undefined = node;
|
||||
|
||||
for (const handler of handlers) {
|
||||
newNode = handler(newNode as Spec.JSNode);
|
||||
if (newNode === false || typeof newNode === 'undefined') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
|
||||
return mapValue(
|
||||
data,
|
||||
(data) => {
|
||||
return isJSExpression(data) || isJSFunction(data);
|
||||
},
|
||||
(node: Spec.JSExpression | Spec.JSFunction) => {
|
||||
return this.resolveExprOrFunction(node, options);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private resolveExprOrFunction(
|
||||
node: Spec.JSExpression | Spec.JSFunction,
|
||||
options: ResolveOptions,
|
||||
) {
|
||||
const scope = options.scope || this.codeScope;
|
||||
const v = this.run(node.value, scope) as any;
|
||||
|
||||
if (typeof v === 'undefined' && node.mock) {
|
||||
return this.resolve(node.mock, options);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顺序执行 handler
|
||||
*/
|
||||
onResolve(handler: NodeResolverHandler): EventDisposable {
|
||||
this.onResolveHandlers.push(handler);
|
||||
return () => {
|
||||
this.onResolveHandlers = this.onResolveHandlers.filter((h) => h !== handler);
|
||||
};
|
||||
}
|
||||
|
||||
createChildScope(value: PlainObject): ICodeScope {
|
||||
return this.codeScope.createChild(value);
|
||||
return options.parentScope
|
||||
? new CodeRuntime(options)
|
||||
: this.rootRuntime.createChild<T>(options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,27 +9,28 @@ const unscopables = trustedGlobals.reduce((acc, key) => ({ ...acc, [key]: true }
|
||||
__proto__: null,
|
||||
});
|
||||
|
||||
export interface ICodeScope {
|
||||
readonly value: PlainObject;
|
||||
set(name: string, value: any): void;
|
||||
setValue(value: PlainObject, replace?: boolean): void;
|
||||
createChild(initValue: PlainObject): ICodeScope;
|
||||
export interface ICodeScope<T extends PlainObject = PlainObject> {
|
||||
readonly value: T;
|
||||
|
||||
set(name: keyof T, value: any): void;
|
||||
setValue(value: Partial<T>, replace?: boolean): void;
|
||||
createChild<V extends PlainObject = PlainObject>(initValue: Partial<V>): ICodeScope<V>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 双链表实现父域值的获取
|
||||
*/
|
||||
interface IScopeNode {
|
||||
parent?: IScopeNode;
|
||||
current: PlainObject;
|
||||
interface IScopeNode<T extends PlainObject> {
|
||||
parent?: IScopeNode<PlainObject>;
|
||||
current: Partial<T>;
|
||||
}
|
||||
|
||||
export class CodeScope implements ICodeScope {
|
||||
__node: IScopeNode;
|
||||
export class CodeScope<T extends PlainObject = PlainObject> implements ICodeScope<T> {
|
||||
__node: IScopeNode<T>;
|
||||
|
||||
private proxyValue: PlainObject;
|
||||
private proxyValue: T;
|
||||
|
||||
constructor(initValue: PlainObject) {
|
||||
constructor(initValue: Partial<T>) {
|
||||
this.__node = {
|
||||
current: initValue,
|
||||
};
|
||||
@ -37,15 +38,15 @@ export class CodeScope implements ICodeScope {
|
||||
this.proxyValue = this.createProxy();
|
||||
}
|
||||
|
||||
get value() {
|
||||
get value(): T {
|
||||
return this.proxyValue;
|
||||
}
|
||||
|
||||
set(name: string, value: any): void {
|
||||
set(name: keyof T, value: any): void {
|
||||
this.__node.current[name] = value;
|
||||
}
|
||||
|
||||
setValue(value: PlainObject, replace = false) {
|
||||
setValue(value: Partial<T>, replace = false) {
|
||||
if (replace) {
|
||||
this.__node.current = { ...value };
|
||||
} else {
|
||||
@ -53,15 +54,15 @@ export class CodeScope implements ICodeScope {
|
||||
}
|
||||
}
|
||||
|
||||
createChild(initValue: PlainObject): ICodeScope {
|
||||
createChild<V extends PlainObject = PlainObject>(initValue: Partial<V>): ICodeScope<V> {
|
||||
const childScope = new CodeScope(initValue);
|
||||
childScope.__node.parent = this.__node;
|
||||
|
||||
return childScope;
|
||||
}
|
||||
|
||||
private createProxy(): PlainObject {
|
||||
return new Proxy(Object.create(null) as PlainObject, {
|
||||
private createProxy(): T {
|
||||
return new Proxy(Object.create(null) as T, {
|
||||
set: (target, p, newValue) => {
|
||||
this.set(p as string, newValue);
|
||||
return true;
|
||||
@ -74,7 +75,7 @@ export class CodeScope implements ICodeScope {
|
||||
private findValue(prop: PropertyKey) {
|
||||
if (prop === Symbol.unscopables) return unscopables;
|
||||
|
||||
let node: IScopeNode | undefined = this.__node;
|
||||
let node: IScopeNode<PlainObject> | undefined = this.__node;
|
||||
while (node) {
|
||||
if (Object.hasOwnProperty.call(node.current, prop)) {
|
||||
return node.current[prop as string];
|
||||
@ -86,7 +87,7 @@ export class CodeScope implements ICodeScope {
|
||||
private hasProperty(prop: PropertyKey): boolean {
|
||||
if (prop in unscopables) return true;
|
||||
|
||||
let node: IScopeNode | undefined = this.__node;
|
||||
let node: IScopeNode<PlainObject> | undefined = this.__node;
|
||||
while (node) {
|
||||
if (prop in node.current) {
|
||||
return true;
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import { type EvalCodeFunction } from './codeRuntime';
|
||||
|
||||
export const evaluate: EvalCodeFunction = (code: string, scope: any) => {
|
||||
return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)(
|
||||
scope,
|
||||
);
|
||||
};
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './codeScope';
|
||||
export * from './codeRuntimeService';
|
||||
export * from './codeRuntime';
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { createDecorator, Provide, type PlainObject } from '@alilc/lowcode-shared';
|
||||
import { isObject } from 'lodash-es';
|
||||
import { ICodeRuntimeService } from '../code-runtime';
|
||||
import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime';
|
||||
import { IRuntimeUtilService } from '../runtimeUtilService';
|
||||
import { IRuntimeIntlService } from '../runtimeIntlService';
|
||||
|
||||
export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any };
|
||||
|
||||
export interface IBoostsApi {
|
||||
readonly codeRuntime: ICodeRuntimeService;
|
||||
readonly codeRuntime: ICodeRuntime;
|
||||
|
||||
readonly intl: Pick<IRuntimeIntlService, 't' | 'setLocale' | 'getLocale' | 'addTranslations'>;
|
||||
|
||||
@ -39,12 +39,14 @@ export class BoostsService implements IBoostsService {
|
||||
private _expose: any;
|
||||
|
||||
constructor(
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@ICodeRuntimeService codeRuntimeService: ICodeRuntimeService,
|
||||
@IRuntimeIntlService private runtimeIntlService: IRuntimeIntlService,
|
||||
@IRuntimeUtilService private runtimeUtilService: IRuntimeUtilService,
|
||||
) {
|
||||
this.builtInApis = {
|
||||
codeRuntime: this.codeRuntimeService,
|
||||
get codeRuntime() {
|
||||
return codeRuntimeService.rootRuntime;
|
||||
},
|
||||
intl: this.runtimeIntlService,
|
||||
util: this.runtimeUtilService,
|
||||
temporaryUse: (name, value) => {
|
||||
@ -75,7 +77,7 @@ export class BoostsService implements IBoostsService {
|
||||
|
||||
toExpose<Extends>(): IBoosts<Extends> {
|
||||
if (!this._expose) {
|
||||
this._expose = new Proxy(Object.create(null), {
|
||||
this._expose = new Proxy(this.builtInApis, {
|
||||
get: (_, p, receiver) => {
|
||||
return (
|
||||
Reflect.get(this.builtInApis, p, receiver) ||
|
||||
|
||||
@ -25,6 +25,19 @@ export interface ILifeCycleService {
|
||||
when(phase: LifecyclePhase, listener: () => void | Promise<void>): EventDisposable;
|
||||
}
|
||||
|
||||
export function LifecyclePhaseToString(phase: LifecyclePhase): string {
|
||||
switch (phase) {
|
||||
case LifecyclePhase.Starting:
|
||||
return 'Starting';
|
||||
case LifecyclePhase.OptionsResolved:
|
||||
return 'OptionsResolved';
|
||||
case LifecyclePhase.Ready:
|
||||
return 'Ready';
|
||||
case LifecyclePhase.Destroying:
|
||||
return 'Destroying';
|
||||
}
|
||||
}
|
||||
|
||||
export const ILifeCycleService = createDecorator<ILifeCycleService>('lifeCycleService');
|
||||
|
||||
@Provide(ILifeCycleService)
|
||||
@ -55,18 +68,3 @@ export class LifeCycleService implements ILifeCycleService {
|
||||
return this.phaseWhen.on(LifecyclePhaseToString(phase), listener);
|
||||
}
|
||||
}
|
||||
|
||||
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.Inited:
|
||||
return 'Inited';
|
||||
case LifecyclePhase.Destroying:
|
||||
return 'Destroying';
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
invariant,
|
||||
uniqueId,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime';
|
||||
import { type ICodeRuntime } from '../code-runtime';
|
||||
import { IWidget, Widget } from '../widget';
|
||||
|
||||
export interface NormalizedComponentNode extends Spec.ComponentNode {
|
||||
@ -13,26 +13,16 @@ export interface NormalizedComponentNode extends Spec.ComponentNode {
|
||||
props: Spec.ComponentNodeProps;
|
||||
}
|
||||
|
||||
export interface InitializeModelOptions {
|
||||
defaultProps?: PlainObject | undefined;
|
||||
stateCreator: ModelScopeStateCreator;
|
||||
dataSourceCreator?: ModelScopeDataSourceCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据低代码搭建协议的容器组件描述生成的容器模型
|
||||
*/
|
||||
export interface IComponentTreeModel<Component, ComponentInstance = unknown> {
|
||||
readonly id: string;
|
||||
|
||||
readonly codeScope: ICodeScope;
|
||||
|
||||
readonly codeRuntime: ICodeRuntimeService;
|
||||
readonly codeRuntime: ICodeRuntime;
|
||||
|
||||
readonly widgets: IWidget<Component, ComponentInstance>[];
|
||||
|
||||
initialize(options: InitializeModelOptions): void;
|
||||
|
||||
/**
|
||||
* 获取协议中的 css 内容
|
||||
*/
|
||||
@ -56,12 +46,18 @@ export interface IComponentTreeModel<Component, ComponentInstance = unknown> {
|
||||
buildWidgets(nodes: Spec.NodeType[]): IWidget<Component, ComponentInstance>[];
|
||||
}
|
||||
|
||||
export type ModelScopeStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi;
|
||||
export type ModelScopeDataSourceCreator = (...args: any[]) => Spec.InstanceDataSourceApi;
|
||||
export type ModelStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi;
|
||||
export type ModelDataSourceCreator = (
|
||||
dataSourceSchema: Spec.ComponentDataSource,
|
||||
codeRuntime: ICodeRuntime<Spec.InstanceApi>,
|
||||
) => Spec.InstanceDataSourceApi;
|
||||
|
||||
export interface ComponentTreeModelOptions {
|
||||
id?: string;
|
||||
metadata?: PlainObject;
|
||||
|
||||
stateCreator: ModelStateCreator;
|
||||
dataSourceCreator?: ModelDataSourceCreator;
|
||||
}
|
||||
|
||||
export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
@ -71,16 +67,14 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
|
||||
public id: string;
|
||||
|
||||
public codeScope: ICodeScope;
|
||||
|
||||
public widgets: IWidget<Component>[] = [];
|
||||
|
||||
public metadata: PlainObject = {};
|
||||
|
||||
constructor(
|
||||
public componentsTree: Spec.ComponentTree,
|
||||
public codeRuntime: ICodeRuntimeService,
|
||||
options?: ComponentTreeModelOptions,
|
||||
public codeRuntime: ICodeRuntime<Spec.InstanceApi>,
|
||||
options: ComponentTreeModelOptions,
|
||||
) {
|
||||
invariant(componentsTree, 'componentsTree must to provide', 'ComponentTreeModel');
|
||||
|
||||
@ -92,39 +86,28 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
if (componentsTree.children) {
|
||||
this.widgets = this.buildWidgets(componentsTree.children);
|
||||
}
|
||||
|
||||
this.initialize(options);
|
||||
}
|
||||
|
||||
initialize({ defaultProps, stateCreator, dataSourceCreator }: InitializeModelOptions) {
|
||||
const {
|
||||
state = {},
|
||||
defaultProps: defaultSchemaProps,
|
||||
props = {},
|
||||
dataSource,
|
||||
methods = {},
|
||||
} = this.componentsTree;
|
||||
private initialize({ stateCreator, dataSourceCreator }: ComponentTreeModelOptions) {
|
||||
const { state = {}, defaultProps, props = {}, dataSource, methods = {} } = this.componentsTree;
|
||||
const codeScope = this.codeRuntime.getScope();
|
||||
|
||||
this.codeScope = this.codeRuntime.createChildScope({
|
||||
props: {
|
||||
...props,
|
||||
...defaultSchemaProps,
|
||||
...defaultProps,
|
||||
},
|
||||
});
|
||||
const initalProps = this.codeRuntime.resolve(props);
|
||||
codeScope.setValue({ props: { ...defaultProps, ...codeScope.value.props, ...initalProps } });
|
||||
|
||||
const initalProps = this.codeRuntime.resolve(props, { scope: this.codeScope });
|
||||
this.codeScope.setValue({ props: { ...defaultProps, ...initalProps } });
|
||||
|
||||
const initalState = this.codeRuntime.resolve(state, { scope: this.codeScope });
|
||||
const initalState = this.codeRuntime.resolve(state);
|
||||
const stateApi = stateCreator(initalState);
|
||||
this.codeScope.setValue(stateApi);
|
||||
codeScope.setValue(stateApi);
|
||||
|
||||
let dataSourceApi: Spec.InstanceDataSourceApi | undefined;
|
||||
if (dataSource && dataSourceCreator) {
|
||||
const dataSourceProps = this.codeRuntime.resolve(dataSource, { scope: this.codeScope });
|
||||
dataSourceApi = dataSourceCreator(dataSourceProps, stateApi);
|
||||
const dataSourceProps = this.codeRuntime.resolve(dataSource);
|
||||
dataSourceApi = dataSourceCreator(dataSourceProps, this.codeRuntime);
|
||||
}
|
||||
|
||||
this.codeScope.setValue(
|
||||
codeScope.setValue(
|
||||
Object.assign(
|
||||
{
|
||||
$: (ref: string) => {
|
||||
@ -141,9 +124,9 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
);
|
||||
|
||||
for (const [key, fn] of Object.entries(methods)) {
|
||||
const customMethod = this.codeRuntime.resolve(fn, { scope: this.codeScope });
|
||||
const customMethod = this.codeRuntime.resolve(fn);
|
||||
if (typeof customMethod === 'function') {
|
||||
this.codeScope.set(key, customMethod);
|
||||
codeScope.set(key, customMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,9 +146,9 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
|
||||
const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName];
|
||||
|
||||
const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema, { scope: this.codeScope });
|
||||
const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema);
|
||||
if (typeof lifeCycleFn === 'function') {
|
||||
lifeCycleFn.apply(this.codeScope.value, args);
|
||||
lifeCycleFn.apply(this.codeRuntime.getScope().value, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { createDecorator, Provide, invariant, type Spec } from '@alilc/lowcode-shared';
|
||||
import {
|
||||
createDecorator,
|
||||
Provide,
|
||||
invariant,
|
||||
type Spec,
|
||||
type PlainObject,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { ICodeRuntimeService } from '../code-runtime';
|
||||
import {
|
||||
type IComponentTreeModel,
|
||||
@ -7,15 +13,19 @@ import {
|
||||
} from './componentTreeModel';
|
||||
import { ISchemaService } from '../schema';
|
||||
|
||||
export interface CreateComponentTreeModelOptions extends ComponentTreeModelOptions {
|
||||
codeScopeValue?: PlainObject;
|
||||
}
|
||||
|
||||
export interface IComponentTreeModelService {
|
||||
create<Component>(
|
||||
componentsTree: Spec.ComponentTree,
|
||||
options?: ComponentTreeModelOptions,
|
||||
options?: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component>;
|
||||
|
||||
createById<Component>(
|
||||
id: string,
|
||||
options?: ComponentTreeModelOptions,
|
||||
options?: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component>;
|
||||
}
|
||||
|
||||
@ -32,20 +42,32 @@ export class ComponentTreeModelService implements IComponentTreeModelService {
|
||||
|
||||
create<Component>(
|
||||
componentsTree: Spec.ComponentTree,
|
||||
options?: ComponentTreeModelOptions,
|
||||
options: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component> {
|
||||
return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options);
|
||||
return new ComponentTreeModel(
|
||||
componentsTree,
|
||||
this.codeRuntimeService.createCodeRuntime({
|
||||
initScopeValue: options?.codeScopeValue,
|
||||
}),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
createById<Component>(
|
||||
id: string,
|
||||
options?: ComponentTreeModelOptions,
|
||||
options: CreateComponentTreeModelOptions,
|
||||
): IComponentTreeModel<Component> {
|
||||
const componentsTrees = this.schemaService.get('componentsTree');
|
||||
const componentsTree = componentsTrees.find((item) => item.id === id);
|
||||
|
||||
invariant(componentsTree, 'componentsTree not found');
|
||||
|
||||
return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options);
|
||||
return new ComponentTreeModel(
|
||||
componentsTree,
|
||||
this.codeRuntimeService.createCodeRuntime({
|
||||
initScopeValue: options?.codeScopeValue,
|
||||
}),
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,6 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
this.injectScope();
|
||||
|
||||
this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => {
|
||||
const config = this.schemaService.get('config');
|
||||
const i18nTranslations = this.schemaService.get('i18n');
|
||||
@ -56,6 +54,8 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
this.addTranslations(key, i18nTranslations[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.injectScope();
|
||||
});
|
||||
}
|
||||
|
||||
@ -96,6 +96,6 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
},
|
||||
};
|
||||
|
||||
this.codeRuntimeService.getScope().setValue(exposed);
|
||||
this.codeRuntimeService.rootRuntime.getScope().setValue(exposed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { isPlainObject } from 'lodash-es';
|
||||
import { IPackageManagementService } from './package';
|
||||
import { ICodeRuntimeService } from './code-runtime';
|
||||
import { ISchemaService } from './schema';
|
||||
import { ILifeCycleService, LifecyclePhase } from './lifeCycleService';
|
||||
|
||||
export interface IRuntimeUtilService {
|
||||
add(utilItem: Spec.Util, force?: boolean): void;
|
||||
@ -27,8 +28,11 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@IPackageManagementService private packageManagementService: IPackageManagementService,
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
@ILifeCycleService private lifeCycleService: ILifeCycleService,
|
||||
) {
|
||||
this.injectScope();
|
||||
this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => {
|
||||
this.injectScope();
|
||||
});
|
||||
|
||||
this.schemaService.onChange('utils', (utils = []) => {
|
||||
for (const util of utils) {
|
||||
@ -93,7 +97,7 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
const { content } = utilItem;
|
||||
return {
|
||||
key: utilItem.name,
|
||||
value: this.codeRuntimeService.run(content.value),
|
||||
value: this.codeRuntimeService.rootRuntime.run(content.value),
|
||||
};
|
||||
} else {
|
||||
return this.packageManagementService.getLibraryByComponentMap(utilItem.content);
|
||||
@ -113,6 +117,6 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
},
|
||||
});
|
||||
|
||||
this.codeRuntimeService.getScope().set('utils', exposed);
|
||||
this.codeRuntimeService.rootRuntime.getScope().set('utils', exposed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { type Spec, uniqueId } from '@alilc/lowcode-shared';
|
||||
import { clone } from 'lodash-es';
|
||||
import { IComponentTreeModel } from '../model';
|
||||
|
||||
export interface IWidget<Component, ComponentInstance = unknown> {
|
||||
readonly key: string;
|
||||
|
||||
readonly node: Spec.NodeType;
|
||||
readonly rawNode: Spec.NodeType;
|
||||
|
||||
model: IComponentTreeModel<Component, ComponentInstance>;
|
||||
|
||||
@ -15,9 +14,7 @@ export interface IWidget<Component, ComponentInstance = unknown> {
|
||||
export class Widget<Component, ComponentInstance = unknown>
|
||||
implements IWidget<Component, ComponentInstance>
|
||||
{
|
||||
public __raw: Spec.NodeType;
|
||||
|
||||
public node: Spec.NodeType;
|
||||
public rawNode: Spec.NodeType;
|
||||
|
||||
public key: string;
|
||||
|
||||
@ -27,8 +24,7 @@ export class Widget<Component, ComponentInstance = unknown>
|
||||
node: Spec.NodeType,
|
||||
public model: IComponentTreeModel<Component, ComponentInstance>,
|
||||
) {
|
||||
this.node = clone(node);
|
||||
this.__raw = node;
|
||||
this.rawNode = node;
|
||||
this.key = (node as Spec.ComponentNode)?.id ?? uniqueId();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ import { type Spec } from '@alilc/lowcode-shared';
|
||||
import { type Plugin } from './services/extension';
|
||||
import { type ISchemaService } from './services/schema';
|
||||
import { type IPackageManagementService } from './services/package';
|
||||
import { type CodeRuntimeInitializeOptions } from './services/code-runtime';
|
||||
import { type ModelScopeDataSourceCreator } from './services/model';
|
||||
import { type CodeRuntimeOptions } from './services/code-runtime';
|
||||
import { type ModelDataSourceCreator } from './services/model';
|
||||
|
||||
export interface AppOptions {
|
||||
schema: Spec.Project;
|
||||
@ -16,11 +16,11 @@ export interface AppOptions {
|
||||
/**
|
||||
* code runtime 设置选项
|
||||
*/
|
||||
codeRuntime?: CodeRuntimeInitializeOptions;
|
||||
codeRuntime?: CodeRuntimeOptions;
|
||||
/**
|
||||
* 数据源创建工厂函数
|
||||
*/
|
||||
dataSourceCreator?: ModelScopeDataSourceCreator;
|
||||
dataSourceCreator?: ModelDataSourceCreator;
|
||||
}
|
||||
|
||||
export type RendererApplication<Render = unknown> = {
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export function evaluate(code: string, scope: any) {
|
||||
return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)(
|
||||
scope,
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import { AnyFunction, PlainObject } from '../index';
|
||||
import { JSExpression } from './lowcode-spec';
|
||||
|
||||
/**
|
||||
* 在上述事件类型描述和变量类型描述中,在函数或 JS 表达式内,均可以通过 this 对象获取当前组件所在容器的实例化对象
|
||||
@ -62,7 +61,7 @@ export interface DataSourceMapItem<T = any> {
|
||||
* 调用单个数据源
|
||||
* @param params 替换 ComponentDataSourceItemOptions 对象描述中的 params
|
||||
*/
|
||||
load(params: any): Promise<T>;
|
||||
load(params?: any): Promise<T>;
|
||||
/**
|
||||
* 数据源请求的返回状态
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user