From 3b9b177b052169cd0c1078cf8b488f04cb374dac Mon Sep 17 00:00:00 2001 From: Clarence-pan Date: Mon, 11 Apr 2022 12:23:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E8=A7=A3=E5=86=B3=20reac?= =?UTF-8?q?t=20=E4=B8=AD=20jsx=20=E5=87=BA=E7=A0=81=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=80=99=E5=AF=B9=E4=BA=8E=E5=BE=AA=E7=8E=AF=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BC=8F=E5=8C=85=20=5F=5F$evalArray=20=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/code-generator/package.json | 1 + .../component/react/containerInjectUtils.ts | 17 +++++ .../src/plugins/component/react/jsx.ts | 5 +- modules/code-generator/src/types/core.ts | 1 + modules/code-generator/src/types/jsx.ts | 13 +++- .../code-generator/src/utils/jsExpression.ts | 3 +- modules/code-generator/src/utils/nodeToJSX.ts | 21 +++++- .../demo-project/src/pages/Test/index.jsx | 2 +- .../demo-project/src/pages/Aaaa/index.jsx | 4 ++ .../demo-project/src/pages/Test/index.jsx | 4 ++ .../demo-project/src/pages/Test/index.jsx | 2 +- .../demo-project/src/pages/Example/index.jsx | 4 ++ .../src/components/Index/index.jsx | 25 ++++--- .../demo-project/src/pages/Test/index.jsx | 6 +- .../demo-project/src/pages/Test/index.jsx | 27 +++---- .../tolerate-eval-errors-1-loop.schema.json | 58 +++++++++++++++ .../tolerate-eval-errors-1-loop.test.ts | 52 ++++++++++++++ ...rate-eval-errors-2-nested-loop.schema.json | 70 +++++++++++++++++++ ...tolerate-eval-errors-2-nested-loop.test.ts | 56 +++++++++++++++ .../p0-condition-at-root.test.ts.snap | 2 +- 20 files changed, 337 insertions(+), 36 deletions(-) create mode 100644 modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.schema.json create mode 100644 modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.test.ts create mode 100644 modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.schema.json create mode 100644 modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.test.ts diff --git a/modules/code-generator/package.json b/modules/code-generator/package.json index 077d7aada..04c463086 100644 --- a/modules/code-generator/package.json +++ b/modules/code-generator/package.json @@ -75,6 +75,7 @@ "change-case": "^3.1.0", "commander": "^6.1.0", "debug": "^4.3.2", + "fp-ts": "^2.11.9", "fs-extra": "9.x", "glob": "^7.2.0", "html-entities": "^2.3.2", diff --git a/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts b/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts index 09da40220..6c614f143 100644 --- a/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts +++ b/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts @@ -83,6 +83,23 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); + } else { + // useRef 为 false 的时候是指没有组件在 props 中配置 ref 属性,但这个时候其实也可能有代码访问 this.$/$$ 所以还是加上个空的代码 + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + content: ` $ = () => null; `, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + content: ` $$ = () => []; `, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + }); } return next; diff --git a/modules/code-generator/src/plugins/component/react/jsx.ts b/modules/code-generator/src/plugins/component/react/jsx.ts index 8d3dc3160..445dce7a5 100644 --- a/modules/code-generator/src/plugins/component/react/jsx.ts +++ b/modules/code-generator/src/plugins/component/react/jsx.ts @@ -47,9 +47,9 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => // 这里会将内部的一些子上下文的访问(this.xxx)转换为 __$$context.xxx 的形式 // 与 Rax 所不同的是,这里不会将最顶层的 this 转换掉 const customHandlers: HandlerSet = { - expression(input: JSExpression, scope: IScope) { + expression(input: JSExpression, scope: IScope, config) { return transformJsExpr(generateExpression(input, scope), scope, { - dontWrapEval: !tolerateEvalErrors, + dontWrapEval: !(config?.tolerateEvalErrors ?? tolerateEvalErrors), dontTransformThis2ContextAtRootScope: true, }); }, @@ -71,6 +71,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => const generatorPlugins: NodeGeneratorConfig = { handlers: customHandlers, tagMapping: (v) => nodeTypeMapping[v] || v, + tolerateEvalErrors, }; if (next.contextData.useRefApi) { diff --git a/modules/code-generator/src/types/core.ts b/modules/code-generator/src/types/core.ts index b68f21ecc..99ca8c97a 100644 --- a/modules/code-generator/src/types/core.ts +++ b/modules/code-generator/src/types/core.ts @@ -217,6 +217,7 @@ export interface HandlerSet { export interface CompositeValueGeneratorOptions { handlers?: HandlerSet; nodeGenerator?: NodeGenerator; + tolerateEvalErrors?: boolean; } /** diff --git a/modules/code-generator/src/types/jsx.ts b/modules/code-generator/src/types/jsx.ts index 4b6b25189..c79f1d207 100644 --- a/modules/code-generator/src/types/jsx.ts +++ b/modules/code-generator/src/types/jsx.ts @@ -15,7 +15,10 @@ export interface CodePiece { type: PIECE_TYPE; } -export interface AttrData { attrName: string; attrValue: CompositeValue } +export interface AttrData { + attrName: string; + attrValue: CompositeValue; +} // 对 JSX 出码的理解,目前定制点包含 【包装】【标签名】【属性】 export type AttrPlugin = BaseGenerator; export type NodePlugin = BaseGenerator; @@ -26,4 +29,12 @@ export interface NodeGeneratorConfig { attrPlugins?: AttrPlugin[]; nodePlugins?: NodePlugin[]; self?: NodeGenerator; + + /** + * 是否要容忍对 JSExpression 求值时的异常 + * 默认:true + * 注: 如果容忍异常,则会在求值时包裹 try-catch 块 -- 通过 __$$eval / __$$evalArray + * catch 到异常时默认会抛出一个 CustomEvent 事件里面包含异常信息和求值的表达式 + */ + tolerateEvalErrors?: boolean; } diff --git a/modules/code-generator/src/utils/jsExpression.ts b/modules/code-generator/src/utils/jsExpression.ts index 73a8ab649..d9fb67892 100644 --- a/modules/code-generator/src/utils/jsExpression.ts +++ b/modules/code-generator/src/utils/jsExpression.ts @@ -79,10 +79,11 @@ export function isJsCode(value: unknown): boolean { export function generateExpression(value: any, scope: IScope): string { if (isJSExpression(value)) { - const exprVal = (value as JSExpression).value; + const exprVal = (value as JSExpression).value.trim(); if (!exprVal) { return 'null'; } + const afterProcessWithLocals = transformExpressionLocalRef(exprVal, scope); return afterProcessWithLocals; } diff --git a/modules/code-generator/src/utils/nodeToJSX.ts b/modules/code-generator/src/utils/nodeToJSX.ts index 84b914933..0b5919902 100644 --- a/modules/code-generator/src/utils/nodeToJSX.ts +++ b/modules/code-generator/src/utils/nodeToJSX.ts @@ -1,4 +1,5 @@ import _ from 'lodash'; +import { pipe } from 'fp-ts/function'; import { NodeSchema, isNodeSchema, NodeDataType, CompositeValue } from '@alilc/lowcode-types'; import { @@ -17,6 +18,7 @@ import { getStaticExprValue } from './common'; import { executeFunctionStack } from './aopHelper'; import { encodeJsxStringNode } from './encodeJsxAttrString'; import { unwrapJsExprQuoteInJsx } from './jsxHelpers'; +import { transformThis2Context } from '../core/jsx/handlers/transformThis2Context'; function mergeNodeGeneratorConfig( cfg1: NodeGeneratorConfig, @@ -255,6 +257,8 @@ export function generateReactLoopCtrl( next?: NodePlugin, ): CodePiece[] { if (nodeItem.loop) { + const tolerateEvalErrors = config?.tolerateEvalErrors ?? true; + const loopItemName = nodeItem.loopArgs?.[0] || 'item'; const loopIndexName = nodeItem.loopArgs?.[1] || 'index'; @@ -262,9 +266,20 @@ export function generateReactLoopCtrl( const subScope = scope.createSubScope([loopItemName, loopIndexName]); const pieces: CodePiece[] = next ? next(nodeItem, subScope, config) : []; - const loopDataExpr = generateCompositeType(nodeItem.loop, scope, { - handlers: config?.handlers, - }); + // 生成循环变量表达式 + const loopDataExpr = pipe( + nodeItem.loop, + // 将 JSExpression 转换为 JS 表达式代码: + (expr) => + generateCompositeType(expr, scope, { + handlers: config?.handlers, + tolerateEvalErrors: false, // 这个内部不需要包 try catch, 下面会统一加的 + }), + // 将 this.xxx 转换为 __$$context.xxx: + (expr) => transformThis2Context(expr, scope, { ignoreRootScope: true }), + // 如果要容忍错误,则包一层 try catch (基于助手函数 __$$evalArray) + (expr) => (tolerateEvalErrors ? `__$$evalArray(() => (${expr}))` : expr), + ); pieces.unshift({ value: `(${loopDataExpr}).map((${loopItemName}, ${loopIndexName}) => ((__$$context) => (`, diff --git a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx index 581f3c883..2f53ec455 100644 --- a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx @@ -152,7 +152,7 @@ class Test$$Page extends React.Component {
- {["a", "b", "c"].map((item, index) => + {__$$evalArray(() => ["a", "b", "c"]).map((item, index) => ((__$$context) => !!__$$eval(() => index >= 1) && (