「大前端」Weex在达人店的一年实践

Weex在达人店的一年实践

本文来自尚妆移动端团队路飞javascript

发表于尚妆github博客,欢迎订阅!css

尚妆达人店接入weex也一年的时间了,在此期间,也陆陆续续出了一些文章:html

「Android」 详细全面的基于vue2.0Weex接入过程(Android视角)前端

「前端」weex页面传参vue

「大前端」weex里native主动发送事件到JS的方案实现html5

weex 三端实现Pager 组件(ViewPager) - 本仁笔记java

记录团队weex实践过程当中须要特殊注意的点react

这里就详细地作一个总结,但愿能够给你们带来一些参考。咱们团队也比较小,App的量级也不大,不少作得不够好的地方,还但愿大神不吝赐教。android

1、什么是Weex

Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能、可扩展的 native 应用,为了作到这些,Weex 与 Vue 合做,使用 Vue 做为上层框架,并遵循 W3C 标准实现了统一的 JSEngine 和 DOM API,这样一来,你甚至可使用其余框架驱动 Weex,打造三端一致的 native 应用。webpack

前言引用了Weex官网的定义,咱们在实践的过程当中也实际地体会到了这些。如下是提炼出的几个关键字:

image

还未接触过weex的同窗,若是想先看一下效果,能够访问 Weex 提供的 在线Playground,进行编辑和浏览,App端下载playgroundplayground进行扫码浏览效果。

image

能够看到,Weex能够经过本身设计的DSL,用vue像写 web 页面同样写一个 app 的页面,整个页面书写分红了3段,templatestylescript,借鉴了成熟的MVVM的思想。

后面会讲到,理论上也能够横向支持采用React、angular等框架来书写页面。阿里开源的Rax,就是基于React的标准,支持在Weex渲染,具体能够看知乎上一个问答如何看待阿里开源的Rax框架?

而Playground集成了Weex SDK,扫码后,获得了编译好的JS Bundle,而后经过JS Framework层解析,输出Json格式的Visual Dom,而后经过JS-Native Bridge 来渲染成Native界面,也经过Bridge来进行Js-Native的事件传递。以下是官网给出的架构图:

image

经过断点调试能够看到,JSFramework传给SDK的渲染指令是这样子,SDK 再根据不一样的type和参数,渲染成对应的Native组件。

image

传统的App,Native UI 是能够直接获取 Device Power的,而Weex App里,Native UI 和 Device Power之间经过JavaScript来链接,如图所示(图来自weex官网):

image

在开始接入以前,关于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提供的组件有问题,也均可以直接重写替换。

image

对于一个新技术的接入,咱们首先会去考虑这个技术的优缺点,能给团队和业务带来什么效益;而后考虑接入的成本,包括团队成员的学习成本,对项目的修改为本,时间成本;开发体验,性能监控,容灾处理等。 在考虑完这些以后,OK,咱们开始决定接入Weex。

image

2、达人店接入Weex

达人店目前是一个量级比较小的应用,在一年时间里,目前有46个页面。目前总体都比较稳定,后续全部页面也都会采用weex进行开发。

image

由于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的编辑页面

image

如下是开发环境的示例,因此引入的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。

image

(二) Native 接入

请直接参考官网集成 Weex 到已有应用,SDK的依赖,初始化,渲染,都已说明。

说到底,最后的渲染结果都是返回一个View,理论上根据业务需求,能够将view放置在页面的任何地方。咱们达人店,都是整个页面的形式来引入weex。

在Android方面,咱们把weex的接入放入了自定义的WeexFragment。另外,新建WeexActivity,引用WeexFragment。这样使用起来更灵活。

在iOS方面,咱们把weex的接入放入了自定义的WeexViewController。

(三)跳转规则

Native 渲染weex页面的时候,须要传入构建出来的js bundle,即一个js文件。可是,无论是Native的平常写法仍是前端的惯经常使用法,都不会直接跳转到一个js文件。因此,考虑到符合前端的平常写法,跳转时,统一跳转到url,以下图:

image

无论是weex,native,webview里的跳转都是url,而后再根据必定的规则进行match,根据match结果来决定是用weex、native仍是webview来打开。

  • 要作到weex,native,webview里的跳转都是url,这里须要作两点:
    • 一、跳转须要调用统一的openUrl,weex里的a标签href直接能够写目标url,而后在Native端对a标签的跳转进行拦截;
    • 二、webview 里的跳转进行拦截,每一个url都要进行规则匹配
  • 定义规则,App内置一份,并能够动态下发
    • 一、url 和 原先 Native 页面的对应关系,page能够根据原先App里的Router设计来定义。
    • 二、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页面配置。

image

(五)支持相对地址

按照日常前端的写法,跳转以及a标签写的基本都是相对地址,这样对于线下、线上环境都不用作特别的处理。以下:

image

开始介入weex的时候,大概版本是0.8左右,那时候默认还不支持相对地址,而咱们就已经开始本身作了。在weex sdk 0.9.4开始 默认支持了相对地址,可是经过测试和源代码查看,它取的host是js bundle的host,如图:

image

而咱们把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标签跳转的处理

image

自定义event module.

image

(六)预加载方案

如图,是在本地开发时抓的包,加载的js bundle 虽然也不大,duration也很短。可是为了让速度更进一步,咱们仍是作了预加载方案。

image

方案设计以下:

  • 1)每次更新完配置文件,遍历,check pagename.js文件的md5
  • 2)若是本地存在md5一致的文件,就跳过,不然下载
  • 3)下载完成后,保存格式为pagename.js,已存在则覆盖,校验md5来保证文件的完整性:
    • 相同的话,记录文件的最后修改时间;
    • 不一样的话,删除已下载文件,从新下载,重复校验流程。
  • 4)每次打开指定页面的时候:
    • 先检查本地是否有对应page文件
      • 若是不存在,则直接使用配置里的remote url
      • 若是存在,则校验记录的修改时间是否与该文件的最后修改时间是否一致(这么作,是为了防篡改;不直接计算md5来校验,是考虑到md5的计算有时间消耗)
        • 一致就加载
        • 不一致就用配置里的remote url

(七)Native-JS通讯

一、JS 调用Native
  • Weex 提供了Module扩展接口,开发者能够本身注册Module,Module里定义接口;
    image
二、Native调用JS
  • Module接口能够设置Callback,接口实现处理完后,能够直接调用Callback,回调JS.

  • WXSDKInstance.fireEvent 是元素级别的,fireEvent 是instance的成员函数,须要传递elementRef。

    image

  • WXSDKInstance.fireGlobalEventCallback 是页面级别的,须要传递instanceID

    image

(八)错误监控

*Native 端能够经过接口 IWXRenderListener 中的 onException 方法进行处理,这里包括render error,js exception,network error等。

  • Weex层,自定义loge接口来实现错误的监控

(九)页面传参

关于页面传参,咱们团队的南洋同窗写过一篇文章(Weex页面传参)[https://juejin.im/post/5992db27518825244249e2db],为了方便阅读,这里再讲述一遍。

一、正向传参: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要实现这个效果,自己没有提供直接可使用的方法,下面是咱们目前采起的方案。

  • 首先自定义定义Module,增长setResult接口,而后再weex调用,参数是k-v的形式。接口的实现,就是把数据先存在本地;

    image

  • 回到上个页面,resume/willappear时候,获取存储的k-v,并经过fireGlobalEventCallback把数据传递到weex页面。,而且remove数据。

    image

  • 在weex页面进行监听,并处理

    image

(十) 降级方案

所谓降级,就是当前新页面渲染失败,或者当前App版本不够新,没法支持新页面,故会访问h5页面。这里咱们区分了两种状况:

  • 一、渲染失败: 一致跳转到h5页面
  • 二、版本控制:
    • 新增的页面:没法支持新页面的App版本就降级访问h5页面
    • 老页面的修改:没法支持新页面的App版本会访问老页面
      image

(十一)屏幕适配

屏幕适配一直是移动端开发不可避开的话题。在Weex的世界里,定义了一个默认屏幕尺寸,用来适配iOS,Android各类不一样大小的屏幕。weex框架在底层作了针对不一样屏幕的适配工做,具体计算公式为 实际高宽 = 代码高宽 * (屏幕宽度 / 750)

image

目前咱们设计给的视觉稿是375的,咱们开发的时候只要拿到值x2,就能够了。 其中有一种广泛会遇到须要的计算的地方,这里详细讲一下。

image

使用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。

3、咱们遇到的一些问题和解决方案

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)); }

4、咱们还在作的事情

(一)weex组件库

一年的实践,咱们也积累了一些基础组件和业务组件,如图,有description、import、example、preview、qrcode等。

image

看下 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踩坑攻略-出自大量实践与沉淀

相关文章
相关标签/搜索