揭秘!TinyEngine低代码源码如何玩转双向转换?
本文由TinyEngine低代码源码转换功能贡献者张珈瑜原创。
背景
当前主流低代码平台普遍采用“单向出码”模式,仅支持将 DSL(Domain Specific Language,领域特定语言)转换为 Vue 或 React 源代码。一旦开发者在生成代码后手动修改了源码,平台通常无法将这些修改同步回可视化编辑器,导致代码与可视化配置割裂,严重影响开发效率与协同维护。本项目旨在构建低代码 Vue/React 源代码到 DSL 的反向转换机制,打通可视化搭建与源码开发之间的断层,实现从 UI 配置到源码编写的无缝协同。
Vue-To-DSL 方案
目标
将 Vue 单文件组件(SFC)、整包工程或 ZIP 压缩包逆向转换为 TinyEngine 所需的 DSL Schema。
核心依赖
-
@vue/compiler-sfc/@vue/compiler-dom:解析 SFC 与模板 AST -
@babel/parser/traverse/types:脚本 AST(支持 TS/JSX) -
jszip:ZIP 文件读取(Node 与浏览器双端支持) -
vue:仅用于类型对齐
数据流
解析流程详解
1. SFC 粗分
- 使用
@vue/compiler-sfc.parse获取descriptor - 提取
template/script/scriptSetup/styles/customBlocks - 保留语言类型(如
lang="ts")和 scoped 状态
2. 模板解析(Template)
- 使用
@vue/compiler-dom.parse构建 AST - 递归生成组件树节点
{ componentName, props, children } -
指令处理:
-
v-if→condition: JSExpression -
v-for→loop: { type: 'JSExpression', value: '...' } -
v-model/v-show/v-on/v-bind→ 映射至 props 或事件 -
v-slot→slot: name
-
-
文本节点:
- 纯文本 →
Text组件 - 插值表达式 →
Text+JSExpression
- 纯文本 →
-
组件名归一化:
- 优先使用
componentMap - HTML 原生标签保留小写
-
tiny-icon-*→ 统一为Icon,name属性设为 PascalCase 名称
- 优先使用
3. 脚本解析(Script)
- 使用 Babel 解析 TS/JSX
-
组合式 API(script setup):
-
reactive()/ref()→state -
computed()→computed - 顶层函数 →
methods -
onMounted等 →lifecycle
-
-
Options API:
- 识别
data/methods/computed/props/ 生命周期钩子
- 识别
-
源码恢复:
- 利用 AST 节点位置切片还原函数体
- 箭头函数转为命名函数字符串
- 错误处理:非 strict 模式下收集错误,不中断流程
4. 样式解析(Style)
- 合并所有 `` 块内容
- 记录
scoped与lang - 提供辅助工具(不直接写入 Schema):
-
parseCSSRules:抽取 CSS 规则 -
extractCSSVariables:提取 CSS 变量 -
extractMediaQueries:媒体查询识别
-
5. Schema 生成与归一化
-
Page Schema:
- 根节点为
Page,自动填充fileName、meta、id - 行为域统一包装为
JSFunction/JSExpression - 深度清理多余空白字符
- 根节点为
-
App Schema(多页面聚合):
- 页面:
src/views/**/*.vue - 国际化:
src/i18n/{en_US,zh_CN}.json - 工具函数:
src/utils.js(正则解析导出项) - 数据源:
src/lowcodeConfig/dataSource.json - 全局状态:
src/stores/*.js(轻量识别 PiniadefineStore) - 路由元信息:从
src/router/index.js提取name/path/isHome
- 页面:
6. 转换器接口
convertFromString(code, fileName?)convertFromFile(filePath)convertMultipleFiles(files)convertAppDirectory(appDir)convertAppFromZip(zipBuffer)
React-To-DSL 方案
目标
将单个 React 组件(JSX/TSX)逆向转换为 TinyEngine 可消费的 DSL(IAppSchema),当前聚焦 单文件 → 单页面/区块 场景。
核心依赖
-
@babel/parser/traverse/generator:AST 解析与代码生成 -
nanoid:生成唯一 ID
转换流程
关键步骤说明
1. AST 解析
- 启用
jsx+typescript插件 - 定位首个返回 JSX 的函数/类组件
- 记录
useState初始值节点、组件定义路径
2. JSX → children 树构建
-
组件名:
-
JSXIdentifier→ 直接使用 -
JSXMemberExpression→ 拼接如Form.Item - 兜底为
Fragment
-
-
Props 处理:
- 字面量 → 直接值
- 表达式 →
JSExpression - Spread 属性 → 特殊 key
'...'
-
Children:
- 文本 → 包装为
span+props.children - 表达式容器:
- 若为
arr.map(item => )→ 提取arr作为loop - 否则 →
Fragment+JSExpression
- 若为
- 文本 → 包装为
3. 表达式序列化
- 字面量(string/number/bool/null)→ 原值
- 对象/数组 → 递归处理,Spread 元素标记为
'...' - 其他表达式(函数调用、三元等)→ 源码字符串 +
JSExpression
4. State 与方法提取
-
State:仅首个
useState的初始值 -
Methods:
- 函数组件:顶层函数声明或变量赋值函数
- 类组件:非
render的ClassMethod或箭头属性
-
生命周期:类组件中的
componentDidMount等白名单方法
5. 组件归一化
- 应用
defaultComponentMap(如Form→TinyForm) -
DatabaseOutlined→Icon+props.name = 'IconPanelMini' -
style对象 → 转为kebab-case: value;字符串 -
value→modelValue(适配 Tiny 组件)
6. Schema 装配
-
PageSchema:
-
componentName:'Page'或'Block' -
meta: 默认isHome=true,router='/' -
children: 来自 JSX 树 -
state/methods/lifeCycles: 提取结果
-
- AppSchema:包裹单个 Page,其余字段初始化为空
(本项目为开源之夏活动贡献,欢迎大家体验并使用)源码可参考:
快速上手
前置条件: 已安装 Node.js (>=18)、pnpm (>=8)、git,本地 8090 端口可用。
启动项目
git clone -b ospp-2025/source-to-dsl https://github.com/opentiny/tiny-engine.git
cd tiny-engine
pnpm install
pnpm dev
启动成功后,访问 http://localhost:8090/?type=app&id=1&tenant=1。
可视化导入 Vue 文件
进入上方地址后,点击页面右上角的“导入”。支持三种来源:
- 单个
.vue文件:适合导入单页或区块。 - 项目目录:自动识别
src/views下的页面文件。 - ZIP 压缩包:打包后的 Vue 项目一键导入。
导入流程(任选其一):
- 单个
.vue文件
- 选择“单页上传”,挑选本地
.vue文件。 - 若存在同名页面,按提示“覆盖/跳过/全部覆盖”进行处理。
- 导入完成后,在“静态页面”列表中可见,双击打开编辑。
- 项目目录
- 选择“目录上传”,指向本地项目根目录。
- 系统自动扫描
src/views并导入页面;遇到重名同样可选择覆盖策略。 - 完成后,页面会按目录结构展示在左侧列表。
- ZIP 压缩包
- 选择“项目压缩包”,上传打包好的 Vue 项目 zip。
- 支持批量导入与重名处理,完成后即可在列表中浏览与打开。
选择单页vue文件上传方式进行导入:
由于已经存在CreateVm页面,弹出了提示框,需要选择是否覆盖,这里点击确定:
在静态页面列表可查看到导入的页面,双击即可点开:
选择项目目录或项目压缩包上传方式进行导入:
选择目录导入则选择本地的目录进行上传:
选择项目压缩包则选择vue项目zip进行上传:
导入时有重名文件则会弹出提示框,选择是否覆盖,这里选择全选+确定:
可以看到整个项目已经被导入到可视化编辑器了
React-To-DSL 测试用例
当前 React-To-DSL 以测试用例形态展示能力,可在包内直接运行:
cd packages/react-to-dsl
pnpm test
输入样例:查看用例中的 React 组件源码(JSX)
输出结果:测试通过时会生成/断言对应的 DSL 结构,便于对照验证
(本项目为开源之夏活动贡献,欢迎大家体验并使用)源码可参考:
结语
本项目成功实现了 Vue/React 源码 ↔ DSL 的双向转换机制,有效解决了低代码平台“单向出码”导致的协同断层问题。通过模块化解析、健壮的错误处理与灵活的组件映射策略,确保了转换的准确性与实用性。
感谢导师的悉心指导,以及 OpenTiny 社区与开源之夏活动组委会的支持,让我有机会参与这一具有实际价值的开源项目!
关于OpenTiny
欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:
OpenTiny 代码仓库:
TinyVue 源码:
TinyEngine 源码:
欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~
如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~