返回洞察
工程实践#engineering#AI生成代码可视化编辑#AST#前端架构#编译器

如何让 AI 生成的前端代码支持可视化编辑

探讨将 AI 生成的 React 代码与可视化画布编辑相结合的工程演进。我们分析了源码灵活性与画布交互之间的落差,探讨通过 AST 解析、TypeScript 符号提取、Value Provenance、编译期插桩、实例覆盖映射,配合 Identity Reconciliation 与 CAS 并发提交实现受控状态更新。

发布 2026年10月28日Reading time: 14 分钟Foundax
如何让 AI 生成的前端代码支持可视化编辑

AI 可以快速生成前端代码,交付一份完整的网页设计。但在交付源码之后,如果修改往往仍要回到代码编辑器,或者继续向模型描述需求并等待它生成下一版,对于只想改一句文案、一张图片或某处间距的商家而言,操作成本依然偏高。

可视化编辑为这类修改提供了更直接的入口:在页面上选择元素,调整文字与样式,再将修改写回项目。然而,经营一个电商网站还需要维护商品、价格和库存。如果这些业务信息在生成时被硬编码为静态页面内容,一次后台调价不仅需要同步修改页面并重新发布,多个页面之间也容易出现数据不一致。

Foundax 需要同时处理源码可视化编辑与动态数据绑定(Data Binding)。商家可以直接在画布上修改 AI 生成的页面;同时商品卡片保留对底层业务数据的引用,在业务数据按读取与缓存策略刷新后,展示当前价格与库存。此外,Agent 还会继续在后台调整代码布局, 所以源码编辑、画布修改与业务数据更新必须能够持续协作。

这让问题从“怎样生成一个页面”,延伸到“怎样长期维护生成的前端”。我们曾经尝试要求模型在源码中遵循额外的编辑协议,后来逐步转向由编译器与运行时分析普通 React 代码,建立元素、源码和数据之间的映射。

个性化网站把我们推向了源码创作

在组件集合和可编辑字段明确的系统中,用结构化数据描述页面有明显优势。一个标题、一张图片、一组卡片,各自对应固定字段,编辑器知道应该改什么,渲染器也知道应该怎样显示。

但品牌网站的需求会不断走出既有配置。商家希望调整叙事顺序、重组商品呈现、改变布局或设计一段交互。如果每遇到一种新组合,都去增加一种底层结构和属性,会让系统的表达能力与维护成本一起增长。

普通的 React、TypeScript 和 CSS 提供了另一种空间。模型可以拆组件、传参数、用数组生成卡片、根据条件显示内容,像处理一个普通代码项目那样创作页面。对于已经擅长编码的模型而言,这些是其最熟悉的表达方式。

然而,切换到源码形态之后,原来由固定字段提供的编辑映射关系就需要重新建立。浏览器渲染出了一个标题,并不直接说明编辑器应该去哪里修改这个标题的代码。

我们曾经让模型承担太多编辑协议

一个看起来直接的方案,是要求模型在生成代码时,一并写好编辑器需要的信息。例如,为节点维护稳定标记,按指定目录拆分区块,限制可以出现在可编辑位置的表达式,并为业务绑定提供额外说明。

这些约束单独看都有各自的理由:固定区块方便调度与重试;稳定标记帮助找到节点;限制表达式可以让修改位置更容易判断。它们都在试图减少平台的不确定性。

问题出现在约束叠加之后。模型写一段页面时,既要处理设计与业务逻辑,还要记住文件角色、节点身份、列表约定和特定编辑要求。外表仍是 React,但实际接受的写法已经带上了一套额外规则。

一个常见的数组遍历,可能可以正常渲染,却因为缺少附加声明而超出当时的可编辑范围;一个合法的组件参数传递,也可能无法直接被编辑器识别修改。如果将这些情况当成代码失败,模型就要为了满足编辑器的规则去重新改写原本正常的源码。

这条路线的维护成本并不只在提示词。生成协议、校验器、修复流程和画布写回需要始终保持一致。增加一个特殊约定,往往意味着多处实现一起维护。模型还要在修复循环中判断,问题究竟来自 React 语法、业务接口,还是平台自己的要求。

我们后来的演进方向,是把节点标记、编辑索引和可追踪关系更多地交给编译与运行时处理。模型仍然需要使用平台提供的 SDK、了解业务接口并遵守基础工程约定,但不必再去手写编辑器内部的身份协议。

通过编译分析建立编辑索引

这次变化让模型的创作方式回归自然,也把更难的映射工作留给了平台。

编译器通过抽象语法树(AST)与 TypeScript 符号解析,静态分析模块、组件、字段表达式和引用关系,生成全局的编辑索引(Edit Index)。页面预览阶段再通过编译插桩(Compiler Instrumentation)补充元素选择和实例观测信息, 从而使一次画布操作可以被解析为明确的源码修改。

源码、页面引用与实例覆盖共同组成版本化的项目状态。编辑索引和预览产物由指定项目版本与工具链派生,可以随时重建,而不必单独维护一份可独立修改的页面树。

这里有三层信息,需要协同工作:

信息记录什么能回答什么
源码与字段索引组件定义、调用位置、字段表达式和可追踪来源这个值经过哪些代码产生,有哪些可能的修改入口
运行时实例信息当前渲染的调用路径、集合成员和实际来源用户选中的是哪个组件实例或集合成员
版本化项目状态源码文件、页面引用,以及明确范围的局部覆盖修改怎样保存,怎样与下一次代码变更协调

模型只负责提交普通源码,编译器负责派生这些编辑信息。画布提交的是选中了什么、想改什么、希望影响谁,服务端再根据当前版本解析真实的修改目标。这样,编辑能力可以随着平台的静态分析能力扩展,而不必要求模型提前声明完整的编辑协议。

从生成到修改的链路如下:

静态索引、运行时实例和编辑命令共同解析源码、实例覆盖或业务写入目标

这里需要保留一个严格的边界:代码能够编译运行,与某个显示值能够直接被可视化改写,是两件事。平台可以先保留合法源码的执行与展示结果,再基于分析判断某一项修改能力是否可用。对于无法可靠追踪的动态表达式,需要引导用户回到明确的代码或配置入口处理。

平台因此承担了解析、来源追踪和实例状态的成本,换来的是更符合工程直觉的代码创作方式,以及集中维护编辑规则的机会。

从 DOM 选择追踪到源码写入位置

最简单的情况是修改直接文案:

<h1>为小空间设计的日常用品</h1>

用户修改标题,系统可以轻易定位这段源码中的文字。但在实际页面中,更常见的是组件复用:

function Note({ title }: { title: string }) {
  return <h2>{title}</h2>;
}

function Page() {
  return (
    <>
      <Note title="为小空间而设计" />
      <Note title="为小空间而设计" />
    </>
  );
}

假设用户只想修改画布上第二处标题。这两处内容相同,且组件内部均指向同一个 h2 定义。按文字搜索全部替换会改到第一处;而直接潜入组件,把内部的 title 换成固定文案,则会影响所有的调用方。

编辑器需要进一步追踪:这次选中的是哪个调用产生的显示,title 从哪个外层参数传入,用户希望改局部实例还是组件定义。在这个例子里,合理的修改目标应当落到第二个调用点上的参数。

这条追踪链条可以写得更为具体:

第二处显示的标题 → 映射到 Note 中的 title 参数 → 追溯到第二个组件调用 → 定位到该调用上的字符串。

这属于值来源追踪(Value Provenance):根据变量的实际声明、引用和参数传递拓扑关系定位来源,而不能仅凭变量名匹配。如果 title 被重命名为另一个局部变量或经过外层组件传入,只要传递关系确认,就应沿着关系继续寻找。不同作用域中恰好同名的变量,属于不同来源。

Source Map 虽能帮助定位原始代码,但其主要不足在于缺乏字段归属与实例语义。编辑索引必须记录字段的来源与候选修改入口,否则定位到 h2 之后,依旧无法判断是应该修改外层参数,还是修改组件定义。

静态来源分析与运行时插桩协作

静态源码分析能够确认 Note 的标题来自参数。但同一个定义同时产生了页面上的两处标题,用户到底点击了哪一处,需要当前预览环境提供信息。因此,编译器会为预览补充运行时观测(Runtime Observation),把 DOM 实例与源码节点、组件调用路径连接起来。

列表渲染会进一步放大这个区别。一次 items.map(...) 可能产生十张卡片,它们对应同一个 JSX 调用位置。这时,“组件定义”、“调用位置”、“集合成员”与“当前 DOM 元素”是四种不同的身份。仅修改调用位置,可能影响十张卡片; 仅记录 DOM 树的第三项顺序,排序之后又可能指向另一个不同的成员。

实例地址需要结合经过验证的组件调用路径与成员身份。React 的 key 有助于内部列表协调,但不能单独决定持久的修改目标;同一个商品也可能同时出现在首页推荐和底部区域,其商品 ID 相同,但展示实例却完全独立。

值的来源还会在运行时发生分支选择。例如:

function Heading({ product, fallbackTitle }: {
  product: { title?: string | null };
  fallbackTitle: string;
}) {
  const title = product.title ?? fallbackTitle;
  return <h2>{title}</h2>;
}

单看静态的 h2,无法确定当前显示是来自商品标题还是备用文案。静态分析可以保留所有的候选来源,运行时再补充实际的读取与取值路径。如果来自商品字段,修改应遵循业务数据的归属通道;如果来自备用文案,还要继续在源码中寻找唯一的可写位置。

这类观测还有一个重要约束:绝不能为了追踪来源,而在运行时重新执行一次原表达式。属性读取可能挂载了 getter,函数也可能带有副作用。重复求值会导致页面状态异常。我们的运行时追踪会在受支持的路径上,记录已经求出的值及其对象、字段映射关系, 使用对象身份和属性描述信息核对来源,避免仅凭相同的显示文字建立关联。

运行时观测用于缩小并锁定候选范围,服务端仍需与当前版本的编辑索引核对。浏览器传来的节点位置不能直接当作任意文件的修改地址,当对应关系无法确认时,应进入明确的代码编辑入口。

将页面展示状态与业务数据解耦

Foundax 的页面承载着真实商品,这让“修改显示内容”有了更多不同的含义。用户在画布上点击一个价格,可能只是想改颜色,也可能想换商品,或者真的需要调整售价。

用户意图修改应该落在哪里
把这张卡片的价格改成紫色当前展示范围的 CSS 样式
把杯子换成托盘页面引用的商品 ID 或查询参数
把售价调整为 20 元底层商品业务服务
把价格改成不显示小数价格格式或展示层代码

如果把页面上的价格文本直接替换成“20 元”,画面变了,商品底层记录却可能未变,结算依旧会使用原价。因此,编辑器需要理解值的业务归属,并提供对应的操作路径。

正确的做法是,页面工程持久化商品引用、查询条件和展示规则,运行时通过业务读取服务取得商品数据。例如,商家在后台将售价从 24 元调整为 26 元,商品数据按读取与缓存策略刷新后,卡片会继续通过原有数据绑定显示新价格,之前设置的颜色和布局仍然保留。 这次调价不需要修改前端源码或重新发布页面;而更换展示商品或调整页面结构,则属于项目源码的修改。

写回路径需要跟随字段归属和修改范围。独立调用点上的文案,可以直接修改对应的组件传参;商品引用的更换修改页面配置;真实售价调整继续由业务服务处理。当一个独立参数即可表达修改时,就不必再保存一份额外的数据覆盖。

对于局部的差异展示,则适合使用实例覆盖(Instance Override)。例如,循环产生多张卡片,商家只想让其中某一张卡片的价格变色。如果修改组件默认样式会影响全部,而复制整个组件又增加了冗余代码。受支持的实例覆盖可以只保存针对这一处、这个字段的样式差异。

这份覆盖需要与源码一起版本化,包含可解析的实例目标、字段与作用范围。在支持的范围内,还要区分设备终端等条件:手机端的一处间距调整,不应被无意应用到桌面端。只有编译与发布管线读取同一份有效的覆盖,才能让局部编辑在保存后继续成立。

这要求页面状态与业务状态划定清晰的界限。商品引用是项目状态,商品售价是业务状态,它们在卡片上结合,但各自的更新和撤销方式需要保持解耦。

使用版本前置条件与 CAS 提交修改

在代码可能被高频修改的环境下,当用户在画布选中基于版本 r1 渲染的标题时,还未确认修改前,Agent 可能已经重构了代码并提交了新版本 r2。原来的字符位置已经偏移,旧指令不能继续按原地址写入。

因此,画布命令需要带上基准版本、选择引用、目标字段、操作和作用范围。简化结构如下:

{
  "expectedRevision": "r1",
  "selectionId": "second-note-heading",
  "field": "text",
  "operation": "set-text",
  "scope": "instance",
  "value": "为日常使用而设计"
}

服务端在接收后,先检查项目权限与携带版本,使用属于该版本的索引去解析选择。同时核对原文件和字段值,确保当前源码内容确实与修改依据相符。在修改计划准备就绪、实际提交时,再原子地检查版本化项目的头指针(Head)是否仍为 r1;如果在准备期间已有新版本提交,则拦截此次编辑。 这种机制称为比较并交换(Compare-and-Swap,CAS),用于防范版本化项目的并发冲突。

两次检查解决不同阶段的问题:预解析阶段拒绝基于过期代码的选择,提交阶段防范处理过程中的并发变化。成功提交后,新的预览与编辑索引需要基于更新后的源码和工具链派生。

对于同一页面内的多处 UI 修改,可以汇集后合并提交。而修改真实商品价格等业务操作,则脱离页面版本,走商品服务自有的权限、版本和事务控制。两者的版本分离,也决定了它们撤销机制的分离。

跨版本身份协调与实例覆盖失效

我们可以用一个连续的过程来检查这套架构对于持续编辑的支持:

发生了什么页面需要保留什么
Agent 生成杯子的商品卡片建立商品引用与代码展示的关系
商家修改第二张提示卡的文案只影响选定卡片的修改范围
商家把商品价格改成紫色保留外观样式覆盖,同时维持价格的数据来源绑定
Agent 调整整体页面布局对仍可确认结构目标的地方,保留原有的局部覆盖
后台更新杯子的售价使用业务系统提供的新价格,保留页面此前的样式
商家把商品替换为托盘后再点击撤销恢复原商品的代码引用,按读取策略展示当前业务数据

沿着这个过程能够看出,顺利修改一次标题,只验证了架构的一小部分。更关键的在于跨版本身份协调(Identity Reconciliation):在 Agent 重构源码后,先确认新旧代码中的元素对应关系,再判断原来的覆盖是否仍然适用。

如果 Agent 只是在子树外侧增加了一个包裹元素,平台可以通过结构特征保留节点身份,用新索引重新解析写入位置。但如果 Agent 复制了两张缺乏稳定标识的卡片,或者打乱了一个列表的渲染逻辑,系统就不能武断地仅凭相同的文字或旧顺序,把旧覆盖强加过去。 结构对应的范围必须明确,无法确认的歧义也需要被拦截保留。

即使元素身份得以延续,覆盖指令仍要经历条件检查。商家曾给某张卡片改过文案,若 Agent 随后在源码底层修改了该文案的基础生成逻辑,旧覆盖如果不被校验就会遮盖住有效的新代码。反之,如果只要文件有差异就清空所有覆盖,那 Agent 调整一个无关的布局也会导致商家的局部修改丢失。

系统会按覆盖涉及的字段,比较变更前后的来源依据,检查相关源码、组件调用关系和基础值是否仍然成立。在可确认的安全范围内,继续保留与重构无关的样式修改;对于因代码重写导致失去溯源依据的覆盖,停止应用并保留诊断记录,避免其悄悄污染新目标。元素身份匹配与字段来源校验, 在这套逻辑中是两个独立的核准条件。

撤销机制也遵循同样的边界。撤销页面中被替换的商品,应当恢复项目状态层面的代码引用。它不应该连带撤销后来在后台发生的真实业务调价。预览环境与发布环境遵循一致的源码解释和覆盖规则,才能让操作保持稳定可靠的含义。

让编辑能力跟上持续变化的页面

这套架构让模型使用原生的代码进行创作,让商家通过可视化画布调整网站,同时让核心业务数据继续交由专业系统维护。其工程代价是,平台必须持续处理并调度来源分析、实例解析、写回验证与版本协调。

性能和正确性需要兼顾考察。在同一项目版本内,静态索引可以被高效复用;但在单个文件发生变动后,系统需要重新判断受影响的依赖关系,不能简单地认定其他调用链依然安全有效,这就要求局部重算和解析链路具备可接受的响应速度。

代码可视化编辑在业界已有丰富的实践,例如 Onlook、 Utopia 的数据追踪, 以及 Puck 的外部数据接入。在持续经营的动态网站里,这些问题会进一步交织:Agent 改布局、商家改设计、后台改数据, 这三种变化必须在同一套工程体系中正交存在且保留各自的清晰含义。

我们将这部分工程推演整理为 Agent Editable UI 开源仓库,公开了来源追踪、修改作用域与版本协调的参考设计, 并提供使用普通 TSX 与合成商品数据的连续静态推演案例。该仓库适合用于探讨研究“修改指令应当落在哪行代码上”,完整的生产级编译器和编辑器实现未包含其中。

类似的问题也会广泛出现在 AI 建站、CMS 迭代以及内部可视化开发平台中。我们希望与正在探索此类架构的团队进行技术交流:哪些普通代码的写法需要更好的编辑解析支持,怎样在保留代码表达自由的同时,让跨端的高频修改准确、持续有效。欢迎通过仓库的 Issues、 Discussions,或直接 [联系 Foundax 团队](mailto:contact@novariseintl.com) 分享案例与实现经验。

继续阅读