低代码设计器和低代码设计引擎架构综述

低代码设计器如果只解决"拖拽组件",很快就会遇到一系列工程问题:页面结构无法稳定表达,组件属性和运行语义混在一起,预览环境不可控,版本差异难以追踪,出码只能依赖模板拼接,插件扩展也容易变成各自维护状态的孤岛。

一个可持续演进的低代码平台,首先需要解决的不是"左边放什么面板,右边放什么属性",而是要先回答几个更底层的问题:

txt 复制代码
应用应该用什么协议描述?
设计器内核应该管理什么?
插件如何扩展能力而不破坏状态一致性?
运行时如何解释 Schema 中的状态、事件和表达式?
物料、Setter、数据源、资源和出码如何形成生态?

这篇文章从整体架构出发,梳理一套应用级低代码设计器的技术架构、业务架构和设计引擎分层。它不是某一个功能点的实现教程,而是一篇面向设计原理和架构边界的综述。

术语速览

下面这些词会在全文反复出现,先给一个简要说明,方便对照阅读:

txt 复制代码
AppSchema       描述整个应用的协议数据,是设计器真正编辑的对象
Designer Core   设计器内核,统一管理状态、命令、历史、校验
Workbench       工作台,插件的容器和交互入口(页面管理、属性面板等都是 Workbench 里的插件)
Renderer        把 SchemaNode 渲染成真实组件树的模块
Runtime         负责解释运行语义(状态、表达式、事件、生命周期)的模块
Simulator       画布的隔离运行环境,通常用 iframe 实现
Setter          属性编辑器,负责把 Schema 字段暴露成可操作的 UI
Codegen         出码模块,把 AppSchema 转换成可运行的工程代码

一句话定义低代码设计器

这套低代码设计器可以被定义为:

txt 复制代码
以 AppSchema 为核心协议,
以 Designer Core 为状态和命令中枢,
以插件化 Workbench 为交互入口,
以 Renderer / Runtime / Simulator 为执行层,
以物料、Setter 和设计器插件为扩展生态,
最终服务于预览、版本管理、源码生成和 AI 辅助开发的应用设计平台。

这里有几个关键词。

第一,核心不是 UI,而是 AppSchema。画布、属性面板、页面树、路由面板、资源面板、主题面板和版本面板,都只是编辑 AppSchema 的不同入口。UI 可以换,插件可以增删,运行模式可以扩展,但协议必须稳定。

第二,设计器需要一个内核。设计器不是几个 Vue 组件拼起来的后台页面,它需要统一管理当前文档、选区、拖拽状态、命令、历史、校验、工作台布局和持久化状态。

第三,插件不是业务真相。插件负责展示信息、收集输入、注册能力入口,然后通过统一命令修改应用协议。插件不应该绕过内核直接改 Schema。

第四,运行时必须独立。画布不是静态预览图,而是一个受控运行环境。表达式、状态、方法、生命周期、资源解析、模块加载和事件执行,都需要运行时负责解释。

第五,生态比单点功能更重要。一个低代码设计器真正有生命力,不是内置了多少组件,而是能否持续接入物料、Setter、设计器插件、运行时插件、数据源协议和出码能力。

总体分层架构

从架构视角看,一个应用级低代码设计器可以拆成六层:

txt 复制代码
Presentation Layer
  官网、工作台 Shell、插件面板、画布、Inspector、Toolbar

Designer Engine Layer
  Designer Core、Command、Selection、History、Validation、Workspace

Application Protocol Layer
  AppSchema、DocumentSchema、SchemaNode、Route、Theme、AssetRef、DataSource

Runtime Engine Layer
  Renderer、RuntimeContext、Expression、Simulator、Asset Resolver、Module Loader

Extension Ecosystem Layer
  Material、Setter、Designer Plugin、Runtime Plugin、Codegen Plugin

Persistence & Delivery Layer
  Draft、Version、Migration、Diff、Preview、Codegen、Publish

这几层的关系不是简单的上下调用,而是围绕同一份应用协议协作。

用户在工作台中操作插件和画布,插件把意图交给 Designer Core,Core 通过命令修改 AppSchema,校验系统重新计算诊断信息,运行时和模拟器重新消费 Schema,最终形成预览、保存、发布或出码产物。

核心链路可以概括为:

txt 复制代码
用户操作
  -> Workbench / Plugin / Canvas / Inspector
  -> Designer Command
  -> History Transaction
  -> Mutate AppSchema
  -> Validation / Diagnostics
  -> Runtime / Simulator
  -> Preview / Persist / Codegen

这个链路里最重要的原则是:Schema mutation 必须收敛到 Designer Core。

设计器引擎内核

设计器引擎内核是整个低代码平台的"大脑"。它不负责具体 UI 长什么样,而是负责保证设计行为的一致性和可追踪性。

一个典型的 Designer Core 至少需要管理这些状态:

ts 复制代码
interface DesignerState {
  schema: AppSchema
  currentDocument: CurrentDocumentRef
  selectedNodeId: string | null
  hoveredNodeId?: string | null
  activePanel: string
  runtimeMode: 'design' | 'preview'
  viewport: CanvasViewport
  dragState: DesignerDragState
  history: DesignerHistoryState
  persistence: DesignerPersistenceState
  workspace: DesignerWorkspaceState
}

这里要特别注意:DesignerState 不等于 AppSchema

AppSchema 描述应用是什么。它会被保存、发布、迁移、diff,将来还会进入源码生成。

DesignerState 描述用户当前如何编辑这个应用。它包含选区、面板、拖拽、画布缩放、打开的 tabs、工作台布局等会话状态。这些状态帮助用户编辑,但不是应用本身。

命令系统

低代码设计器中,所有真正改变应用结构的操作都应该进入命令系统。 例如用户在属性面板里修改按钮文案,表面上只是一个输入框变化,但完整链路应该是:

txt 复制代码
Inspector input
  -> plugin event
  -> designer.commands.updateNodeProp
  -> history transaction
  -> update SchemaNode.props
  -> validation recompute
  -> simulator receives schema
  -> renderer rerender

如果属性面板直接修改节点对象,短期看起来简单,长期会产生很多问题:

  • History 不知道这次修改。
  • Validation 无法准确定位变化。
  • 版本 diff 和保存状态会失真。
  • iframe simulator 和主设计器状态可能不一致。
  • 未来协同编辑无法抽象操作边界。

所以内核设计的第一原则是:

txt 复制代码
UI 可以很多,Schema mutation 只能有一个入口。

History 与 Transaction

撤销重做不是简单保存数组快照。低代码设计器里的操作有时是单次更新,有时是连续拖拽,有时是多个字段一起变化。

因此更合理的模型是 transaction:

txt 复制代码
begin transaction
  update node props
  update selected node
  recompute diagnostics
commit transaction
  push history entry

这样可以把多个底层变化合并成用户可理解的一次操作,例如"修改按钮属性""移动组件""新增页面""恢复版本"。

Validation 与 Diagnostics

低代码设计器不能等到出码或运行时才发现错误。协议校验应该在编辑阶段持续运行。

典型诊断包括:

  • 节点引用了不存在的物料。
  • 本地组件出现循环引用。
  • 路由 path 冲突。
  • 表达式引用了不存在的状态字段。
  • 数据源参数缺失。
  • 资源引用找不到对应 asset。
  • 组件 props 类型不匹配。

Diagnostics 不只是错误提示,它也是 AI 辅助、出码检查和发布前校验的基础上下文。

应用业务架构:AppSchema 如何描述真实应用

低代码设计器真正编辑的不是 DOM,而是一个应用模型。

这个应用模型可以用 AppSchema 表达:

txt 复制代码
AppSchema
  pages
  components
  pageGroups
  componentGroups
  routes
  router
  layouts
  dataSources
  assets
  modules
  theme
  globals
  materialPackages
  schemaVersion

这些字段共同描述一个真实应用需要的业务结构。

页面是业务入口,组件是可复用业务单元,路由决定用户如何进入页面,数据源决定业务数据如何接入,资产和模块决定应用依赖的外部资源,主题和全局状态决定应用级运行环境。

DocumentSchema:统一页面和组件

页面和本地组件可以统一抽象为 DocumentSchema

ts 复制代码
type DocumentKind = 'page' | 'component'

interface DocumentSchema {
  id: string
  kind: DocumentKind
  name: string
  code: string
  props: Record<string, ValueDeclaration>
  emits: Record<string, EmitDeclaration>
  slots: Record<string, SlotDeclaration>
  state: Record<string, ValueDeclaration>
  methods: Record<string, MethodDeclaration>
  computed?: Record<string, ComputedDeclaration>
  watch?: Record<string, WatchDeclaration>
  css?: string
  componentsTree: SchemaNode[]
}

页面和组件不是两套完全不同的东西。它们都可以有组件树、状态、方法、样式、props、emits 和 slots。

区别在于:

txt 复制代码
Page
  可以被 route 指向
  可以有 SEO
  可以声明 route params/query
  是应用入口之一

Component
  可以被页面或其他组件引用
  可以作为本地组件物料出现
  更强调对外契约

统一成 DocumentSchema 之后,画布、结构树、逻辑面板、属性面板、历史记录、校验和出码能力都可以复用。

SchemaNode:描述组件实例和运行语义

页面或组件内部的结构由 SchemaNode 描述:

ts 复制代码
interface SchemaNode {
  id: string
  componentName: string
  componentType?: 'builtin' | 'local'
  componentId?: string
  props: Record<string, NodePropValue>
  condition?: JSExpression
  show?: JSExpression
  loop?: SchemaNodeLoop
  model?: SchemaNodeModel
  events?: Record<string, EventBinding>
  layout?: SchemaNodeLayout
  children?: SchemaNode[]
  slots?: Record<string, SchemaSlotValue>
}

这里的字段可以分成三类。

第一类是组件实例信息:

txt 复制代码
componentName
componentType
componentId
props
children
slots

第二类是运行语义:

txt 复制代码
condition
show
loop
model
events

第三类是布局和设计态结构信息:

txt 复制代码
layout
slotProps
id

这三类不能混在一起理解。props 是传给组件的属性,showloop 不是组件 props,而是运行时解释的节点语义。layout 也不应该散落成不可分析的 className 字符串,而应该尽量保持结构化,方便校验、响应式布局和出码。

Runtime 架构:设计器为什么需要独立运行时

画布不是普通的静态预览,它需要执行低代码协议里的运行语义。

运行时可以拆成几部分:

txt 复制代码
SchemaRenderer
  负责把 SchemaNode 渲染成真实组件树

RuntimeContext
  负责 state、computed、methods、watch、lifecycle

Expression Engine
  负责解释 JSExpression、条件渲染、循环、模型绑定

Asset Resolver
  负责把 AssetRef 解析成真实 URL 或资源对象

Module Loader
  负责把应用声明的外部模块加载到 ctx.modules

Simulator
  负责隔离设计器外壳和用户应用运行环境

Renderer 和 Runtime 必须分开。

Renderer 只关心如何渲染节点,Runtime 负责解释运行语义。这样做可以避免画布组件越来越膨胀,也能让同一套 Runtime 服务于设计态画布、预览模式、未来出码验证和测试环境。

iframe simulator 的价值

应用级低代码设计器通常需要 iframe simulator。 原因很直接:用户页面的样式、事件、路由、全局变量和第三方依赖不应该污染设计器外壳。

iframe simulator 可以提供隔离边界:

txt 复制代码
Parent Designer
  Workbench、Inspector、Plugins、Selection

Iframe Runtime
  User App、Renderer、RuntimeContext、Assets、Modules

Bridge
  schema sync、selection report、hover report、drop event、runtime error

这样设计器可以在外部管理编辑状态,在 iframe 内部运行用户应用。两者通过 bridge 协作,而不是共享一堆隐式全局状态。

引擎生态:物料、Setter、插件和出码扩展

低代码设计器的长期价值取决于生态扩展能力。

一个平台不可能靠内置组件覆盖所有业务场景。它必须允许团队持续扩展物料、属性设置器、设计器插件、运行时能力和交付插件。

物料生态

物料是低代码设计器能识别、展示、拖拽、配置和渲染的组件能力单元。

典型物料来源包括:

txt 复制代码
内置组件
原生 HTML 物料
本地 Schema 组件
远程物料包
团队业务组件库

一个物料不仅仅是 Vue 组件本身,还需要描述设计器元信息:

ts 复制代码
interface MaterialDefinition {
  name: string
  title: string
  category: string
  runtimeComponent: unknown
  propsSchema: PropSchema[]
  snippets: MaterialSnippet[]
  setters?: SetterDefinition[]
}

设计器靠这些元信息生成物料面板、默认拖拽片段、属性面板和运行时渲染能力。

Setter 生态

Setter 是属性编辑器。

简单属性可以用文本、数字、颜色、开关、选择器来编辑。复杂属性则需要更强的 Setter:

txt 复制代码
表达式编辑器
事件动作编排器
数据源绑定器
样式编辑器
响应式布局编辑器
资源选择器
路由参数编辑器

Setter 的作用不是把表单做漂亮,而是把复杂协议用可理解的方式暴露给用户。它是"Schema 协议"和"用户操作"之间的翻译层。

设计器插件生态

设计器插件负责扩展工作台能力。

常见插件包括:

txt 复制代码
页面管理
组件管理
资源管理
主题配置
路由管理
全局状态
数据源管理
物料包管理
结构树
逻辑面板
版本管理
Schema 查看器
历史记录

插件不应该拥有业务真相。它应该通过 Designer Context 读取状态,通过 commands 修改 Schema,通过 diagnostics 展示问题,通过 contribution 注册 UI 入口。

一个抽象的插件接口可以是:

ts 复制代码
interface DesignerPlugin {
  id: string
  title: string
  area: 'activity' | 'document' | 'bottom' | 'toolbar'
  setup(ctx: DesignerContext): void
}

插件注册的是能力入口,而不是新的状态孤岛。

交付扩展

低代码平台最终要交付应用,而不仅是交付一份配置。

交付相关扩展包括:

txt 复制代码
Preview
Schema Diff
Version Publish
Vue3 Codegen
API Codegen
AI Generate Suggestion
Deployment Adapter

其中出码能力尤其依赖协议稳定。只有 AppSchema、运行语义、资源引用、模块依赖和路由声明足够清晰,Codegen 才能从"字符串模板拼接"变成"协议到工程项目的确定性翻译"。

三类状态边界

低代码设计器里最容易混乱的是状态。

至少要分清三类状态。

第一类是业务应用状态:

txt 复制代码
AppSchema
  pages
  components
  routes
  theme
  globals
  assets
  modules
  dataSources

这是应用的一部分,需要保存、发布、迁移、diff,将来还会出码。

第二类是设计器工作区状态:

txt 复制代码
selectedNodeId
hoveredNodeId
activePanel
openedTabs
leftPanelOpen
bottomPanelOpen
viewport
dragState

这些状态帮助用户编辑,但不是应用本身。

第三类是运行时临时状态:

txt 复制代码
ctx.state
ctx.computed
runtime errors
loaded modules
resolved asset urls
watch stops
mounted lifecycle state

这些状态用于模拟和预览应用运行,不应该直接写回 Schema。

把这三类状态混在一起,是很多低代码项目后期难以维护的根源。

为什么这套架构适合 AI 和源码生成

AI 生成代码最怕缺少稳定、完整、可验证的上下文。

低代码设计器恰好在做相反的事情:把业务应用需要的结构、语义和约束沉淀为协议。

这些信息包括:

txt 复制代码
页面结构
组件契约
事件绑定
状态声明
数据源声明
路由声明
主题 token
资源引用
版本差异
校验错误

当这些上下文存在于 AppSchema 和 diagnostics 里,AI 就不再只能"看图猜代码"或"看一句话猜需求"。它可以基于明确上下文做补全、解释、修复、重构和出码建议。

源码生成也是同理。

Codegen 不应该是把一堆字符串拼起来,而应该是:

txt 复制代码
AppSchema
  -> normalized application model
  -> route files
  -> Vue SFC
  -> stores / composables
  -> assets / modules
  -> buildable project

低代码协议提供确定性,AI 提供生成和推理能力。两者结合,比单独依赖任何一方都更稳。

架构设计原则总结

低代码设计器不是把 UI 拖出来,而是把应用语义结构化。

要让这个系统长期可演进,需要守住几个原则:

txt 复制代码
协议优先
  所有能力最终都要回到 AppSchema。

命令收口
  所有 Schema 修改都进入 Designer Core。

状态分层
  应用状态、设计器状态和运行时状态必须分开。

运行时隔离
  用户应用的执行环境不能污染设计器外壳。

生态插件化
  物料、Setter、设计器插件和交付能力都应该可扩展。

交付可验证
  预览、校验、版本、diff、出码和发布必须共享同一份协议。

如果说低代码设计器的第一阶段是"让用户能拖拽搭页面",那么应用级低代码设计引擎的目标应该更进一步:

txt 复制代码
把应用结构、业务语义、运行上下文和工程交付链路统一到一套可演进的协议和引擎体系里。

只有做到这一点,低代码平台才能从演示工具变成真正可维护、可扩展、可出码、可被 AI 理解的工程系统。

相关推荐
Ruihong1 小时前
🎉 VuReact 1.9.0 发布,支持 Vue 3.4 defineModel 编译到 React
vue.js·react.js·面试
Hilaku1 小时前
Node.js 还能再战十年?给你一个不换引擎的理由
前端·javascript·程序员
颜进强1 小时前
AI性能参数-截断、延迟与流式输出
前端·后端·ai编程
spmcor2 小时前
React 架构师之路:Next.js 全栈革命(第八篇)
前端·react.js
英勇无比的消炎药2 小时前
TinyRobot 源码深度分析:OpenTiny 的 AI 对话组件库
前端·vue.js·github
假如让我当三天老蒯2 小时前
React基础、进阶(学习用)
前端·react.js·面试
风骏时光牛马2 小时前
HTML十大经典实战代码案例合集
前端
weedsfly2 小时前
前端必知必会:从 IIFE 到 ESM,模块化到底在解决什么?
前端·javascript
笨鸟飞不快2 小时前
从单个服务到集群:一次完整的性能排查复盘
java·前端