跳到正文
前端知识库
工程化

低代码系统:Schema、渲染器与插件化架构

从页面 DSL 到编辑器、运行时渲染和发布,梳理低代码平台的核心边界与工程取舍。

8 分钟低代码 · Schema · DSL · 渲染器 · 插件化 · 可视化 · 编辑器 · 物料

低代码系统:Schema、渲染器与插件化架构

本文聚焦低代码系统的设计边界、演进方式和工程取舍。

1. 低代码平台解决什么问题

低代码平台把“组件、属性、布局、数据和交互”编码成可保存、可迁移的 Schema,再由编辑器和运行时共同解释它。它不是简单的拖拽页面:真正困难的是 Schema 的长期兼容、组件能力治理、表达式安全和编辑态/运行态的一致性。

建议拆成四层:

物料层:组件、属性定义、事件、图标、文档
编辑层:画布、选中/拖拽、属性面板、撤销重做
运行层:Schema 校验、数据绑定、布局与事件执行
发布层:版本、预览、权限、导出、回滚

编辑器负责产生 Schema,运行时只消费经过校验和迁移的 Schema;不要让画布组件直接依赖编辑器内部状态。

1.1 先做领域建模,再抽象 DSL

DSL 不是把页面字段随意堆成 JSON。设计前应先盘点领域中已经出现过的页面形态、交互和数据流,再把稳定的共性抽成模型。这样既能让搭建器复用模板,也能为后续比较同类页面、分析业务效果留下可用的数据维度。

以运营弹窗这类领域为例,可以先按职责拆出以下模块:

  • 基础信息:名称、类型和版本等元数据。
  • 组件:图片、文字、倒计时、热区、容器、视频、状态和关闭按钮等物料。
  • 样式:跨 H5、iOS、Android 都能解释的最小样式集合,避免把某个终端私有属性写成通用协议。
  • 请求:曝光前后的请求描述,并明确串行、并行和失败策略。
  • 行为:用户事件与声明式动作,和组件的展示属性分离。

字段应保持精简、模块高内聚低耦合,并优先通过增加受控属性值支持新需求,而不是频繁改变结构。DSL 的每次变更都要有文档、兼容策略和迁移记录;废弃或只在部分终端生效的字段必须显式标注。

来源:大促搭投实践&高可用性保障(简化版).pdf 第 2-6 页。

2. Schema 设计要点

Schema 至少需要稳定的版本号、节点 ID、组件类型、受控属性和子节点:

{
  "schemaVersion": "1.0",
  "root": {
    "id": "page-root",
    "type": "Page",
    "props": { "title": "订单列表" },
    "children": [
      {
        "id": "orders-table",
        "type": "DataTable",
        "props": { "columns": ["id", "status"] },
        "dataSource": { "kind": "query", "name": "orders" }
      }
    ]
  }
}

关键约束:

  • id 在文档内稳定且唯一,便于选中、差异比较和埋点。
  • type 只引用物料注册表中的类型,不允许任意组件路径。
  • 属性值与事件动作分离,避免把函数序列化进 JSON。
  • 数据源、权限和敏感配置使用引用或服务端 ID,不能把密钥写入页面 Schema。
  • 用 JSON Schema/Valibot 等运行时校验器限制字段类型、深度、数组长度和字符串大小。

Schema 升级必须有迁移函数,例如 1.0 -> 1.1;读取旧版本时先迁移到当前内部模型,再交给渲染器。不要在各个组件里散落版本判断。

2.1 把行为和请求建模成协议

行为协议应描述“什么事件触发、执行什么动作、作用于谁以及携带什么内容”,而不是把函数直接序列化进 Schema。一个通用的动作可以表示为:

{
  "type": "click",
  "behavior": "switch",
  "target": "success-state",
  "content": null
}

type 可以是 clicktabSwitch 等事件;behavior 应限制在经过审核的动作集合,例如 navigateswitchrequestcloseshowhidetargetcontent 的含义由动作类型决定。一个事件通常只执行一个动作,确有组合需求时才使用有序的 actions 列表,并逐项校验权限、参数和失败处理。

请求也应作为声明式数据描述,复用于曝光前和曝光后流程,并明确串行或并行关系、超时、重试和降级策略。运行时只执行白名单动作,不能使用 evalnew Function 解释任意用户输入。

来源:大促搭投实践&高可用性保障(简化版).pdf 第 3-4 页。

3. 物料注册表与渲染器

物料定义应同时描述运行时组件、可编辑属性、事件和默认值:

type ComponentMeta = {
  type: string
  version: string
  render: React.ComponentType<Record<string, unknown>>
  propsSchema: unknown
  events?: string[]
  defaults?: Record<string, unknown>
}

const registry = new Map<string, ComponentMeta>()

function register(meta: ComponentMeta) {
  if (registry.has(meta.type)) throw new Error(`duplicate component: ${meta.type}`)
  registry.set(meta.type, meta)
}

渲染器递归遍历节点,先查注册表、校验属性,再渲染子节点。缺失组件、版本不兼容或数据异常时应显示可定位的降级节点,而不是让整个页面崩溃。编辑器可以额外提供拖拽占位、选中框等装饰,但这些状态不应写入运行时 Schema。

3.1 编辑器数据层与所见即所得

编辑器的内部状态可以把每次用户操作归档为不可变快照,并将快照压入历史栈。快照必须是经过校验的 Schema,而不是夹杂选中框、拖拽指针等编辑态临时字段。预览模块直接消费最新快照,并尽量复用线上渲染引擎,才能让编辑态和运行态保持一致。

快照天然支持撤销、重做、操作记录和故障复现;文档很大时可以改用命令日志加周期性快照,避免每次复制整棵树。无论采用哪种实现,历史记录都应有大小上限,并在发布前固定 Schema、物料版本和数据源版本。

3.2 逻辑层用标准接口和插槽组合

将工具栏、物料区、画布、属性面板、预览和发布等功能拆成带标准输入输出的模块,由 Layout 基座提供稳定的插槽和宿主 API。模块通过注册和插拔接入,彼此不直接依赖内部状态;不同角色只暴露被授权的模块,新增或移除一个功能区不会牵连整棵组件树。

来源:大促搭投实践&高可用性保障(简化版).pdf 第 5-6 页。

4. 属性面板与事件模型

属性面板由 propsSchema 驱动,避免为每个组件手写一套表单。编辑操作应生成可逆的命令:

type Command = {
  do(): void
  undo(): void
  label: string
}

撤销/重做栈保存命令或不可变快照;大文档可以使用操作日志和定期快照,避免每次复制整棵树。事件动作建议使用受限的声明式列表,例如 navigatesetStaterequest,由运行时逐项校验和执行,禁止 eval 执行任意表达式。

4.1 画布分层和编排事件

复杂画布可以把职责分到不同层:底层负责根据 DSL 渲染,覆盖层处理右键和选中态,再上一层处理快捷键或辅助操作。各层通过明确的事件总线通信,避免让每个物料都监听全局 DOM 事件。事件总线应有命名空间、解绑机制和生命周期边界,不能演变成无法追踪的全局状态。

编排能力可按复杂度逐步开放。块级物料适合先实现拖拽、排序和选中;自由画布再增加缩放、旋转、辅助线和吸附。吸附计算应基于模型节点的边、中心线等几何信息,并在拖拽过程中节流;拖拽结束只提交一次可回放的命令。节点 ID 必须稳定且唯一,不能使用不可复现的随机值作为长期标识。

这类编辑器既可以使用队列和指针实现撤销/重做,也可以把操作转成命令对象;关键是让数据、视图和操作解耦,预览始终是 DSL 的纯函数结果。

来源:低代码可视化平台架构设计内丹.pdf 第 3-5、13-16 页。

5. 插件化边界

插件可以扩展物料、属性编辑器、数据源适配器和发布能力,但应通过明确的宿主 API 接入:

type LowCodePlugin = {
  name: string
  version: string
  install(api: {
    registerComponent(meta: ComponentMeta): void
    registerDataSource(kind: string, handler: unknown): void
  }): void
}

插件加载时检查版本兼容性和权限;插件不能直接改写核心状态或绕过 Schema 校验。第三方插件的网络请求、文件访问和代码执行能力应隔离在服务端或独立沙箱中。

插件化设计可以用三件套来审查边界:

  1. 插件底座:维护核心状态、生命周期和宿主能力。
  2. 插件接口:声明可调用的钩子、输入输出、版本和权限。
  3. 插件实现:只通过接口注册能力,不修改底座内部实现。

物料通常由 type 和资源引用组成。注册表负责校验类型唯一性、建立 type -> renderer 映射,并在运行时注入给渲染器;在线物料还需要签名、来源审核、懒加载、卸载和错误隔离。不要把任意组件路径或第三方代码直接放进页面 Schema。

来源:低代码可视化平台架构设计内丹.pdf 第 7-12 页。

6. 数据绑定与安全

数据绑定要明确数据流方向:查询结果进入组件 props,表单事件产生受控变更,再由动作层提交。服务端必须重新校验查询参数、分页上限和资源权限。表达式只允许访问显式上下文(如 rowformroute),采用解释器或预编译白名单,不要直接 new Function

页面发布时固定 Schema、物料版本和数据源配置,生成不可变发布版本;预览、灰度和回滚都以版本为单位。审计记录谁修改了哪些节点、何时发布以及使用了哪些插件。

7. 性能与测试

  • 大画布使用虚拟化、局部渲染和节流后的拖拽更新;不要在每个鼠标移动事件中序列化整份 Schema。
  • 运行时按页面或物料拆分代码,组件注册表可懒加载。
  • 以 Schema fixture 做渲染快照和迁移测试,以交互测试覆盖选中、复制、撤销、数据加载和错误降级。
  • 对发布产物做 Schema、权限和资源大小检查,防止不可运行页面进入生产。

7.1 出码、版本和协同扩展

“出码”本质上是把经过校验的 Schema 输出为可部署的 JSON 或渲染产物。发布记录应绑定 Schema 版本、物料版本、数据源和插件清单,预览、灰度和回滚都以不可变版本为单位。

历史记录、模板、分享和主题属于围绕版本的基础能力;协同编辑、流程图逻辑、定时任务和微前端集成属于进阶扩展。协同方案可按冲突模型选择操作转换或 CRDT(如 Yjs),但必须先定义节点 ID、权限和离线合并规则,不能只引入算法名。

来源:低代码可视化平台架构设计内丹.pdf 第 4-5 页。

8. 设计取舍

自由度越高,Schema 校验、迁移、权限和调试成本越高。企业场景通常优先选择受控物料目录和少量声明式动作,再逐步开放扩展点;“能拖出来”不是平台质量指标,可维护性和可回滚性才是。