jeremygo

jeremygo

我是把下一颗珍珠串在绳子上的人

Vue 可視化模組引擎

背景#

團隊內行銷活動及落地頁需求迭代比較頻繁,經過調研後在集團自研可視化平台基礎上自建了更貼近團隊業務特點的行銷可視化搭建平台。在渲染層實現上,考慮到較多歷史專案頁面仍屬於核心業務需要長期迭代維護,除了常規的頁面框架渲染外還需要擴展支持模組級渲染,讓歷史專案能直接通過正常的元件形式低成本引入使用,因此設計實現了一個可視化模組引擎,已在團隊內兩個業務線專案中推廣使用,特此總結記錄一下。

具體實現#

首先考慮模組引擎的基礎形態與功能:

形態上,團隊內 C 端的 H5 專案技術棧都是 Vue,因此這個渲染引擎的基礎形態就是一個 Vue 元件(從專案接入體驗考量,實際對外導出的是一個 Vue 插件,內部會全局註冊上這個元件);

功能上,保證開箱即用的體驗,至少應具備獲取配置物料及渲染的閉環邏輯,因此渲染引擎需要知道配置物料對應的 json key、接入專案服務的環境(區分配置物料的請求地址)以及元件渲染源(即打包後的元件庫代碼),同時還需要考慮到業務專案與內部元件靈活地通信與互動,模組引擎需要支持任意的屬性與監聽事件透傳;

最終對外的元件 API 設計如下:

Props:

屬性說明類型
s支持直接傳入 json key,模組引擎可據此獲取配置物料string
env獲取配置物料對應的請求環境 (測試、預發、線上)string
widgets元件渲染源,模組引擎解析配置物料時匹配對應元件處理function
props業務元件自定義屬性,模組引擎會合併傳入至所有配置元件下object

Events:
@getXpubData(data)
獲取到配置物料後的回調函數,可以基於配置數據自定義處理邏輯
@xxx(...)
業務元件自定義事件,模組引擎會合併監聽所有配置元件上拋的對應事件

至於 Vue 元件中常用的 Slot 插槽,因其作用是自定義元件內部特定位置的渲染內容,而這一部分由可視化編輯器完全接管,所以在此不考慮。

專案接入示例:

import Roo from '@vision/roo'
// 業務元件庫需要自行安裝,引入相應 js/css 源文件
import renderWidgets from '@vision-xxx/dist/h5/index.js'
import '@vision-xxx/dist/h5/index.css'

// 插件安裝
Vue.use(Roo, {
  renderWidgets: [
    renderWidgets
  ]
})

// 全局元件使用
<template>
  <roo-component
    // 傳入 json key 與相應環境由模組引擎內部請求獲取
    s="op-json-xxx"
    env="production"
    // 傳入的 props 會注入到內部使用的所有元件內
    props="{
      token: 'xxx'
    }"
    // 可監聽元件內暴露的事件作相應處理,同名事件會重複觸發
    @customEvent="eventHandler"
    // 模組引擎內暴露的物料配置接收事件
    @getXpubData="handlerXpubData"
  />
</template>

其次在核心的渲染邏輯上,將可視化編輯器發布產出的標準配置數據進行解析轉換生成渲染函數,詳細流程如下:

  • 首先會依據 json key 從 cdn 請求獲取物料配置數據;
// 配置數據示例
{
  // 模組 id 
  "id": "000",
  // 頁面標題
  "title": "問卷",
  // 通用的全局相關擴展字段
  "global": {
    "dir": "ltr",
    "code": {
      "js": "",
      "css": "",
      "jsForHeader": ""
    },
    "share": {},
    "mixins": {
      "globalmixin": {
          "pageStatus": 1
      }
    },
    "pubArea": "dpub",
    "canShare": true,
    "onlineUrl_json": "http://xxx.com/op-json-xxx.json"
  },
  // 頁面元件、樣式展示相關字段
  "scenes": {
    "music": {
      "url": "",
      "name": ""
    },
    // 頂部節點樣式
    "style": {
      "overflow": "auto",
      "minHeight": 603,
      "backgroundSize": "100% 100%",
      "backgroundColor": "rgba(0,0,0,0)",
      "backgroundImage": "",
      "backgroundRepeat": "no-repeat",
      "backgroundPosition": "center center"
    },
    "dsList": [],
    // 元件列表配置
    "layers": [
      {
        // 元件 id、key
        "id": "widget-xxx",
        "key": "ManhattanQuestionnaireNps",
        // 元件名稱
        "name": "小問卷 - NPS",
        // 元件元素類型
        "type": "ManhattanQuestionnaireNps",
        "label": "",
        // 元件傳入屬性
        "props": {
          "img": {
            "url": "https://xxx.com/xxx.png",
            "fileName": "nps背景.png"
          },
          "maxText": "10分肯定會推薦",
          "minText": "0分絕對不會推薦"
        },
        // 元件樣式
        "style": {
          "top": 0,
          "left": 0,
          "color": "#333",
          "right": "unset",
          "border": "0px solid #000000",
          "boxShadow": "#000000 0px 0px 0px 0px",
          "marginRight": 0,
          "marginBottom": 0,
          "backgroundColor": ""
        },
        "id_name": "小問卷 - NPS3",
        "effectList": [],
        "editorStatus": {
          "show": true,
          "active": false,
          "isLock": false,
          "status": 0
        }
      }
    ]
  }
}
  • 依據配置數據中的頁面狀態字段 global?.mixins?.globalmixin?.pageStatus 判斷是否展示,不展示則直接返回空節點(模組配置下線場景),展示則上拋 getXpubData 事件(見上文定義);
  • 結合 widgets 元件渲染源生成渲染函數返回:
    • 從配置數據中獲取元件列表 layers,遍歷增加傳入的自定義屬性與監聽事件對象(當前元件上下文中的 listeners),分別掛載到每個元件對象的 propson 字段上,生成一個初始的頂部節點對象;
      // 頂部節點對象示例
      {
         type: 'div',
         style: {
           "overflow": "auto",
           "minHeight": 603,
           "backgroundSize": "100% 100%",
           "backgroundColor": "rgba(0,0,0,0)",
           "backgroundImage": "",
           "backgroundRepeat": "no-repeat",
           "backgroundPosition": "center center"
         },
         children: [
           {
             "id": "widget-xxx",
             "key": "ManhattanQuestionnaireNps",
             "name": "小問卷 - NPS",
             "type": "ManhattanQuestionnaireNps",
             "label": "",
             "props": {
               "img": {
                 "url": "https://xxx.com/xxx.png",
                 "fileName": "nps背景.png"
               },
               "maxText": "10分肯定會推薦",
               "minText": "0分絕對不會推薦",
               // 掛載的傳入屬性
               "token": "xxx"
             },
             // 掛載的事件對象
             "on": {
                "customEvent": eventHandler
             },
             "style": {
               "top": 0,
               "left": 0,
               "color": "#333",
               "right": "unset",
               "border": "0px solid #000000",
               "boxShadow": "#000000 0px 0px 0px 0px",
               "marginRight": 0,
               "marginBottom": 0,
               "backgroundColor": ""
             },
             "id_name": "小問卷 - NPS3",
             "effectList": [],
             "editorStatus": {
               "show": true,
               "active": false,
               "isLock": false,
               "status": 0
             }
           }
         ],
       }
      
    • 依據頂部節點對象生成 createElement 渲染函數字串:
      • 首個標籤參數區分 HTML 保留元素與元件元素兩種類型;
      • 第二個參數數據對象將 propsstyleattrson 轉成對應的字串形式,其中 style 數值統一轉換成 vw 基準,on 事件對象使用全局的自定義事件池進行事件註冊及觸發處理;
      • 最後的子級元素參數遞歸執行生成。
    • 將渲染函數字串通過 new Function 生成函數聲明並將 widgets 綁定至函數作用域上(匹配對應元件元素標籤)後返回渲染函數。

最終返回的渲染函數交由專案中的 Vue 渲染器進行渲染展示。

專案中完整的配置接入及解析流程如下:

image

接入流程性能優化#

模組引擎可以接入到頁面中的任意位置,默認由模組引擎內部閉環獲取配置數據與渲染的流程如下:

image

對於性能要求非常高的頁面,上面的流程導致元件整體的加載渲染時間過長,最終影響頁面整體的可互動時長,因此在這種場景下需要考慮以更合理的方式做接入。

在高性能要求的業務專案中,我們提供了一套新的加載流程方案,將請求物料配置與各元件內請求業務數據提前至統一的頁面初始化請求接口中調用,模組引擎只做靜態渲染,因此對模組引擎新增擴展了一個 data 屬性用於直接接收配置數據場景。

同時不同業務元件的請求接口與處理邏輯可能都會不同,我們也約定了一套通用的業務接口與數據處理函數的配置標準,對應實現了一個解析處理的 SDK,在初始化接口調用時直接調用該 SDK 即可。

獲取數據前置後的渲染流程如下:

image

大部分業務專案也都存在前置初始數據獲取節點,接入後整體可互動時長基本無影響。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。