megalo -- 網易考拉小程序解決方案

megalo 是基於 Vue 的小程序框架(沒錯,又是基於 Vue 的小程序框架),可是它不只僅支持微信小程序,還支持支付寶小程序,同時還支持在開發時使用更多 Vue 的特性。javascript

背景

對於用戶而言,小程序能提供更好的體驗,但對於開發者而言,要讓一個應用跑在多個平臺上,則須要寫多套代碼。如何提升小程序開發效率讓不少開發者都感到頭疼。html

業界也有相關的解決方案,如 taro 和 mpvue,兩者都是基於 react 和 vue 的開發模式實現,讓開發者可以以他們熟知的 react 或 vue 模式來開發小程序,提升開發效率。前端

mpvue 的發佈給了咱們不少啓發,更早的時候,咱們基於 RegularJS(網易自研的前端框架)開發了一個名爲 mpregular 的小程序框架。在 mpregular 的開發和實際使用過程當中,咱們發現若是小程序框架所支持的特性只是原框架的子集(例如不支持 filter、模版複雜表達式等),開發效率會大打折扣。vue

因此,咱們在方案上作了不少嘗試,目的是支持更多的特性,減小小程序與 H5 開發以前的差別。目前 mpregular 已經在考拉的小程序業務中大量應用,相關業務的開發同窗紛紛表示,學習成本變低,跨端業務(H5 和小程序)的開發效率提高近一倍。java

方案通過一段時間驗證後,咱們決定把這套方案用 vue 再實現一次,一是爲了適應技術棧的變動升級,二是爲社區作一點微小的工做,因而就便有了 megalo。node

特性

支持更多模版語法特性

相比於其餘小程序開發模式,因爲支持更多特性,megalo 更貼近 Vue 原生的開發模式。react

特性 小程序 mpvue megalo
computed 計算屬性 ⭕️ ⭕️
v-model 雙向綁定 ⭕️
slot 插槽 ⭕️ ⭕️ ⭕️
scoped-slot 插槽 ⭕️
filter 過濾器 ⭕️
v-html 富文本 ⭕️
複雜表達式插值 ⭕️

從表格能夠看到,megalo 最大的特色之一是,支持更多的 Vue 語法特性。這意味着,若是你有一個需求是要把現有的 Vue 代碼遷移到小程序上,不須要太多改動。由於你的代碼中可能大量使用 filter、scoped-slot、複雜表達式插值。webpack

基本語法

支持 vue 的基本模版語法,包括 v-forv-if。class 和 style 的綁定方式沒有限制,官方的用法都支持。git

<!-- v-if & v-for -->
<div v-for="(item, i) in list">
  <div v-if="isEven(i)">{{ i }} - {{ item }}</div>
</div>

<!-- style & class -->
<div :class="classObject"></div>
<div :class="{ active: true }"></div>
<div :class="[activeClass, errorClass]"></div>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="styleObject"></div>
<div :style="[baseStyles, overridingStyles]"></div>
複製代碼

slot & scoped-slot

支持 slot 和 scoped-slot。github

<div>
  <Container>
    <Card>
      <div slot="head"> {{ title }} </div>
      <div> I'm body </div>
      <div slot="foot"> I'm footer </div>
    </Card>
  </Container>
  <List :list="list">
    <span slot-scope="scopeProps">{{ scopeProps.item.label }}</span>
  </List>
<div>
複製代碼

複雜表達式 & filter

能夠在模版裏面寫複雜表達式、調用實例上的方法,固然也能夠用更簡潔的 filter 語法,跟平時用 Vue 開發同樣。

<div>
  <div>{{ message.toUpperCase() }}</div>
  <div>{{ toUpperCase( message ) }</div>
  <div>{{ message | toUpperCase }}</div>
</div>
複製代碼

v-html

要使用 v-html 須要添加插件 @megalo/vhtml-plugin,並引入模版解析庫 octoparse,在頁面入口安裝一下插件:

import Vue from 'vue'
import VHtmlPlugin from '@megalo/vhtml-plugin'

Vue.use(VHtmlPlugin)
複製代碼

利用 v-html 指令而後就能夠在小程序中渲染 html 了。

<div v-html="'<h1>megalo</h1>'"></div>
複製代碼

更好的數據更新性能

小程序的官方明確說明,在調用 setData 更新數據時若是數據量過大或頻率更高,會引起性能問題。megalo 在框架底層已經幫開發者對此進行優化,每次數據發生變化時,megalo 只會將視圖中要展現的、且發生變化的數據進行更新,將 setData 的數據更新量最小化,同時對更新數據頻率進行了限制。

像下面這段代碼,若是視圖只須要展現 user.name 這個字段的話,在進行數據同步時只會將 user.name 這個字符串更新到視圖層,其他字段是不會同步到小程序的對象上的。

<div>{{ user.name }}</div>
<script> export default { data() { return { user: { name: 'kaola', age: 3, favorite: [ 'encalyptus', 'sleeping' ] } } } } </script>
複製代碼

支持更多平臺

今年以來,各大流量平臺都在小程序領域有所動做,螞蟻金服成立小程序事業部,百度、今日頭條也紛紛推出本身的小程序。megalo 目前已經支持微信和支付寶小程序,百度小程序等平臺的支持也在計劃當中。

1424614_old

微信和支付寶小程序

使用

使用 megalo 開發很是簡單,只需在常見的 Vue 項目 webpack 構建配置上配置 @megalo/target 並引入 @magalo/template-compiler 便可。若是須要編譯成支付寶小程序,只須要設置 platform: 'alipay'

const { createMegaloTarget } = require('@megalo/target');
module.exports = {
  target: createMegaloTarget( {
    compiler: require('@megalo/template-compiler'),
    platform: 'wechat'
  } )
  // 其餘 webpack 配置,如 vue-loader 等
};
複製代碼

接着,就能夠像開發 Vue 應用同樣去開發小程序。示例能夠參考 megalo-demo

若是想用 typescript 進行開發,能夠參考 megalo-ts-simple

實現

小程序在結構上主要有 Service(JavascriptCore) 和 View(WebView) 兩部分組成(微信和支付寶小程序有着相似的結構,下文均以微信小程序爲例,並簡稱爲小程序),分別運行在獨立的環境上,之間不具有共享數據通道,兩者的通訊方式是將數據封裝在 js 腳本後傳遞。Page 實例就在 Service 中,經過 setData 方法將數據傳遞到 View。View 則經過事件綁定將視圖層觸發的事件傳遞給 Service。Service 層中沒法操做視圖層的 DOM 節點。

小程序

實際開發中,小程序的邏輯和模版須要寫在 .js.wxml 兩個文件中,分別在 Service 和 View 中執行。若是要將在瀏覽器端的 Vue 放到小程序中跑,須要將 .vue 文件中的 template 片斷轉換成 .wxml 文件,並對 Vue 的 runtime 部分改造,將其中的 DOM 操做移除,經過小程序的 Service 中的頁面實例上的 API 與 View 進行通訊。

最終的運行效果是,當 Vue 的 vm 上數據發生更新時,會從新渲染出 vdom,在的 patch 階段,框架不在去操做 DOM,而是經過 Page 上的 setData 方法將變化的數據更新到視圖層,完成 Vue 和 小程序的視圖更新,這就是 megalo 底層所作的工做。

小程序

megalo 的實現,主要分紅如下四個部分,下面本文將對每一個部分進行介紹。

生命週期

小程序中,每個頁面(Page)是一個實例,頁面的生命週期鉤子有不少,但和實例建立的兩個關鍵生命週期分別是 onLoadonReady,它們分別在「頁面加載,實例初始化後」和「初次頁面渲染完成」時觸發。Vue 的實例要和小程序實例創建起聯繫,則須要在小程序 Page 實例建立好之後,即在 onLoad 的鉤子函數裏,去初始化對應的 Vue 根實例,將頁面實例 page 掛載到 Vue 實例的 $mp 上,此時也會觸發 Vue 的生命週期鉤子 created。在頁面初次渲染完成後,則會調用 $mount 方法,與在瀏覽器中掛載 DOM 節點不一樣,這裏會將 Vue 實例上的數據初始化到視圖層中。由此,Vue 實例就與小程序的 Page 實創建起了聯繫。

小程序

除了這兩個生命週期鉤子之外,像小程序的 onShowonHide 等生命週期鉤子在觸發時,也會嘗試觸發 Vue 實例上的同名鉤子函數,實現兩種實例間生命週期的綁定。在小程序頁面退出銷燬時,會觸發 onUnload 鉤子,此時 Vue 的實例也會跟着銷燬。

模版轉換

小程序有它特有的模版語法和文件名後綴,因此在構建階段,咱們會將 .vue 文件中的 template 部分提取出來並轉換成對應的 .wxml 文件。標籤名、語法都會進行相應的轉換,如圖所示。

小程序

這一部分是在構建階段完成的,這意味着,megalo 不支持 render 函數的寫法。在構建階段除了將模版轉換成 .wxml 之外,還須要對模版中的每一個節點進行轉換,並在生產的 render 函數中加入相關的節點標記信息,數據映射和事件代理須要這些信息。

數據映射

因爲沒法直接操做視圖層的 DOM,因此咱們只能利用 page.setData 這個方法完成數據到視圖層的映射。最簡單暴力的方法,是將 Vue 實例上的全部數據通通收集起來,經過調用 page.setData 方法更新 Page 實例的數據,這個方法會將數據掛載到 Page 實例上,同時也會把數據傳遞給視圖層。

小程序

可是,這種粗暴的更新方式有兩個弊端:

i. 全量更新 vm 上的數據是沒法區分哪些數據是視圖層須要的,冗餘無用的數據會被更新到 page 實例上。像下圖這個例子,視圖層只須要展示兩個字符串,若是 vm 上還存在兩個大數組,它們也會被無腦同步到 page 上。

小程序

ii. 同步到 page 實例上的數據其實就是原始數據,並非視圖層實際要展現的數據,因此展現數據的格式化與轉換須要依賴小程序模版的解析能力,致使一些 Vue 支持的模版語法沒法支持,例如 filter、複雜表達式、傳遞 class 對象等。

小程序

固然以上兩個弊端不會對功能開發形成影響,但在實際的業務開發中,會讓開發體驗不一致,尤爲是 H5 代碼遷移到小程序時,對效率影響頗大。爲了解決這個問題,megalo 採用另外一種方式,即將 render 時生成的 vnode 上的數據更新到視圖,vnode 的數據就是已經處理好的展現數據,根據 vnode 構造每一個節點的數據結構,再同步到視圖層。

例如如下這段代碼,在構建階段 megalo 會對每一個節點進行標記,使 render 時生成的 vnode 和模版中每一個插值可以對應上。

<!-- 編譯前的 Vue 模版 .vue -->
<div :class=「classObj」>
  {{ date | format( 'YYYY' ) }}
</div>

<!-- 編譯後的小程序模版 .wxml -->
<view class="{{ node_1.class }}">
  {{ node_2.text }}
</view>
複製代碼

以這種方式實現的數據映射,只有視圖層須要的兩個字符串數據會被同步到小程序的 Page 實例上,其他數據則被認爲與視圖無關則不會進行同步。

export default {
  data() {
    return {
      classObj: {
        'kaola': true
      },
      date: new Date(),
      users: {
        // big object
      }
    }
  }
}
複製代碼

以下圖所示,Vue 渲染出來的 vnode 會被以特定的數據結構映射到 page 上,再同步到小程序視圖層。

小程序

以這種方式實現的數據映射,能夠更好地支持 Vue 的模版語法,且更大限度地減小更新視圖時傳輸的數據量,從框架層面規避 setData 的性能問題。

事件代理

小程序視圖觸發事件後,會將 event 對象通知到 Page 實例,那麼咱們只須要將視圖層中全部的事件都代理到 page.proxy 這個方法中,而後再靠這個方法從 Vue 的實例樹上找到對應的 vmhandler 作事件處理。爲了實現這一目的,在構建階段對模版進行編譯時,除了要將事件監聽方法轉換爲 proxy 之外,還須要經過 data- 在元素上標記對應的組件 compid 和節點 nodeid

<!-- 編譯前的 Vue 模版 .vue -->
<div @click="onClick"></div>

<!-- 編譯後的小程序模版 .wxml -->
<view bindtap="proxy" data-compid="0" data-nodeid="0"></view>
複製代碼

事件觸發時,proxy 方法會從 event 對象上獲取對應的 id 信息和事件類型,進而從 Vue 的根 vm 開始查找,最終在 vnode 上找到對應的 handler 並執行事件處理,完成小程序事件到 Vue 實例的事件代理。

小程序

如今與將來

目前,megalo 已經逐步在考拉的小程序應用開發中投入使用,但 megalo 的數據映射方案早已經過 mpregular 在考拉的大量小程序應用中獲得了驗證。如今,megalo 支持 typescript 開發,支持支付寶小程序。

百度智能小程序的支持也在計劃以內,同時,咱們還計劃開發一個兼容個平臺的 UI 組件庫、API 庫,嘗試將跨 H5 和各小程序平臺的應用開發之間的差別最小化,提高開發效率。

github

參考

相關文章
相關標籤/搜索