滴滴開源小程式框架Mpx

滴滴WebApp架構組發表於2018-12-11

Mpx是一款致力於提高小程式開發體驗的增強型小程式框架,通過Mpx,我們能夠以最先進的web開發體驗(Vue + Webpack)來開發生產效能深度優化的小程式,Mpx具有以下一些優秀特性:

  • 資料響應特性(watch/computed)
  • 增強的模板語法(動態元件/樣式繫結/類名繫結/內聯事件函式/雙向繫結等)
  • 深度效能優化(原生自定義元件/基於依賴收集和資料變化的setData)
  • Webpack編譯(npm/迴圈依賴/Babel/ESLint/css預編譯/程式碼優化等)
  • 單檔案元件開發
  • 狀態管理(Vuex規範/多例項/可合併)
  • 跨團隊合作(packages)
  • 邏輯複用能力(mixins)
  • 腳手架支援
  • 小程式自身規範的完全支援
  • 支付寶小程式的支援

設計思路

目前業界主流的小程式框架主要有WePY,mpvue和Taro,這三者都是將其他的語法規範轉譯為小程式語法規範,我們稱其為轉譯型框架。不同於上述三者,Mpx是一款基於小程式語法規範的增強型框架,我們使用Vue中優秀的語法特性增強了小程式,而不是讓使用者直接使用vue語法來開發小程式,之所以採用這種設計主要是基於如下考慮:

  • 轉譯型框架無法支援源框架的所有語法特性(如Vue模板中的動態特性或React中動態生成的jsx),使用者在使用源框架語法進行開發時可能會遇到不可預期的錯誤,具有不確定性
  • 小程式本身的技術規範在不斷地更新進步,許多新的技術規範在轉譯型框架中無法支援或需要很高的支援成本,而對於增強型框架來說只要新的技術規範不與增強特性衝突,就能夠直接支援

技術實現

小程式剛推出時不支援自定義元件,無法進行元件化開發,WePY和mpvue做了一系列的工作來支援了這一關鍵特性,大大提高了使用者的開發體驗和效率。WePY和mpvue的元件化支援是基於編譯做的元件化封裝,使用者編寫的元件模板會被編譯為Page中模板的一部分,在元件中定義的資料會被編譯為Page中的資料,對元件進行資料更新也會基於路徑對映呼叫Page.setData。這在當時的技術條件下是很棒的技術方案,但該方案在複雜的小程式應用中存在效能問題,原因在於該方案中頁面的資料量會很大,而且每個元件的區域性更新實際上都會成為頁面級別的全域性更新,在元件較多的頁面中產生很大的效能開銷。

在小程式自定義元件推出後,我們通過效能測試確認了小程式自定義元件支援元件級別的區域性更新,具有良好的更新效能。由於自定義元件在當時是最新的技術標準,業內的小程式框架都未支援,而我們在業務上又有複雜小程式的開發需求,於是我們就基於小程式自定義元件啟動了Mpx框架的設計與開發。

Page與Component setData效能對照

Component Page
區域性更新 1975ms 7186ms
全域性更新 6210ms 24612ms

效能衡量標準說明:
區域性更新:文件中存在1000個節點,其中一個節點更新1000次的耗時;
全域性更新:文件中存在5個節點,5個節點獨立更新1000次的耗時 以上資料在iPhone7微信小程式環境下測試得出

資料響應與效能優化

資料響應作為Vue最核心的特性,在我們的日常開發中被大量使用,能夠極大地提高前端開發體驗和效率,我們在框架設計初期最早考慮的就是如何將資料響應特性加入到小程式開發中。在資料響應的實現上,我們引入了MobX,一個實現了純粹資料響應能力的知名開源專案。藉助MobX和mixins,我們在小程式元件建立初期建立了一個響應式資料管理系統,該系統觀察著小程式元件中的所有資料(data/props/computed)並基於資料的變更驅動檢視的渲染(setData)及使用者註冊的watch回撥,實現了Vue中的資料響應程式設計體驗。與此同時,我們基於MobX封裝實現了一個Vuex規範的資料管理store,能夠方便地注入元件進行全域性資料管理。為了提高跨團隊開發的體驗,我們對store新增了多例項可合併的特性,不同團隊維護自己的store,在需要時能夠合併他人或者公共的store生成新的store例項,我們認為這是一種比Vuex中modules更加靈活便捷的跨團隊資料管理模式

作為一個接管了小程式setData的資料響應開發框架,我們高度重視Mpx的渲染效能,通過小程式官方文件中提到的效能優化建議可以得知,setData對於小程式效能來說是重中之重,setData優化的方向主要有兩個:

  1. 儘可能減少setData呼叫的頻次
  2. 儘可能減少單次setData傳輸的資料

為了實現以上兩個優化方向,我們做了以下幾項工作:

  • 將元件的靜態模板編譯為可執行的render函式,通過render函式收集模板資料依賴,只有當render函式中的依賴資料發生變化時才會觸發小程式元件的setData,同時通過一個非同步佇列確保一個tick中最多隻會進行一次setData,這個機制和Vue中的render機制非常類似,大大降低了setData的呼叫頻次;
  • 將模板編譯render函式的過程中,我們還記錄輸出了模板中使用的資料路徑,在每次需要setData時會根據這些資料路徑與上一次的資料進行diff,僅將發生變化的資料通過資料路徑的方式進行setData,這樣確保了每次setData傳輸的資料量最低,同時避免了不必要的setData操作,進一步降低了setData的頻次。 基於以上優化,我們大大減少了小程式setData的呼叫頻次和傳遞資料量,與初版Mpx中track全量資料的方案相比提示顯著。

Mpx setData優化效果

舊版Mpx 新版Mpx
setData次數 164 88
setData資料量 370kB 38kB

以上資料由較複雜小程式在固定互動流程後統計得出

Mpx資料響應機制流程示意圖
Mpx資料響應機制流程示意圖

編譯構建

我們希望使用目前設計最強大、生態最完善的編譯構建工具Webpack來實現小程式的編譯構建,讓使用者得到web開發中先進強大的工程化開發體驗。使用過Webpack的同學都知道,通常來說Webpack都是將專案中使用到的一系列碎片化模組打包為一個或幾個bundle,而小程式所需要的檔案結構是非常離散化的,如何調解這兩者的矛盾成為了我們最大的難題。一種非常直觀簡單的思路在於遍歷整個src目錄,將其中的每一個.mpx檔案都作為一個entry加入到Webpack中進行處理,這樣做的問題主要有兩個:

  1. src目錄中用不到的.mpx檔案也會被編譯輸出,最終也會被小程式打包進專案包中,無意義地增加了包體積;
  2. 對於node_modules下的.mpx檔案,我們不認為遍歷node_modules是一個好的選擇。

最終我們採用了一種基於依賴分析和動態新增entry的方式來進行實現,使用者在Webpack配置中只需要配置一個入口檔案app.mpx,loader在解析到json時會解析json中pages域和usingComponents域中宣告的路徑,通過動態新增entry的方式將這些檔案新增到Webpack的構建系統當中(注意這裡是新增entry而不是新增依賴,因為只有entry能生成獨立的檔案,滿足小程式的離散化檔案結構),並遞迴執行這個過程,直到整個專案中所有用到的.mpx檔案都加入進來,在輸出前,我們藉助了CommonsChunkPlugin/SplitChunksPlugin的能力將複用的模組抽取到一個外部的bundle中,確保最終生成的包中不包含重複模組。我們提供了一個Webpack外掛和一個.mpx檔案對應的loader來實現上述操作,使用者只需要將其新增到Webpack配置中就可以以打包web專案的方式正常打包小程式,沒有任何的前置和後置操作,支援Webpack本身的完整生態。

Mpx編譯構建機制流程示意圖
Mpx編譯構建機制流程示意圖

現狀和未來

目前Mpx框架已經在滴滴內部大量使用,支撐了滴滴出行、青桔單車、街兔電單車、營銷、車服等業務在小程式上的實現,線上執行穩定,收到了大量的好評反饋。未來我們在對框架進行持續迭代優化的同時會持續跟進微信和支付寶最新的技術標準,同時也會將在更多的小程式平臺上進行適配。由於我們的設計初衷和專注點在於增強小程式開發體驗,Mpx並沒有進行跨H5甚至是跨Native的支援,但現實業務當中確實存在這樣的訴求,未來我們會在Mpx的基礎上對跨端進行一定的嘗試,與此同時我們依然會持續維護升級Mpx,原因在於跨端意味著靈活性受限及能力的缺失,我們希望能給使用者提供第二種選擇。

Mpx與業內主流小程式框架異同對比

WePY mpvue Taro Mpx
程式碼規範 自定義 Vue React 小程式+
元件化實現 Page封裝 Page封裝 Component Component
資料響應 髒值檢查 Vue Mobx
狀態管理 Redux Vuex Redux 類Vuex

結語

如果你注重開發體驗和產品效能,專注於小程式開發,那Mpx會是一個很好的選擇。最後感謝開源社群源源不斷湧現出的優秀專案,給我們提供了無限的啟發和巨大的技術幫助。

Github: github.com/didi/mpx

使用文件: didi.github.io/mpx/

使用者交流: 點我進群

相關文章