背景
IntelliPro 开发中,C 类页面可通过 GUI 侧 DSL 由低代码引擎直接渲染;但 A/B 类页面含 AI 生成的自定义组件,存在"预览难用 GUI 渲染、代码渲染无法编辑"的核心矛盾。本文调研 V0.dev、Weavefox、VTJ、builder.io 四大低代码平台的实时预览与编辑技术原理,为 IntelliPro 功能优化提供可行参考。
1. 平台概览
| 维度 | V0.dev | Weavefox | VTJ | builder.io |
|---|---|---|---|---|
| 技术栈 | React | React | Vue 3 | React |
| 组件库 | shadcn/ui + tailwind | AntdUI / Radix UI + less/tailwind | Element Plus | Radix UI |
| AI 能力 | 支持全局/局部 AI 对话修改 | 全局/局部 | 全局/局部 | 全局/局部 |
| 输出产物 | 代码 | 代码 | DSL、代码 | 代码 |
| 预览(库存管理页) | 代码渲染,效果简洁 | 代码渲染,流程完整 | 低代码(DSL)渲染,联动性强 | 代码渲染,生成双页面 |
| 可视化编辑 | 全元素可编辑,支持 CSS/Text,可删不可增 | 仅 jsx 可编辑,支持 CSS/Text,可删不可增 | 部分元素可编辑,支持组件属性,可删不可增 | 全元素可编辑,支持 CSS/Text,不可删增 |
| AI 对话框编辑 | 增量修改 | 增量修改 | 全量重生成 | 增量修改 |
2. V0.dev
2.1 核心架构与原理
核心为"纯代码生成+多模块联动"模式,无中间 DSL 层。Element Tree 为核心枢纽,存储元素完整信息(含行列号);属性编辑器、预览区、代码编辑器通过 Event Bus 通信,确保编辑操作实时同步。
Babel/SWC 编译
渲染
转换
转换
AI 生成 TSX 代码
代码编辑器
编译后的代码
预览区
Element Tree(核心枢纽)
属性编辑器
Event Bus 通信
2.2 关键流程
2.2.1 预览区选中编辑流程
预览区选中元素
Element Tree 定位信息
提取 CSS/Text 可编辑属性
属性编辑器展示
用户编辑
核心依赖 Element Tree 里的文件路径、行列号、元素类型 3 大关键信息。
2.2.2 核心同步逻辑
- 属性编辑:同步触发 DOM 原生事件(更新预览)和 devtools_sync_design 事件(精准修改源码);
- 代码编辑(参考 2.1 的流程图):触发重新编译,注入行列号更新 Element Tree,同步刷新预览与属性编辑器。
2.2.3 行列号来源
development 环境启用 source map,通过 Babel/SWC 编译插件,将源码行列号、文件路径等编译信息注入 source map 和 Element Tree,支撑精准编辑。
Element Tree 核心 JSON 示意:
json
{
"__v0_remote__": 1,
"type": "devtools_selected_state",
"parts": [{"lineNumber": 10, "columnNumber": 5, "value": "flex-1 h-8 border-slate-200"}],
"file": "components/demo.tsx",
"content": {"file": "components/demo.tsx", "line": 12, "column": 3, "type": "jsxText", "value": "Button Text"},
"name": "Button",
"line": 8, "column": 5,
"lib": {"name": "Button", "source": "@/components/ui/button"}
}
2.3 核心特点
- 优点:无学习成本(标准 React 代码)、灵活性高、样式修改简洁、全元素可编辑;
- 缺点:仅支持 CSS/Text 编辑,代码规范要求高,魔改代码会导致可视化编辑失效。
3. Weavefox
3.1 核心架构与原理
与 V0.dev 架构逻辑一致,核心差异的是编译信息注入方式和可编辑范围,更贴合 IntelliPro 的 antd+less 技术栈。
3.2 与 V0.dev 核心差异
- 编译信息注入:不依赖 source map,自定义插件直接注入 DOM,定位更直接;
- 可编辑范围:仅支持 jsx 元素编辑,原因是:自定义编译插件(编译时生成可编辑元素相关信息)仅能识别源码中的 jsx 元素;同时 antd 封装组件的常量属性也不可编辑。
- 可修改属性:基于 antd+less 规范,开放属性少于 V0.dev,贴合组件库场景。
3.3 核心特点
最贴近 IntelliPro 开发场景(antd+less),代码生成工作流完整可借鉴;不足是可编辑元素、属性范围较窄,可视化编辑灵活性低。
4. VTJ 整体运行原理、架构及机制详解
4.1 整体运行原理和架构
VTJ 运行原理与 V0/Weavefox 差异显著,核心优势是无需编译时记录源码行列号,可在运行时直接将 DSL(领域特定语言)渲染为页面,且源码通过 DSL 以工程化手段生成,因此 DSL 是整套架构的核心。
VTJ 自研并开源 3 大核心引擎------Parser(解析器)、Generator(生成器)、Renderer(渲染器),三者协同支撑系统运行,整体架构如下:
Coder/Parser
Generator
Parser
加载
AI 生成 DSL
DSL(核心关键)
token + template
Renderer
源码
代码编辑器
DSL 编辑器
预览区
预设物料
属性编辑器组件面板
4.2 机制详解
结合上述架构图,按核心流程拆解 VTJ 运行机制,重点讲解复杂逻辑环节(DSL 到 DSL 编辑器仅为可视化呈现,不展开),具体分为 4 部分:
- DSL 到预览区:即 VTJ 的渲染机制,实现页面实时预览
- DSL 到代码编辑器:即 DSL 转换为源码的过程,支撑源码面板展示
- 代码编辑器到 DSL:即源码转换为 DSL 的过程,实现 DSL 与源码的双向转换
- 属性编辑器:结合预设物料,支撑组件属性的可视化配置
4.2.1 渲染机制
VTJ 中 DSL 到实时预览的核心流程简洁高效,高度依赖 Vue 渲染原理及 h() 函数,流程如下:
DSL
Renderer 解析
h() 函数调用
VNode
DOM
预览区展示
渲染过程分为 3 个核心环节,对应不同核心函数,具体拆解如下:
1. 页面渲染:核心函数 createDslRenderer
createDslRenderer 是页面渲染入口,输入为 DSL 编辑器中的 DSL 内容,核心作用是将 DSL 转为可渲染的 Vue 组件,为后续渲染提供基础。
相关渲染核心代码片段(基于 Vue 响应式原理):
javascript
// 页面渲染入口:将 DSL 转为可渲染 Vue 组件(核心函数 createDslRenderer)
const { renderer: rendererIns, context: renderContext } = this.provider.createDslRenderer(this.dsl, {
window: windowObj, // 窗口实例
mode: ca.Design, // 设计模式
Vue: VueIns, // Vue 实例
apis: apiList, // 接口列表
libs: libList, // 依赖库列表
components: compList, // 组件列表
dataSources: { getData: () => {} } // 数据源(简化示例)
})
// 基于 Vue.reactive 创建响应式 DSL
this.dsl = VueIns.reactive(t.toDsl());
try {
// 区分平台(web/uniapp)创建应用
const { platform: platformType = "web" } = this.project || {};
this.app = platformType === "uniapp" ? this.createUniApp(platformType, n, rendererIns) : this.createWebApp(platformType, n, rendererIns);
} catch (error) {
console.error("渲染异常:", error);
Ue(error.message || "未知错误", "运行时错误");
// 上报错误信息
this.report.error(error, {
project: this.project?.toDsl(),
file: t.toDsl()
});
}
2. 组件渲染:核心函数 Vue.defineComponent
组件渲染核心是通过 Vue.defineComponent 动态创建 Vue 组件,动态生成 props、state 等核心内容,底层依赖 Vue 原生能力,核心代码如下:
javascript
// 组件渲染核心:动态创建 Vue 组件(基于 Vue.defineComponent)
const rendererComp: DefineComponent<any, any, any, any> = Vue.defineComponent({
name: dsl.value.name, // 组件名称(取自 DSL)
// 作用域 ID(避免样式冲突)
scopeId: dsl.value.id ? `data-v-${dsl.value.id}` : undefined,
props: { ...createProps(dsl.value.props ?? [], renderContext) }, // 动态生成 props
setup(props: any) {
renderContext.$props = props;
renderContext.props = props;
// 基于 Vue.reactive 创建响应式状态
if (dsl.value.id) {
adoptedStyleSheets(windowObj || window, dsl.value.id, dsl.value.css || '', true);
}
renderContext.state = createState(Vue, dsl.value.state ?? {}, renderContext);
// 动态创建计算属性、方法、依赖注入、数据源
const computed = createComputed(Vue, dsl.value.computed ?? {}, renderContext);
const methods = createMethods(Vue, dsl.value.methods ?? {}, renderContext);
const injects = createInject(Vue, dsl.value.inject, renderContext);
// 处理依赖注入(底层依赖 Vue.inject)
Object.entries(injects || {}).forEach(([key, value]) => {
injects[key] = Vue.inject(key, value);
});
const dataSources = createDataSources({ ...dsl.value.dataSources || {} });
return { ...renderContext, computed, methods, injects, dataSources };
}
});
3. 节点渲染:核心函数 nodeRender
nodeRender 函数负责解析 DSL 节点信息,处理指令、属性和插槽,最终调用 Vue.createVNode 生成虚拟节点(VNode),支撑 DOM 渲染,核心代码如下:
javascript
// 节点渲染核心:生成 Vue 虚拟节点(VNode)
// component:组件实例,key:唯一标识,stylescope:样式作用域,props:组件属性,events:事件,slots:插槽
let vnode = Vue.createVNode(
component,
{ key: `${nodeId}_${seq}`, ...stylescope, ...props, ...events },
slots
);
4.2.2 DSL 转换为代码
DSL 转代码的流程运行在服务端,前端实时渲染依赖 DSL 运行时渲染,切换「源码」面板时发起请求完成转换,核心流程如下:
DSL
Collector
Parser
Token
Template
Formatter
Vue SFC
代码编辑器展示
以下拆解流程各核心环节,结合代码示例说明其功能与原理:
1. Generator:转换入口
Generator 是 DSL 转代码的入口函数,串联全流程,接收 DSL、组件映射等参数,返回格式化后的 Vue SFC 代码,核心代码如下:
javascript
// DSL 转代码入口:串联收集、解析、编译、格式化全流程
export async function generator(dsl, compMap, dependencies, platform) {
const collector = new Collector(cloneDeep(dsl), dependencies); // 收集 DSL 相关信息(依赖、样式等)
const token = parser(collector, compMap, platform); // 解析 DSL 生成 Token(连接 DSL 与 Vue SFC 的桥梁)
const script = scriptCompiled(token); // 编译脚本部分(setup、methods 等)
const vueSfc = vueCompiled({ template: token.template, css: token.css, script, style: token.style }); // 组装 Vue 单文件组件
return await vueFormatter(vueSfc); // 格式化代码(提升可读性)
}
2. Collector:信息收集
Collector 核心作用是遍历 DSL,收集转换所需信息,为 Parser 解析和 Token 生成提供基础,核心收集内容如下表:
| 收集内容 | 说明 |
|---|---|
| imports | 组件库依赖导入(如 element-plus 的 ElButton、@vtj/ui 的 XIcon 等) |
| context | 节点上下文信息(如 v-for 的 item/index、插槽参数等) |
| style | 提取 DSL 中节点的内联样式,用于后续 CSS 组装 |
| urlSchemas | URL 远程组件的定义信息 |
| blockPlugins | 插件式区块组件的相关信息 |
3. Parser:核心解析(DSL → Token)
Parser 是 DSL 转代码的核心,负责将 Collector 收集的信息和原始 DSL 解析为 Token(连接 DSL 与 Vue SFC 的桥梁),包含组件渲染和脚本运行的所有关键信息。以下通过完整 DSL 与 Token 的转换示例,直观展示解析逻辑:
完整 DSL 与 Token 转换示例
输入原始 DSL:
javascript
// DSL Schema(核心字段:基础信息、响应式状态、组件结构等)
const dsl = {
id: 'tree-node-01', // 组件唯一 ID
name: 'TreeNode', // 组件名称
__VERSION__: '1756258696674', // 版本号
// 响应式状态
state: {
count: { type: 'JSExpression', value: '0' },
loading: { type: 'JSExpression', value: 'false' }
},
// Props 定义(核心必填项)
props: [{ name: 'node', required: true, type: 'Object' }],
// 核心方法(组件交互逻辑)
methods: {
handleClick: {
type: 'JSFunction',
value: "() => { this.$emit('node-click', this.node.name); }"
}
},
// CSS 样式(组件基础样式)
css: '.tree-item { padding: 8px; cursor: pointer; }',
// 节点树(组件模板结构,简化为核心节点)
nodes: [
{
id: 'tree-item-01',
name: 'div',
props: { class: 'tree-item', title: { type: 'JSExpression', value: 'node.name' } },
events: { click: { handler: { type: 'JSFunction', value: 'this.handleClick' } } },
children: [{ id: 'item-name-01', name: 'span', children: { type: 'JSExpression', value: 'node.name' } }]
}
]
};
输出 Token(对应 DSL 解析结果):
javascript
const token = {
id: 'tree-node-01',
version: '1756258696674',
name: 'TreeNode',
// 状态解析(简化为可直接用于 reactive 的格式)
state: 'count:0,loading:false',
// Props 解析(明确类型和必填项)
props: `node: { type:[Object], required: true, default: undefined }`,
// 方法解析(移除冗余 this.,简化语法)
methods: `handleClick() { $emit('node-click', node.name); }`,
// 依赖导入(根据 DSL 节点自动收集)
imports: `
import { defineComponent, reactive } from 'vue';
import { XIcon } from '@vtj/ui';`,
components: 'XIcon', // 组件注册
returns: 'XIcon', // setup 返回值
// 模板解析(转换为标准 Vue 模板语法)
template: `
<div class="tree-item" :title="node.name" @Click">
{{ node.name }}`,
css: '.tree-item { padding: 8px; cursor: pointer; }', // 样式直接复用
// 其他冗余字段简化为空(不影响核心逻辑)
inject: '', expose: '', watch: '', lifeCycles: '', computed: ''
};
Token 的 TS 类型定义
为保证 Token 格式规范,定义明确的 TS 类型,约束各字段格式:
typescript
// Token 类型定义:约束 DSL 解析后的数据格式
export interface Token {
id: string; // 组件唯一 ID
version: string; // 版本号
name: string; // 组件名称
state: string; // 响应式状态(简化后字符串)
props: string; // Props 定义
methods: string; // 方法定义
imports: string; // 依赖导入语句
components: string; // 组件注册列表
template: string; // Vue 模板字符串
css: string; // CSS 样式
// 其他可选字段(简化为空不影响核心逻辑)
inject: string; expose: string; watch: string; lifeCycles: string; computed: string;
returns: string; urlSchemas: string; blockPlugins: string; asyncComponents: string;
uniComponents: string[]; renderer: string;
}
4. Template:模板填充
Template 负责将 Token 填充到预设模板,转为 Vue 代码,采用类似 EJS 的模板语法,核心作用是组装规范的 Vue SFC 结构,模板示例如下:
javascript
// 模板填充核心:将 Token 数据注入模板,生成 Vue SFC 代码(类似 EJS 语法)
// 1. 脚本模板(生成组件核心逻辑)
const scriptTemplate = `
import { useProvider } from '<%= renderer %>';
export default defineComponent({
name: '<%= name %>',
<% if(props) { %> props: { <%= props %> }, <% } %> // 注入 props 定义
setup(props) {
const state = reactive({ <%= state %> }); // 注入响应式状态
return { state, props, provider };
},
<% if(computed) { %> computed: { <%= computed %> }, <% } %>
<% if(methods) { %> methods: { <%= methods %> }, <% } %>
});
`;
// 2. Vue SFC 模板(组装完整单文件组件)
const vueTemplate = `
<template><%= template %></template>
<script lang="<%= scriptLang %>"><%= script %></script>
<style lang="<%= styleLang %>" scoped><%= css %></style>
`;
注:该模板存在限制,仅能输出 Vue SFC 单文件组件代码,与部分样板间规范存在不兼容性。
5. Formatter:代码格式化
Formatter 是转换流程的最后一步,负责对 Vue SFC 代码进行格式化(采用 Prettier 等规则),确保代码规范、可读性强,此处不展开讲解。
4.2.3 源码转换为 DSL(双向转换)
VTJ 支持 DSL 与源码双向转换,"源码转 DSL"与"DSL 转源码"逻辑相反:用户修改源码后,Parser 解析源码并提取核心信息(组件结构、props 等),反向生成 DSL,同步更新至 DSL 编辑器和预览区,实现实时同步。
4.2.4 属性编辑器
属性编辑器与预设物料协同工作,预设物料加载后接入组件面板,用户可通过可视化操作配置组件属性(样式、props 等),配置信息实时同步至 DSL,进而更新预览区和代码编辑器,实现"可视化配置 → DSL → 预览/源码"的联动。
4.3 二次编辑实现原理
VTJ 二次编辑的核心逻辑的是:所有编辑操作均围绕 DSL 展开,通过不同方式修改 DSL 后,由渲染机制同步呈现预览效果,具体编辑方式分为 4 种,核心流程如下:
- DSL 编辑器:直接编辑 DSL,编辑完成后通过渲染机制实时渲染,是最直接的编辑方式;
- 属性编辑器:直接编辑 DSL 的 JSON 结构,编辑后无需额外转换,通过渲染机制实时渲染;
- 代码编辑器:依托双向转换机制,将修改后的源码转为 DSL,再同步渲染预览;
- AI 对话编辑:AI 生成源码后,通过双向转换机制同步转为 DSL,再由渲染机制呈现预览。
综上,VTJ 本质是一套基于 DSL 的自研低代码引擎,DSL 与源码的双向转换,是其二次编辑功能实现的核心基础。
4.4 核心特点
VTJ 基于 Vue 3 技术栈构建,围绕 DSL 打造完整低代码能力,其核心特点、优点及限制如下:
核心特点
- 基于 Vue 3 技术栈,采用 Composition API 开发,贴合 Vue 生态。
- 定义完整 DSL 协议,可全面描述页面结构、状态及交互信息。
- 采用运行时渲染,通过 Vue 的
h()函数动态渲染 DSL,实现实时预览。
优点
- 灵活性强,可编辑属性丰富,支持函数、对象等复杂类型编辑,适配多样需求。
- 核心引擎开源,支持基于源码二次开发与定制。
限制
- 技术栈受限:仅支持 Vue 技术栈,无法适配 React 等其他前端框架。
- 输出格式受限:仅能生成 Vue SFC 单文件组件,与部分样板间规范不兼容。
- 编辑范围受限:与 Weavefox 类似,VTJ 因依赖组件库存在编辑范围限制 ------ Weavefox 仅支持 JSX 节点编辑,VTJ 仅支持 VNode 节点编辑;非 VNode 节点(如列表页表头计算属性)无法直接编辑,需通过修改 DSL、源码或 AI 对话调整。
5. builder.io
5.1 核心特性简述
与 IntelliPro 现有模式相似度高,支持可视化编辑、代码编辑、AI 对话 3 种二次修改方式,重点说明核心效果与问题:
- AI 对话:直接 AI 对话进行修改;
- 可视化编辑:修改元素时自动生成 AI 对话完成编辑;
- 代码编辑:源码实时渲染,无延迟,编辑体验与标准开发一致,无预览与源码不一致问题。
6. 架构横向对比总结
| 维度 | V0.dev | Weavefox | VTJ | builder.io |
|---|---|---|---|---|
| 中间层 | 无(纯代码) | 无(纯代码) | JSON DSL | 无(纯代码) |
| 渲染方式 | 编译执行 | 编译执行 | 运行时 h() 函数 | 编译执行 |
| 代码出处获取 | 编译时 | 编译时 | 运行时 | 无 |
| 输出产物 | React 代码 | React 代码 | Vue 代码 | React 代码 |
7. IntelliPro 可能的实现方案
核心前提:明确 GUI 二次编辑范围(需编辑元素、可修改属性),梳理 4 种核心方案、2 种组合方案,对比实现成本与编辑能力。
7.1 核心方案及成本对比
| 实现成本\编辑能力 | 高编辑能力 | 低编辑能力 |
|---|---|---|
| 高实现成本 | 自研 AST 编译插件 | 低编辑能力、高成本(最差,不考虑) |
| 低实现成本 | 暂无方案(最佳,待探索) | 使用现有编译插件、可视化编辑转 AI 指令 |
方案 1:自研 AST 编译插件
核心:编译时记录源码信息,自研 AST 插件实现全元素、全属性编辑。
优势:可扩展性强,适配 IntelliPro 定制化;
劣势:开发、维护成本极高。
方案 2:使用现有编译插件
核心:参考 V0/Weavefox,复用现有插件,编译时记录源码,快速实现基础编辑。
优势:开发成本低、落地快;
劣势:仅支持 jsx 元素及基础属性编辑。
方案 3:可视化编辑转化为 AI 指令
核心:参考 builder.io,将可视化操作转为 AI 指令,由 AI 修改源码。
优势:开发成本低;
劣势:耗时长,易出现"所见非所得"。
方案 4:类似 VTJ 的 DSL 方案
核心:以 DSL 为中间层,参考 VTJ 双向转换机制。
优势:可编辑性强;
劣势:可行性低,生成源码难符合 IntelliPro 样板间规范,维护成本高。
7.2 组合方案
- 整体 GUI 渲染+局部代码真实渲染:降低耗时,存在预览与编辑不一致风险;
- 结合 Weavefox 与 React Devtools 原理,可识别自定义组件的 props,且所有组件都支持可视化编辑,但存在两点限制:需依据组件物料完成 props 配置的补全;仅 JSX 节点可映射回源码位置。
7.3 DSL 与 Code 互转可行性分析
该方向可行性低,核心原因:
- 生成源码可读性、维护性差(以 VTJ 为例:仅支持 Vue SFC,复杂页面维护难度高);
- React 语法灵活,Hooks 组合多样,反向解析难度大。