天天看點

Weex實踐

尚妝達人店接入weex也一年的時間了,在此期間,也陸陸續續出了一些文章:

「Android」 詳細全面的基于vue2.0Weex接入過程(Android視角)

「前端」weex頁面傳參

「大前端」weex裡native主動發送事件到JS的方案實作

weex 三端實作Pager 元件(ViewPager) - 本仁筆記

記錄團隊weex實踐過程中需要特殊注意的點

這裡就詳細地做一個總結,希望可以給大家帶來一些參考。我們團隊也比較小,App的量級也不大,很多做得不夠好的地方,還希望大神不吝賜教。

一、什麼是Weex

Weex 是一套簡單易用的跨平台開發方案,能以 web 的開發體驗建構高性能、可擴充的 native 應用,為了做到這些,Weex 與 Vue 合作,使用 Vue 作為上層架構,并遵循 W3C 标準實作了統一的 JSEngine 和 DOM API,這樣一來,你甚至可以使用其他架構驅動 Weex,打造三端一緻的 native 應用。

前言引用了Weex官網的定義,我們在實踐的過程中也實際地體會到了這些。以下是提煉出的幾個關鍵字:

Weex實踐

還未接觸過weex的同學,如果想先看一下效果,可以通路 Weex 提供的 線上Playground,進行編輯和浏覽,App端下載下傳playgroundplayground進行掃碼浏覽效果。

Weex實踐

可以看到,Weex可以通過自己設計的DSL,用vue像寫 web 頁面一樣寫一個 app 的頁面,整個頁面書寫分成了3段,

template

style

script

,借鑒了成熟的MVVM的思想。

後面會講到,理論上也可以橫向支援采用React、angular等架構來書寫頁面。阿裡開源的Rax,就是基于React的标準,支援在Weex渲染,具體可以看知乎上一個問答如何看待阿裡開源的Rax架構?

而Playground內建了

Weex SDK

,掃碼後,得到了編譯好的

JS Bundle

,然後通過JS Framework層解析,輸出Json格式的

Visual Dom

,然後通過

JS-Native Bridge

來渲染成Native界面,也通過Bridge來進行Js-Native的事件傳遞。如下是官網給出的架構圖:

Weex實踐

通過斷點調試可以看到,JSFramework傳給SDK的渲染指令是這樣子,SDK 再根據不同的type和參數,渲染成對應的Native元件。

Weex實踐

傳統的App,Native UI 是可以直接擷取 Device Power的,而Weex App裡,Native UI 和 Device Power之間通過JavaScript來連接配接,如圖所示(圖來自weex官網):

Weex實踐

在開始接入之前,關于Weex的頁面結構,需要了解一下,具體可以檢視Weex官網的Weex頁面結構。為了閱讀友善,下面直接引用:

Weex 頁面結構

界面展示、邏輯處理、裝置能力使用、生命周期管理等部分。

Dom模型

Weex 頁面通過類似 HTML DOM 的方式管理界面,首先頁面會被分解為一個 DOM 樹,,每個 DOM 結點都代表了一個相對獨立的 native 視圖的單元。然後不同的視圖單元之間通過樹形結構組合在了一起,構成一個完整的頁面。

元件

Weex 支援文字text、圖檔image、視訊video等内容型元件,也支援 div、list、scroller 等容器型元件,還包括 slider、input、textarea、switch 等多種特殊的元件。Weex 的界面就是由這些元件以 DOM 樹的方式建構出來的。

布局系統

Weex 頁面中的元件會按照一定的布局規範來進行排布,我們這裡提供了 CSS 中的盒模型、flexbox 和 絕對/相對/固定/吸附布局這三大塊布局模型。

功能

Weex 提供了非常豐富的系統功能 API,包括彈出存儲、網絡、導航、彈對話框和 toast 等,開發者可以在 Weex 頁面通過擷取一個 native module 的方式引入并調用這些用戶端功能 API。

生命周期

每個 Weex 頁面都有其自身的生命周期,頁面從開始被建立到最後被銷毀,會經曆到整個過程。這是通過對 Weex 頁面的建立和銷毀,在路由中通過 SDK 自行定義并實作的。

Weex的擴充性很好,可以對網絡、圖檔、存儲、UT、元件、接口等根據自身App和業務需求進行擴充,即使weex提供的元件有問題,也都可以直接重寫替換。

Weex實踐

對于一個新技術的接入,我們首先會去考慮這個技術的優缺點,能給團隊和業務帶來什麼效益;然後考慮接入的成本,包括團隊成員的學習成本,對項目的修改成本,時間成本;開發體驗,性能監控,容災處理等。在考慮完這些之後,OK,我們開始決定接入Weex。

Weex實踐

二、達人店接入Weex

達人店目前是一個量級比較小的應用,在一年時間裡,目前有46個頁面。目前整體都比較穩定,後續所有頁面也都會采用weex進行開發。

Weex實踐

因為Weex給我們帶來的效益是顯而易見的:

  • 3人/日 -> 1人/日
  • 大程度擺脫App更新限制
  • Native 體驗

在接入的過程中,我們在各方面做了很多事情,包括腳手架、配置下發、跳轉規則、相對位址、預加載、降級、錯誤監控、建立元件庫、頁面傳參等等。下面詳細介紹一下這個過程,如果您有更好的方法,非常歡迎進行讨論交流。

(一) 前端

首先要建立Weex項目,這個可以看做是一個前端的項目,Weex也提供了腳手架工具。

weex 推薦的腳手架全家桶:

  • weex-toolkit

    :用來初始化項目,編譯,運作,debug所有工具。
  • weexpack

    :用來打包JSBundle的,實際也是對Webpack的封裝。
  • playground

    :一個上架的App,這個可以用來通過掃碼實時在手機上顯示出實際的頁面。
  • code snippets

    :這個是一個線上的playground。
  • weex devtools

    :就是為weex前端和native開發工程師服務的一款調試工具。
  • weex-loader

    :Webpack 的一個加載器,針對 Android 和 iOS 平台,用于編譯 .vue 格式的單檔案元件

達人店沒有使用weex提供的腳手架,而是我們前端同學定義了适合我們業務的項目結構,以下是達人店的Weex項目結構的一部分,每個頁面有一個檔案夾,包含了html,js,vue:

html檔案

:接入weex 的h5頁面

js檔案

:webpack編譯的入口檔案

vue檔案

:weex的編輯頁面

Weex實踐
以下是開發環境的示例,是以引入的js都沒有版本号,正式環境的path裡會有版本号

HTML示例其中,/dist/weex.js 引入

weex-vue-render

,進行了擴充,包括注冊module,注冊新的自定義元件。

weex-vue-render

可以了解為weex在H5的SDK。詳情見 HTML擴充

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
  </head>
  <body>
    <div id="weex"></div>
	<!-- entry -->
	<script src="//assets.showjoy.net/joyf2e/vendor/weex-extend/dist/weex.js" type="text/javascript"></script>
	<script src="./register-weex.min.js" type="text/javascript"></script>
  </body>
</html>
           

Js示例#weex 就是對應html裡

<div id="weex"></div>

,vue渲染後會挂在這個div上。

import weexComponent from './register-weex.vue';
weexComponent.el = '#weex';
export default new Vue(weexComponent);
           

Vue示例

<template>
  <div class="wrapper">
  <div>
</template>
<style scoped>
  .wrapper {
    background-color: #fff;
    flex: 1;
  }
</style>
<script>
</script>
           

建構的時候定義了兩套webpackConfig,分别用于編譯給h5和Native的JS。之是以需要分開編譯,是出于weex的要求,下文來自Weex官網,我們在Jenkins上實作了遠端建構。

編譯環境的差異

在 Weex 中使用 Vue.js ,你所需要關注的運作平台除了 Web 之外還有 Android 和 iOS ,在開發和編譯環境上還有一些不同點。針對 Web 和原生平台,将 Vue 項目源檔案編譯成目标檔案,有兩種不同的方式:
  • 針對 Web 平台,和普通 Vue 2.X 項目一樣,可以使用任意官方推薦的方式編譯源檔案,如 Webpack + vue-loader 或者 Browserify + vueify 。
  • 針對 Android 和 iOS 平台,我們提供了 weex-loader 工具支援編譯 .vue 格式的單檔案元件;也就是說,目前隻能使用 Webpack + weex-loader 來生成原生端可用的 js bundle。
Weex實踐

(二) Native 接入

請直接參考官網內建 Weex 到已有應用,SDK的依賴,初始化,渲染,都已說明。

說到底,最後的渲染結果都是傳回一個

View

,理論上根據業務需求,可以将view放置在頁面的任何地方。

我們達人店,都是整個頁面的形式來引入weex。

在Android方面,我們把weex的接入放入了自定義的WeexFragment。另外,建立WeexActivity,引用WeexFragment。這樣使用起來更靈活。

在iOS方面,我們把weex的接入放入了自定義的WeexViewController。

(三)跳轉規則

Native 渲染weex頁面的時候,需要傳入建構出來的js bundle,即一個js檔案。但是,不管是Native的日常寫法還是前端的慣常用法,都不會直接跳轉到一個js檔案。是以,考慮到符合前端的日常寫法,跳轉時,統一跳轉到url,如下圖:

Weex實踐

不管是weex,native,webview裡的跳轉都是url,然後再根據一定的規則進行match,根據match結果來決定是用weex、native還是webview來打開。

  • 要做到weex,native,webview裡的跳轉都是url,這裡需要做兩點:
    • 1、跳轉需要調用統一的openUrl,weex裡的a标簽href直接可以寫目标url,然後在Native端對a标簽的跳轉進行攔截;
    • 2、webview 裡的跳轉進行攔截,每個url都要進行規則比對
  • 定義規則,App内置一份,并可以動态下發
    • 1、url 和 原先 Native 頁面的對應關系,page可以根據原先App裡的Router設計來定義。
    • 2、url 和 weex js的對應關系,

      hideTitleBar

      :是否隐藏native的titlebar;

      v

      :支援最低App版本,不支援就降級;

      page

      : 頁面名稱,作為本地預加載的檔案名;

      h5

      : h5的url;

      url

      : js的路徑;

      md5

      : js檔案的md5,用于完整性校驗

url 和 Native 頁面的對應關系示例

[
			    {
			        "page":"chat",
			        "url":"(.*)//shop.m.showjoy.net/shop/chat\?type=1",
			        "v":"1.7.0"
			    },
			    {
			        "page":"main",
			        "url":"(.*)//shop.m.showjoy.net/shop/seller_home",
			        "v":"1.12.0"
			    }
	]
           

url和weex頁面對應關系示例

[
	{
	"hideTitleBar": "",
	"v": "1.7.0",
	"page": "order",
	"h5": "http://shop.m.showjoy.com/u/trade.html",
	"url": "http://cdn1.showjoy.com/assets/f2e/showjoy-assets/shop-weex-m/0.8.1/order-list-weex/order-list-weex.weex.min.js",
	"md5": "8b3268ef136291f2e9b8bd776e625c6b"
	},
	{
	"hideTitleBar": "",
	"v": "1.7.0",
	"page": "shoporder",
	"h5": "http://shop.m.showjoy.com/user/tradePage",
	"url": "http://cdn1.showjoy.com/assets/f2e/showjoy-assets/shop-weex-m/0.1.1/shop-order-weex/shop-order-weex.weex.min.js",
	"md5": "ca818a24588509bfe083cd4b99855841"
	}
]
           

(四)配置平台

針對跳轉規則的配置,我們做了自己的配置平台,針對全量、預發、線下提供不同的配置。參數:

  • appType:1代表android 2代表iOS
  • preTest: true 代表預發 false 代表全量
  • appVersion:App的版本号

平台會根據三個參數,下發目前App支援渲染的js頁面配置。

Weex實踐

(五)支援相對位址

按照平常前端的寫法,跳轉以及a标簽寫的基本都是相對位址,這樣對于線下、線上環境都不用做特别的處理。如下:

Weex實踐

開始介入weex的時候,大概版本是0.8左右,那時候預設還不支援相對位址,而我們就已經開始自己做了。在weex sdk 0.9.4開始 預設支援了相對位址,但是通過測試和源代碼檢視,它取的host是js bundle的host,如圖:

Weex實踐

而我們把js bundle放在了cdn,日常頁面的域名是shop.m.showjoy.com,兩者不一緻,是以在Native端,我們重寫了

URIAdapter

(Android)和

WXURLRewriteProtocol

(iOS),對url進行了處理,如果是相對位址,加上日常h5頁面的

host

,請求也是一樣。如此就支援了相對位址。Andoird

//這裡還可以配置其他的adapter,比如image,storage等
WXSDKEngine.initialize(application,
                new InitConfig.Builder()
                        .setURIAdapter(new SHCustomURIAdapter())
                        .build());
           

iOS

[WXSDKEngine registerHandler:[WXSJNetworkDefaultlmpl new] withProtocol:@protocol(WXURLRewriteProtocol)];
           
實作的時候,重寫rewrite接口,我們會根據線下、線上、預發等環境配置不一樣的host,另外還會支援Native的協定,如:sms://, weixin://dl/privacy
PS: A 标簽的跳轉,Native SDK的實作是調用Module“event”的openURL接口。可是預設沒有注冊“event”的Module,是以需要自己注冊event,或者自己重新實作 a标簽。sdk裡對a标簽跳轉的處理
Weex實踐
自定義event module.
Weex實踐

(六)預加載方案

如圖,是在本地開發時抓的包,加載的js bundle 雖然也不大,duration也很短。但是為了讓速度更進一步,我們還是做了預加載方案。

Weex實踐

方案設計如下:

  • 1)每次更新完配置檔案,周遊,check pagename.js檔案的md5
  • 2)如果本地存在md5一緻的檔案,就跳過,否則下載下傳
  • 3)下載下傳完成後,儲存格式為pagename.js,已存在則覆寫,校驗md5來保證檔案的完整性:
    • 相同的話,記錄檔案的最後修改時間;
    • 不同的話,删除已下載下傳檔案,重新下載下傳,重複校驗流程。
  • 4)每次打開指定頁面的時候:
    • 先檢查本地是否有對應page檔案
      • 如果不存在,則直接使用配置裡的remote url
      • 如果存在,則校驗記錄的修改時間是否與該檔案的最後修改時間是否一緻(這麼做,是為了

        防篡改

        ;不直接計算md5來校驗,是考慮到md5的計算有時間消耗)
        • 一緻就加載
        • 不一緻就用配置裡的remote url

(七)Native-JS通信

1、JS 調用Native
  • Weex 提供了Module擴充接口,開發者可以自己注冊Module,Module裡定義接口;
    Weex實踐
2、Native調用JS
  • Module接口可以設定Callback,接口實作處理完後,可以直接調用Callback,回調JS.
  • WXSDKInstance.fireEvent 是元素級别的,fireEvent 是instance的成員函數,需要傳遞elementRef。
    Weex實踐
  • WXSDKInstance.fireGlobalEventCallback 是頁面級别的,需要傳遞instanceID
    Weex實踐

(八)錯誤監控

*Native 端可以通過接口 IWXRenderListener 中的 onException 方法進行處理,這裡包括render error,js exception,network error等。

  • Weex層,自定義loge接口來實作錯誤的監控

(九)頁面傳參

關于頁面傳參,我們團隊的南洋同學寫過一篇文章(Weex頁面傳參)[https://juejin.im/post/5992db27518825244249e2db],為了友善閱讀,這裡再講述一遍。

1、正向傳參:x.com/a.html 跳轉到 x.com/b.html?age=12Native 渲染的時候,除了傳入JS Bundle,還有options參數,我們把url後面的參數都存入options,然後傳到weex頁面。

[_instance renderWithURL:[NSURL URLWithString:mstrURL] options:[self SHWeexOptionsWithH5URL:mstrH5URL withURL:mstrURL] data:nil];
           

這個參數,在書寫weex時,可以通過

weex.config.age

擷取。

為了擷取參數的統一性,H5頁面也一樣,打開一個url時,首先擷取url後面的參數,存入window.weex.config。

for (let key in urlParamObj) {
  window.weex.config[key] = encodeURIComponent(urlParamObj[key]);
}
           

2、反向傳參:x.com/b.html 回退到 x.com/a.html,帶回參數age=2這個是為了實作類似Android裡 onActivityResult的功能,可以把參數傳回給上個頁面。而實作這樣的功能,iOS Native的實作也隻要加個Delegate就可以了。

在weex要實作這個效果,本身沒有提供直接可以使用的方法,下面是我們目前采取的方案。

  • 首先自定義定義Module,增加setResult接口,然後再weex調用,參數是k-v的形式。接口的實作,就是把資料先存在本地;
    Weex實踐
  • 回到上個頁面,resume/willappear時候,擷取存儲的k-v,并通過fireGlobalEventCallback把資料傳遞到weex頁面。,并且remove資料。
    Weex實踐
  • 在weex頁面進行監聽,并處理
    Weex實踐

(十) 降級方案

所謂降級,就是目前新頁面渲染失敗,或者目前App版本不夠新,無法支援新頁面,故會通路h5頁面。這裡我們區分了兩種情況:

  • 1、渲染失敗: 一緻跳轉到h5頁面
  • 2、版本控制:
    • 新增的頁面:無法支援新頁面的App版本就降級通路h5頁面
    • 老頁面的修改:無法支援新頁面的App版本會通路老頁面
      Weex實踐

(十一)螢幕适配

螢幕适配一直是移動端開發不可避開的話題。在Weex的世界裡,定義了一個預設螢幕尺寸,用來适配iOS,Android各種不同大小的螢幕。weex架構在底層做了針對不同螢幕的适配工作,具體計算公式為

實際高寬 = 代碼高寬 * (螢幕寬度 / 750)

Weex實踐

目前我們設計給的視覺稿是375的,我們開發的時候隻要拿到值x2,就可以了。其中有一種普遍會遇到需要的計算的地方,這裡詳細講一下。

Weex實踐

使用List和scroll的時候,高度是需要設定的,而這個高度需要根據不同頁面進行計算,以上圖為例,首先想到的是:list高度 = screen高度 - titlebarHeight

weex可以通過

$getConfig().env.deviceHeight

$getConfig().env.deviceWidth

的形式來擷取手機螢幕的高度但是其實這樣是不準确的,因為Android Native的總高度,事實上是可供顯示的全屏高度,而不一定是實體螢幕的高度,因為有狀态欄,虛拟按鍵欄,Smartbar等等安卓碎片化引入的額外顯示元素,實際全屏高度很有可能小于實體螢幕高度。是以真正的容器高度,需要由外部傳入,

List實際高度 = ContainnerHeight - titleBar的高度字面量 * 轉換比例ratio轉化比例ratio = this.$getConfig().env.deviceWidth / 750

ps: 外部傳入的ContainnerHeight通過Module的接口傳入

list的字面量高度 = list實際高度 / 轉換比例ratio = ContainnerHeight / ratio - titleBar的高度字面量

另外,weex也提供

this.$getConfig().env.scale

,如有需要可以利用它來計算dp2px。

三、我們遇到的一些問題和解決方案

1)Android 的weex sdk 0.13.1,input元件初始值是空時,粘貼的時候無法觸發事件@input

設定初始值,點選時,如果初始值與placeholder一緻,就清空

2)在iOS9.x系統中文本被截斷

在iOS9.x系統中不支援line-height,被強行繪制,存在相容性問題,暫時不要使用font-size和line-height相同大小

3)class 的動态綁定

vue的寫法 :class={'header': true} weex的寫法 :class=“[true ? 'header' : '']"

4)animation動畫在iOS 8及以下的H5頁面失效

對于webkit不相容的css樣式(transform)進行相容

5)scroller橫向滾動時iOS裝置元素無法橫向排列

需要給scroller設定樣式 flex-deriction: row,這樣可以確定三端顯示一緻。

6)Js Date 轉換時間,Android差8小時

dateConfigTimeZone(timeValue, offset) { const date = new Date(timeValue); // UTC時間 (1970-1-1至今毫秒數 + 本地時間與GMT分鐘差) const utc = date.getTime() + (date.getTimezoneOffset() * 60 * 1000); // 傳回 (UTC時間 + 時區差) return new Date(utc + (60 * 60 * 1000 * offset)); }

四、我們還在做的事情

(一)weex元件庫

一年的實踐,我們也積累了一些基礎元件和業務元件,如圖,有description、import、example、preview、qrcode等。

Weex實踐

看下 spon-ui 元件庫項目的目錄結構。

|- spon-ui
||-- build
||-- docs
||-- examples
||-- packages
|||--- weex-field
||||---- index.js
||||---- field.vue
||||---- example.vue
||||---- readme.md
||||---- package.json
||-- src
           
  • build 中存放一些腳本執行檔案,用于工程的調試、釋出。
  • docs 中存放文檔調試的腳本,生成一個文檔調試伺服器。
  • examples 中存放元件調試的腳本,生成一個元件調試伺服器。(不存放元件例子)
  • packages 存放真實元件,以及元件的文檔和例子。
  • src 存放元件可以使用的公共方法。

詳情請檢視我們的前端同學南洋寫「大前端」尚妝達人店 UI 元件化 工程實踐

(二) 其他

  • Cookie 支援
  • HttpDNS 接入
  • 圖檔支援裁剪、webp
  • 性能監控,正在做
  • 增量更新,正在做
以上就是我們這一年的總結,希望能給大家帶來參考。歡迎讨論交流文中的不足。
感謝團隊所有成員,以上是我們一起努力的結果。
@嘉文,資深iOS,github,部落格
@黎鶴,資深iOS,github
@路遠,資深Android,github,部落格
@米奇,前端女神,歡迎關注微網誌
@南洋,前端大神,歡迎關注微網誌 ,技術文章産出高,
@路飛,移動端負責人,github,部落格
感謝以下大神文章提供的幫助:(看了很多文章,如果沒有加了,麻煩告知一聲)
Weex官網
Weex github
Rax官網
網易嚴選App感受Weex開發
由FlexBox算法強力驅動的Weex布局引擎
Weex 事件傳遞的那些事兒
Weex 中别具匠心的 JS Framework
地球上最全的weex踩坑攻略-出自大量實踐與沉澱

作者:尚妝産品技術刊讀

連結:https://juejin.im/post/5a2a730cf265da431f4afd35

來源:掘金

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。