feat(react): 未求值的 ${…} 到达 DOM 时给出开发期响亮诊断 (#4795 方向 3) - #5129
Merged
Conversation
…DOM (#4795) A value only reaches the user if SchemaRenderer EVALUATES it and the renderer READS IT BACK, and those two sets do not fully overlap. Where they miss, the failure was silent: measured on this baseline with dataSource { n: 99 }, `{ type: 'ui:statistic', value: '$' + '{data.n}' }` puts the raw source text on screen, because the evaluation memo covers `content`, the `properties` / `props` bags and the predicate keys and passes every other top-level key through untouched. An author -- increasingly, an AI authoring metadata -- got no signal at all. SchemaRenderer now reports such a value once per node via console.error, naming the node type and id, the key the raw source survived on (spelled the way it was authored), the expression source verbatim, and the channels that work today. It also catches the harder shape: an expression that WAS evaluated but THREW, which ExpressionEvaluator returns as its own source text. Diagnostic only -- no evaluation behaviour changes, no DOM attribute is added, and the module sits behind the module-load NODE_ENV constant a bundler folds away, so production pays nothing. Two boundaries are deliberate. The scan is exactly as deep as evaluation is -- shallow -- because a nested value keeps its raw source today by decision (objectui#4799 pinned that on both bags). And schema METADATA is never reported: the predicate keys hold raw source by design, and the scan reads the set of values that actually leaves for the DOM, after the metadata destructure has stripped them. Part of objectui#4795 (Direction 3, per the maintainer's 2026-08-17 ruling). Direction 1 stays deferred behind its named restart condition; this does not pre-empt it. Direction 2 is permanently rejected and untouched. Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片;Part of #4795 —— 方向 3 render-time 半边) 实物核验:merge-base 验收要点:
三件套照常:本评论 → undraft → auto-merge(SQUASH)。 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4795 —— 维护者裁定(评论 5312061331)的方向 3,render-time 半边。
做了什么
在
SchemaRenderer渲染路径上加一条仅开发构建的响亮诊断:当一个未求值的${…}即将被原样交给 renderer / 展开进 DOM 时,console.error点名 —— 节点type与id、残留所在的键(按作者书写的通道拼写)、表达式原文,以及当下真正走得通的通道。NODE_ENV常量之后,打包器直接折叠掉这个分支。前提复测(基线
0046d8f8c,含 PR #5122)卡面表格在新基线上逐行实测(真 renderer,
dataSource: { total: 99, n: 99 }):{ type: 'ui:statistic', value: '${data.n}' }${data.n}上屏{ type: 'ui:card', title: '${data.total}' }title="${data.total}"{ type: 'ui:button', label: '${data.total}' }{ type: 'ui:statistic', props: { value: '${data.n}' } }schema.value){ type: 'ui:statistic', properties: { value: '${data.n}' } }99{ type: 'ui:text', content: 'Total: ${data.total}' }Total: 99成因判断复核通过:evaluation memo 处理
content、properties.*、props.*与谓词键,title/label/value/description原样透传(SchemaRenderer.tsx求值 memo)。卡面写的是「除
content外没有任何文本键既被求值又被读回」。在含 PR #5122 的新基线上这句已不再成立:写成properties信封时,value/title/label三个键都既被求值又被读回(实测均为99)—— 因为 #5122 让properties.*参与求值,而既有的 hoist 又把求值后的值抄到节点顶层,正好是StatisticRenderer/CardTitle/Button读的位置。这条是裁定(2026-08-17)之后才由 #5122 造成的新事实,因此诊断文案里我没有写它:裁定原文把可用通道列为「
content, or host pre-resolution」,而「properties信封是不是ui:*的正式创作面」是契约/教学面判断(#4786 刚把props信封这个 workaround 从教学面撤掉),属维护者的裁量,不该由我在一条诊断消息里替仓库定下来。请裁:文案是否加上properties。不覆盖的那一类(如实记录)
props信封那一行(求值了但读不回 → 渲染成空白)诊断不响,而且按定义响不了:那里根本没有未求值的${…}到达 DOM,值已经变成99了,只是落错了地方。裁定的方向 3 只认「未求值的${…}到达 DOM」,所以这一类不在本 PR 的射程内 —— 它是卡面的另一半,随方向 1 / 契约裁量走。设计取舍
挂点:放在 metadata 解构之后,扫
componentProps。那正是离开本组件去 DOM 的那组值(下面被展开成 React props,renderer 也从schema.加同名键读到同一批)。解构剥掉的全是 schema 元数据 ——visible/visibleWhen/hidden/disabled/ … 按设计就装着原始谓词源码(它们被当条件求值,从不被放置)。所以剥离清单天然就是诊断的排除清单,不需要第二份会漂移的副本。这条不是注释里的说法,有 7 条钉盯着(见反向验证 (c))。深度:与求值完全同深 —— 只扫一层。嵌套的
aria: { label: '${…}' }今天保留原文是既定决定(#4799 在两个信封上都钉住了这个浅层性),再深一层就会把仓库尚未决定要改的形态报成缺陷,诊断就比它所代表的契约更吵。求值哪天变深,这个扫描跟着变深,不提前。表达式的定义:复用求值器自己的
\$\{([^}]+)\}—— 少一个字符的${}与未闭合的${对求值器都不是表达式(实测:原样返回),对诊断也就不是。这是把散文和代码示例挡在诊断之外的关键。开销:每节点每渲染一次浅遍历 +
includes('${'),仅 dev;命中后按 schema 对象WeakSet去重(与同文件validateSchemaOnce同一套路),重渲染不刷屏。测试
packages/react/src/__tests__/SchemaRenderer.unevaluatedExpressionDiagnostic.test.tsx,25 钉:value/title/label/description字面量 ⇒ 响,且值照旧字面量渲染(证明只读);消息点名 type / id / 键 / 表达式原文。content、props、properties正常求值 ⇒ 静默;静态值 ⇒ 静默;表达式求值成空(数据缺失)⇒ 静默(缺数据不是本缺陷)。NODE_ENV=production⇒ 零输出,缺陷本身照旧存在。for解析到页面表单的控件 #5092 的教训,只钉「spy 被调用过」的诊断在被 no-op 化那天就会变绿。${1 +}经ExpressionEvaluator原样返回)。假阳性普查:把诊断临时改成落盘记录(绕开测试里的 console mock),跑遍消费半径 ——
react+components+fields+plugin-dashboard+plugin-grid+plugin-list,488 文件 / 5682 测试全绿,共 9 次触发,全部来自我自己的测试与探针文件,既有测试零假阳性。仓根
type-check81/81;eslint 改动文件 0 errors(23 条no-explicit-anywarning,与周边同款);check-control-bytesOK,另做了控制字节自扫。反向验证(先书面预判,再跑;四条全部与预判一致,无反转)
4 failed | 21 passed,正是那 4 条6 failed | 19 passed,正是那 6 条7 failed | 18 passed,正是那 7 条[^}]*${}那条)红1 failed | 24 passed留后续
publish-time 半边不做:侦查后自然挂点落在 CLI 校验器文件,#5115 席正在飞,按不撞文件纪律留给后续,在此如实记录。
方向 1 的重启条件在裁定评论 5312061331 第 2 条(第一个真实的 schema 编排仪表盘需求撞上这个缺口),本 PR 未预告、未实现。是否关卡留给 PM 验收时定 —— 卡面还载着方向 1 的重启条件。
Generated by Claude Code