引子
這篇文章是筆者近期關於Weex在iOS端的一些研究和實踐心得,和大家一起分享分享,也算是對學習成果的總結。文章裡面提到的做法也許不是最佳實踐,也許裡面的方法稱不算是一份標準的指南手冊,所以標題就只好叫“偽最佳實踐指北”了。有更好的方法歡迎大家一起留言討論,一起學習。
由於筆者不太瞭解Android,所以以下的文章不會涉及到Android。
一. React Native 和 Weex
自從Weex出生的那一天起,就無法擺脫和React Native相互比較的命運。React Native宣稱“Learn once, write anywhere”,而Weex宣稱“Write Once, Run Everywhere”。Weex從出生那天起,就被給予了一統三端的厚望。React Native可以支援iOS、Android,而Weex可以支援iOS、Android、HTML5。
在Native端,兩者的最大的區別可能就是在對JSBundle是否分包。React Native官方只允許將React Native基礎JS庫和業務JS一起打成一個JS bundle,沒有提供分包的功能,所以如果想節約流量就必須製作分包打包工具。而Weex預設打的JS bundle只包含業務JS程式碼,體積小很多,基礎JS庫包含在Weex SDK中,這一點Weex與Facebook的React Native和微軟的Cordova相比,Weex更加輕量,體積小巧。
在JS端,Weex又被人稱為Vue Native,所以 React Native 和 Weex 的區別就在 React 和 Vue 兩者上了。
筆者沒有寫過React Native,所以也沒法客觀的去比較兩者。不過知乎上有一個關於Weex 和 React Native很好的對比文章《weex&React Native對比》,推薦大家閱讀。
前兩天@Allen 許帥也在Glow 技術團隊部落格上面釋出了一篇《React Native 在 Glow 的實踐》這篇文章裡面也談了很多關於React Native實踐相關的點,也強烈推薦大家去閱讀。
二. 入門基礎
關於小白想入門Weex,當然最基礎的還是要通讀文件,文件是官方最好的學習資料。官方的基礎文件有兩份:
在文件手冊裡面包含了Weex所有目前有的元件,模組,每個元件和模組的用法和屬性。遇到問題可以先過來翻翻。很有可能有些元件和模組沒有那些屬性。
1. Weex全家桶和腳手架
看完官方文件以後,就可以開始上手構建工程專案了。
我司在知乎上面寫了4篇關於《Weex入坑指南的》。這四篇文章還是很值得看的。
Weex也和前端專案一樣,擁有它自己的腳手架全家桶。weex-toolkit + weexpack + playground + code snippets + weex-devtool。
weex-toolkit是用來初始化專案,編譯,執行,debug所有工具。
weexpack是用來打包JSBundle的,實際也是對Webpack的封裝。
playground是一個上架的App,這個可以用來通過掃碼實時在手機上顯示出實際的頁面。
code snippets這個是一個線上的playground。
我相信大家應該都有Native的App,如果真的App都沒有,那就用weexpack命令初始化一個新的專案。如果已經有App專案了,那麼weex命令就只是用來執行和除錯的。
已經有iOS專案的,可以通過cocospod直接安裝Weex的SDK,初始化SDK以後,Native就可以使用Weex了。載入的JS的地址改成自己公司伺服器的IP。
#define CURRENT_IP @"your computer device ip"
// ...
// 修改埠號到你的埠號
#define DEMO_URL(path) [NSString stringWithFormat:@"http://%@:8080/%s", DEMO_HOST, #path]
// 修改 JS 檔案路徑
#define HOME_URL [NSString stringWithFormat:@"http://%@:8080/app.weex.js", DEMO_HOST]複製程式碼
這樣整個專案就可以跑起來了。
這裡還有一點需要說明的是,專案雖然跑起來了,但是每次執行都需要啟動npm,開啟Weex的前端環境。這裡有兩個做法。
第一種做法是直接Hook Xcode的run命令,在Xcode配置裡面加入啟動npm的指令碼。比如下面這樣:
第二種做法就是每次執行之前,自己手動npm run dev。我個人還是喜歡這種方式,因為在Xcode執行完成之前,一定可以在命令列上面打完這些命令。
再說說如何Debug,這塊使用的是weex-devtool。
這個工具和前端在Chrome裡面除錯的體驗基本相同。
具體使用方法看這兩篇文章即可,這裡不再贅述:
《Weex 入坑指南:Debug 除錯是一門手藝活》
《Weex除錯神器——Weex Devtools使用手冊》
2. Weex Market外掛
在日常開發中,我們可以全部自己開發完所有的Weex介面,當然還可以用一些已有的優秀的輪子。Weex的所有優秀的輪子都在Weex Market裡面。
在這個Market裡面有很多已經寫好的輪子,直接拿來用,可以節約很多時間。
比如這裡很火的weex-chart。weex-chart圖表外掛是通過g2-mobile依賴gcanvas外掛實現的
如果你想使用Weex Market的Plugin外掛,你可以使用weex plugin 命令:
$ weex plugin add plugin_name複製程式碼
你只需要輸入外掛的名稱就可以從遠端新增外掛到你本地的專案,比如新增 weex-chart,我們可以輸入命令:
$ weex plugin add weex-chart複製程式碼
我們可以使用plugin remove移除外掛,比如移除安裝好的 weex-cahrt:
$ weex plugin remove weex-chart複製程式碼
這個外掛庫裡面我用過weex-router,還不錯,用它來做weex的路由管理。推薦使用。
3. iOS打包和釋出
weex官方提供了weexpack命令。我覺得這個命令是提供給不懂iOS的前端的人用的。如果是Native來打包,依舊使用的Xcode的Archive打包。
完全不懂iOS的前端開發者可以使用weexpack build ios 打包,中間會要求輸入證照,開發者賬號等資訊。都輸入正確以後就可以打出ipa檔案了。全程傻瓜操作。
如果是iOS開發者,原來怎麼打包現在還是怎麼打包。只不多JS這塊要單獨進行打包。建議是把Weex這塊單獨用一個git分支進行管理,專門針對這個分支進行weexpack或者Webpack進行打包。webpack的具體配置由每個公司自己配置。
這裡額外說一點,這一點也是前端大神告訴我的。webpack打完包以後是可以通過webpack官方網站檢視這個包裡面究竟打入了哪些檔案和依賴。雖然我打包都是一股腦的都打完,但是資深前端開發也許還會再去檢查一下是否有多的檔案被打進去了。極限壓縮包的體積,1KB的檔案也不多放進去。
再談談釋出的問題。由於有了Weex以後,每次釋出都會把上個版本累計到這個版本的hotPatch都累計修復掉,並在新版裡面直接內建最新的JSBundle檔案。內建JS的目的也是為了首屏載入秒開。
4. 熱更新
關於熱更新的作用大家都明白,不然用Weex的意義就少了好多。不過這裡還有一點需要說明的是——熱更新的策略。
在日常開發過程中,我們在瀏覽器上面連著手機除錯,也並不是實時重新整理的。(不過通過在手機上掃描二維碼,並且手機和電腦在同一個區域網之內,可以做到實時更新)
所以在實際生產環境中,熱更新的策略應該是這樣:有新的HotPatch就下發到客戶端,然後客戶端在下次啟動的時候,先比對版本資訊,如果是新版本,就去載入這個最新的HotPatch,然後渲染在螢幕上。
曾經我幻想著能實時線上更新,就是線上一發布,所有使用者在聯網的情況下,下發HotPatch完畢以後直接載入,聯網的使用者可以實現秒級別的熱更新。這種雖然可以做到,但是意義不大。做法是專門維護一套Websocket,直連伺服器,下發完畢以後可以通過呼叫Native的通知,Native客戶端自己重新整理頁面即可。(目前應該沒有多少公司是這樣做的吧?)
5. JSBundle版本管理與部署
關於JSBundle的版本管理這塊是應該交給前端來管理。前端可能會用版本號來管理各個包的版本。部署也會牽扯到每個公司前端部署的流程。他們會更加了解。部署一般也會放到CDN上加速。
6. 踩坑和避坑
如果說Weex一點坑都沒有,那是不可能的。
比如說在某些介面連續Push的時候,頁面邊緣會有一些線條從螢幕上掃過。還有捕捉JS錯誤或者異常的時候,Weex並不能可靠的捕捉到異常,這點需要靠Native來做,Native捕捉到異常以後再傳遞事件給JS Runtime去處理。
計算頁面寬高尺寸這點是最需要注意的。Weex進行介面適配的時候是用750為標準的,所以需要根據750去換算。還有一點是Weex裡面有四捨五入的操作,是會丟失一點精度的。具體這塊請看《Weex 事件傳遞的那些事兒》這篇文章裡面的原始碼分析。
Weex JS 引擎也不支援 HTML DOM APIs 和 HTML5 JS APIs,這包括 document, setTimeout 等。
Weex關於Web標準的實現現在還沒有達到100%,所以用Vue來寫Weex的話,有些是不支援的。
比如說一些CSS樣式,最令人想不到的就是不支援< br>,還不支援< form>,< table>,< tr>,< td>,不支援CSS percentage 單位,不支援類似 em,rem,pt 這樣的 CSS 標準中的其他長度單位。不支援 hsl(), hsla(), currentColor, 8個字元的十六進位制顏色。
Weex對W3C上的FlexBox的規範也沒有支援完全,暫不支援inline,也不支援Z軸上面的變化,不過移動端在Z軸上的需求真的沒有。Weex的Layout是用的Yoga之前的某個版本,解決問題的方式也比較直接,後期升級到最新版的Yoga,便可以支援更多的Flex的標準了。
具體還有不支援的就要多翻翻文件,比如這裡的《Weex 目前不支援的Web 標準有哪些》。這些最好先看看,心裡有個數,以免開發時候遇到一些莫名的bug,殊不知最終是因為不支援導致的。
然後還有一些是元件暫時還不支援同步方法。這裡是Vue 2.0還不支援,官方預計是在 0.12 版本支援。
額外提醒一點,由於蘋果前段時間對JSPatch的封殺,所以導致Weex官方對自定義模組給出了一個警告:
Weex 所有暴露給 JS 的內建 module 或 component API 都是安全和可控的, 它們不會去訪問系統的私有 API ,也不會去做任何 runtime 上的 hack 更不會去改變應用原有的功能定位。
如果需要擴充套件自定義的 module 或者 component ,一定注意不要將 OC 的 runtime 暴露給 JS , 不要將一些諸如 dlopen(), dlsym(), respondsToSelector:,performSelector:,method_exchangeImplementations() 的動態和不可控的方法暴露給JS, 也不要將系統的私有API暴露給JS
上述警告特別強調了不要用dlopen(), dlsym(), respondsToSelector:,performSelector:,method_exchangeImplementations()這幾個函式。這也是為什麼同樣是用Weex有些人沒有通過稽核,有些人卻能通過稽核的原因。
聽說安卓上有Refresh Control的一些bug,安卓在Weex上的表現我沒有怎麼了解過,不過這塊如果出現在iOS上,我覺得可以直接用Native來替換掉這塊,有bug的地方都用原生來做。
總之Weex還是多多少少有一些問題,但是目前使用來看,不影響使用,只要懂得靈活變通,遇到實在過不去的坎,或者是真的一時hold不住的bug,那麼多考慮用原生來替代。
三. 更多高階的玩法
接下來說一下稍微高階的玩法。以下這些即使沒有做,也不影響Weex正常上線。
1.頁面降級
Weex預設是支援頁面降級的。比如出現了錯誤,就會降級到H5。這裡建議最好做一個線上的開關。我司在處理頁面降級的問題上採取了兩種級別的開關:
- App級的開關。這個開關是管理使用者App是否使用Weex SDK的,這塊是可以線上配置的。
- 頁面級的開關。這個開關是管理某個頁面是否開啟Weex的。如果不開啟就降級成H5頁面。
除了降級以後,還對應採取了灰度的策略,這樣保證線上bug降低到最低。
比如在使用者量低峰期的時候開啟開關進行灰度。還有一級灰度就通過線上實時錯誤監控平臺來控制,如果因為突發事件導致Crash率陡升,那麼就立即關閉Weex的開關,立即進行降級處理。
2. 效能監控和埋點
在Weex給的官方Demo裡面有一個M的小圓點浮框,點開會看到如下的介面:
在這裡我們點開效能的按鈕:
在這裡我們可以看到監控了CPU,幀率,記憶體,電量,流量等資料,這些資料也是我們在Native APM中監控的常見資料。當然,這個M圓點並不沒有開源。所以這塊需要各個公司自己做一套自己的監控系統。這塊可能每個公司的前端已經做好了,所以Weex需要接入到前端的效能監控裡。
如果我們再點開工具的介面,就會看到如下的選項:
這裡就有埋點監控。在初期可能Weex埋點還是由Native進行埋點,因為各家都有自家的Native完整的埋點系統了。後期埋點這塊也可以交給前端在前端埋點。
3. 增量更新和全量更新
暫時筆者還沒有實踐過Weex的增量更新,所以這裡就不提增量更新了。全量更新就比較簡單,下發整個JSBundle,App在下次啟動的時候再載入即可。Weex的包比RN的包小很多,一般就100-200K左右。阿里的一次Weex分享裡面提到他們gzip壓縮以後能達到60-80K。
4. 首屏載入時間極致優化
上圖是阿里在Weex Conf大會上提出的一個挑戰,網路請求加上首屏渲染的時間加起來小於1秒。
這裡面涉及到3方面的因素,網路下載耗時,JS和Native通訊耗時,還有渲染耗時。
網路下載耗時可以通過支援HTTP / 2,配置Spdy協議,域名收斂,支援http-cache,極致壓縮JSBundle的大小,JSBundle預載入。
JSBundle預載入的時候可以在App啟動時候預先下載JS。遠端伺服器推包的時候通過長連通道Push,這裡可以是全量 / 增量,被動 / 強制更新相互結合。
阿里關於JS和Native通訊耗時,渲染耗時的相關優化見上圖。這兩方面筆者也沒有相關的實踐。
5. Vue全家桶
雖然Weex有屬於它自己的全家桶,但是在支援了Vue 2.0以後,它的全家桶完全可以換成Vue的全家桶。Vue,Vue-Router,Vuex,原來還有Vue-resource,不過尤大後來去掉了這個Vue-resource,更加推薦axios了。所以全家桶裡面就是Vue,Vue-Router,Vuex,axios。
如果全部都換成了Vue以後,那麼前端首屏渲染的速度就需要Vue來解決了。為了提高首屏渲染速度,wns快取+直出 是必不可少的。在Vue 1. x 時代,沒有 server-side-render 方案,直出需要專門給寫一份首屏非Vue語法的模板。Vue2.0 server-side-render(簡稱Vue SSR)的推出,成功地讓前後端渲染模板程式碼同構。
如果只用了Vue-Router以後,當打包構建應用時,JSBundle 包會變得非常大,影響頁面載入。如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應元件,這樣就更加高效了。
結合 Vue 的 非同步元件 和 Webpack 的 code splitting feature, 輕鬆實現路由元件的懶載入。減少JSBundle的體積。
還有一點需要注意的是,Vue-Router 提供了三種執行模式:
hash : 使用 URL hash 值來作路由。預設模式。
history : 依賴 HTML5 History API 和伺服器配置。
abstract: 支援所有 JavaScript 執行環境,如 Node.js 伺服器端。
不過Weex 環境中只支援使用 abstract 模式!
就在7天前,Vue 釋出了v2.3.0版本,官方支援了SSR。所以在支援了SSR以後,可以大幅提升SEO,也可以做到首屏秒開。所以為了效能,SSR必做!
四. 頂級玩法
最後的最後,還有一些“前瞻性”的玩法。
1. 強大的JSService
JS service 和 Weex 例項在 JS runtime 中並行執行。Weex 例項的生命週期可呼叫 JS Service 生命週期。目前提供建立、重新整理、銷燬生命週期。
這塊我在官方的Demo裡面也沒有找到相關的例子。在官方的文件裡面有相關的例子。在官方手冊裡面有這樣一句話:
重要提醒: JS Service 非常強大但也很危險,請小心使用!
可見,這塊非常強大,也許可以做很多“神奇的”事情。
2. Weex可能有更大的“野心”
在官方手冊《擴充JS framework》這一章節裡面,提到了可以橫向擴充JS framework。這個功能可能一般公司都不會去擴充套件。
Weex 希望能夠尊重儘可能多的開發者的使用習慣,所以除了 Weex 官方支援的 Vue 2.0 之外,開發者還可以定製並橫向擴充套件自己的或自己喜歡的 JS Framework。
定製完自己的JS Framework以後,就可能出現下面的程式碼:
import * as Vue from '...'
import * as React from '...'
import * as Angular from '...'
export default { Vue, React, Angular };複製程式碼
這樣還可以橫向擴充套件支援Vue,React,Angular。
如果在 JS Bundle 在檔案開頭帶有如下格式的註釋:
// { "framework": "Vue" }
...複製程式碼
這樣 Weex JS 引擎就會識別出這個 JS bundle 需要用 Vue 框架來解析。並分發給 Vue 框架處理。
所以,Weex 支援同時多種框架在一個移動應用中共存並各自解析基於不同框架的 JS bundle。
可以支援多種框架並存這點非常強大,當然還沒有完,one more thing……
如果正常使用API,看官方文件,不開原始碼,是不會發現Rax的身影的。官方文件絲毫沒有提及到它。
Rax是什麼呢?
在《淘寶雙促中的 Rax》這篇文章裡面介紹了Rax:
Rax 是一個基於 React 方式的跨容器的 JS 框架。
Rax 經過 gzip 以後的大小 8k,與 Angular、React、Vue 相比更加輕量。相比React的43.7kb,小了太多。
Rax 在設計上抽象出 Driver 的概念,用來支援在不同容器中渲染,比如目前所支援的:Web, Weex, Node.js 都是基於 Driver 的概念,未來即使出現更多的容器(如 VR ,AR等),Rax 也可以從容應對。Rax 在設計上儘量抹平各個端的差異性,這也使得開發者在差異性和相容性方面再也不需要投入太多精力了。
如果說RN和Weex這些技術是用來跨端的技術,那Rax是用來跨容器的:Browser、Weex、Node.js等。
那麼Weex裡面加了Rax能幹些什麼事情呢?值得期待!