本文由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: JSExpressionv-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()→statecomputed()→computed- 頂層函數 →
methods onMounted等 →lifecycle
-
Options API:
- 識別
data/methods/computed/props/ 生命週期鈎子
- 識別
-
源碼恢復:
- 利用 AST 節點位置切片還原函數體
- 箭頭函數轉為命名函數字符串
- 錯誤處理:非 strict 模式下收集錯誤,不中斷流程
4. 樣式解析(Style)
- 合併所有
<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 => <El />)→ 提取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,其餘字段初始化為空
(本項目為開源之夏活動貢獻,歡迎大家體驗並使用)源碼可參考:
https://github.com/opentiny/tiny-engine/tree/ospp-2025/source-to-dsl
快速上手
前置條件: 已安裝 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 結構,便於對照驗證
(本項目為開源之夏活動貢獻,歡迎大家體驗並使用)源碼可參考:
https://github.com/opentiny/tiny-engine/tree/ospp-2025/source-to-dsl
結語
本項目成功實現了 Vue/React 源碼 ↔ DSL 的雙向轉換機制,有效解決了低代碼平台“單向出碼”導致的協同斷層問題。通過模塊化解析、健壯的錯誤處理與靈活的組件映射策略,確保了轉換的準確性與實用性。
感謝導師的悉心指導,以及 OpenTiny 社區與開源之夏活動組委會的支持,讓我有機會參與這一具有實際價值的開源項目!
關於OpenTiny
歡迎加入 OpenTiny 開源社區。添加微信小助手:opentiny-official 一起參與交流前端技術~
OpenTiny 官網:https://opentiny.design
OpenTiny 代碼倉庫:https://github.com/opentiny
TinyVue 源碼:https://github.com/opentiny/tiny-vue
TinyEngine 源碼: https://github.com/opentiny/tiny-engine
歡迎進入代碼倉庫 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~
如果你也想要共建,可以進入代碼倉庫,找到 good first issue 標籤,一起參與開源貢獻~