Weex在达人店的一年实践

Posted 悬笔e绝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Weex在达人店的一年实践相关的知识,希望对你有一定的参考价值。

前言

weex,迄今为止都还未在前端早读课分享过,今天这一篇算是第一次,带来分享的是尚妆@路飞在达人店一年的实践分享。

@路飞,来自杭州尚妆大前端团队。团队内负责weex在三端的集成应用

正文从这开始~

一、什么是Weex

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




image




还未接触过weex的同学,如果想先看一下效果,可以访问 Weex 提供的 在线Playground,进行编辑和浏览,App端下载playground进行扫码浏览效果。



Weex在达人店的一年实践
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的事件传递。如下是官网给出的架构图:



Weex在达人店的一年实践
image



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



Weex在达人店的一年实践
image



Weex的扩展性很好,可以对网络、图片、存储、UT、组件、接口等根据自身App和业务需求进行扩展,即使weex提供的组件有问题,也都可以直接重写替换。




Weex在达人店的一年实践
image




二、达人店接入Weex

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




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 的一个加载器,针对 androidios 平台,用于编译 .vue 格式的单文件组件

达人店没有使用weex提供的脚手架,而是我们前端同学定义了适合我们业务的项目结构,以下是达人店的Weex项目结构的一部分,每个页面有一个文件夹,包含了html,js,vue:

  • html文件:接入weex 的h5页面

  • js文件:webpack编译的入口文件

  • vue文件:weex的编辑页面



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。




Weex在达人店的一年实践
image



(二) Native 接入

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

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

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

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

(三)跳转规则

Native 渲染weex页面的时候,需要传入构建出来的js bundle,即一个js文件。但是,不管是Native的日常写法还是前端的惯常用法,都不会直接跳转到一个js文件。所以,考虑到符合前端的日常写法,跳转时,统一跳转到url,如下图:




Weex在达人店的一年实践
image




不管是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在达人店的一年实践
image




(五)支持相对地址



Weex在达人店的一年实践
image





Weex在达人店的一年实践
image



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在达人店的一年实践
image

自定义event module.

Weex在达人店的一年实践
image

(六)预加载方案

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



Weex在达人店的一年实践
image



方案设计如下:

  • 1)每次更新完配置文件,遍历,check pagename.js文件的md5

  • 2)如果本地存在md5一致的文件,就跳过,否则下载

  • 3)下载完成后,保存格式为pagename.js,已存在则覆盖,校验md5来保证文件的完整性:

    • 相同的话,记录文件的最后修改时间;

    • 不同的话,删除已下载文件,重新下载,重复校验流程。

  • 4)每次打开指定页面的时候:

    • 如果不存在,则直接使用配置里的remote url

    • 如果存在,则校验记录的修改时间是否与该文件的最后修改时间是否一致(这么做,是为了防篡改;不直接计算md5来校验,是考虑到md5的计算有时间消耗)

    • 一致就加载

    • 不一致就用配置里的remote url

    • 先检查本地是否有对应page文件

(七)Native-JS通信

1、JS 调用Native
  • Weex 提供了Module扩展接口,开发者可以自己注册Module,Module里定义接口;

    Weex在达人店的一年实践
    image
2、Native调用JS
  • Module接口可以设置Callback,接口实现处理完后,可以直接调用Callback,回调JS.

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

    Weex在达人店的一年实践
    image
  • WXSDKInstance.fireGlobalEventCallback 是页面级别的,需要传递instanceID

    Weex在达人店的一年实践
    image

(八)错误监控

*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=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]);
}

2、反向传参:x.com/b.html 回退到 x.com/a.html,带回参数age=2

这个是为了实现类似Android里 onActivityResult的功能,可以把参数传回给上个页面。而实现这样的功能,iOS Native的实现也只要加个Delegate就可以了。

在weex要实现这个效果,本身没有提供直接可以使用的方法,下面是我们目前采取的方案。

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

    Weex在达人店的一年实践
    image
  • 回到上个页面,resume/willappear时候,获取存储的k-v,并通过fireGlobalEventCallback把数据传递到weex页面。,并且remove数据。

    Weex在达人店的一年实践
    image
  • 在weex页面进行监听,并处理

    Weex在达人店的一年实践
    image

(十) 降级方案

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

  • 1、渲染失败: 一致跳转到h5页面

  • 2、版本控制:

    • 新增的页面:无法支持新页面的App版本就降级访问h5页面

    • 老页面的修改:无法支持新页面的App版本会访问老页面

      Weex在达人店的一年实践
      image

(十一)屏幕适配

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




Weex在达人店的一年实践
image



目前我们设计给的视觉稿是375的,我们开发的时候只要拿到值x2,就可以了。
其中有一种普遍会遇到需要的计算的地方,这里详细讲一下。



Weex在达人店的一年实践
image



使用List和scroll的时候,高度是需要设置的,而这个高度需要根据不同页面进行计算,以上图为例,首先想到的是:
list高度 = screen高度 - titlebarHeight

weex可以通过weex.config.env.deviceHeightweex.config.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等。




image




详情请查看我们的前端同学南洋写「大前端」尚妆达人店 UI 组件化 工程实践

(二) 其他

  • Cookie 支持

  • HttpDNS 接入

  • 图片支持裁剪、webp

  • 性能监控,正在做

  • 增量更新,正在做


关于本文

原文:https://github.com/ShowJoy-com/showjoy-blog/issues/38




以上是关于Weex在达人店的一年实践的主要内容,如果未能解决你的问题,请参考以下文章

1号店的分布式搜索引擎的架构实践

Weex 学习与实践:Weex ,你需要知道的事

Weex实战分享|Weex在盛大游戏中的应用实践

阿里前端工程师分享移动开发与 Weex 的改变和被改变|北京源创会

Weex学习与实践:Weex,你需要知道的事

Weex黑科技的用户实践