fix: renderer bugs fix

This commit is contained in:
1ncounter 2024-07-01 19:03:35 +08:00
parent a855c05d67
commit 450d08e500
26 changed files with 472 additions and 376 deletions

View File

@ -1,7 +1,8 @@
import { createRenderer } from '@alilc/lowcode-renderer-core'; import { createRenderer } from '@alilc/lowcode-renderer-core';
import { type Root, createRoot } from 'react-dom/client'; import { type Root, createRoot } from 'react-dom/client';
import { type ReactAppOptions, RendererContext } from './context'; import { RendererContext } from './context';
import { ApplicationView, boosts } from '../app'; import { ApplicationView, boosts } from '../app';
import { type ReactAppOptions } from './types';
export const createApp = async (options: ReactAppOptions) => { export const createApp = async (options: ReactAppOptions) => {
return createRenderer(async (context) => { return createRenderer(async (context) => {

View File

@ -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 { FunctionComponent } from 'react';
import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/schema'; import {
import { RendererContext } from '../api/context'; type LowCodeComponentProps,
createComponent as createSchemaComponent,
} from '../runtime/createComponent';
import { RendererContext } from './context';
import { type ReactAppOptions } from './types';
interface Render { interface Render {
toComponent(): FunctionComponent<LowCodeComponentProps>; toComponent(): FunctionComponent<LowCodeComponentProps>;
} }
export async function createComponent(options: AppOptions) { export async function createComponent(options: ReactAppOptions) {
const creator = createRenderer<Render>((context) => { const creator = createRenderer<Render>((context) => {
const { schema } = 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 }; const contextValue = { ...context, options };
function Component(props: LowCodeComponentProps) { function Component(props: LowCodeComponentProps) {

View File

@ -1,9 +1,6 @@
import { type ComponentType, createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import { type AppOptions, type RenderContext } from '@alilc/lowcode-renderer-core'; import { type RenderContext } from '@alilc/lowcode-renderer-core';
import { type ReactAppOptions } from './types';
export interface ReactAppOptions extends AppOptions {
faultComponent?: ComponentType<any>;
}
export const RendererContext = createContext<RenderContext & { options: ReactAppOptions }>( export const RendererContext = createContext<RenderContext & { options: ReactAppOptions }>(
undefined!, undefined!,

View 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>;
}

View File

@ -1,10 +1,10 @@
import { useRendererContext } from '../api/context'; import { useRendererContext } from '../api/context';
import { getComponentByName } from '../runtime/schema'; import { getComponentByName } from '../runtime/createComponent';
import { boosts } from './boosts'; import { boosts } from './boosts';
export function ApplicationView() { export function ApplicationView() {
const rendererContext = useRendererContext(); const rendererContext = useRendererContext();
const { schema } = rendererContext; const { schema, options } = rendererContext;
const appWrappers = boosts.getAppWrappers(); const appWrappers = boosts.getAppWrappers();
const Outlet = boosts.getOutlet(); const Outlet = boosts.getOutlet();
@ -16,7 +16,7 @@ export function ApplicationView() {
if (layoutConfig) { if (layoutConfig) {
const componentName = layoutConfig.componentName; const componentName = layoutConfig.componentName;
const Layout = getComponentByName(componentName, rendererContext); const Layout = getComponentByName(componentName, rendererContext, options.component);
if (Layout) { if (Layout) {
const layoutProps: any = layoutConfig.props ?? {}; const layoutProps: any = layoutConfig.props ?? {};

View File

@ -6,5 +6,11 @@ export * from './router';
export { LifecyclePhase } from '@alilc/lowcode-renderer-core'; export { LifecyclePhase } from '@alilc/lowcode-renderer-core';
export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared'; 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'; export type { ReactRendererBoostsApi } from './app/boosts';

View File

@ -2,12 +2,12 @@ import { useMemo } from 'react';
import { useRendererContext } from '../api/context'; import { useRendererContext } from '../api/context';
import { OutletProps } from '../app/boosts'; import { OutletProps } from '../app/boosts';
import { useRouteLocation } from './context'; import { useRouteLocation } from './context';
import { createComponentBySchema } from '../runtime/schema'; import { createComponent } from '../runtime/createComponent';
export function RouteOutlet(props: OutletProps) { export function RouteOutlet(props: OutletProps) {
const context = useRendererContext(); const context = useRendererContext();
const location = useRouteLocation(); const location = useRouteLocation();
const { schema, packageManager } = context; const { schema, packageManager, options } = context;
const pageConfig = useMemo(() => { const pageConfig = useMemo(() => {
const pages = schema.get('pages') ?? []; const pages = schema.get('pages') ?? [];
@ -27,11 +27,12 @@ export function RouteOutlet(props: OutletProps) {
const componentsMap = schema.get('componentsMap'); const componentsMap = schema.get('componentsMap');
packageManager.resolveComponentMaps(componentsMap); packageManager.resolveComponentMaps(componentsMap);
const LowCodeComponent = createComponentBySchema(pageConfig.mappingId, { const LowCodeComponent = createComponent(pageConfig.mappingId, {
displayName: pageConfig.id, displayName: pageConfig.id,
modelOptions: { modelOptions: {
metadata: pageConfig, metadata: pageConfig,
}, },
...options.component,
}); });
return <LowCodeComponent {...props} />; return <LowCodeComponent {...props} />;

View File

@ -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;

View File

@ -3,23 +3,23 @@ import { forwardRef, useRef, useEffect } from 'react';
import { isValidElementType } from 'react-is'; import { isValidElementType } from 'react-is';
import { useRendererContext } from '../api/context'; import { useRendererContext } from '../api/context';
import { reactiveStateFactory } from './reactiveState'; import { reactiveStateFactory } from './reactiveState';
import { type ReactComponent, type ReactWidget, createElementByWidget } from './components'; import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
import { ModelContextProvider } from './context';
import { appendExternalStyle } from '../utils/element'; import { appendExternalStyle } from '../utils/element';
import type { import type {
RenderContext, RenderContext,
IComponentTreeModel, IComponentTreeModel,
ComponentTreeModelOptions, CreateComponentTreeModelOptions,
} from '@alilc/lowcode-renderer-core'; } from '@alilc/lowcode-renderer-core';
import type { ReactInstance, CSSProperties, ForwardedRef } from 'react'; import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
export interface ComponentOptions { export interface ComponentOptions {
displayName?: string; displayName?: string;
modelOptions?: ComponentTreeModelOptions; modelOptions?: Pick<CreateComponentTreeModelOptions, 'id' | 'metadata'>;
widgetCreated?(widget: ReactWidget): void; beforeElementCreate?(widget: ReactWidget): ReactWidget;
componentRefAttached?(widget: ReactWidget, instance: ReactInstance): void; elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
componentRefAttached?(widget: ReactWidget, instance: ReactInstance | null): void;
} }
export interface LowCodeComponentProps { export interface LowCodeComponentProps {
@ -37,6 +37,7 @@ const lowCodeComponentsCache = new Map<string, ReactComponent>();
export function getComponentByName( export function getComponentByName(
name: string, name: string,
{ packageManager, boostsManager }: RenderContext, { packageManager, boostsManager }: RenderContext,
componentOptions: ComponentOptions = {},
): ReactComponent { ): ReactComponent {
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name); 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, displayName: name,
modelOptions: { modelOptions: {
id: metadata.id, id: metadata.id,
@ -76,40 +78,49 @@ export function getComponentByName(
return result; return result;
} }
export function createComponentBySchema( export function createComponent(
schema: string | Spec.ComponentTreeRoot, schema: string | Spec.ComponentTreeRoot,
{ displayName = '__LowCodeComponent__', modelOptions }: ComponentOptions = {}, componentOptions: ComponentOptions = {},
) { ) {
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
const LowCodeComponent = forwardRef(function ( const LowCodeComponent = forwardRef(function (
props: LowCodeComponentProps, props: LowCodeComponentProps,
ref: ForwardedRef<any>, ref: ForwardedRef<any>,
) { ) {
const renderContext = useRendererContext(); const context = useRendererContext();
const { options, componentTreeModel } = renderContext; const { options: globalOptions, componentTreeModel } = context;
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>(); const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
if (!modelRef.current) { if (!modelRef.current) {
const finalOptions: CreateComponentTreeModelOptions = {
...modelOptions,
codeScopeValue: {
props,
},
stateCreator: reactiveStateFactory,
dataSourceCreator: globalOptions.dataSourceCreator,
};
if (typeof schema === 'string') { if (typeof schema === 'string') {
modelRef.current = componentTreeModel.createById(schema, modelOptions); modelRef.current = componentTreeModel.createById(schema, finalOptions);
} else { } 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!; const model = modelRef.current!;
console.log('%c [ model ]-103', 'font-size:13px; background:pink; color:#bf2c9f;', model);
const isConstructed = useRef(false); const isConstructed = useRef(false);
const isMounted = useRef(false); const isMounted = useRef(false);
if (!isConstructed.current) { if (!isConstructed.current) {
model.initialize({
defaultProps: props,
stateCreator: reactiveStateFactory,
dataSourceCreator: options.dataSourceCreator,
});
model.triggerLifeCycle('constructor'); model.triggerLifeCycle('constructor');
isConstructed.current = true; isConstructed.current = true;
@ -142,11 +153,9 @@ export function createComponentBySchema(
}, []); }, []);
return ( return (
<ModelContextProvider value={model}>
<div id={props.id} className={props.className} style={props.style} ref={ref}> <div id={props.id} className={props.className} style={props.style} ref={ref}>
{model.widgets.map((w) => createElementByWidget(w, model.codeScope))} {model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))}
</div> </div>
</ModelContextProvider>
); );
}); });

View File

@ -1,6 +1,6 @@
import { import {
type IWidget, type IWidget,
type ICodeScope, type ICodeRuntime,
type NormalizedComponentNode, type NormalizedComponentNode,
mapValue, mapValue,
} from '@alilc/lowcode-renderer-core'; } from '@alilc/lowcode-renderer-core';
@ -15,38 +15,40 @@ import {
import { type ComponentType, type ReactInstance, useMemo, createElement } from 'react'; import { type ComponentType, type ReactInstance, useMemo, createElement } from 'react';
import { useRendererContext } from '../api/context'; import { useRendererContext } from '../api/context';
import { useReactiveStore } from './hooks/useReactiveStore'; import { useReactiveStore } from './hooks/useReactiveStore';
import { useModel } from './context'; import { getComponentByName, type ComponentOptions } from './createComponent';
import { getComponentByName } from './schema';
export type ReactComponent = ComponentType<any>; export type ReactComponent = ComponentType<any>;
export type ReactWidget = IWidget<ReactComponent, ReactInstance>; export type ReactWidget = IWidget<ReactComponent, ReactInstance>;
interface WidgetRendererProps { interface WidgetRendererProps {
widget: ReactWidget; widget: ReactWidget;
codeScope: ICodeScope; codeRuntime: ICodeRuntime;
options: ComponentOptions;
[key: string]: any; [key: string]: any;
} }
export function createElementByWidget( export function createElementByWidget(
widget: IWidget<ReactComponent, ReactInstance>, widget: ReactWidget,
codeScope: ICodeScope, codeRuntime: ICodeRuntime,
options: ComponentOptions,
) { ) {
const { key, node } = widget; const getElement = (widget: ReactWidget) => {
const { key, rawNode } = widget;
if (typeof node === 'string') { if (typeof rawNode === 'string') {
return node; return rawNode;
} }
if (isJSExpression(node)) { if (isJSExpression(rawNode)) {
return <Text key={key} expr={node} codeScope={codeScope} />; return <Text key={key} expr={rawNode} codeRuntime={codeRuntime} />;
} }
if (isJSI18nNode(node)) { if (isJSI18nNode(rawNode)) {
return <I18nText key={key} i18n={node} codeScope={codeScope} />; return <I18nText key={key} i18n={rawNode} codeRuntime={codeRuntime} />;
} }
const { condition, loop } = widget.node as NormalizedComponentNode; const { condition, loop } = widget.rawNode as NormalizedComponentNode;
// condition为 Falsy 的情况下 不渲染 // condition为 Falsy 的情况下 不渲染
if (!condition) return null; if (!condition) return null;
@ -54,31 +56,98 @@ export function createElementByWidget(
if (Array.isArray(loop) && loop.length === 0) return null; if (Array.isArray(loop) && loop.length === 0) return null;
if (isJSExpression(loop)) { if (isJSExpression(loop)) {
return <LoopWidgetRenderer key={key} loop={loop} widget={widget} codeScope={codeScope} />; return (
<LoopWidgetRenderer
key={key}
loop={loop}
widget={widget}
codeRuntime={codeRuntime}
options={options}
/>
);
} }
return <WidgetComponent key={key} widget={widget} codeScope={codeScope} />; return (
<WidgetComponent key={key} widget={widget} codeRuntime={codeRuntime} options={options} />
);
};
if (options.beforeElementCreate) {
widget = options.beforeElementCreate(widget);
}
const element = getElement(widget);
if (options.elementCreated) {
return options.elementCreated(widget, element);
}
return element;
} }
export function WidgetComponent(props: WidgetRendererProps) { export function WidgetComponent(props: WidgetRendererProps) {
const { widget, codeScope, ...otherProps } = props; const { widget, codeRuntime, options, ...otherProps } = props;
const componentNode = widget.node as NormalizedComponentNode; const componentNode = widget.rawNode as NormalizedComponentNode;
const { ref, ...componentProps } = componentNode.props; const { ref, ...componentProps } = componentNode.props;
const rendererContext = useRendererContext(); const context = useRendererContext();
const Component = useMemo( const Component = useMemo(
() => getComponentByName(componentNode.componentName, rendererContext), () => getComponentByName(componentNode.componentName, context, options),
[widget], [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({ const state = useReactiveStore({
target: { target: {
condition: componentNode.condition, condition: componentNode.condition,
props: preprocessProps(componentProps, widget, codeScope), props: processedProps,
}, },
valueGetter(expr) { valueGetter(expr) {
return widget.model.codeRuntime.resolve(expr, { scope: codeScope }); return codeRuntime.resolve(expr);
}, },
}); });
@ -88,6 +157,8 @@ export function WidgetComponent(props: WidgetRendererProps) {
} else { } else {
if (ref) widget.model.removeComponentRef(ref); if (ref) widget.model.removeComponentRef(ref);
} }
options.componentRefAttached?.(widget, ins);
}; };
if (!state.condition) { if (!state.condition) {
@ -107,58 +178,15 @@ export function WidgetComponent(props: WidgetRendererProps) {
key: widget.key, key: widget.key,
ref: attachRef, 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) { function Text(props: { expr: Spec.JSExpression; codeRuntime: ICodeRuntime }) {
// 先将 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();
const text: string = useReactiveStore({ const text: string = useReactiveStore({
target: props.expr, target: props.expr,
getter: (obj) => { 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'; Text.displayName = 'Text';
function I18nText(props: { i18n: Spec.JSI18n; codeScope: ICodeScope }) { function I18nText(props: { i18n: Spec.JSI18n; codeRuntime: ICodeRuntime }) {
const model = useModel();
const text: string = useReactiveStore({ const text: string = useReactiveStore({
target: props.i18n, target: props.i18n,
getter: (obj) => { getter: (obj) => {
return model.codeRuntime.resolve(obj, { scope: props.codeScope }); return props.codeRuntime.resolve(obj);
}, },
}); });
@ -184,32 +211,34 @@ I18nText.displayName = 'I18nText';
function LoopWidgetRenderer({ function LoopWidgetRenderer({
loop, loop,
widget, widget,
codeScope, codeRuntime,
options,
...otherProps ...otherProps
}: { }: {
loop: Spec.JSExpression; loop: Spec.JSExpression;
widget: ReactWidget; widget: ReactWidget;
codeScope: ICodeScope; codeRuntime: ICodeRuntime;
options: ComponentOptions;
[key: string]: any; [key: string]: any;
}) { }) {
const { condition, loopArgs } = widget.node as NormalizedComponentNode; const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode;
const state = useReactiveStore({ const state = useReactiveStore({
target: { target: {
loop, loop,
condition, condition,
}, },
valueGetter(expr) { 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) { if (state.condition && Array.isArray(state.loop) && state.loop.length > 0) {
return state.loop.map((item: any, idx: number) => { return state.loop.map((item: any, idx: number) => {
const childScope = codeScope.createChild({ const childRuntime = codeRuntime.createChild({
initScopeValue: {
[loopArgs[0]]: item, [loopArgs[0]]: item,
[loopArgs[1]]: idx, [loopArgs[1]]: idx,
},
}); });
return ( return (
@ -217,7 +246,8 @@ function LoopWidgetRenderer({
{...otherProps} {...otherProps}
key={`loop-${widget.key}-${idx}`} key={`loop-${widget.key}-${idx}`}
widget={widget} widget={widget}
codeScope={childScope} codeRuntime={childRuntime}
options={options}
/> />
); );
}); });

View File

@ -1,2 +1,2 @@
export * from './schema'; export * from './createComponent';
export * from './components'; export * from './elements';

View 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,
});
}
}

View File

@ -1,126 +1,33 @@
import { import { createDecorator, invariant, Provide, type PlainObject } from '@alilc/lowcode-shared';
type PlainObject, import { type ICodeRuntime, type CodeRuntimeOptions, CodeRuntime } from './codeRuntime';
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;
export interface ICodeRuntimeService { 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; createCodeRuntime<T extends PlainObject = PlainObject>(
options: CodeRuntimeOptions<T>,
resolve(value: PlainObject, options?: ResolveOptions): any; ): ICodeRuntime<T>;
onResolve(handler: NodeResolverHandler): EventDisposable;
createChildScope(value: PlainObject): ICodeScope;
} }
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService'); export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');
export interface CodeRuntimeInitializeOptions {
evalCodeFunction?: (code: string, scope: any) => any;
}
@Provide(ICodeRuntimeService) @Provide(ICodeRuntimeService)
export class CodeRuntimeService implements ICodeRuntimeService { export class CodeRuntimeService implements ICodeRuntimeService {
private codeScope: ICodeScope = new CodeScope({}); rootRuntime: ICodeRuntime;
private evalCodeFunction = evaluate; initialize(options?: CodeRuntimeOptions) {
this.rootRuntime = new CodeRuntime(options);
private onResolveHandlers: NodeResolverHandler[] = [];
initialize(options: CodeRuntimeInitializeOptions) {
if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction;
} }
getScope() { createCodeRuntime<T extends PlainObject = PlainObject>(
return this.codeScope; 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 { return options.parentScope
if (!code) return undefined; ? new CodeRuntime(options)
: this.rootRuntime.createChild<T>(options);
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);
} }
} }

View File

@ -9,27 +9,28 @@ const unscopables = trustedGlobals.reduce((acc, key) => ({ ...acc, [key]: true }
__proto__: null, __proto__: null,
}); });
export interface ICodeScope { export interface ICodeScope<T extends PlainObject = PlainObject> {
readonly value: PlainObject; readonly value: T;
set(name: string, value: any): void;
setValue(value: PlainObject, replace?: boolean): void; set(name: keyof T, value: any): void;
createChild(initValue: PlainObject): ICodeScope; setValue(value: Partial<T>, replace?: boolean): void;
createChild<V extends PlainObject = PlainObject>(initValue: Partial<V>): ICodeScope<V>;
} }
/** /**
* *
*/ */
interface IScopeNode { interface IScopeNode<T extends PlainObject> {
parent?: IScopeNode; parent?: IScopeNode<PlainObject>;
current: PlainObject; current: Partial<T>;
} }
export class CodeScope implements ICodeScope { export class CodeScope<T extends PlainObject = PlainObject> implements ICodeScope<T> {
__node: IScopeNode; __node: IScopeNode<T>;
private proxyValue: PlainObject; private proxyValue: T;
constructor(initValue: PlainObject) { constructor(initValue: Partial<T>) {
this.__node = { this.__node = {
current: initValue, current: initValue,
}; };
@ -37,15 +38,15 @@ export class CodeScope implements ICodeScope {
this.proxyValue = this.createProxy(); this.proxyValue = this.createProxy();
} }
get value() { get value(): T {
return this.proxyValue; return this.proxyValue;
} }
set(name: string, value: any): void { set(name: keyof T, value: any): void {
this.__node.current[name] = value; this.__node.current[name] = value;
} }
setValue(value: PlainObject, replace = false) { setValue(value: Partial<T>, replace = false) {
if (replace) { if (replace) {
this.__node.current = { ...value }; this.__node.current = { ...value };
} else { } 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); const childScope = new CodeScope(initValue);
childScope.__node.parent = this.__node; childScope.__node.parent = this.__node;
return childScope; return childScope;
} }
private createProxy(): PlainObject { private createProxy(): T {
return new Proxy(Object.create(null) as PlainObject, { return new Proxy(Object.create(null) as T, {
set: (target, p, newValue) => { set: (target, p, newValue) => {
this.set(p as string, newValue); this.set(p as string, newValue);
return true; return true;
@ -74,7 +75,7 @@ export class CodeScope implements ICodeScope {
private findValue(prop: PropertyKey) { private findValue(prop: PropertyKey) {
if (prop === Symbol.unscopables) return unscopables; if (prop === Symbol.unscopables) return unscopables;
let node: IScopeNode | undefined = this.__node; let node: IScopeNode<PlainObject> | undefined = this.__node;
while (node) { while (node) {
if (Object.hasOwnProperty.call(node.current, prop)) { if (Object.hasOwnProperty.call(node.current, prop)) {
return node.current[prop as string]; return node.current[prop as string];
@ -86,7 +87,7 @@ export class CodeScope implements ICodeScope {
private hasProperty(prop: PropertyKey): boolean { private hasProperty(prop: PropertyKey): boolean {
if (prop in unscopables) return true; if (prop in unscopables) return true;
let node: IScopeNode | undefined = this.__node; let node: IScopeNode<PlainObject> | undefined = this.__node;
while (node) { while (node) {
if (prop in node.current) { if (prop in node.current) {
return true; return true;

View File

@ -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,
);
};

View File

@ -1,2 +1,3 @@
export * from './codeScope'; export * from './codeScope';
export * from './codeRuntimeService'; export * from './codeRuntimeService';
export * from './codeRuntime';

View File

@ -1,13 +1,13 @@
import { createDecorator, Provide, type PlainObject } from '@alilc/lowcode-shared'; import { createDecorator, Provide, type PlainObject } from '@alilc/lowcode-shared';
import { isObject } from 'lodash-es'; import { isObject } from 'lodash-es';
import { ICodeRuntimeService } from '../code-runtime'; import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime';
import { IRuntimeUtilService } from '../runtimeUtilService'; import { IRuntimeUtilService } from '../runtimeUtilService';
import { IRuntimeIntlService } from '../runtimeIntlService'; import { IRuntimeIntlService } from '../runtimeIntlService';
export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any }; export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any };
export interface IBoostsApi { export interface IBoostsApi {
readonly codeRuntime: ICodeRuntimeService; readonly codeRuntime: ICodeRuntime;
readonly intl: Pick<IRuntimeIntlService, 't' | 'setLocale' | 'getLocale' | 'addTranslations'>; readonly intl: Pick<IRuntimeIntlService, 't' | 'setLocale' | 'getLocale' | 'addTranslations'>;
@ -39,12 +39,14 @@ export class BoostsService implements IBoostsService {
private _expose: any; private _expose: any;
constructor( constructor(
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, @ICodeRuntimeService codeRuntimeService: ICodeRuntimeService,
@IRuntimeIntlService private runtimeIntlService: IRuntimeIntlService, @IRuntimeIntlService private runtimeIntlService: IRuntimeIntlService,
@IRuntimeUtilService private runtimeUtilService: IRuntimeUtilService, @IRuntimeUtilService private runtimeUtilService: IRuntimeUtilService,
) { ) {
this.builtInApis = { this.builtInApis = {
codeRuntime: this.codeRuntimeService, get codeRuntime() {
return codeRuntimeService.rootRuntime;
},
intl: this.runtimeIntlService, intl: this.runtimeIntlService,
util: this.runtimeUtilService, util: this.runtimeUtilService,
temporaryUse: (name, value) => { temporaryUse: (name, value) => {
@ -75,7 +77,7 @@ export class BoostsService implements IBoostsService {
toExpose<Extends>(): IBoosts<Extends> { toExpose<Extends>(): IBoosts<Extends> {
if (!this._expose) { if (!this._expose) {
this._expose = new Proxy(Object.create(null), { this._expose = new Proxy(this.builtInApis, {
get: (_, p, receiver) => { get: (_, p, receiver) => {
return ( return (
Reflect.get(this.builtInApis, p, receiver) || Reflect.get(this.builtInApis, p, receiver) ||

View File

@ -25,6 +25,19 @@ export interface ILifeCycleService {
when(phase: LifecyclePhase, listener: () => void | Promise<void>): EventDisposable; 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'); export const ILifeCycleService = createDecorator<ILifeCycleService>('lifeCycleService');
@Provide(ILifeCycleService) @Provide(ILifeCycleService)
@ -55,18 +68,3 @@ export class LifeCycleService implements ILifeCycleService {
return this.phaseWhen.on(LifecyclePhaseToString(phase), listener); 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';
}
}

View File

@ -5,7 +5,7 @@ import {
invariant, invariant,
uniqueId, uniqueId,
} from '@alilc/lowcode-shared'; } from '@alilc/lowcode-shared';
import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime'; import { type ICodeRuntime } from '../code-runtime';
import { IWidget, Widget } from '../widget'; import { IWidget, Widget } from '../widget';
export interface NormalizedComponentNode extends Spec.ComponentNode { export interface NormalizedComponentNode extends Spec.ComponentNode {
@ -13,26 +13,16 @@ export interface NormalizedComponentNode extends Spec.ComponentNode {
props: Spec.ComponentNodeProps; props: Spec.ComponentNodeProps;
} }
export interface InitializeModelOptions {
defaultProps?: PlainObject | undefined;
stateCreator: ModelScopeStateCreator;
dataSourceCreator?: ModelScopeDataSourceCreator;
}
/** /**
* *
*/ */
export interface IComponentTreeModel<Component, ComponentInstance = unknown> { export interface IComponentTreeModel<Component, ComponentInstance = unknown> {
readonly id: string; readonly id: string;
readonly codeScope: ICodeScope; readonly codeRuntime: ICodeRuntime;
readonly codeRuntime: ICodeRuntimeService;
readonly widgets: IWidget<Component, ComponentInstance>[]; readonly widgets: IWidget<Component, ComponentInstance>[];
initialize(options: InitializeModelOptions): void;
/** /**
* css * css
*/ */
@ -56,12 +46,18 @@ export interface IComponentTreeModel<Component, ComponentInstance = unknown> {
buildWidgets(nodes: Spec.NodeType[]): IWidget<Component, ComponentInstance>[]; buildWidgets(nodes: Spec.NodeType[]): IWidget<Component, ComponentInstance>[];
} }
export type ModelScopeStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi; export type ModelStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi;
export type ModelScopeDataSourceCreator = (...args: any[]) => Spec.InstanceDataSourceApi; export type ModelDataSourceCreator = (
dataSourceSchema: Spec.ComponentDataSource,
codeRuntime: ICodeRuntime<Spec.InstanceApi>,
) => Spec.InstanceDataSourceApi;
export interface ComponentTreeModelOptions { export interface ComponentTreeModelOptions {
id?: string; id?: string;
metadata?: PlainObject; metadata?: PlainObject;
stateCreator: ModelStateCreator;
dataSourceCreator?: ModelDataSourceCreator;
} }
export class ComponentTreeModel<Component, ComponentInstance = unknown> export class ComponentTreeModel<Component, ComponentInstance = unknown>
@ -71,16 +67,14 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
public id: string; public id: string;
public codeScope: ICodeScope;
public widgets: IWidget<Component>[] = []; public widgets: IWidget<Component>[] = [];
public metadata: PlainObject = {}; public metadata: PlainObject = {};
constructor( constructor(
public componentsTree: Spec.ComponentTree, public componentsTree: Spec.ComponentTree,
public codeRuntime: ICodeRuntimeService, public codeRuntime: ICodeRuntime<Spec.InstanceApi>,
options?: ComponentTreeModelOptions, options: ComponentTreeModelOptions,
) { ) {
invariant(componentsTree, 'componentsTree must to provide', 'ComponentTreeModel'); invariant(componentsTree, 'componentsTree must to provide', 'ComponentTreeModel');
@ -92,39 +86,28 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
if (componentsTree.children) { if (componentsTree.children) {
this.widgets = this.buildWidgets(componentsTree.children); this.widgets = this.buildWidgets(componentsTree.children);
} }
this.initialize(options);
} }
initialize({ defaultProps, stateCreator, dataSourceCreator }: InitializeModelOptions) { private initialize({ stateCreator, dataSourceCreator }: ComponentTreeModelOptions) {
const { const { state = {}, defaultProps, props = {}, dataSource, methods = {} } = this.componentsTree;
state = {}, const codeScope = this.codeRuntime.getScope();
defaultProps: defaultSchemaProps,
props = {},
dataSource,
methods = {},
} = this.componentsTree;
this.codeScope = this.codeRuntime.createChildScope({ const initalProps = this.codeRuntime.resolve(props);
props: { codeScope.setValue({ props: { ...defaultProps, ...codeScope.value.props, ...initalProps } });
...props,
...defaultSchemaProps,
...defaultProps,
},
});
const initalProps = this.codeRuntime.resolve(props, { scope: this.codeScope }); const initalState = this.codeRuntime.resolve(state);
this.codeScope.setValue({ props: { ...defaultProps, ...initalProps } });
const initalState = this.codeRuntime.resolve(state, { scope: this.codeScope });
const stateApi = stateCreator(initalState); const stateApi = stateCreator(initalState);
this.codeScope.setValue(stateApi); codeScope.setValue(stateApi);
let dataSourceApi: Spec.InstanceDataSourceApi | undefined; let dataSourceApi: Spec.InstanceDataSourceApi | undefined;
if (dataSource && dataSourceCreator) { if (dataSource && dataSourceCreator) {
const dataSourceProps = this.codeRuntime.resolve(dataSource, { scope: this.codeScope }); const dataSourceProps = this.codeRuntime.resolve(dataSource);
dataSourceApi = dataSourceCreator(dataSourceProps, stateApi); dataSourceApi = dataSourceCreator(dataSourceProps, this.codeRuntime);
} }
this.codeScope.setValue( codeScope.setValue(
Object.assign( Object.assign(
{ {
$: (ref: string) => { $: (ref: string) => {
@ -141,9 +124,9 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
); );
for (const [key, fn] of Object.entries(methods)) { 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') { 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 lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName];
const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema, { scope: this.codeScope }); const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema);
if (typeof lifeCycleFn === 'function') { if (typeof lifeCycleFn === 'function') {
lifeCycleFn.apply(this.codeScope.value, args); lifeCycleFn.apply(this.codeRuntime.getScope().value, args);
} }
} }

View File

@ -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 { ICodeRuntimeService } from '../code-runtime';
import { import {
type IComponentTreeModel, type IComponentTreeModel,
@ -7,15 +13,19 @@ import {
} from './componentTreeModel'; } from './componentTreeModel';
import { ISchemaService } from '../schema'; import { ISchemaService } from '../schema';
export interface CreateComponentTreeModelOptions extends ComponentTreeModelOptions {
codeScopeValue?: PlainObject;
}
export interface IComponentTreeModelService { export interface IComponentTreeModelService {
create<Component>( create<Component>(
componentsTree: Spec.ComponentTree, componentsTree: Spec.ComponentTree,
options?: ComponentTreeModelOptions, options?: CreateComponentTreeModelOptions,
): IComponentTreeModel<Component>; ): IComponentTreeModel<Component>;
createById<Component>( createById<Component>(
id: string, id: string,
options?: ComponentTreeModelOptions, options?: CreateComponentTreeModelOptions,
): IComponentTreeModel<Component>; ): IComponentTreeModel<Component>;
} }
@ -32,20 +42,32 @@ export class ComponentTreeModelService implements IComponentTreeModelService {
create<Component>( create<Component>(
componentsTree: Spec.ComponentTree, componentsTree: Spec.ComponentTree,
options?: ComponentTreeModelOptions, options: CreateComponentTreeModelOptions,
): IComponentTreeModel<Component> { ): IComponentTreeModel<Component> {
return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options); return new ComponentTreeModel(
componentsTree,
this.codeRuntimeService.createCodeRuntime({
initScopeValue: options?.codeScopeValue,
}),
options,
);
} }
createById<Component>( createById<Component>(
id: string, id: string,
options?: ComponentTreeModelOptions, options: CreateComponentTreeModelOptions,
): IComponentTreeModel<Component> { ): IComponentTreeModel<Component> {
const componentsTrees = this.schemaService.get('componentsTree'); const componentsTrees = this.schemaService.get('componentsTree');
const componentsTree = componentsTrees.find((item) => item.id === id); const componentsTree = componentsTrees.find((item) => item.id === id);
invariant(componentsTree, 'componentsTree not found'); invariant(componentsTree, 'componentsTree not found');
return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options); return new ComponentTreeModel(
componentsTree,
this.codeRuntimeService.createCodeRuntime({
initScopeValue: options?.codeScopeValue,
}),
options,
);
} }
} }

View File

@ -42,8 +42,6 @@ export class RuntimeIntlService implements IRuntimeIntlService {
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
@ISchemaService private schemaService: ISchemaService, @ISchemaService private schemaService: ISchemaService,
) { ) {
this.injectScope();
this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => { this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => {
const config = this.schemaService.get('config'); const config = this.schemaService.get('config');
const i18nTranslations = this.schemaService.get('i18n'); const i18nTranslations = this.schemaService.get('i18n');
@ -56,6 +54,8 @@ export class RuntimeIntlService implements IRuntimeIntlService {
this.addTranslations(key, i18nTranslations[key]); 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);
} }
} }

View File

@ -9,6 +9,7 @@ import { isPlainObject } from 'lodash-es';
import { IPackageManagementService } from './package'; import { IPackageManagementService } from './package';
import { ICodeRuntimeService } from './code-runtime'; import { ICodeRuntimeService } from './code-runtime';
import { ISchemaService } from './schema'; import { ISchemaService } from './schema';
import { ILifeCycleService, LifecyclePhase } from './lifeCycleService';
export interface IRuntimeUtilService { export interface IRuntimeUtilService {
add(utilItem: Spec.Util, force?: boolean): void; add(utilItem: Spec.Util, force?: boolean): void;
@ -27,8 +28,11 @@ export class RuntimeUtilService implements IRuntimeUtilService {
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
@IPackageManagementService private packageManagementService: IPackageManagementService, @IPackageManagementService private packageManagementService: IPackageManagementService,
@ISchemaService private schemaService: ISchemaService, @ISchemaService private schemaService: ISchemaService,
@ILifeCycleService private lifeCycleService: ILifeCycleService,
) { ) {
this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => {
this.injectScope(); this.injectScope();
});
this.schemaService.onChange('utils', (utils = []) => { this.schemaService.onChange('utils', (utils = []) => {
for (const util of utils) { for (const util of utils) {
@ -93,7 +97,7 @@ export class RuntimeUtilService implements IRuntimeUtilService {
const { content } = utilItem; const { content } = utilItem;
return { return {
key: utilItem.name, key: utilItem.name,
value: this.codeRuntimeService.run(content.value), value: this.codeRuntimeService.rootRuntime.run(content.value),
}; };
} else { } else {
return this.packageManagementService.getLibraryByComponentMap(utilItem.content); 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);
} }
} }

View File

@ -1,11 +1,10 @@
import { type Spec, uniqueId } from '@alilc/lowcode-shared'; import { type Spec, uniqueId } from '@alilc/lowcode-shared';
import { clone } from 'lodash-es';
import { IComponentTreeModel } from '../model'; import { IComponentTreeModel } from '../model';
export interface IWidget<Component, ComponentInstance = unknown> { export interface IWidget<Component, ComponentInstance = unknown> {
readonly key: string; readonly key: string;
readonly node: Spec.NodeType; readonly rawNode: Spec.NodeType;
model: IComponentTreeModel<Component, ComponentInstance>; model: IComponentTreeModel<Component, ComponentInstance>;
@ -15,9 +14,7 @@ export interface IWidget<Component, ComponentInstance = unknown> {
export class Widget<Component, ComponentInstance = unknown> export class Widget<Component, ComponentInstance = unknown>
implements IWidget<Component, ComponentInstance> implements IWidget<Component, ComponentInstance>
{ {
public __raw: Spec.NodeType; public rawNode: Spec.NodeType;
public node: Spec.NodeType;
public key: string; public key: string;
@ -27,8 +24,7 @@ export class Widget<Component, ComponentInstance = unknown>
node: Spec.NodeType, node: Spec.NodeType,
public model: IComponentTreeModel<Component, ComponentInstance>, public model: IComponentTreeModel<Component, ComponentInstance>,
) { ) {
this.node = clone(node); this.rawNode = node;
this.__raw = node;
this.key = (node as Spec.ComponentNode)?.id ?? uniqueId(); this.key = (node as Spec.ComponentNode)?.id ?? uniqueId();
} }
} }

View File

@ -2,8 +2,8 @@ import { type Spec } from '@alilc/lowcode-shared';
import { type Plugin } from './services/extension'; import { type Plugin } from './services/extension';
import { type ISchemaService } from './services/schema'; import { type ISchemaService } from './services/schema';
import { type IPackageManagementService } from './services/package'; import { type IPackageManagementService } from './services/package';
import { type CodeRuntimeInitializeOptions } from './services/code-runtime'; import { type CodeRuntimeOptions } from './services/code-runtime';
import { type ModelScopeDataSourceCreator } from './services/model'; import { type ModelDataSourceCreator } from './services/model';
export interface AppOptions { export interface AppOptions {
schema: Spec.Project; schema: Spec.Project;
@ -16,11 +16,11 @@ export interface AppOptions {
/** /**
* code runtime * code runtime
*/ */
codeRuntime?: CodeRuntimeInitializeOptions; codeRuntime?: CodeRuntimeOptions;
/** /**
* *
*/ */
dataSourceCreator?: ModelScopeDataSourceCreator; dataSourceCreator?: ModelDataSourceCreator;
} }
export type RendererApplication<Render = unknown> = { export type RendererApplication<Render = unknown> = {

View File

@ -1,5 +0,0 @@
export function evaluate(code: string, scope: any) {
return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)(
scope,
);
}

View File

@ -1,5 +1,4 @@
import { AnyFunction, PlainObject } from '../index'; import { AnyFunction, PlainObject } from '../index';
import { JSExpression } from './lowcode-spec';
/** /**
* JS this * JS this
@ -62,7 +61,7 @@ export interface DataSourceMapItem<T = any> {
* *
* @param params ComponentDataSourceItemOptions params * @param params ComponentDataSourceItemOptions params
*/ */
load(params: any): Promise<T>; load(params?: any): Promise<T>;
/** /**
* *
*/ */