小程序的开发不可避免的会面临跨平台开发的问题。各小程序平台有哪些特点?如何处理各平台的差异?本文分享淘票票在跨平台开发上的经验总结,包含了技术演进及差异控制策略,希望能帮助同学们提前避坑。

在 2019 年,阿里巴巴文娱的淘票票几乎涉足了当时市面上所有的小程序,其中在不少平台上,我们是阿里第一批吃螃蟹的技术团队。回顾过往,我们做过很多尝试,也踩过很多坑。

我们特别整理了支付宝小程序、百度小程序、字节跳动小程序、快应用的开发经验,希望为你带来启发。

一 支付宝小程序

支付宝内的淘票票用户主要是以购票为主,工具属性比较明显。淘票票入口众多,均引导跳转到小程序,引导用户在小程序内进行购票、娱乐消费、收藏、添加到首页/桌面、分享等行为。

淘票票支付宝小程序,经历了近一年的起步开发,以及一年多的版本迭代,业务开发涵盖了购票、视频、资讯、社区等多个场景。

1 小程序开发

1) 在核心业务中慎用 web-view

实际项目线上运行情况及用户反馈表明:web-view 初始化较慢、易受网络干扰、性能差(对比离线包及普通 H5 页面)、问题较多,建议不要在核心业务中使用 web-view 进行承载。

2) 自有城市体系与支付宝城市组件的适配技巧

由于支付宝的城市组件是基于自身城市体系的,淘票票拥有独立的城市体系,所以需要城市选择组件适配不同城市体系的场景。经过几轮推动迭代,淘票票线上已使用城市选择组件,已支持复写当前定位城市、历史访问城市、热门城市、城市列表信息等。使用my.chooseCity、my.onLocatedComplete、my.setLocatedCity 三个 JSAPI 可实现对应效果。

3) 如何实现沉浸式效果(透明导航栏)?

  • 首先在 page.json 配置 “transparentTitle” 为 “auto” 属性,开启沉浸式布局。

  • 其次,页面布局适配沉浸式顶部透明导航栏即可,通过 my.getSystemInfo 获取 titleBarHeight 及 statusBarHeight 可计算出顶部透明高度。

注意:Android 5.0 以下由于不支持沉浸式状态栏,所以页面会从状态栏下开始布局。

4) 小程序 tabBar 换肤、红点

主要使用的JSAPI及event:my.setTabBarStyle、my.setTabBarItem、page.onTabItemTap,参数参考官方文档即可。注意事项如下:

  • 小程序触发 relaunch 时,tabBar 的样式会被清除,需要再次设置,目前建议在 app.onShow 里多次触发设置逻辑。

  • 尽量使用本地图片,在线图片有个下载的过程,体验不太好,且弱网下图片载入可能失败。

  • my.setTabBarItem 的参数每一项均需要赋值,否则 Android 可能会报 “invalid parameter”。

2 小程序开发注意事项

  • 不要使用 tnpm 安装依赖,tnpm 软连接目前支持有问题。

  • devDependencies 和 dependencies 需要分开,将 devDependencies 移到项目代码外层,否则会额外增加包大小。

  • 设置 transparent/pullRefresh 等 window 配置时,跳转别的页面会被继承,需要在 app.json 初始化此类配置信息规避。

  • web-view 里面的页面会失去下拉刷新、resume 等特性。

  • Android 低版本不支持 sticky 属性。

  • 某个值控制 dom 是否渲染,下次更新时此值若为 undefined 时不会销毁掉会被忽略掉。

  • window.atob、window.btoa 需要第三方库来替代。

  • lodash 某些方法不能直接使用,因为小程序构建时无 global 对象。

3 小程序监控

使用阿里云的 ARMS 平台,参考官方文档接入即可。

优点:有实时大盘,排查用户日志方便,上报更自由、丰富。

缺点:有接入成本、需要开发,增加包大小,且要收费。

4 小程序权限

小程序有权限管控,无论是申请 JSAPI 权限还是 H5 域名配置,都是需要打新的包上传才能生效。

二 百度小程序

1 背景

以微信小程序为蓝本的百度小程序,也同样具备相似的商业定位。依赖百度这样的老牌搜索门户,百度小程序更加偏向目的性强的搜索热词进行小程序的关联调起和互动,这也是百度小程序和其他小程序的区别。

由此,我们在 2018 年底进行了百度小程序的开发工作,由于在前期积累了小程序开发经验,百度小程序的开发更加的平稳和快速,不到一个月就上线运营了。

2 应用场景

百度小程序入口:

三种入口:百度App搜索关联、百度贴吧关联、其他百度生态搜索关联。

3 开发实战

下面是淘票票百度小程序开发过程中遇到的坑和总结:

1)基础开发

百度小程序的开发与微信、头条小程序的开发方式和框架概念非常相似,都属于前端开发的一块子集,主要可以分为 4 块来构建百度小程序的页面:

  • 第一块:HTML。构建页面框架:使用 xxx.swan 文件进行页面元素框架的搭建,具有独特的 HTML 标签如 view,scroll-view 等。

  • 第二块:CSS。管理页面样式:使用 xxx.css 文件进行页面样式的管理,基本的 CSS 的样式都大部分支持。

  • 第三块:JS。编写页面逻辑:使用 xxx.js 文件进行页面逻辑的书写,小程序具有其独特的生命周期管理方法。

  • 第四块:JSON。组件注册:百度小程序支持通过组件的方式进行页面的搭建,通过在 xxx.json 中注册组件供页面使用。

2)template 模板使用

与其他的小程序相同,百度小程序也提供了模板 template 的能力,使用模板可以提高工程化和代码可维护性,开发者可以在模板中设计代码片段,向外暴露接口注入外界变量之后,可以在合适的时机去使用该代码片段。

但是在百度小程序使用 template 使用时,需要注意传递数据时需要使用 {{{ }}} 三层花括号包裹对象,否则数据注入时会出现异常。

百度小程序的 template 的使用:

<template is="xxx" data="{{{item}}}"/>

作为对比,头条、微信小程序 template 需要两层花括号:

<template is="xxx" data="{{item}}"/>

3)组件属性的 observer 使用

在使用组件(Component)是大概率会使用到属性的 observer 方法,当属性被改变时会执行属性对应的 observer 方法,此处需要注意在使用 observer 方法时,避免使用下划线开头的方法名,可能会造成 observer 方法的循环调用问题。

或者当发现 properties 中的 observer 方法被循环调用时,检查一下 observer 绑定的方法是否有下划线。方法命名移除下划线,大概率可以解决循环调用问题。

会出现 observer 循环调用的情况:

isShowLoadMore: {             type: Boolean,             value: false,             observer: '_isshowChange'       },

推荐的写法:

isShowLoadMore: {             type: Boolean,             value: false,             observer: 'isshowChange'       },

4)scroll-view 的使用

在使用 scroll-view 的开发过程中,对存在多个可滑动区域的页面且其中一个滑动区域为 fixed 样式时,iOS 机型会偶现 scroll-view 空白的问题。

可能存在异常的页面布局如下:

<view class='头部组件' style='position:fixed'/>  <scroll-view class='可滑动区域1' style='position:fixed' />  <view class='可滑动区域2' />

其中 “可滑动区域 2” 为依赖内容撑开的页面 View,当内容到达一定长度时,页面 View 会提供滑动能力。如果使用上述写法可能会出现 scroll-view 空白的问题。

推荐的写法:

<view class='头部组件' style='position:fixed'/>  <scroll-view class='可滑动区域1' style='position:fixed;height:44px' />  <scroll-view class='可滑动区域2' style='height:80vh' />

5)小程序 DSL 页与 WebView 页的登录流程

小程序的页面实现方式可以分为两种:一种为小程序原生的页面;另外一种是使用 WebView 组件,将 H5 页面展示在小程序中。处理两种页面的登录时一般是先进行 DSL 页登录(小程序原生页面),完成 DSL 页登录后,再进行 H5 容器页的登录。

a) DSL 页登录

先进行小程序的登录授权,获取到小程序的登录凭证,拿着登录凭证去自己的业务服务端获取真实的小程序登录信息,当开发者完成上述流程之后,将登录态信息加密后存储在本地。下次进行需要登录校验的页面时,进行本地登录信息的登录校验,则 DSL 页的登录流程就完成了。

b) WebView 容器页登录

由于百度小程序无法操作到 WebView 容器的 cookie 信息,所以在 WebView 容器页进行登录时,势必要进行一次从服务端获取登录 cookie 的过程。目前可以在进入需要登录校验登录的 WebView 容器页之前,先发起获取 cookie 的服务端请求,服务端处理好用户登录信息校验之后就可以提供一个同步 cookie 的专用页面。当接口返回链接之后,小程序的 WebView 容器需要做的就是访问这条链接将服务端返回的 cookie 同步到 WebView 容器中,这样 WebView 容器就具备了可供校验的登录信息。

完成上述页面的登录操作之后,小程序登录流程就结束了。

4 百度小程序总结

本文着重描述的是开发过程中大概率会遇到的问题和解决方案,最好结合官方文档一起查看。

三 字节跳动小程序

1 背景

字节跳动小程序是基于字节跳动全产品矩阵开发, 与图文、视频等场景有着天然的搭配性,带动小程序分发,由内容为小程序带量以及裂变。作为一个大流量且高度活跃的平台,具有很大用户增量挖掘空间。

对于头条系应用,同一小程序可以同步上线多个宿主端,目前已开放今日头条、抖音、头条极速版。无论是抖音,还是今日头条,都属于内容分发平台,相比公众号读者,抖音用户相对更年轻,而头条则拥有大量三四线城市读者,这正好契合了电影作为内容消费的特质,帮助淘票票更好的拉动下沉用户。基于头条、抖音平台自身的优势,我们在 2019 年上线了淘票票字节跳动小程序。

2 应用场景

今日头条的六个主要场景:

  • 信息流推荐

  • 搜索直达

  • 头条号挂载小程序

  • 分享

  • 中心化入口

  • 留存入口

今日头条

抖音的四个主要场景:

  • 小视频挂载

  • 企业号主页

  • 搜索展示

  • 留存入口

广告投放

3 基础介绍

字节跳动小程序基本开发思路类似于前端开发,并增强调用大量端能力,性能体验优于普通 Web 。上层架构基于 JS 开发,可以辅助开发者进行良好得开发。框架结构和开放式类似于支付宝小程序、微信小程序和百度小程序。

目录结构:主要分为以下几类的文件:

  • .json 为后缀的 JSON 配置文件,这个文件配置了小程序所有页面的路径和界面展现样式等。

  • .ttml 结尾的模板文件,用来描述当前这个页面的文件结构,类似于网页中的 HTML 文件。

  • .ttss 结尾的样式文件,描述页面样式,类似于网页中的 CSS 文件。

  • .js 结尾的 JS 文件,处理这个页面和用户的交互。

目录结构

四 快应用卡片

1 概述

当前,基于超级 APP 推出的各种小程序,对手机厂商的分发能力及话语权有明显削弱趋势。因此国内各手机厂商在推出快应用后,也逐渐对外开放手机负一屏的能力,为快应用及其他服务提供直接的入口。

2 卡片类型

快应用的卡片类型可以分为:应用类型的卡片、服务类型的卡片和其它类型的卡片。

  • 应用类型的卡片:是用户订阅的一种卡片,内容相对固定。

  • 服务类型的卡片:针对用户关心数据的状态,内容实时变更。

  • 其它类型的卡片:自定义卡片,根据实现对应内容展示及跳转。

3 应用卡片的具体接入

卡片的开发基于快应用,所使用的 API 是快应用的子集,部分 API 不能在卡片中使用。目前已知的 vivo,OPPO,NUBIA 都需要卡片的开发不依赖主 rpk,也就是需要保证卡片能脱离主 rpk 独立渲染,为保证卡片的独立渲染,不能使用 this.$app 相关对象及文件 app.ux 中声明的工具类或生成的对象。

1)卡片的初始化配置

a) 配置文件

所有的卡片都需要和快应用中声明页面一样在 manifest.json 中声明。具体是在 router.widgets 中配置,各厂商之间有部分差异,但差异不大。

b) 卡片配置文件注意事项

  • widgets 中配置的 key 值请使用路径名字,如果路径为两级(例:/A/B),则 key 值配置为 "A/B",且该值最终对应到厂商后台上传卡片时填入的卡片路径,基于此厂商才能正确解析到上传到联盟上统一的快应用包中对应的卡片。

  • 卡片的属性 features 中需要声明该卡片会用到的系统 API,这些 API 在外层应用的 features 中已经声明过,此处需要再次声明,否则不能使用。

2)应用类型卡片接入(以 vivo 为例)

负一屏卡片线上效果图

a) 卡片的声明

在 manifest.json 中的除了上面提到的配置之外,对于卡片需要注意卡片属性中的以下字段:

  • params 字段用来配置卡片更为详细的参数,及特有的支持参数,需要按照文档进行配置。

  • targetManufactorys 为对应厂商适配标明该卡片匹配的厂商,具体参看文档。

b) 卡片的开发的不同点(所使用的 API 及组件为其子集具体参看官方文档)

  • vivo 卡片是单独工程,不能配置在快应用工程中,需要另外建立新的工程。

  • 对应包打出也需要单独配置和主快应用不相同,需要用到 vivo 给出的相关工具。

  • 卡片有单独设置主题的功能。

  • 卡片有折叠功能。

  • 卡片视觉稿由内容提供方给出。

  • 卡片开发只需开发内容区域,title 区域无需开发(由 vivo 负一屏容器完成绘制)同时意味着下半部分的圆角需要自己绘制。

  • 上传卡片包时需要提供对应的 icon。

c) 卡片调试

卡片调试需要使用 vivo 方提供的工具打出来的 rpk 文件,同时需要使用 vivo 方提供的专用内核及容器,具体按照文档执行即可。

d) 卡片提交

首先需要完成自测,自测之后需要使用 vivo 提供的专用打包工具,打包之后到 Jovi 后台地址提交,同时需要提供一个应用图标。

4 负一屏服务类型卡接入

以下以 OPPO 服务卡接入为例:

触发场景:用户在淘票票快应用中购票之后,在影片上映前的固定时间内触发该卡片内容展示,进而提醒用户取票,即消息触发场景。

OPPO 负一屏卡片线上效果图

1)OPPO服务卡卡片的声明

在 manifest.json 中的 router 字段中添加 widgets 字段,并在该字段中添加对应的配置,与 OPPO 应用卡片完全相同。

2) 卡片开发

OPPO 服务卡开发涉及用户关心数据状态改变触发卡片的场景,因此整体需要解决以下几个问题:首先是触发时机问题,然后是要确认触发的卡片 ID,还要解决要触发哪一个 OPPO 用户。

3)OPPO 服务卡整体开发流程

前提:要开通 OPPO 账号服务,保证在快应用中能够拿到 OPPO 当前登录的用户的授权码。

a) 账号绑定,即 OPPO 账号和快应用账号的绑定

账号绑定的入口:该入口由 OPPO 负一屏容器统一提供,位置如下图左:

OPPO 服务卡绑定入口及自定义绑定页面

该入口对应一个快应用内的绑定页面。

b) 绑定页面开发,该页面是快应用页面,主要提供绑定功能

作用是让内容商服务端知道自己的账号和 OPPO 测的对应关系,及换取发消息到 OPPO 端时所需要的 TOKEN 值。

c) 触发对应 OPPO 用户负一屏的卡片

内容服务商在用户关心数据变更时,触发推送消息到 OPPO 服务端,该消息按 OPPO 文档约定,带上对应的 TOKEN 值,要触发的卡片 ID,消息内容,要触发的时机及时长,OPPO 服务端会根据该 TOKEN 找到对应的用户下发消息,并在需要的时机拉起对应 ID 的卡片。

d) 卡片消失

由发送消息中定义的卡片时长决定,展示时间到点后,负一屏容器会自动移除该卡片。

e) 调试

  • 首先,需要 OPPO 服务端参与

  • 其次,需要 OPPO 提供负一屏开发环境版本,以保证 OPPO 服务端日常环境的数据能够到达。

  • 再次,需要提供初步测试完成包含服务卡的 rpk 到 OPPO 侧,把该 rpk 配置到 OPPO 对应的环境。

f) 提交市场

测试完成可以在 OPPO 后台提交该卡片,同时同步正式生成的卡片标示到自己的服务端用来推送消息使用。

g) 综上涉及各端的开发工作如下:

  • 首先,涉及服务端账号绑定开发,TOKEN 刷新维护,触发的消息推送到 OPPO。

  • 其次,涉及前端的服务卡片开发以及绑定页面开发。

  • 涉及其他:OPPO 账号服务开通。

5 踩坑记录

  • 负一屏的 UA 和快应用中不同如果有与 UA 相关的配置需要注意。

  • 对于调试时更新了 rpk 之后,实际打开对应更改没有体现时,可以尝试清除对应卡片容器的 cache,同时保证该容器有相应的读取存储的权限。

  • 对于同一个业务,由于各厂商适配不同及平台不同,需要多处代码编写,但基本业务逻辑基本一致,唯一不同是 UI 展示,所以在一开始还是需要抽离数据逻辑,不同厂商给不一样的 UI 展示即可。

四 淘票票小程序构建演进

我们做了很多的小程序,对多端同构也做了一些尝试。

1 从一端到多端

1) 起步:小程序原生开发

2018 年,随着小程序平台爆发,我们首次踏出了淘宝域内,进行了头条小程序的尝试。这次尝试,使用的是原生的小程序 DSL 语法编写。为了方便复用已有的 H5 样式,加入了 Gulp,用来编译 Less。

这种开发方式轻快,但是同时也暴露出了很多问题:

  • 包体积很难控制。

  • 原生 DSL 没有任何复用性,并且需要重新学习。

  • 无法使用 NPM,一些很常用的社区包,团队基础工具链无法使用。

  • 机型兼容性不好,没有 babel 支持。

2) 摸索:两个端,一套代码?

在开发百度小程序的过程中,吸取了第一次的教训,加入了 webpack 来做一层编译,一是解决了包引入问题,二是加入了 babel 插件,解决 JS 兼容性问题,开启 CommonChunk 插件,解决包大小问题。

总体上,从输出一端变为输出两端,所以出现一些差异。对这些差异,编写了一个插件,对业务层抹平。比如微信端引入 index.wx.js,头条端引入 index.tt.js。

脱离了刀耕火种,开发效率明显提升,但是还不够好,视图层还是两份,而且以后每新增一端就要新拉出来一份。

3) 优化:走向社区,跨多端

在开发头条和百度小程序时,业内也已经有了在小程序 DSL 上封装的框架,但是当时看都不是很成熟,基本都是专注于一个平台,没有什么跨端能力,就没有用到生产环境,而是持续关注更新近况。

2019 年进军微信小程序,再次看市面上的框架,发展的很快,同时也注意到跨端开发这个需求点,选择了 Taro 作为主力框架。这种框架横评就不展开了,市面上很多,简单说几个选择 Taro 的原因:社区相对活跃、支持渐进式切换、TS、react like。

a) 平滑迁移

Taro 支持渐进式切换,也就是 Taro 和 DSL 混写的能力,所以迁移成本可以接受。我们先将首页 Taro 化,后面慢慢迭代将所有的页面都切换为了 Taro,这里值得一提的是,Taro 的跨端差异化处理和我们之前的处理思路一样,因此 Util 迁移起来几乎 0 成本,成本主要集中在视图层。

b) 好处是什么,缺点在哪里

使用 Taro 的好处是解决了我们之前遇到的主要问题,是一个一揽子解决方案。

同时这种上层框架在扩展新端时成本低,机动性很高,框架提供了新平台包,适配成本低。

当然也遇到了一些新的问题,比较严重的是调试,因为代码被转译过一次,同时不支持 Soucemap,导致 debug 时体验很差。

2 多端差异

多端必然会有一些差异,业务的差别、端上 API 的差异等,比如微信上的分享能力,抖音上的抖音拍摄器,百度的 feed 流等。最终落在业务上,差别可以分为三部分,输出不同的页面、不使用同的组件(有的端使用原生组件),细到不同的逻辑。

1) 输出不同的页面

在使用 Taro 时发现不支持,想到可以使用 babel-preval 来编译时输出页面配置,这样包体积也不会受影响,最后我们也反哺回社区。

使用不同的组件,不同的逻辑。根据端上不同的组件我们使用的最多的是多态模式,底层组件对外暴露相同的接口,端上调用时不需要考虑端上的差异,在 import 层会根据不同的端来引入不同的具体组件。

2) 端上逻辑

如果是一些简单的逻辑差异,可以直接使用环境变量来做控制,走不同的逻辑。这种方式针对小一些的逻辑还可以,不过这种代码一多,就不容易维护。

3) 针对维护性的建议

这里推荐几种维护性比较强的差异处理方式:

  • 设计组件时插件化:比如路由,不同端在跳转前后需要有一些不同的操作,实现了插件化后,每一个端只需挂载不同的插件即可。

  • 配置抽离:针对一些端上不同的配置,比如一些文案、固定内容等,可以抽离到一个统一的地方维护,可以少很多 ifelse 逻辑。

  • 用好函数 hook:针对不同端相同的逻辑放在函数中,有差异的逻辑可以单拆函数作为 beforeHook 和 afterHook。

3 项目维护策略

项目输出多端后,每次改动回归就成为一个比较重要的问题,如何保证自己的代码不会再其它端上出问题?每次改一个小程序其他都要立即回归吗?如何快速整理其他端的改动?下面针对多端项目的维护总结了一些经验。

1) 单测

针对核心逻辑编写测试,unit test 和 snapshot test,我们内部维护了一个针对端上 API 的 mock 测试库,整个测试可以在 node 环境中运行,保证运行效率。

2) Commit 规范

指定一个 commit message 规范,可一眼看出你在做什么,在改哪一个端,以及后面回归策略时用到。

3) git-hook

使用 githook 能保证上面的规范能够真正的遵循,保证每次提交的质量。pre-commit 时跑一下 Eslint,然后校验一下 commit-message 是否符合规则,最后 push 时会跑一次整体的测试。

4) 多端的回归策略

没有做 E2E 的主要原因是小程序限制,case 编写难度比较大,并且维护性低,无法自动化。

目前我们是人工回归的,如何保证代码不会再其他端上出问题?难道每一次改一个小程序其它都要立即回归吗?回答下这两个问题,编写代码时考虑影响面,提交时提供足够的改动信息,合并时主要测当前端即可,不需要回归所有端。等另一个端需要发布时,拉出版本的 commit-message,然后梳理出变更范围,在该端做回归即可。这样做减少对测试的集中消耗,保障质量。

4 展望

以上是我们对跨端项目的经验总结,包含技术演进历史以及差异控制策略。跨端项目的难点就是处理差异。

  • 端上能力的参差不齐、业务针对不同场景的定制,一旦控制不好,整个人项目的维护性就会大大降低。

  • 业务方面要思考清楚,不同的端,是相似的更多,还是差异多。

  • 框架方面,最近看到有开发者已经给 W3C 提小程序的白皮书了,总体朝着良性方向发展,这是一个好的开始,期待能够标准化小程序框架。