本文來自尚妝移動端團隊路飛
發表於尚妝github博客,歡迎訂閱!javascript
尚妝達人店接入weex也一年的時間了,在此期間,也陸陸續續出了一些文章:
「Android」 詳細全面的基於vue2.0Weex接入過程(Android視角)
「前端」weex頁面傳參
「大前端」weex裏native主動發送事件到JS的方案實現
weex 三端實現Pager 組件(ViewPager) - 本仁筆記
記錄團隊weex實踐過程當中須要特殊注意的點css
這裏就詳細地作一個總結,但願能夠給你們帶來一些參考。咱們團隊也比較小,App的量級也不大,不少作得不夠好的地方,還但願大神不吝賜教。html
Weex 是一套簡單易用的跨平臺開發方案,能以 web 的開發體驗構建高性能、可擴展的 native 應用,爲了作到這些,Weex 與 Vue 合做,使用 Vue 做爲上層框架,並遵循 W3C 標準實現了統一的 JSEngine 和 DOM API,這樣一來,你甚至可使用其餘框架驅動 Weex,打造三端一致的 native 應用。前端
前言引用了Weex官網的定義,咱們在實踐的過程當中也實際地體會到了這些。如下是提煉出的幾個關鍵字:vue
還未接觸過weex的同窗,若是想先看一下效果,能夠訪問 Weex 提供的 在線Playground,進行編輯和瀏覽,App端下載playgroundplayground進行掃碼瀏覽效果。html5
能夠看到,Weex能夠經過本身設計的DSL,用vue像寫 web 頁面同樣寫一個 app 的頁面,整個頁面書寫分紅了3段,template
、style
、script
,借鑑了成熟的MVVM的思想。java
後面會講到,理論上也能夠橫向支持採用React、angular等框架來書寫頁面。阿里開源的Rax,就是基於React的標準,支持在Weex渲染,具體能夠看知乎上一個問答如何看待阿里開源的Rax框架?react
而Playground集成了Weex SDK
,掃碼後,獲得了編譯好的JS Bundle
,而後經過JS Framework層解析,輸出Json格式的Visual Dom
,而後經過JS-Native Bridge
來渲染成Native界面,也經過Bridge來進行Js-Native的事件傳遞。以下是官網給出的架構圖:android
經過斷點調試能夠看到,JSFramework傳給SDK的渲染指令是這樣子,SDK 再根據不一樣的type和參數,渲染成對應的Native組件。webpack
傳統的App,Native UI 是能夠直接獲取 Device Power的,而Weex App裏,Native UI 和 Device Power之間經過JavaScript來鏈接,如圖所示(圖來自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提供的組件有問題,也均可以直接重寫替換。
對於一個新技術的接入,咱們首先會去考慮這個技術的優缺點,能給團隊和業務帶來什麼效益;而後考慮接入的成本,包括團隊成員的學習成本,對項目的修改爲本,時間成本;開發體驗,性能監控,容災處理等。
在考慮完這些以後,OK,咱們開始決定接入Weex。
達人店目前是一個量級比較小的應用,在一年時間裏,目前有46個頁面。目前總體都比較穩定,後續全部頁面也都會採用weex進行開發。
由於Weex給咱們帶來的效益是顯而易見的:
在接入的過程當中,咱們在各方面作了不少事情,包括腳手架、配置下發、跳轉規則、相對地址、預加載、降級、錯誤監控、創建組件庫、頁面傳參等等。下面詳細介紹一下這個過程,若是您有更好的方法,很是歡迎進行討論交流。
首先要創建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的編輯頁面
如下是開發環境的示例,因此引入的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 到已有應用,SDK的依賴,初始化,渲染,都已說明。
說到底,最後的渲染結果都是返回一個View
,理論上根據業務需求,能夠將view放置在頁面的任何地方。咱們達人店,都是整個頁面的形式來引入weex。
在Android方面,咱們把weex的接入放入了自定義的WeexFragment。另外,新建WeexActivity,引用WeexFragment。這樣使用起來更靈活。
在iOS方面,咱們把weex的接入放入了自定義的WeexViewController。
Native 渲染weex頁面的時候,須要傳入構建出來的js bundle,即一個js文件。可是,無論是Native的平常寫法仍是前端的慣經常使用法,都不會直接跳轉到一個js文件。因此,考慮到符合前端的平常寫法,跳轉時,統一跳轉到url,以下圖:
無論是weex,native,webview裏的跳轉都是url,而後再根據必定的規則進行match,根據match結果來決定是用weex、native仍是webview來打開。
要作到weex,native,webview裏的跳轉都是url,這裏須要作兩點:
定義規則,App內置一份,並能夠動態下發
`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" } ]
針對跳轉規則的配置,咱們作了本身的配置平臺,針對全量、預發、線下提供不一樣的配置。參數:
平臺會根據三個參數,下發當前App支持渲染的js頁面配置。
按照日常前端的寫法,跳轉以及a標籤寫的基本都是相對地址,這樣對於線下、線上環境都不用作特別的處理。以下:
開始介入weex的時候,大概版本是0.8左右,那時候默認還不支持相對地址,而咱們就已經開始本身作了。在weex sdk 0.9.4開始 默認支持了相對地址,可是經過測試和源代碼查看,它取的host是js bundle的host,如圖:
而咱們把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標籤跳轉的處理自定義event module.
如圖,是在本地開發時抓的包,加載的js bundle 雖然也不大,duration也很短。可是爲了讓速度更進一步,咱們仍是作了預加載方案。
方案設計以下:
3)下載完成後,保存格式爲pagename.js,已存在則覆蓋,校驗md5來保證文件的完整性:
4)每次打開指定頁面的時候:
先檢查本地是否有對應page文件
若是存在,則校驗記錄的修改時間是否與該文件的最後修改時間是否一致(這麼作,是爲了防篡改
;不直接計算md5來校驗,是考慮到md5的計算有時間消耗)
*Native 端能夠經過接口 IWXRenderListener 中的 onException 方法進行處理,這裏包括render error,js exception,network error等。
關於頁面傳參,咱們團隊的南洋同窗寫過一篇文章(Weex頁面傳參)[https://juejin.im/post/5992db...],爲了方便閱讀,這裏再講述一遍。
一、正向傳參:x.com/a.html 跳轉到 x.com/b.html?age=12
Native 渲染的時候,除了傳入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]); }
二、反向傳參:x.com/b.html 回退到 x.com/a.html,帶回參數age=2
這個是爲了實現相似Android裏 onActivityResult的功能,能夠把參數傳回給上個頁面。而實現這樣的功能,iOS Native的實現也只要加個Delegate就能夠了。
在weex要實現這個效果,自己沒有提供直接可使用的方法,下面是咱們目前採起的方案。
所謂降級,就是當前新頁面渲染失敗,或者當前App版本不夠新,沒法支持新頁面,故會訪問h5頁面。這裏咱們區分了兩種狀況:
二、版本控制:
屏幕適配一直是移動端開發不可避開的話題。在Weex的世界裏,定義了一個默認屏幕尺寸,用來適配iOS,Android各類不一樣大小的屏幕。weex框架在底層作了針對不一樣屏幕的適配工做,具體計算公式爲 實際高寬 = 代碼高寬 * (屏幕寬度 / 750)
目前咱們設計給的視覺稿是375的,咱們開發的時候只要拿到值x2,就能夠了。
其中有一種廣泛會遇到須要的計算的地方,這裏詳細講一下。
使用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)); }
`
一年的實踐,咱們也積累了一些基礎組件和業務組件,如圖,有description、import、example、preview、qrcode等。
看下 spon-ui 組件庫項目的目錄結構。
|- spon-ui ||-- build ||-- docs ||-- examples ||-- packages |||--- weex-field ||||---- index.js ||||---- field.vue ||||---- example.vue ||||---- readme.md ||||---- package.json ||-- src
詳情請查看咱們的前端同窗南洋寫「大前端」尚妝達人店 UI 組件化 工程實踐
以上就是咱們這一年的總結,但願能給你們帶來參考。歡迎討論交流文中的不足。
感謝團隊全部成員,以上是咱們一塊兒努力的結果。
@嘉文,資深iOS,github,博客
@黎鶴,資深iOS,github
@路遠,資深Android,github,博客
@米奇,前端女神,歡迎關注微博
@南洋,前端大神,歡迎關注微博 ,技術文章產出高,
@路飛,移動端負責人,github,博客感謝如下大神文章提供的幫助:(看了不少文章,若是沒有加了,麻煩告知一聲)
Weex官網
Weex github
Rax官網
網易嚴選App感覺Weex開發
由FlexBox算法強力驅動的Weex佈局引擎
Weex 事件傳遞的那些事兒
Weex 中別具匠心的 JS Framework
地球上最全的weex踩坑攻略-出自大量實踐與沉澱