mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 11:20:11 +00:00
feat: added thisRequiredInJSE API to control whether JSExpression expression access context must use this (#702)
This commit is contained in:
parent
2022308e21
commit
da7f77ee91
@ -228,6 +228,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
return this.get('requestHandlersMap') || null;
|
return this.get('requestHandlersMap') || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get thisRequiredInJSE(): any {
|
||||||
|
return engineConfig.get('thisRequiredInJSE') ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
@computed get componentsAsset(): Asset | undefined {
|
@computed get componentsAsset(): Asset | undefined {
|
||||||
return this.get('componentsAsset');
|
return this.get('componentsAsset');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,6 +133,10 @@ const VALID_ENGINE_OPTIONS = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
description: '数据源引擎的请求处理器映射',
|
description: '数据源引擎的请求处理器映射',
|
||||||
},
|
},
|
||||||
|
thisRequiredInJSE: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'JSExpression 是否只支持使用 this 来访问上下文变量',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export interface EngineOptions {
|
export interface EngineOptions {
|
||||||
/**
|
/**
|
||||||
@ -248,6 +252,12 @@ export interface EngineOptions {
|
|||||||
* 数据源引擎的请求处理器映射
|
* 数据源引擎的请求处理器映射
|
||||||
*/
|
*/
|
||||||
requestHandlersMap?: RequestHandlersMap;
|
requestHandlersMap?: RequestHandlersMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @default true
|
||||||
|
* JSExpression 是否只支持使用 this 来访问上下文变量,假如需要兼容原来的 'state.xxx',则设置为 false
|
||||||
|
*/
|
||||||
|
thisRequiredInJSE?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStrictModeValue = (engineOptions: EngineOptions, defaultValue: boolean): boolean => {
|
const getStrictModeValue = (engineOptions: EngineOptions, defaultValue: boolean): boolean => {
|
||||||
|
|||||||
@ -219,6 +219,7 @@ class Renderer extends Component<{
|
|||||||
onCompGetRef={(schema: any, ref: any) => {
|
onCompGetRef={(schema: any, ref: any) => {
|
||||||
documentInstance.mountInstance(schema.id, ref);
|
documentInstance.mountInstance(schema.id, ref);
|
||||||
}}
|
}}
|
||||||
|
thisRequiredInJSE={host.thisRequiredInJSE}
|
||||||
documentId={document.id}
|
documentId={document.id}
|
||||||
getNode={(id: string) => documentInstance.getNode(id) as any}
|
getNode={(id: string) => documentInstance.getNode(id) as any}
|
||||||
rendererName="PageRenderer"
|
rendererName="PageRenderer"
|
||||||
|
|||||||
@ -189,6 +189,7 @@ class Renderer extends Component<{
|
|||||||
setSchemaChangedSymbol={this.setSchemaChangedSymbol}
|
setSchemaChangedSymbol={this.setSchemaChangedSymbol}
|
||||||
getNode={(id: string) => documentInstance.getNode(id) as Node}
|
getNode={(id: string) => documentInstance.getNode(id) as Node}
|
||||||
rendererName="PageRenderer"
|
rendererName="PageRenderer"
|
||||||
|
thisRequiredInJSE={host.thisRequiredInJSE}
|
||||||
customCreateElement={(Component: any, props: any, children: any) => {
|
customCreateElement={(Component: any, props: any, children: any) => {
|
||||||
const { __id, ...viewProps } = props;
|
const { __id, ...viewProps } = props;
|
||||||
viewProps.componentId = __id;
|
viewProps.componentId = __id;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
getValue,
|
getValue,
|
||||||
parseData,
|
parseData,
|
||||||
parseExpression,
|
parseExpression,
|
||||||
|
parseThisRequiredExpression,
|
||||||
parseI18n,
|
parseI18n,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
isSchema,
|
isSchema,
|
||||||
@ -83,11 +84,13 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
getLocale: any;
|
getLocale: any;
|
||||||
setLocale: any;
|
setLocale: any;
|
||||||
styleElement: any;
|
styleElement: any;
|
||||||
|
parseExpression: any;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
||||||
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
|
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression;
|
||||||
this.__beforeInit(props);
|
this.__beforeInit(props);
|
||||||
this.__init(props);
|
this.__init(props);
|
||||||
this.__afterInit(props);
|
this.__afterInit(props);
|
||||||
@ -112,7 +115,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
|
|
||||||
if (func) {
|
if (func) {
|
||||||
if (isJSExpression(func) || isJSFunction(func)) {
|
if (isJSExpression(func) || isJSFunction(func)) {
|
||||||
const fn = parseExpression(func, this);
|
const fn = props.thisRequiredInJSE ? parseThisRequiredExpression(func, this) : parseExpression(func, this);
|
||||||
return fn(props, state);
|
return fn(props, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +196,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
if (fn) {
|
if (fn) {
|
||||||
// TODO, cache
|
// TODO, cache
|
||||||
if (isJSExpression(fn) || isJSFunction(fn)) {
|
if (isJSExpression(fn) || isJSFunction(fn)) {
|
||||||
fn = parseExpression(fn, this);
|
fn = this.parseExpression(fn, this);
|
||||||
}
|
}
|
||||||
if (typeof fn !== 'function') {
|
if (typeof fn !== 'function') {
|
||||||
console.error(`生命周期${method}类型不符`, fn);
|
console.error(`生命周期${method}类型不符`, fn);
|
||||||
@ -219,7 +222,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
this.__customMethodsList = customMethodsList;
|
this.__customMethodsList = customMethodsList;
|
||||||
forEach(__schema.methods, (val: any, key: string) => {
|
forEach(__schema.methods, (val: any, key: string) => {
|
||||||
if (isJSExpression(val) || isJSFunction(val)) {
|
if (isJSExpression(val) || isJSFunction(val)) {
|
||||||
val = parseExpression(val, this);
|
val = this.parseExpression(val, this);
|
||||||
}
|
}
|
||||||
if (typeof val !== 'function') {
|
if (typeof val !== 'function') {
|
||||||
console.error(`自定义函数${key}类型不符`, val);
|
console.error(`自定义函数${key}类型不符`, val);
|
||||||
@ -414,7 +417,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
|
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
|
||||||
|
|
||||||
if (isJSExpression(schema)) {
|
if (isJSExpression(schema)) {
|
||||||
return parseExpression(schema, scope);
|
return this.parseExpression(schema, scope);
|
||||||
}
|
}
|
||||||
if (isI18nData(schema)) {
|
if (isI18nData(schema)) {
|
||||||
return parseI18n(schema, scope);
|
return parseI18n(schema, scope);
|
||||||
@ -434,7 +437,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
const _children = this.getSchemaChildren(schema);
|
const _children = this.getSchemaChildren(schema);
|
||||||
// 解析占位组件
|
// 解析占位组件
|
||||||
if (schema?.componentName === 'Fragment' && _children) {
|
if (schema?.componentName === 'Fragment' && _children) {
|
||||||
const tarChildren = isJSExpression(_children) ? parseExpression(_children, scope) : _children;
|
const tarChildren = isJSExpression(_children) ? this.parseExpression(_children, scope) : _children;
|
||||||
return this.__createVirtualDom(tarChildren, scope, parentInfo);
|
return this.__createVirtualDom(tarChildren, scope, parentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,7 +499,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
let scopeKey = '';
|
let scopeKey = '';
|
||||||
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
||||||
if (Comp.generateScope) {
|
if (Comp.generateScope) {
|
||||||
const key = parseExpression(schema.props?.key, scope);
|
const key = this.parseExpression(schema.props?.key, scope);
|
||||||
if (key) {
|
if (key) {
|
||||||
// 如果组件自己设置key则使用组件自己的key
|
// 如果组件自己设置key则使用组件自己的key
|
||||||
scopeKey = key;
|
scopeKey = key;
|
||||||
@ -647,7 +650,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
|
|
||||||
_children.forEach((_child: any) => {
|
_children.forEach((_child: any) => {
|
||||||
const _childVirtualDom = this.__createVirtualDom(
|
const _childVirtualDom = this.__createVirtualDom(
|
||||||
isJSExpression(_child) ? parseExpression(_child, scope) : _child,
|
isJSExpression(_child) ? this.parseExpression(_child, scope) : _child,
|
||||||
scope,
|
scope,
|
||||||
{
|
{
|
||||||
schema,
|
schema,
|
||||||
@ -754,7 +757,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
return checkProps(props);
|
return checkProps(props);
|
||||||
}
|
}
|
||||||
if (isJSExpression(props)) {
|
if (isJSExpression(props)) {
|
||||||
props = parseExpression(props, scope);
|
props = this.parseExpression(props, scope);
|
||||||
// 只有当变量解析出来为模型结构的时候才会继续解析
|
// 只有当变量解析出来为模型结构的时候才会继续解析
|
||||||
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
|
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,7 @@ export default function rendererFactory(): IRenderComponent {
|
|||||||
schema: {} as RootSchema,
|
schema: {} as RootSchema,
|
||||||
onCompGetRef: () => { },
|
onCompGetRef: () => { },
|
||||||
onCompGetCtx: () => { },
|
onCompGetCtx: () => { },
|
||||||
|
thisRequiredInJSE: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
static findDOMNode = findDOMNode;
|
static findDOMNode = findDOMNode;
|
||||||
|
|||||||
@ -128,6 +128,11 @@ export interface IRendererProps {
|
|||||||
faultComponent?: IGeneralComponent;
|
faultComponent?: IGeneralComponent;
|
||||||
/** 设备信息 */
|
/** 设备信息 */
|
||||||
device?: string;
|
device?: string;
|
||||||
|
/**
|
||||||
|
* @default true
|
||||||
|
* JSExpression 是否只支持使用 this 来访问上下文变量
|
||||||
|
*/
|
||||||
|
thisRequiredInJSE?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRendererState {
|
export interface IRendererState {
|
||||||
@ -148,12 +153,13 @@ export interface IBaseRendererProps {
|
|||||||
__host?: BuiltinSimulatorHost;
|
__host?: BuiltinSimulatorHost;
|
||||||
__container?: any;
|
__container?: any;
|
||||||
config?: Record<string, any>;
|
config?: Record<string, any>;
|
||||||
designMode?: 'live' | 'design';
|
designMode?: 'design';
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
getSchemaChangedSymbol?: () => boolean;
|
getSchemaChangedSymbol?: () => boolean;
|
||||||
setSchemaChangedSymbol?: (symbol: boolean) => void;
|
setSchemaChangedSymbol?: (symbol: boolean) => void;
|
||||||
|
thisRequiredInJSE?: boolean;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
getNode?: any;
|
getNode?: any;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -242,7 +242,7 @@ export function transformStringToFunction(str: string) {
|
|||||||
* @param self scope object
|
* @param self scope object
|
||||||
* @returns funtion
|
* @returns funtion
|
||||||
*/
|
*/
|
||||||
export function parseExpression(str: any, self: any) {
|
export function parseExpression(str: any, self: any, thisRequired = false) {
|
||||||
try {
|
try {
|
||||||
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
|
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
|
||||||
contextArr.push('return ');
|
contextArr.push('return ');
|
||||||
@ -259,14 +259,18 @@ export function parseExpression(str: any, self: any) {
|
|||||||
if (inSameDomain() && (window.parent as any).__newFunc) {
|
if (inSameDomain() && (window.parent as any).__newFunc) {
|
||||||
return (window.parent as any).__newFunc(tarStr)(self);
|
return (window.parent as any).__newFunc(tarStr)(self);
|
||||||
}
|
}
|
||||||
const code = `with($scope || {}) { ${tarStr} }`;
|
const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`;
|
||||||
return new Function('$scope', code)(self);
|
return new Function('$scope', code)(self);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('parseExpression.error', err, str, self);
|
logger.error('parseExpression.error', err, str, self?.__self ?? self);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseThisRequiredExpression(str: any, self: any) {
|
||||||
|
return parseExpression(str, self, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* capitalize first letter
|
* capitalize first letter
|
||||||
* @param word string to be proccessed
|
* @param word string to be proccessed
|
||||||
|
|||||||
@ -994,6 +994,24 @@ exports[`JSExpression JSExpression props with loop 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`JSExpression JSExpression props with loop, and thisRequiredInJSE is true 1`] = `
|
||||||
|
<div
|
||||||
|
className="lce-page"
|
||||||
|
style={Object {}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="div-ut"
|
||||||
|
forwardRef={[Function]}
|
||||||
|
name1="1"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="div-ut"
|
||||||
|
forwardRef={[Function]}
|
||||||
|
name1="2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`JSExpression JSFunction props 1`] = `
|
exports[`JSExpression JSFunction props 1`] = `
|
||||||
<div
|
<div
|
||||||
className="lce-page"
|
className="lce-page"
|
||||||
|
|||||||
@ -145,7 +145,9 @@ describe('JSExpression', () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
getComp(schema, components.Div).then(({ component, inst }) => {
|
getComp(schema, components.Div, {
|
||||||
|
thisRequiredInJSE: false,
|
||||||
|
}).then(({ component, inst }) => {
|
||||||
// expect(inst[0].props.visible).toBeTruthy();
|
// expect(inst[0].props.visible).toBeTruthy();
|
||||||
expect(inst.length).toEqual(2);
|
expect(inst.length).toEqual(2);
|
||||||
[1, 2].forEach((i) => {
|
[1, 2].forEach((i) => {
|
||||||
@ -157,6 +159,50 @@ describe('JSExpression', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('JSExpression props with loop, and thisRequiredInJSE is true', (done) => {
|
||||||
|
const schema = {
|
||||||
|
componentName: 'Page',
|
||||||
|
props: {},
|
||||||
|
state: {
|
||||||
|
isShowDialog: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: "Div",
|
||||||
|
loop: [
|
||||||
|
{
|
||||||
|
name: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
className: "div-ut",
|
||||||
|
name1: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'this.item.name',
|
||||||
|
},
|
||||||
|
name2: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'item.name',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
getComp(schema, components.Div).then(({ component, inst }) => {
|
||||||
|
expect(inst.length).toEqual(2);
|
||||||
|
[0, 1].forEach((i) => {
|
||||||
|
expect(inst[i].props[`name1`]).toBe(i + 1 + '');
|
||||||
|
expect(inst[i].props[`name2`]).toBe(undefined);
|
||||||
|
})
|
||||||
|
componentSnapshot = component;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// it('JSFunction props with loop', (done) => {
|
// it('JSFunction props with loop', (done) => {
|
||||||
// const schema = {
|
// const schema = {
|
||||||
// componentName: 'Page',
|
// componentName: 'Page',
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import {
|
import {
|
||||||
isSchema,
|
isSchema,
|
||||||
isFileSchema,
|
isFileSchema,
|
||||||
inSameDomain,
|
inSameDomain,
|
||||||
getFileCssName,
|
getFileCssName,
|
||||||
isJSSlot,
|
isJSSlot,
|
||||||
getValue,
|
getValue,
|
||||||
getI18n,
|
getI18n,
|
||||||
transformArrayToMap,
|
transformArrayToMap,
|
||||||
@ -15,9 +15,11 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
serializeParams,
|
serializeParams,
|
||||||
parseExpression,
|
parseExpression,
|
||||||
|
parseThisRequiredExpression,
|
||||||
parseI18n,
|
parseI18n,
|
||||||
parseData,
|
parseData,
|
||||||
} from '../../src/utils/common';
|
} from '../../src/utils/common';
|
||||||
|
import logger from '../../src/utils/logger';
|
||||||
|
|
||||||
describe('test isSchema', () => {
|
describe('test isSchema', () => {
|
||||||
it('should be false when empty value is passed', () => {
|
it('should be false when empty value is passed', () => {
|
||||||
@ -335,19 +337,55 @@ describe('test parseExpression ', () => {
|
|||||||
const result = parseExpression(mockExpression, { scopeValue: 1 });
|
const result = parseExpression(mockExpression, { scopeValue: 1 });
|
||||||
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
|
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('[success] JSExpression handle without this use scopeValue', () => {
|
||||||
|
const mockExpression = {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "state"
|
||||||
|
};
|
||||||
|
const result = parseExpression(mockExpression, { state: 1 });
|
||||||
|
expect(result).toBe((1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[success] JSExpression handle without this use scopeValue', () => {
|
||||||
|
const mockExpression = {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.state"
|
||||||
|
};
|
||||||
|
const result = parseExpression(mockExpression, { state: 1 });
|
||||||
|
expect(result).toBe((1));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('test parseExpression ', () => {
|
describe('test parseThisRequiredExpression', () => {
|
||||||
it('can handle JSExpression', () => {
|
it('can handle JSExpression', () => {
|
||||||
const mockExpression = {
|
const mockExpression = {
|
||||||
"type": "JSExpression",
|
"type": "JSExpression",
|
||||||
"value": "function (params) { return this.scopeValue + params.param1 + 5;}"
|
"value": "function (params) { return this.scopeValue + params.param1 + 5;}"
|
||||||
};
|
};
|
||||||
const result = parseExpression(mockExpression, { scopeValue: 1 });
|
const result = parseThisRequiredExpression(mockExpression, { scopeValue: 1 });
|
||||||
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
|
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
it('[error] JSExpression handle without this use scopeValue', () => {
|
||||||
|
const mockExpression = {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "state.text"
|
||||||
|
};
|
||||||
|
const fn = logger.error = jest.fn();
|
||||||
|
parseThisRequiredExpression(mockExpression, { state: { text: 'text' } });
|
||||||
|
expect(fn).toBeCalledWith('parseExpression.error', new ReferenceError('state is not defined'), {"type": "JSExpression", "value": "state.text"}, {"state": {"text": "text"}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[success] JSExpression handle without this use scopeValue', () => {
|
||||||
|
const mockExpression = {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.state"
|
||||||
|
};
|
||||||
|
const result = parseThisRequiredExpression(mockExpression, { state: 1 });
|
||||||
|
expect(result).toBe((1));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
describe('test parseI18n ', () => {
|
describe('test parseI18n ', () => {
|
||||||
it('can handle normal parseI18n', () => {
|
it('can handle normal parseI18n', () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user