vivo 商品中臺的視覺化微前端實踐

vivo網際網路技術發表於2022-03-29

一、背景

在電商領域內,商品是一個重要組成部分,與其對應的商品管理系統,則負責商品的新建、編輯、複製等功能。隨著商品管理系統的成熟穩定和業務上的擴充套件需求,催化出了商品中臺的誕生。它可以將現有商品功能最大效率的複用在很多業務上(公司內業務、公司外業務等)。而不是僅限於當前團隊的業務使用。

在設計商品中臺的前端系統時,我們使用了微前端和視覺化技術,其可以達到如下效果:

  • 視覺化技術可以讓各個業務方的運營等相關人員,直觀的看到其配置的資料在頁面上的展示效果;

  • 微前端可以幫助商品中臺更快更好的適配到各個業務方的專案中。

至此,本篇文章的背景介紹完畢,下面將會闡述如何在商品中臺前端系統中做微前端和視覺化。

二、視覺化技術

目前商品中臺的頁面如下圖所示:

圖片

圖中左側內容,就是商品視覺化,它的核心能力如下:

  • 圖中右側所有的變動,都能在左側得到實時更新和展示,如主圖、 sku 組合、價格、圖文詳情、商品引數等功能。

  • 圖中左側的視覺化區域是一個標準的 h5 頁面,可以把它看成一個子頁面,它與外層的父頁面在 ui 上是完全隔離的,同時在資料上又是共享的。

下面我將會對視覺化技術原理做完整闡述,請大家繼續往下看。

三、視覺化技術原理

視覺化整體技術原理圖如下:

圖片

從上圖可以獲得以下資訊:

  • 子視窗用 iframe 展示;

  • 子視窗用 vuex 做狀態管理;

  • 子視窗和父視窗通過共享狀態 ( vue store )來完成資料通訊。

看到這,小夥伴可能會有以下疑問:

iframe 和父視窗的資料通訊是通過 postMessage 完成的,這裡為什麼不使用 postMessage 呢?

使用 vuex 完成 iframe 資料通訊是如何實現的呢?

回答第一個疑問:為什麼不使用 postMessage

實踐過的小夥伴應該深有體會,使用 postMessage 的話,資料通訊架構基本如下圖所示:

圖片

結合上圖可以分析出 postMessage 有以下缺點:

  • 父視窗含有大量邏輯:父視窗需要將 vuex 的資料進行處理,然後通過 postMessage 進行傳輸;

  • 資料通訊方式不純粹:vuex 和 postMessage 組合在一起,互相轉換,使資料通訊更加複雜和難以控制;

  • 不支援 Vue.set , Vue.delete 等;

  • postMessage 只能同步字串,不能 fn。

綜合上面的缺點,在資料通訊方面,沒有使用 postMessage ,而是使用 vuex 替換掉 postMessage ,來完成 iframe 通訊。

回答第二個疑問:使用 vuex 完成 iframe 資料通訊是如何實現的呢?

這個問題的答案就是 uni-render 。通過它,可以做到讓子視窗通過 iframe 展示的同時,父子視窗共享 store 。那這個 uni-render 是什麼呢,可以繼續往下讀,將在下文給於第二個疑問的具體回答。

3.1 uni-render

uni-render 是一個讓父子視窗可以不用 postMessage 就能共享 vue store 的技術方案。它包含以下關鍵內容:

  • 將 iframe 當成一個 dom 節點;

  • 父視窗渲染子視窗( iframe )暴露的元件;

  • 父子視窗共享 vue store;

uni-render 的技術原理圖如下:

圖片

這裡,我結合商品中臺配置視覺化區域做一個通俗解釋:

  • 首先我們把 vue 專案設定為多頁應用,頁面分別是商品預覽頁、商品管理頁;

  • 其次,調整 vue 入口,每個頁面對應一個入口;

  • 編寫 iframe 元件和沙箱 vue;

  • 在商品管理頁入口將沙箱 vue 和 store 掛載到 global 物件下;

  • 在商品預覽入口將 global.parent 下的沙箱 vue 和 store 分別掛到 window 下和 global 下;

  • 最後,其他的內容按照 vue 多頁寫法正常編寫即可。

通過上述 6 個步驟,就可以讓用 iframe 做展示容器的商品預覽頁和商品管理頁共享 store 啦。

這裡,小夥伴可能會有疑問,為什麼要使用沙箱 vue 呢?

這是因為 vue 的單例機制,子視窗(商品管理頁)由父視窗(商品管理頁) new Vue 渲染的, 因此在子視窗中使用 use 、 filter 、 mixin 、 全域性指令 、 全域性元件等, 會覆蓋父視窗 vue 物件。所以需要隔離出一個乾淨的和 vue 一樣的 vue ,然後用隔離出的沙箱 vue 來渲染子視窗(商品預覽頁)的內容。這樣就可以 達到父子視窗的 vue 互不影響。

下面將介紹一些具體實現,如 iframe 元件、沙箱 vue 、入口設計。iframe 元件的實現非常簡單,如下圖所示:

圖片

這個不再解釋了。

沙箱 vue 的實現非常巧妙,如下圖所示:

圖片

在 Function 上掛一個 $$clone 函式,這樣 vue 下就會有 $$clone 函式,通過執行 Vue.$$clone() ,將 vue 的各種屬性掛載到 SandboxVue 上。同時返回 SandboxVue 。即可得到一個乾淨的沙箱 vue 。

注意:這裡的 vue 指的是 vue2 ,目前 vue3 不是單例機制,在 vue3 中是不需要沙箱 vue 的。vue 多頁入口設計,如下圖所示:

圖片

對應上文通俗解釋 4、5步驟。

至此,uni-render 技術方案闡述完了。眾所周知,大多數 h5 、 pc 建站的資料通訊方案都繞不開 postMessage 。而我們通過 uni-render ,讓父視窗和 iframe 子視窗的資料通訊不再需要 postMessage ,同時只使用 vue 生態中的 vuex 做資料通訊。這帶來了非常多的好處,好處如下:

  • 統一資料通訊方案;

  • 對 store 資料的 watch 、 computed 、更加純正,資料通訊功能更加強大;

  • 精簡程式碼,和 postMessage 永久告別;

  • 支援同步函式,前方的路是星辰大海。

3.2 視覺化總結

綜上,商品中臺的視覺化介紹完了,我們通過 uni-render 技術方案讓商品預覽頁( iframe )和商品管理頁的資料通訊只通過 vuex 即可完成。讓視覺化的實時更新更加流暢,視覺化互動更加強大。

介紹完視覺化,下面我將繼續介紹商品中臺在微前端上的實踐,請大家繼續往下閱讀。

四、商品中臺微前端

這裡我們把商品中臺設計成了微前端架構,使其能夠完全適應複雜的外部業務。

這時,你可能會問,微前端是什麼?

4.1 微前端是什麼

概念如下:多個小型應用聚合為一個應用供使用者使用,每個小型應用可以獨立開發、獨立執行、獨立部署,與技術棧無關。大家可以把主應用想象成商場,那子應用就是商戶,這樣就好理解了。

注意:商品中臺不是主應用,它是一個嵌入外部業務的子應用,不具備外部業務嵌入它本身。

微前端和普通的前端有什麼區別呢?區別概括在下圖中:

圖片

結合微前端概念,再閱讀上圖,可以感受到微前端所帶來的優勢和價值。

4.2 為什麼要做微前端

整體概括下,主要有以下兩個目的:

  • 將商品中臺更快、更好的嵌入到各個業務方專案中;

  • 為後面主應用的設計做準備。

因此,我們把商品中臺專案設計成了微前端架構,它可以很好的解決前端中臺化所面臨的各種問題。知道了目的,那麼我們是如何去設計微前端的呢?

五、商品中臺微前端設計

目前微前端領域最主流的技術方案有以下兩種:

  • single-spa 技術方案;

  • iframe 技術方案;

基於這兩種技術方案,業界產出一些成熟的框架,如 qiankun 、 qingtian ( vivo 自研)等。設計架構如下簡圖所示:

圖片

下面將介紹商品中臺使用 qiankun 框架設計的微前端架構的技術實現。

5.1 qiankun 方案設計架構

為什麼使用 qiankun ,最核心的原因是:在國內,使用最多的微前端框架就是 qiankun 。整體效果也不錯,所以我們的中臺需要設計 qiankun 技術架構來適配那些基於 qiankun 的業務。

在說設計架構前,先給大家介紹下 qiankun 的技術原理,如下圖所示:

圖片

從圖中可以知道, qiankun 的核心在於建立微應用容器。瞭解技術核心後,下面開始介紹設計架構。設計架構如下圖所示:

從圖中可以知道,主要有 8 塊內容,下面將會依次介紹這 8 塊內容。

5.1.1  程式碼接入

主應用內註冊微應用

{
  name: 'goods',
  entry: initEntry('goods'),
  container: '#root-view',
  render,
  // activeRule 作為區分不同微應用的關鍵字
  activeRule: genActiveRule('/main/goods'),
  props: msg
}

微應用入口

圖片

微應用打包

5.1.2 介面跨域

解決介面跨域,主要有以下兩種方式:

  • 主應用轉發:介面的 host 與主應用一致,由主應用根據路徑關鍵字 cmmdy 進行轉發。

  • 微應用配置:微應用服務端配置允許跨域

這裡我們選擇了第一種方式,也就是主應用轉發。

5.1.3 路由適配

這裡需要注意的是:微應用 router 需要新增 baseUrl ,並且要與主應用關鍵字 activeRule 保持一致。如下程式碼(簡寫)所示:

const KEY = 'product'
router = new VueRouter({
  mode: 'history',
  base: IN_CMS ? `/main/goods/${KEY}` : `/${KEY}`,
  routes
})

這個 KEY 變數就是關鍵字。

5.1.4 多頁設定

目前多頁設定的程式碼如下圖所示:

圖片

每個頁面作為一個獨立的微應用引入, filename 設定與主應用 activeRule 值保持一致。

5.1.5 資料通訊

思考一個問題,主應用與微應用之間如何通訊?通訊這塊,主要有兩種方案:

  • initGlobalState:也是執行時通訊(官方方案);

  • window:掛載到 window 下。

initGlobalState 方案的優缺點如下:

優點:api 提供了資料的 change 事件,雙方均能監聽到資料變化。

缺點:微應用載入時,獲取初始資料的時機太晚 ,不適合用作微應用資料的初始化。

window 方案的優缺點如下:

優點:微應用程式碼全週期內均可以獲取資料,很好的避免官方方案中獲取資料太晚的問題。

缺點:需要自己處理對資料變化的監聽。

商品中臺採取的資料通訊方案是結合了上述兩種方案,互相取長補短,都有使用。

5.1.6 環境區分

主要有以下兩種場景:

  • 區分 qiankun 與非 qiankun 技術棧:使用 window.__POWERED_BY_QIANKUN__ 即可判斷。

  • 區分同樣使用 qiankun 的不同主應用:主應用與微應用之間約定引數,通過 window 物件或者生命週期函式中的 props 物件傳遞,來進行判斷。

5.1.7 本地聯調

思考一個問題,本地沒有主應用的服務,怎麼實現主應用與微應用間的快速聯調?解決方案如下:

主應用註冊微應用時,將 entry 設定為從 localstorage 中獲取,在 localstorage 中手動修改入口 entry 的值為微服務的本地地址,就可以實現本地的聯調。核心程式碼如下:

const timestamp = new Date().getTime()
const initEntry = (subSys) => {
  const LS_KEY_ENTRY = `__entry__${subSys}`
  const customEntry = localStorage.getItem(LS_KEY_ENTRY)
  if (customEntry) {
    return `${customEntry}`
  }
  if (subSys === 'goods') {
    return `//vshop-commodity.vivo.com.cn/goods/?t=${timestamp}`
  }
  return `${location.origin}/${subSys}/?t=${timestamp}`
}

通過上述程式碼,即可在主應用中對入口地址進行動態適配,達到靈活聯調的目的。這塊還可以將其做成配置中心的形式,這樣就不用在 localstorage 中手動修改入口地址。

5.1.8 許可權管理

這部分是屬於業務強相關內容,做好頂層解耦即可,本文不再闡述。

5.1.9 qiankun 設計架構總結

到此,商品中臺的 qiankun 設計架構的核心內容闡述完了,使用 qiankun 的過程中,也遇到了一些問題,但使用這種流行框架的好處就是, 遇到問題時,可以去查閱相關文件和部落格,基本上都可以找到相關的解決思路。基於 qiankun 的設計架構,表現出了不錯的效果,也讓商品中臺能夠以 signle-spa 的 技術方案嵌入到其他業務專案中。

5.2 踩坑經驗分享

過程中遇到的坑較多,本文挑選幾個踩坑經驗分享出來。

5.2.1 uni-render 相遇 qiankun 跨域問題

現象:專案接入主應用,uni-render 控制的預覽頁面空白,控制檯報跨域錯誤。

原因:iframe 預覽頁面為商品中臺域名,而子應用接入主應用後為主應用域名,從而導致跨域。

解決方案:主應用、子應用 html 入口檔案頭部設定 document.domain ,使兩者 domain 保持一致。

5.2.3 uni-render 、qiankun 、 ueditor ”化學反應“

問題一:

現象:qiankun 子應用中富文字元件 ueditor 功能異常。

原因:qiankun 對 ueditor 劫持,導致 ueditor 某些變數無法獲取到。

解決方案:在主應用中,通過 excludeAssetFilter 讓 ueditor 的靜態資源不要被 qiankun 劫持處理。

問題二:

現象:子應用中 ueditor 的請求 url 報錯。

原因:ueditor 的請求 url 沒加主應用請求字首。

解決方案:子應用環境中,通過 ue.getActionUrl 給 ueditor 的請求 url 增加字首。

問題三:

現象:子應用中 ueditor 單圖上傳失敗。

原因:子應用設定了 domain , ueditor 的單圖上傳是通過 iframe 實現的,但是 iframe 沒有設定 domain ,導致上傳失敗。

解決方案:重寫 ueditor 的單圖上傳,將 iframe 改為 xhr 上傳。

至此,踩坑經驗也分享完了,還有一些踩坑,這裡就不再敘述了。在將幾個技術融合在一起的過程中,總是會有一些預想不到的問題,正所謂兵來將擋、水來土掩,我們用正確的心態去面對和解決這些問題即可。

六、總結

綜上,我們對商品中臺的視覺化和微前端做了整體的闡述,包含以下內容:

  • 通過 uni-render 技術方案解決了視覺化頁面 iframe 和父頁面的資料通訊問題。

  • 通過 qiankun 解決了商品中臺嵌入基於 single-spa 技術方案的外部業務。

  • uni-render 結合 qiankun 的踩坑經驗。

在解決一個場景或者問題時,技術的實現細節不是最重要的,最重要的是脫離技術的 Principles 。

最後用一句話結尾:Principles are higher than techniques. Principles produce techniques in an instant.

作者:vivo網際網路前端團隊-Yang Kun

相關文章