傳統的移動端開發,一個完整的業務需要維護三份終端代碼:Android、iOS、H5,這帶來了極大的開發成本以及維護成本。尤其是對處于業務初創期需要快速試錯的業務以及需要支持定期運營活動的業務。所以業界也一直在探索跨平臺方案,旨在通過一套代碼完成各個終端的業務邏輯。相關方案經過不斷演化,從早期的H5、Hybrid到如今的Cloud Native(云原生),在開發效率和用戶體驗上都在一點點逼近最初的設想。
早期H5和Hybrid方案的核心是利用終端的內置瀏覽器(webview)功能,通過開發web應用滿足跨平臺需求。該方案可以解決跨平臺問題,同時可以提升發版效率。但其最大的弊端在于用戶體驗相較于native開發的app存在較大差距,經常出現頁面卡頓,加載慢等問題。
在這里我還是要推薦下我自己建的web前端開發學習群:617327703,群里都是學web前端開發的,如果你正在學習前端 ,小編歡迎你加入,今天分享的這個案例已經上傳到群文件,大家都是軟件開發黨,不定期分享干貨(只有前端軟件開發相關的),包括我自己整理的一份2018最新的前端進階資料和高級開發教程,歡迎進階中和進想深入前端的小伙伴。
于是后來業界開始探索依舊利用web技術棧開發出媲美原生體驗app的方案,于是以WEEX為代表云原生開發框架開始出現。所謂云原生(Cloud Native)指可以通過云端快速發布(與遠程web應用發布流程類似),同時還可以達到媲美原生App體驗的方案。WEEX依舊采取傳統的web開發技術棧進行開發,同時app在終端的運行體驗不輸native app。其同時解決了開發效率、發版速度以及用戶體驗三個核心問題。那么WEEX是如何實現的?目前WEEX已經完全開源,并捐給Apache基金會,我們可以通過分析其源碼來一探究竟。
WEEX框架主要分為兩部分:
前端JavaScript框架
Native SDK
在上一篇博客中,我們介紹了Native SDK的原理,本文主要介紹其前端JavaScript框架原理。
1 整體架構
首先還是再來看下WEEX開發的整體架構:
可以看到在JS-Native Bridge
將渲染指令發送給Android或者iOS渲染引擎之前,我們的業務代碼運行在JSCore/v8
的執行引擎之中,而在該執行引擎之中除了執行業務JSBundle,還運行著JS Framework,JS Framework
不僅提供了jsbundle必要的運行時環境,同時還提供了與native通信的接口。
而這個JS Framework就是我們今天介紹的重點。
這是前端框架的主要架構:
FRONTEND FRAMEWORK/DSL:這是WEEX的開發框架,目前WEEX主要是使用Vue.js進行開發
WEEX-VUE-LOADER:前端編譯器,將vue文件編譯成es5代碼
WEEX-VUE-FRAMWORK:WEEX核心框架,主要負責將virtual dom轉換成weex的native dom api
WEEX-RUNTIME:負責與native渲染引擎對接,將native dom api轉換成對應平臺(Android、iOS)的platform api,然后傳遞給native渲染引擎,由后者負責渲染工作。
2?Vue.js
首先來看下前端開發框架Vue.js,?Vue.js目前與React、?Angular?構成了三大最流行的前端開發框架,Vue.js具有組件化、virtual dom以及MVVM三大特性,還學習React的Redux,引入了狀態管理模塊Vuex。同時相比起React
主要基于JSX,Vue.js的開發模式更加清晰,簡單,上手速度更快。.vue文件通常可以分為三部分:?、
和?,是必須要有的,其他可選。其中?中的代碼會保留或者被轉換成 ES5 的語法;
中的 CSS 在 Weex 平臺上會被轉換成 JSON 格式的樣式聲明,放到組件的定義中去;會被編譯生成組件定義中 render 函數,可以理解為 render 函數的語法糖。下文是一個簡單的.vue
文件:
3?WEEX-VUE-LOADER
由于.vue文件并不是標準的JavaScript代碼,該代碼不能直接被JS執行引擎識別,所以在編譯過程中需要將.vue
文件轉化成標準的es5代碼。而負責完成這一操作的就是WEEX-VUE-LOADER。
4?WEEX-VUE-FRAMEWORK?&?WEEX-RUNTIME
完成編譯后,輸出的bundle.js即可被JS執行引擎所識別,可以執行其邏輯了。那么接下來就來看下bundle.js的執行過程。
4.1 初始化
首先來看下初始化過程。
圖中畫出了 Weex SDK 的部分內容。其中?weex-vue-framework和?Vue.js是對等的,語法和內部機制都是一樣的,只不過?Vue.js?最終創建的是 DOM 元素,而?weex-vue-framework則是向原生端發送渲染指令,最終渲染生成的是原生組件。Weex Runtime 用來對接上層前端框架( Vue.js )并且負責和原生端之間的通信。Render Engine 就是針對各個端開發的原生渲染器,包含了 Weex 內置組件和模塊的實現,可擴展。
4.2 創建組件
weex接收到bundle.js之后,首先檢查其合法性,如果發現用的是Vue版本(weex早期版本使用.we語法),就會調用weex-vue-framework中提供的createInstance創建實例。一個bundle.js對應一個weex實例,且每一個實例都有唯一的instanceId,與之對應。實例與實例之間相互隔離,互不干擾。
在創建實例的過程中,bundle.js會執行new Vue()創建一個vue組件,并通過其render
函數創建VNode節點,即virtual dom節點。第二節中的示例代碼會最終生成類似如下的vnode節點:
4.3 patch
生成了VNode之后,接下來需要將VNode同步到真實的Dom之上,該過程在Vue.js
中被稱為patch,patch會比較新舊VNode之間的差異,最小化操作集。最后再將Virual Dom整體更新到真實Dom之上。在執行 patch 之前的過程都是 Web 和 Weex 通用的,所以文件格式、打包編譯過程、模板指令、組件的生命周期、數據綁定等上層語法都是一致的。
然而由于目標執行環境不同(瀏覽器和 Weex 容器),在渲染真實 UI 的時候調用的接口也不同。
在vue.js內部,weex平臺和web平臺的patch方法有所不同。簡單來講,在web平臺,patch需要將Virtual Dom利用標準Dom API更新到真實Dom即可。接下來的UI更新就交給瀏覽器即可。
但是由于在weex平臺下,最終的UI渲染是由native渲染引擎執行的,native渲染引擎不能識別Dom API,所以在weex平臺下,需要將Virtual DOM轉化成native 渲染引擎可以識別的Native DOM API。
4.4 發送渲染指令
在上層前端框架調用了 Weex 平臺提供的 Native DOM API 之后,Weex Runtime 會構建一個用于渲染的節點樹,并將操作轉換成渲染指令發送給客戶端。
回顧文中提到的 例子,上層框架調用了 Weex Runtime 中?createBody、createElement、appendChild這三個接口,簡單構建了一個用于渲染的節點樹,最終生成了兩條渲染指令。
Platform API 指的是原生環境提供的 API,這些 API 是 Weex SDK 中原生模塊提供的,不是 js 中方法,也不是瀏覽器中的接口,是 Weex 內部不同模塊之間的約定。
目前來說渲染指令是基于 JSON 描述的,具體格式大致如下所示:
4.5 渲染原生組件
接下來就是WEEX NATIVE SDK的工作了,具體邏輯在上一篇博客已經詳細說明了,此處僅簡單說明。
原生渲染器接收上層傳來的渲染指令,并且逐步將其渲染成原生組件。
渲染指令分很多類,文章中提到的兩個都是用來創建節點的,其他還有?moveElement?、updateAttrs?、addEvent等各種指令。原生渲染器先是解析渲染指令的描述,然后分發給不同的模塊。關于 UI 繪制的指令都屬于?"dom"模塊中,在 SDK 內部有組件的實現,其他還有一些無界面的功能模塊,如 stream 、navigator 等模塊,也可以通過發送指令的方式調用。
這個例子里,第一個?createBody的指令就創建了一個?
上述過程不是分階段一個一個執行的,而是可以實現“流式”渲染的,有可能第一個?
的渲染指令又發過來了。當一個頁面特別大時,能看到一塊一塊的內容逐漸渲染出來的過程。
5 總結
還有很多實現細節文中并未展開,有興趣的朋友可以留言大家一起討論下,另外文章中有錯誤的地方也請大家指正!