博客 / 詳情

返回

揭秘!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-ifcondition: JSExpression
    • v-forloop: { type: 'JSExpression', value: '...' }
    • v-model / v-show / v-on / v-bind → 映射至 props 或事件
    • v-slotslot: name
  • 文本節點

    • 純文本 → Text 組件
    • 插值表達式 → Text + JSExpression
  • 組件名歸一化

    • 優先使用 componentMap
    • HTML 原生標籤保留小寫
    • tiny-icon-* → 統一為 Iconname 屬性設為 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)

  • 合併所有 <style> 塊內容
  • 記錄 scopedlang
  • 提供輔助工具(不直接寫入 Schema):

    • parseCSSRules:抽取 CSS 規則
    • extractCSSVariables:提取 CSS 變量
    • extractMediaQueries:媒體查詢識別

5. Schema 生成與歸一化

  • Page Schema

    • 根節點為 Page,自動填充 fileNamemetaid
    • 行為域統一包裝為 JSFunction / JSExpression
    • 深度清理多餘空白字符
  • App Schema(多頁面聚合):

    • 頁面:src/views/**/*.vue
    • 國際化:src/i18n/{en_US,zh_CN}.json
    • 工具函數:src/utils.js(正則解析導出項)
    • 數據源:src/lowcodeConfig/dataSource.json
    • 全局狀態:src/stores/*.js(輕量識別 Pinia defineStore
    • 路由元信息:從 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

    • 函數組件:頂層函數聲明或變量賦值函數
    • 類組件:非 renderClassMethod 或箭頭屬性
  • 生命週期:類組件中的 componentDidMount 等白名單方法

5. 組件歸一化

  • 應用 defaultComponentMap(如 FormTinyForm
  • DatabaseOutlinedIcon + props.name = 'IconPanelMini'
  • style 對象 → 轉為 kebab-case: value; 字符串
  • valuemodelValue(適配 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 項目一鍵導入。

導入流程(任選其一):

  1. 單個 .vue 文件
  2. 選擇“單頁上傳”,挑選本地 .vue 文件。
  3. 若存在同名頁面,按提示“覆蓋/跳過/全部覆蓋”進行處理。
  4. 導入完成後,在“靜態頁面”列表中可見,雙擊打開編輯。
  5. 項目目錄
  6. 選擇“目錄上傳”,指向本地項目根目錄。
  7. 系統自動掃描 src/views 並導入頁面;遇到重名同樣可選擇覆蓋策略。
  8. 完成後,頁面會按目錄結構展示在左側列表。
  9. ZIP 壓縮包
  10. 選擇“項目壓縮包”,上傳打包好的 Vue 項目 zip。
  11. 支持批量導入與重名處理,完成後即可在列表中瀏覽與打開。

選擇單頁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 標籤,一起參與開源貢獻~

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.