图文讲解uni-app支持PC宽屏

天下苦平台碎片化已久。

在移动互联网以前,开发者只需幸福的面对web。进入移动互联网时代,iOS、Android、H5以及各种小程序快应用层出不穷,开发者再也幸福不起来。
– 学习n个技术
– 开发n个版本
– 各版迭代混乱,无法拉齐,用户茫然、内部痛苦
– 开发测试成本n倍暴涨

结果就是工程师不停加班到头秃,但产品的需求仍然做不完、老板的预算一直压不下去,全都痛苦。

uni-app之前已经实现了手机端全覆盖,支持iOS、Android、H5、微信小程序、阿里小程序、百度小程序、字节跳动小程序、QQ小程序、快应用、360小程序,并且在各端均有优异的运行性能。

从2.9版本起,uni-app 进一步提供了PC等宽屏的适配方案,完成了大统一。

开发者终于可以做到使用一个框架,一把撸掉所有项目。

不说虚的,先来个实际示例,大家直观感受一下:

如下是基于uni-appDCloud社区在mobile端的展示效果,列表、详情分为两个页面,点击列表中的帖子,打开详情页面:

如下是基于uni-appDCloud社区同一套代码,稍作配置后,在pc端的展示效果,列表、详情在同一个页面中左右分栏显示,点击左侧列表中的帖子,刷新右侧详情窗口的内容,这个UI更适合pc宽屏,也更有pc桌面App的体验。

Tips:点击 DCloud社区演示系统,自己感受PC和Mobile的自适应。

怎么样?有没有被酷到?

下面我们具体来讲讲uni-app的pc宽屏适配方案,总的来说,包括三个方面:
– 窗体级适配:leftwindow、rightwindow、topwindow等分栏
– 组件级适配:match-media组件
– rpx的宽屏响应

1. 窗体级适配:leftwindow分栏

手机屏幕和PC显示器的设计不同。为了方便手持及接听电话,大多手机的设备高度大于设备宽度(即为窄屏设计),故mobile App 多为竖屏/窄屏显示的UI。

而pc显示器多为宽屏设计,即设备宽度大于设备高度,在pc上的桌面应用,很多会采用左右分栏的UI设计。

uni-app以目前手机屏幕为主窗体(window),在左/右/上三个方向,新扩展 leftWindowrightWindowtopWindow三个窗体,这三个窗体可设定在屏幕宽度大于某一阀值时自动出现(展现分栏的宽屏设计),屏幕宽度小于某一阀值后自动消失(恢复单窗口的窄屏设计)。

uni-app主窗体和扩展的三个窗体各自独立,支持互相通信,点击链接、切换页面时支持在相应的窗体内刷新,而不是整屏刷新。

我们以本文开头的DCloud社区为例,讲解如何在uni-app中快捷实现宽屏适配。

我们将社区的列表页作为主窗体,将详情内容扩展到rightWindow中,示意如下:

接下来分步说明,如何在uni-app项目中完成分栏实现。

step 1: 新建right-window.vue展现帖子详情

当然,rightWindow无需重写新闻详情页面,是可复用原有代码的,支持把已有详情页面当组件放到 rightWindow 页面中,如下:

<!-- responsive/right-window.vue -->
<template>
  <view>
    <!-- 将原来的详情页(/pages/detail/detail.vue),作为一个组件(pages-detail-detail)使用 -->
    <pages-detail-detail ref="detailPage"></pages-detail-detail>
  </view>
</template>

<script>
  export default {
    created(e) {
      //监听自定义事件,该事件由左侧列表页的点击触发
      uni.$on('updateDetail', (e) => {
        // 执行 detailPage组件,即:/pages/detail/detail.vue 页面的load方法
        this.$refs.detailPage.load(e.detail);
      })
    }
  }
</script>

step 2: 在列表页面,处理点击列表后与rightWindow交互通信的逻辑。

// 列表页的改造
goDetail(detail) {
    if (this._isWidescreen) { 
        //若为宽屏,则触发右侧分栏详情页的自定义事件,通知右侧窗体刷新新闻详情
        uni.$emit('updateDetail', {
            detail: encodeURIComponent(JSON.stringify(detail))
        })
    } else { 
        // 若为窄评,则打开新窗体,在新窗体打开详情页面
        uni.navigateTo({
            url: '/pages/detail/detail?query=' + encodeURIComponent(JSON.stringify(detail))
        });
    }
},

step 3:pages.json中注册rightWindow,如下:

{
  "rightWindow": {
    "path": "responsive/right-window.vue", // 指定 rightWindow 页面文件
    "style": {
      "width": "calc(100vw - 400px)" // 页面宽度
    },
    "matchMedia": {
      "minWidth": 768 //生效条件,当窗口宽度大于768px时显示
    }
  }
}

可以看到,无需太多工作量,就可以快速把一个为手机窄屏开发的应用,快速适配为PC宽屏应用。并且以后的代码维护,仍然是同一套,当业务迭代时不需要多处升级。

这套方案的实施,有如下特征:

  • 原先为手机窄屏开发的代码,基本无需修改,可完全适配到pc宽屏上;后续新增的业务模块,也是一套代码,同时兼容款窄屏。
  • 增加pc宽屏适配后,不影响原先mobile端的窄屏实现,窄屏上会自动隐藏leftWindow/rightWindow等扩展窗体。
  • rightWindow里的页面是复用的,不需要重写新闻详情页面,支持把已有详情页面当组件放到 rightWindow 页面中。

更多配置细节,详见文档:https://uniapp.dcloud.net.cn/collocation/pages?id=topwindow

leftWindow方案除了适用于将原有的Mobile App适配到大屏显示,也适用于新开发的PC应用,尤其是PC Admin管理控制台。

如下是基于leftwindowtopwindow构建的经典pc admin布局:

2. 组件级适配:match-media组件

leftWindow等方案是页面窗体级适配方案,适用于多页面的组合分栏显示。

那么在同一个页面中,组件是否可以适配不同屏宽?当然可以,此时可以使用组件级适配方案。

除了传统的css媒体查询外,uni-app还提供了全平台兼容的 match-media组件 和配套的 uni.createMediaQueryObserver 方法。

match-media是一个媒体查询适配组件,可以更简单的用于动态屏幕适配。

match-media组件中放置内容,并为该组件指定一组 media query 媒体查询规则,如屏幕宽度。运行时,如屏幕宽度满足查询条件,则这个组件就会被展示,反之则隐藏。

match-media组件的优势包括:
1. 开发者能够更方便、显式地使用 Media Query 能力,而不是耦合在 CSS 文件中,难以复用。
2. 能够在模板中结合数据绑定动态地使用,不仅能做到组件的显示或隐藏,在过程式 API 中可塑性也更高。
3. 能够嵌套式地使用 Media Query 组件,即能够满足局部组件布局样式的改变。
4. 组件化之后,封装性更强,能够隔离样式、模版以及绑定在模版上的交互事件,还能够提供更高的可复用性。

uni-app推荐采用运行时动态适配的方案,而不是为PC版单独编写条件编译(虽然你也可以通过自定义条件编译来实现单独的PC版)。这样设计的好处是在ipad等设备的浏览器上可以方便的横竖屏切换。

3. rpx的宽屏响应

设计Mobile App时,设计师常会以 iPhone6 作为视觉稿的标准,即按照750px屏幕宽度出图;程序员以750px作为基准,根据设备实际尺寸,动态换算(缩放)出适合当前设备屏幕的元素宽高。

这就是rpx(responsive pixel)的实现思路,只不过rpx由框架引擎动态换算元素尺寸,无需程序员写代码干预。

面向mobile端时,rpx是一种很理想的解决方案,因为各种移动设备的屏幕宽度差异不是很大,相对于750px微调缩放后的效果,可最大化的还原设计师的设计。

但是,一旦脱离移动设备,在pc屏幕,或者pad横屏状态下,因为屏幕宽度远大于750了。此时rpx根据屏幕宽度变化的结果就严重脱离了预期,大的惨不忍睹。

假设一个图文列表的展现,我们针对左侧缩略图定义如下css:

.uni-media-list-logo {
    width: 180rpx;
    height: 140rpx;411*(180/750)411*(180/750)411*(180/750)
}

在手机端,这个显示效果是比较理想的,如下:

当前选择 pixel 2作为模拟设备,屏幕宽度为411px,故缩略图的宽度变为:180*(411/750) = 98px,高度变为:140*(411/750) = 76px,这个理论计算和实际运行相符,且效果较佳。

同样的代码,如果运行到pc端,假设屏幕宽度为1920px,则缩略图的尺寸将变为:180*(1920/750) = 460px,高度变为:140*(1920/750) = 358px,这个惨不忍睹的大就出来了,一个1920*1080的显示器,只能显示2条记录(主要是缩略图高度放大导致的),效果如下:

为此,在uni-app 2.9+起,新增了 rpx 按750px做基准屏宽的生效范围控制,即屏幕宽度超过某阀值(默认为960px)后,将不再以屏幕实际宽度换算元素宽高,而改以固定屏幕宽度(默认为375px)计算元素宽高。

以上述图文列表为例,当屏幕宽度为1920px(大于960px)时,将采用固定的屏幕宽度(默认375px)计算缩略图的宽高,即:180*(375/750) = 90px,高度变为:140*(375/750) = 70px,按照这个机制,pc端运行效果如下,相比上图的放大变丑,展现更为理想优雅。

Tips:
– 750px生效的屏幕宽度阀值,及宽屏时计算所用的固定屏幕宽度,均支持自定义配置

{
  "globalStyle": {
    "rpxCalcMaxDeviceWidth": 960, // rpx 计算所支持的最大设备宽度,单位 px,默认值为 960
    "rpxCalcBaseDeviceWidth": 375, // 设备实际宽度超出 rpx 计算所支持的最大宽度时,rpx计算所采用的固定屏幕宽度,单位 px,默认值为 375
  }
}
  • 开发者可在rpx宽度管控的基础上,略作调整,实现更好的pc宽屏效果,如宽屏时,固定列表宽度且居中显示,如下图。当然,此处仅为了演示rpx在宽屏下的失控管理,实际应用中,可采用分栏窗口,将列表在左侧分栏中显示,如本文开篇示例截图。

4. 补充

4.1 通过electron打包为windows、mac、linux客户端

有了宽屏适配,uni-app的应用就可以方便的通过electron打包为电脑客户端应用,windows、mac、linux均支持。

开发者可以随意调用electron的API,以调用更多操作系统的能力(为方便多端兼容,可以将这些特殊API写在自定义的条件编译里)

uni-app插件市场有已经封装好的一些插件,详见:https://ext.dcloud.net.cn/search?q=electron

4.2 一个让手机版网页临时可用于pc浏览器的方案

如果你的h5版已经开发完毕,还没来得及适配pc,但想在pc上先用起来。那么可以在pc网页里使用iframe,约定好宽度,在里面套用uni-app的窄屏版。

当然还可以在iframe旁边放置二维码,提供手机版扫码地址,如下是一个实现示例:

5. 结语

uni-app团队将keep running,继续完善uni-app在pc、pad等宽屏设备上的更好适配,并会在ssr、serverless方向上重点投入,提供云端一体的更高效率的解决方案(详见uniCloud),帮助企业更高效,帮助开发者更轻松!

欢迎大家到https://github.com/dcloudio/uni-app上给我们star鼓励😄

uniapp和ionic的区别是什么?

1、uni-app是多webview模式,ionic是单webview模式;故uni-app的窗口动画是webview的原生动画,ionic的动画是spa的div动画,故uni-app的窗口动画效果更佳;

2、uni-app架构和小程序一致,逻辑层、视图层分离,JS运行在v8中,页面渲染在webview中,因此JS的逻辑计算不会和UI渲染竞争资源。而ionic的js运行在webview中,js和UI渲染会竞争资源。

3、uni-app除了支持web渲染,还支持weex原生渲染;ionic仅支持web渲染。

4、uni-app内置支持微信登录、支付、分享等国内常见的SDK,ionic需自己集成或使用三方SDK。

5、uni-app支持跨端发行,可同时生成小程序、H5(包括PC端),这个ionic显然是不行的。

uni-app支持PC版360小程序

uni-app 开发 360 小程序

uni-app 是一个遵循 Vue.js 语法的跨端框架,开发者编写一套代码,可发布到App、H5、小程序(微信/阿里/百度/字节跳动)及快应用。

近期,uni-app新增支持发行到360小程序平台,目前hello uni-app 已上线360小程序,可以在360浏览器中点击链接打开体验:so.mp.360.cn

如下是简易体验教程,Enjoy~

通过 HBuilderX 可视化界面

  1. 下载HBuilderX,官方地址

  2. 创建uni-app项目

HBuilderX开发者工具中,点击文件 -> 新建 -> 项目:选择uni-app类型,输入工程名,选择模板,点击创建,即可成功创建。

  1. 进入已创建的项目,点击工具栏的运行 -> 运行到小程序模拟器 -> 360开发者工具,等待编译完成时自动调起 360浏览器

  1. 在360浏览器中导入编译后的目录,见 HBuilderX 控制台输出

  2. HBuilderX中修改代码,uni-app编译器会热更新到 360浏览器

注意: 运行时为未压缩代码,正式上线时需要点击发行菜单

通过vue-cli命令行

习惯cli脚手架的同学,可以通过vue-cli创建uni-app项目。

  1. 全局安装vue-cli
npm install -g @vue/cli
  1. 创建uni-app项目,选择项目模板
vue create -p dcloudio/uni-preset-vue my-project

此时,会提示选择项目模板,初次体验建议选择 hello uni-app 项目模板,如下所示:

  1. 使用如下命令进行360小程序的编译预览及发行打包
# npm script
# dev 模式,编译预览
$ npm run dev:mp-360
# build 模式,发行打包
$ npm run build:mp-360

发行到360小程序,需要你下载并打开360浏览器,然后选择项目编译目录(dev模式、build 模式编译目录不同,见下方说明)进行预览或发行。

dev模式 和 build 模式的区别:
– dev 模式编译目录为项目根目录下的 /dist/dev/ 目录
– build 模式编译目录为项目根目录下的 /dist/build/ 目录
– dev 模式有 SourceMap 可以方便的进行断点调试
– build 模式会将代码将会进行压缩,体积更小更适合发布为正式版应用

开发规范

uni-app 内置的组件及API已兼容360小程序,详见uni-app官网

对于 360 小程序特殊的组件及API,可以通过条件编译调用360原生能力。

问题反馈

大家在使用 uni-app 开发360小程序时,若碰到问题,欢迎到 DCloud问答社区交流反馈。

跨端开发框架深度横评之2020版

又是一年四月天,距离上次发布跨端开发框架深度横评已过去整整一年。

这一年,小程序在用户规模及商业化方面都取得了极大的成功。微信小程序日活超过3亿,支付宝、百度、字节跳动小程序的月活也纷纷超过3亿。

对应小程序开发领域,这一年也发生了巨大变化。开发框架从单纯的微信小程序开发,过渡到多端框架成为标配,进一步提升开发效率成为开发者的强烈需求。

这一年 mpvue 停止更新,Taro开始探索 taro nextuni-app 产品和生态持续完善,微信新推出了支持H5和微信小程序的 kbone 框架…

去年的深度横评中很多老将已经退出江湖,一些新秀吸引眼球,因此,是时候来一波2020版的新横评了。

评测目标筛选

跨端框架是一个重投入工作,在各框架的1年多的比拼中,很多框架因为投入不足而逐渐被开发者放弃,uni-apptaro依靠持续的大力度投入,成为了市场主流。

taro 在稳定版的基础之上,最近也推出了 taro next,这2个版本差异较大,本次会分别评测。

kbone 框架虽面世不久,但毕竟是微信官方发布,关注的人不少,故本次将 kbone 加入评测目标。

所以,本次评测的对象(按发布时间排序):

本次评测除了运行性能等实测数据外,尽可能得通过框架官网及github、掘金、腾讯课堂等三方社区公开采集数据,希望给大家一个综合全面的评估依据。

功能实现

tarouni-app 是比较典型的多端框架,发布到各个端均可。而 kbone 只支持微信小程序和H5。

tarouni-app 均将常用接口及组件封装了成了跨端API和跨端组件,组件规范沿用微信小程序的规范,部分平台特有API,这两个框架亦有应对方案:

  • taro:支持与小程序代码混写,可通过混写的方式调用框架尚未封装的小程序新增API
  • uni-app:支持条件编译,可在条件编译代码块中,随意调用各个平台新增的API及组件

tarouni-app 可以不受限的调用各家小程序平台所有的API及组件。

kbone 沿用web的开发习惯,使用html标签及js api;涉及微信特有api时,可通过process.env.isMiniprogram判断环境,然后编写微信原生代码。对于html中没有标签可替代的微信内置组件(如swiper),需要使用 wx-component 标签或者使用 wx- 前缀,这样的内置组件会被包裹一层自定义组件,带来相应的性能开销。

除了接口、组件之外,我们以微信小程序为例,找几个典型能力对比框架支持度:

框架 taro uni-app kbone
微信自定义组件 ⭕️ ⭕️ ⭕️
三方插件 ⭕️ ⭕️
分包加载 ⭕️ ⭕️ ⭕️
sitemap ⭕️ ⭕️ ⭕️
wxs ⭕️
云开发 ⭕️ ⭕️ ⭕️

补充说明:

  • 如果在 Taro 项目引用了小程序原生的第三方组件,那么该项目将不再具备多端转换的能力,例如,如果使用了微信小程序的第三方组件,那么项目只能转换成微信小程序,转义成其他平台会失效,详见taro官网
  • uni-app 中使用微信自定义组件的话,支持编译发行到App/H5/微信小程序/QQ小程序4个平台,详见uni-app官网
  • taro 不支持 wxs 的依据:#2959
  • kbone 不支持微信三方插件的依据:#58;不支持wxs的依据:#129
  • 云开发在微信平台,三个框架都支持,但 taro/kbone仅支持微信小程序平台,uni-app支持App/H5/小程序所有平台使用云开发,详见下方 serverless/云开发 章节。

wxs是提升性能体验的重要利器,除了微信小程序的wxs外,还有支付宝的SJS、百度的Filter,这些高级技术 uni-app 均完善支持。参考:谜之wxs,uni-app如何用它大幅提升性能

从如上功能对比来看:微信原生 ~ uni-app > taro > kbone

运行性能(微信小程序)

我们继续沿用去年的测试模型,看看一年来,各家小程序开发框架的性能是否有提升。详细如下:

  • 开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。

  • 界面如下:

  • 开发版本:一共开发了5个版本,包括微信原生版、taro版、uni-app版、kbone版,按照官网指引通过cli方式默认安装。

  • taro 目前稳定版本是2.0版,但近期在宣传跨框架的taro next,故我们基于同样的 react代码,同时测试了taro 2.0 和 taro next 两个版本的数据。

  • 测试代码开源(Github仓库地址:https://github.com/dcloudio/test-framework),
    Tips:若有同学觉得测试代码写法欠妥,欢迎提交 PR 或 Issus

  • 测试机型:红米 Redmi 6 Pro、MIUI 11.0.5 稳定版(最新版)、微信版本 7.0.12(最新版)

  • 测试环境:每个框架开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。

我们以上述仿微博小程序为例,测试2个容易出性能问题的点:长列表加载、大量点赞组件的响应。

长列表加载

仿微博的列表是一个包含很多组件的列表,这种复杂列表对性能的压力更大,很适合做性能测试。

从触发上拉加载到数据更新、页面渲染完成,需要准确计时。人眼视觉计时肯定不行,我们采用程序埋点的方式,制定了如下计时时机:

  • 计时开始时机:交互事件触发,框架赋值之前,如:上拉加载(onReachBottom)函数开头
  • 计时结束时机:页面渲染完毕(微信setData回调函数开头)

Tips:setData回调函数开头可认为是页面渲染完成的时间,是因为微信setData定义如下(微信规范):

测试方式:从页面空列表开始,通过程序自动触发上拉加载,每次新增20条列表,记录单次耗时;固定间隔连续触发 N 次上拉加载,使得页面达到 20*N 条列表,计算这 N 次触发上拉到渲染完成的平均耗时。

测试结果如下:

说明:以400条微博列表为例,从页面空列表开始,每隔1秒触发一次上拉加载(新增20条微博),记录单次耗时,触发20次后停止(页面达到400条微博),计算这20次的平均耗时,结果微信原生在这20次 触发上拉 -> 渲染完成 的平均耗时为538毫秒,最快的uni-app是446毫秒,最慢的kbone是4057毫秒

大家初看这个数据,可能比较疑惑,别急,下方有详细说明

说明1:为何 taro next/kbone 测试数据不完整?

因为 taro nextkbone 采用的是动态渲染方案,这类方案在页面复杂、组件较多时,会大量增加页面 dom 节点数量,甚至超出微信的 dom 节点数限制(如下告警信息)。我们在 红米手机(Redmi 6 Pro)上实测,页面组件超过600个时,taro nextkbone 实现的仿微博App就会报出运行异常,并停止渲染(页面白屏),故这两个测试框架在组件较多时,测试数据不完整。这也就意味着,当页面组件太多时,无法使用这2个框架。

dom limit exceeded please check if there’s any mistake you’ve made

另外,kbone官网有如下介绍:

kbone 是使用一定的性能损耗来换取更为全面的 Web 端特性支持。

taro nextkbone的测试数据,明显和taro 2.0uni-app不是一个量级的。

如果你的应用是长列表场景,那taro nextkbone就明显不太合适。

说明2:为什么测试数据显示uni-app 会比微信原生框架的性能略好呢?

这个问题在去年的测评中,已解释过。为了避免新同学迷惑,这里再次说明一下。

微信原生框架耗时主要在setData调用上,开发者若不单独优化,则每次都会传递大量数据;而 uni-apptaro 都在调用setData之前自动做diff计算,每次仅传递变动的数据。

例如当前页面有20条数据,触发上拉加载时,会新加载20条数据,此时原生框架通过如下代码测试时,setData会传输40条数据

data: {
    listData: []
},
onReachBottom() { //上拉加载
    let listData = this.data.listData;
    listData.push(...Api.getNews());//新增数据
    this.setData({
        listData
    }) //全量数据,发送数据到视图层
}

开发者使用微信原生框架,完全可以自己优化,精简传递数据(每次仅传递变化的20条数据),比如修改如下:

data: {
    listData: []
},
onReachBottom() { //上拉加载
    // 通过长度获取下一次渲染的索引
    let index = this.data.listData.length;
    let newData = {}; //新变更数据
    Api.getNews().forEach((item) => {
        newData['listData[' + (index++) + ']'] = item //赋值,索引递增
    }) 
    this.setData(newData) //增量数据,发送数据到视图层
}

经过如上优化修改后,再次测试,微信原生框架性能数据如下:

从测试结果可看出:

  • 经过开发者手动优化,微信原生框架可达到更好的性能;
  • uni-app相比微信原生,性能接近,算是一个数量级;并且随着数据量增加,性能消耗增加不明显,从438到454,只有16毫秒的变化
  • taro 2.0随着数据量越大,性能损耗随着增大,从595到790,有将近200毫秒的变化;
  • taro nextkbone相比之下,差距就比较大了。

这个结果,和web开发类似,web开发也有原生js开发、vuereact框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vuereact框架的性能。

也恰恰是因为Vuereact框架的优秀,性能好,开发体验好,所以原生js开发已经逐渐减少使用了。

说明3:为何今年的性能测试数据和去年的不同?

细心的同学会发现,同样的测试手机,同样的测试代码,为何今年的性能数据会比去年的数据有大幅提升?

  • taro、uni-app及微信原生,三个框架的数据都有大幅提升,400条记录时,至少都有300毫秒的优化
  • uni-app及优化后的微信原生,随着数据量的增加,耗时数据变化并不明显,但去年是很明显的线性增长

其实,通过微信原生工程的数据对比,就能得出结论:2019年,微信针对小程序运行时做了大幅性能优化。

这对开发者来说应该是个好消息,小程序性能体验更佳,可承载更好的用户业务。

复杂长列表加载下一页评测结论:微信原生开发(手动优化) ~ uni-app > 微信原生开发(未手动优化) ~ taro 2.0 > taro next > kbone

点赞组件响应速度

长列表中的某个组件,比如点赞组件,点击时是否能及时的修改未赞和已赞状态?是这项测试的评测点。

测试方式:
– 选中某微博,点击“点赞”按钮,实现点赞状态状态切换(已赞高亮、未赞灰色),
– 点赞按钮 onclick函数开头开始计时,setData回调函数开头结束计时;

在红米手机(Redmi 6 Pro)上进行多次测试,求其平均值,结果如下:

说明:也就是在列表数量为400时,微信原生开发的应用,点赞按钮从点击到状态变化需要26毫秒。

测试结果数据说明:
– taro next/kbone 测试数据不完整的原因同上,在组件较多时,页面已经不再渲染了
– taro 2.0版本、uni-app和微信原生在点赞组件上的性能体验接近,但taro next和kbone有较大的性能差距,这个也是因为动态运行时框架导致的。

组件数据更新性能测评:uni-app ~ taro 2.0 > taro next > kbone

综上,本性能测试做了2个测试,长列表加载和组件状态更新,综合2个实验,结论如下:

微信原生开发(手动优化) ~ uni-app > 微信原生开发(未手动优化) ~ taro 2.0 > taro next > kbone

运行性能(支付宝小程序)

今年新增基于支付宝小程序的性能测试,我们同样按照如上描述,对长列表加载、点赞组件响应两个场景进行测试。

因 kbone 不支持其他家小程序,故本次仅测试了 taro 2.0 和 uni-app 的性能数据。

场景1:长列表加载

列表条数 taro 2.0 uni-app
200 1954 850
400 3155 1012
600 4284 1119
800 5278 1258
1000 6555 1452

场景2:点赞组件响应

组件数量 taro 2.0 uni-app
200 38 33
400 34 45
600 37 37
800 40 39
1000 51 48

Tips:

  • 基于 taro next 的实现原理,taro next 性能会比 taro 2.0 差不少,这里仅提供 taro 2.0 的数据(上一章节微信小程序的实测数据也已验证了这一推论)
  • 支付宝小程序进行自动化性能测试时,请在项目配置中启用 component2 编译 选项

通过如上测试数据,在支付宝小程序的运行性能,uni-app > taro 2.0

跨端支持

这三个框架都是为了解决平台同构问题,跨端的比较是必需的。

tarouni-app 相对比较成熟,支持主流的所有平台。kbone 只支持微信小程序和 Web 端。我们重点比较一下 tarouni-app

小程序平台

tarouni-app 均支持微信、支付宝、百度、字节跳动小程序,功能基本可以拉齐。

双方都有不少大厂案例,taro有京东、货拉拉、淘票票等公司小程序案例,uni-app有腾讯、华为、vivo、联想、中华英才网等公司小程序案例。

App平台

  • 能力方面

taro与微信小程序引擎拉齐度较低,很多功能需要开发者分别在iOS和Android上做原生开发才能实现。比如App端很常见的三方登录、支付、分享等能力,taro并未封装。

uni-app则在基础引擎层面提供了丰富的能力,还提供了丰富的插件市场,可切实提升开发者的效率。

  • 性能方面

taro在App端使用了react native的渲染引擎,虽然渲染层ui是原生的,但在实时交互和高响应要求的UI操作方面表现一直不佳,js层和视图层的通信损耗让很多开发者深感无力。

uni-app的App引擎同时给开发者提供了原生渲染引擎和小程序引擎的双选方案,并且由于发明了renderjs技术,以及支持wxs、bindingx等技术,解决了js层和视图层的通信损耗问题,在高响应要求的UI操作方面有更好的性能表现。比如这类canvas动画:

  • 开发体验方面

taro的开发者需自行搭建iOS/Android开发环境,比较繁琐,(官方原文地址):

uni-app可以做到让前端开发者不依赖原生工程师独立完成App。其开发的小程序,可以更平滑的变成可商用的App。

使用跨平台开发的核心诉求在于提升效率,如果一个App的开发由前端、iOS、Android等3拨工程师协作完成,其实效率反而非常低。

另外,uni-app还提供了uni小程序sdk,这个工具可以帮助原生App快速搭建自己的小程序平台。这是其他框架所未提供的。

H5平台

taro的H5平台在一年来的进步较多,可用性大幅提升。但相比于uni-app,目前仍然缺失对wxs和小程序组件的支持。

快应用

taro支持快应用的时间比uni-app早。

但快应用发展到2020年有了一些变化,uni-app针对新的形势,提供了2个发行到快应用的方案(当前两个版本都处于社区维护状态):

  • quickapp-vue版:使用 Vue开发快应用。此方案由小米主导,但华为快应用暂不支持。
  • quickapp-light版:基于小程序架构的快应用(Light版),详见https://www.hellohub.cn。此方案由华为主导,但小米快应用暂不支持。

跨端灵活性

跨端开发,离不开条件编译。因为不能用统一来抹杀各个平台的特色。

优良的条件编译能力对各端开发的灵活度至关重要,可以让开发者在共享和个性化之间游刃有余。

tarouni-appkbone 均支持在js代码通过process.env判断平台,然后编写平台特有代码。

taro额外支持统一接口的多端文件编码方式,以及在样式文件中使用ifdef条件编译。

uni-app是全面可条件编译的,目录、文件、配置、组件、js、css,所有一切均可通过ifdef条件编译。

跨端支持小结结论:uni-app > taro > kbone

开发体验

tarouni-appkbone均支持cli模式,可以在主流前端工具中开发,且基本都带有d.ts的语法提示库。三个框架均支持主流的vuereact语法,配套的ide工具链较丰富,着色、校验、格式化完善。

相比微信原生,这三个开发框架的开发体验都更为优秀。

但在开发工具维度上,明显高出一截的框架是uni-app,其出品公司同时也是HBuilderX的出品公司DCloud.io,HBuilderX为uni-app做了很多优化,代码提示、转到定义、easycom、运行调试…故uni-app的开发效率、易用性非其他框架可及。

开发体验维度,对比结果:uni-app > taro,kbone

serverless/云开发

serverless是目前炙手可热的一个概念,被称为下一代的云技术,是真正的”云“。

微信率先将 serverless 技术引入小程序开发领域,即云开发,帮助开发者云端一体的完成业务。随后,支付宝、百度都上线了自己的云开发。根据微信公开的数据,已经有50万开发者在使用微信云开发了。

不过小程序厂家主导的云开发存在一个天然限制,就是和平台绑定过紧,无法和其它平台共享数据。

我们以微信云开发为例,如果你仅开发微信小程序,数据独家存在微信平台,那没问题;但如果你同时还有App或其他家小程序,此时微信小程序的数据存储在微信平台,其它平台的数据存储在开发者自己的服务器上,此时就出现了数据割裂。假设一个用户先使用小程序,个人数据存储在微信平台;有了粘性后升级到App,此时App端无法读取微信平台的数据,则用户就无法查看之前在小程序上的历史数据,甚至在App平台需要重新注册。这种情况对开发者是不利的。

因此,跨端的 serverless 方案才是开发者的最优解。

目前主流框架对云开发的支持情况:

  • Taro:仅支持微信小程序,详见小程序云开发模板
  • uni-app:DCloud 联合阿里云、腾讯云,提供基于 serverless 模式和 js 编程的云开发平台,支持App/H5/小程序所有平台,详见uniCloud
  • kbone:仅支持微信小程序,详见云开发

serverless 维度上,uni-app大幅领先其它框架。

插件市场

一个开发框架能否成功,除了框架自身的高度产品化外,开发者生态的构建也至关重要。

uni-app 于2018年底率先推出插件市场,支持前端组件、js sdk、页面模板、项目模板、原生插件等多种类型,且提供了赞赏、付费购买等手段,刺激轮子作者的创作激情。目前市场上已发布插件接近1500个,众多插件下载量都在万次以上。

Taro 于 2019年5月上线物料市场,目前市场上已发布物料90个;从热门榜单来看,下载量并不大,下载最多的也就数百。

kbone目前还没有插件市场。

框架 插件市场上线时间 插件数量 top 1 插件下载量
taro 2019年5月 90 237
uni-app 2018年12月 1485 50764

Tips:
– 插件数量及下载量数据采集时间为 2020.04.03 16:00

插件市场维度,uni-app独领风骚。

学习资源

除了各大框架官网外,开发者通常还会通过视频教程、社区博客等方式系统学习。

相关学习资源的丰富程度,也能侧面反映一个框架的受欢迎程度,故我们采集了几个三方站点的数据。

视频教程

框架 腾讯课堂 网易云课堂 慕课网
taro 4 1 2
uni-app 24 14 2

Tips:

  • 视频教程数据采集时间为2020.04.26 16:30

开发交流

除了入门的学习资源,开发期的交流也很重要,这个我们主要看官方组织的社区和交流群。

社区论坛

uni-app问答社区,帖子丰富,沉淀较多;目前已沉淀2万多相关帖子,每日更新帖子数百篇,月uv百万。

对于习惯使用 github issue反馈问题的用户,uni-app同样支持,目前累计有1391个issues。

Taro 早期基于github issue进行产品Bug管理,目前累计已有近4898个issue;后于2019年5月上线开发者社区,和物料市场上线时间相同,目前沉淀1300+帖子,每日更新帖子在10个左右,相关数据计算方法如下:

  • 帖子总数:Taro 社区顶部选择板块,计算每个板块下所有主题总数,如下图。
  • 每日更新帖子数:根据帖子列表中的最后回复时间,计算24小时内有回复或评论的主题总数

kbone 在微信开放社区中新增了一个Kbone官方框架的专区,因产品发布较晚,目前只有一百多个帖子。

总结一下社区帖子及issue数据,情况如下(采集时间为 2020.04.03 23:00):

交流群

框架 微信群 QQ群 交流群开发者(预估)
taro 16 8k
uni-app 20 40+ 90k
kbone 1 0.5k

Tips:
– Taro 有16个微信群是根据 Taro 官网上显示Taro 开发交流 15 群 已满推论而出,每个微信群500人,交流群人数: 50016 = 8000人
– uni-app 官网 QQ群有35个,微信群20个,还有十几个专项QQ群,其中有30个QQ群达到2000人,交流群人数: 30 * 2000 + 5
1000 + 20*500 + 5000 = 90000人
– kbone 在 github 的 readme中有一个qq交流群,申请加入时显示500人已满

除了交流群外,DCloud对外公布的uni-app的开发者数量达到百万人,暂未看到tarokbone公布此类数据。

总体而言,开发交流维度比较结果如下:uni-app > taro > kbone

其它指标

github

框架 star 贡献者
taro 24.6k 122
uni-app 19.7k 72
kbone 2.7k 7

在开源社区方面,Taro做的还是非常成功的,它吸引了更多开发者为其贡献代码、文档。

百度指数

通过index.baidu.com,可查看主流框架的搜索指数,它代表了网友的搜索量和相关文章的收录量。目前kbone尚未收录到百度指数中,如下是近期 uni-apptaro的百度指数对比图:

结语

所有的评测都只是提供决策依据,最后的结果还是要依赖开发者的团队技术栈、业务诉求、未来规划等。

不过作为一篇评测文章的结语,我们还是要给出自己的建议:

  • 如果你熟悉React,不懂Vue.js,推荐Taro;
  • 如果你熟悉Vue.js,则推荐 uni-app;
  • 如果你已经有H5代码,只想增加微信小程序平台,并且对性能要求不高,可以考虑kbone;
  • 如果你的业务涉及多端,更推荐 uni-app;
  • 如果你希望通过 serverless 方案快速上线业务,推荐 uni-app。

如有读者认为本文中任何评测失真,欢迎在这里报 issuse

uni-app黑魔法:小程序自定义组件运行到H5平台

引言

移动互联网的初期,囿于设备硬件性能限制,流量以原生App为主,iOS、Android是当时两大平台。

随着硬件及OS的更新换代,H5可承载的体验逐步完善,为提高开发效率、节约资源(复用代码)以及热更新等目的,Hybrid模式成为主流;以及轻应用、服务号等平台的助推,H5网页流量暴涨,成为第三大平台。

2017年1月9日,微信发布小程序,历经3年发展,在今年主题为”未完成 Always Beta“的微信公开课 PRO上,微信团队披露,2019年小程序日活跃用户超过 3 亿,全年累计成交额达8000亿,同比增长超160%。

看到小程序如此惊人的增长力,我们有理由相信,有中国特色的小程序互联网时代已经到来,微信小程序也已成为继iOS、Android、H5之后的第四大流量平台。

平台分裂,为不同平台编写相同的业务代码,是件无趣的事情。

有追求的程序员,一直在探索代码复用的方案,Hybrid App即是代表。

而在如今的小程序时代,对于同样基于WEB技术的H5和小程序,如何实现代码复用,是很多前端工程师探索的方向。业内也已有不少成熟方案,从场景上来说,大致分为三类:

  1. 基于跨端框架,从头开发,一套代码,发行多个平台,比如DCloud出品的uni-app、京东凹凸实验室的taro
  2. 复用H5代码,转换H5代码在小程序环境中执行;适用于有H5平台沉淀,未开发小程序或小程序完善度较低的开发者;

– 美团的mpvue框架是早期探索解决这个问题的代表,但因小程序不支持dom操作,故mpvue适用于vue的无dom操作的H5代码转换;

  • 最近微信官方推出的kbone,也是为了解决“把 Web 端的代码挪到小程序环境内执行”;不过,kbone 相比 mpvue 前进了一步(当然也有了新的性能缺陷),因为:

    kbone实现了一个适配器,在适配层里模拟出了浏览器环境,让 Web 端的代码可以不做什么改动便可运行在小程序里。

  1. 复用小程序代码,转换小程序代码在web环境中运行;适用于有小程序代码沉淀,未开发H5或H5平台完善度较低的开发者;这个方向业内成熟的方案还比较少。

uni-app近期支持了小程序自定义组件运行到H5平台,是对如上第三种场景的一种探索。

需求场景

鉴于小程序的低成本获客特征,很多厂商选择先开发小程序,验证业务模式后,再扩展至H5、App等其它平台。

开发者虽可借助转换器将小程序代码转换为uni-app项目(或其它跨端框架项目),快速实现多平台发行;但不少开发者是不敢轻易决策将跨端版本替换之前线上的小程序版本的,毕竟线上版本已稳定运行了一段时间。

常选的方案是:让原生小程序版本和uni-app跨端版本并行一段时间,微信平台继续使用原生版本,其它平台使用uni-app跨端版本;经过一段时间验证uni-app版本稳定后,再使用uni-app版替换掉原生小程序版本。

在这段并行的时间内,开发者需要同时维护微信原生、uni-app两个版本,新增业务需编写两份逻辑相同的代码,重复劳动,成本叠加,如何改善?

借助uni-app 支持将微信小程序组件运行到H5平台的特性,我们给出一种思路:

  • 开发者在原生小程序项目中,将新增业务以自定义组件的方式开发,优先上线小程序;
  • 拷贝小程序组件的wxml/wxss/js/json文件到uni-app 项目下,通过uni-app的编译器及运行时,保证小程序自定义组件在H5平台的正确运行。

这个方案的好处是:

  • 优先小程序开发,毕竟小程序早已上线,有存量用户

  • 复用小程序组件,新增业务仅需开发一套代码即可,降低开发成本

不止自己开发的小程序组件,业内开源的三方小程序组件,均可复制到uni-app项目项目中,运行到H5平台。

另外,部分公司的产品经理,会要求不同平台有不同的交互,但核心业务逻辑是相同的,开发者常会通过维护不同项目的方式来满足产品经理需求。此时,采取如上方案,同样可满足多个项目复用相同业务逻辑的诉求。

实际上,uni-app之前已支持将小程序自定义组件运行到App平台,对于有小程序组件沉淀或优先小程序的开发者来说,这是个好消息,一套业务组件,快速运行到iOS、Android、H5、微信小程序这四大流量平台(实际上也可运行到QQ小程序平台)。

uni-app 引用小程序组件演示

uni-app项目中使用自定义组件的方法很简单,分为三步:

1、拷贝小程序自定义组件到uni-app项目根目录下的wxcomponents文件夹下

2、在 pages.json 对应页面的 style -> usingComponents引入组件,如:

{
    "pages": [
        {
            "path": "index/index",
            "style": {
                "usingComponents": {
                     "custom": "/wxcomponents/custom/index"
                }
            }
        }
    ]
}

3、在页面中使用自定义组件,如:

<!-- 页面模板 (index.vue) -->
<view>
    <!-- 在页面中对自定义组件进行引用 -->
    <custom name="uni-app"></custom>
</view>

方案实现思路

简单介绍下uni-app的多端发行原理。

uni-app基于Vue.js runtime,页面文件遵循Vue.js 单文件组件 (SFC) 规范,天然对H5的支持比较好,发行到H5平台时,先通过vue-loader解析.vue文件,导出Vue.js 组件选项对象,然后在运行时补充规范实现:

  • 组件:框架提供内置组件(view/swiper/picker等)的实现,保证平台UI及交互的一致性
  • 接口:在H5平台封装框架接口,比如路由跳转,showToast等界面交互
  • 生命周期:Vue.js的理念是一切皆为组件,没有应用和页面的概念;框架需创造出应用及页面的概念,模拟onLaunch、onShow等钩子

uni-app发行到小程序平台时,逻辑又有不同,主要工作有2块:

  • 编译器:将.vue文件拆分成wxml/wxss/js/json4个原生页面文件
  • 运行时:Vue.js和小程序都是逻辑视图层框架,都有数据绑定功能;运行时会实现Vue.js到小程序的数据同步,及小程序到Vue.js的事件代理

小程序自定义组件类似小程序原生的页面开发,一个自定义组件同样由wxml/wxss/js/json 4个文件组成,另有单独的组件规范(如Component 构造器、Behaviors特性等)。

所以,小程序自定义组件运行到H5平台,可借助uni-app已有平台功能快速实现:

  • 编译阶段:将wxml/wxss/js/json4个文件合并为.vue文件(类似 uni-app 发行到小程序的逆过程),然后调用uni-app发行H5平台的编译过程,通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象
  • 运行阶段:实现 Component 构造器、Behaviors特性,模拟自定义组件特有的生命周期

编译:转换文件(mp2vue)

小程序自定义组件发行到H5平台,在编译环节主要有2项工作:

  1. 将自定义组件的wxml/wxss/js/json 4个文件组成,编译转换成.vue文件,即小程序转vue,可简写为mp2vue

  2. 通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象

其中,步骤2是Vue.js项目的标准编译过程,略过不提;我们重点阐述步骤1。

mp2vue将4个独立wxml/wxss/js/json 的文件合并成一个.vue文件,并组装成templatescriptstyle 这种三段式的结构,流程包括:

  1. wxml文件生成template节点,同时完成指令、事件等模板语法转换
  2. js/json文件生成script节点,同时完成组件注册过
  3. wxss文件生成style节点,自动转换部分css兼容语法
  4. 合并为.vue文件

具体实现上,uni-app编译前先扫描wxcomponents目录,若存在则认为有小程序自定义组件,启动文件转换工作(uni-migration插件来完成):

//加载转换器
const migrate = require('@dcloudio/uni-migration') 
//扫描wxcomponents目录
const wxcomponents = path.resolve(process.env.UNI_INPUT_DIR, 'wxcomponents') 
if (fs.existsSync(wxcomponents)) { 
  migrate(wxcomponents, false, {
    silent: true 
  }) // 转换 mp-weixin 小程序组件
}

接着开始对wxml/wxss/js/json文件逐个解析,并合并为一个.vue文件:

module.exports = function transformFile(input, options) {
    //首先转换json文件,判断是否为组件
  const [jsCode, isComponent] = transformJsonFile(filepath + '.json', deps)
  options.isComponent = isComponent
    //转换 wxml 文件
  const [templateCode, wxsCode = '', wxsFiles = []] = transformTemplateFile(filepath + templateExtname, options)
    //转换wxss文件
  const styleCode = transformStyleFile(filepath + styleExtname, options, deps) || ''
  //转换js文件
  const scriptCode = transformScriptFile(filepath + '.js', jsCode, options, deps)
    // 生成合并后的.vue文件源码
  return [
    `${commentsCode}<template>
${templateCode}
</template>
${wxsCode}
<script>
${scriptCode}
</script>
<style platform="mp-weixin">
${styleCode}
</style>`,
    deps,
    wxsFiles
  ]
}

进一步细节说明,wxml文件转为template节点时,需完成各项指令、事件等模板语法的转换,例如:

小程序自定义组件 Vue组件 描述
wx:if v-if 条件渲染
wx:for v-for 列表渲染
bindtap @click 元素点击事件

将一个最简自定义组件,按照如上流程转换,结果示意如下:

运行时:模拟小程序组件环境

uni-app的编译器并不转换小程序组件的 JS 代码,依然保留Component构造器的写法,甚至其中的API依然是wx.开头的方式,这些都依赖uni-app在H5平台的运行时来解决,主要有如下几部分内容:

  • Component构造器:解析小程序组件的各种选项配置,转换为Vue组件定义,包括变通实现其中的差异部分,如小程序组件特有的”组件所在页面的生命周期“
  • Behaviors特性:转换为Vue的混入(mixin)
  • 数据响应:在H5平台实现setData接口及this.data.xx = yy的数据通讯机制
  • API前缀:可在运行时通过代理机制,自动将wx.xx替换为uni.xx,这个比较简单,不详述

Component构造器

uni-app在H5平台定义了一个Component函数,执行到小程序的Component构造器函数后,开始循环解析其属性,并转换成Vue组件属性,流程示意代码如下:

export function Component (options) {
  const componentOptions = parseComponent(options)
  componentOptions.mixins.unshift(polyfill)
  componentOptions.mpOptions.path = global['__wxRoute']
  initRelationsHandler(componentOptions)
  global['__wxComponents'][global['__wxRoute']] = componentOptions
}

export function parseComponent (mpComponentOptions) {
  const {
    data,
    options,
    methods,
    behaviors,
    lifetimes,
    observers,
    relations,
    properties,
    pageLifetimes,
    externalClasses
  } = mpComponentOptions

  const vueComponentOptions = {
    mixins: [],
    props: {},
    watch: {},
    mpOptions: {
      mpObservers: []
    }
  }

    // 开始逐个解析所有属性
  parseData(data, vueComponentOptions)
  parseOptions(options, vueComponentOptions)
  parseMethods(methods, vueComponentOptions)
  parseBehaviors(behaviors, vueComponentOptions)
  parseLifetimes(lifetimes, vueComponentOptions)
  parseObservers(observers, vueComponentOptions)
  parseRelations(relations, vueComponentOptions)
  parseProperties(properties, vueComponentOptions)
  parsePageLifetimes(pageLifetimes, vueComponentOptions)
  parseExternalClasses(externalClasses, vueComponentOptions)
  parseLifecycle(mpComponentOptions, vueComponentOptions)
  parseDefinitionFilter(mpComponentOptions, vueComponentOptions)
    // 返回 Vue 组件
  return vueComponentOptions
}

在这个过程中,需处理小程序自定义组件和 Vue组件的属性对应关系及细节差异,如小程序组件的lifetimes

小程序自定义组件 Vue/uni-app 描述
created onServiceCreated 小程序的created触发时,可以访问子组件信息,而Vuecreated访问不到,故需uni-app框架映射到其它时机(onServiceCreated)执行
attached onServiceAttached 同上
ready mounted Vue 生命周期
moved Vue中不存在该钩子,暂不支持转换
detached destroyed Vue 生命周期

小程序的pageLifetimes(组件所在页面的生命周期)在Vue中是没有的,需要映射为uni-app封装的页面生命周期:

小程序自定义组件 uni-app 描述
ready onPageShow 页面被展示时执行
hide onPageHide 页面被隐藏时执行
resize onPageResize 页面尺寸变化时执行

Behaviors特性的实现过程,类似Component构造器,不再赘述。

数据响应

Vue和小程序都有一套数据绑定系统,但机制不同,比如在Vue体系下,数据赋值是这样的:

this.a = 1

但在小程序中,数据赋值方式则是这样的:

this.setData({
    a:1
}) //响应式
this.data.a = 2 //非响应式

另外,小程序和Vue在数据的propertiesobserver等方面都存在不少差异,经过我们评估,若将小程序的数据响应用法直接映射到Vue体系下,复杂度较高且有性能压力,故uni-app在H5平台按照微信的语法规范,单独实现了一套数据响应系统。

// 小程序的setData在H5平台的实现
function setData (data, callback) {
  if (!isPlainObject(data)) {
    return
  }
  Object.keys(data).forEach(key => {
    if (setDataByExprPath(key, data[key], this.data)) {
      !hasOwn(this, key) && proxy(this, SOURCE_KEY, key);
    }
  });
  this.$forceUpdate();//数据变化,强制视图更新(响应式)
  isFn(callback) && this.$nextTick(callback);
}

setData挂载到 vm 对象上,可通过this.setData这种小程序的方式调用;同时将数据绑定到data属性上,支持this.data.xx的访问方式。

export function initState (vm) {
  const instanceData = JSON.parse(JSON.stringify(vm.$options.mpOptions.data || {}))
  vm[SOURCE_KEY] = instanceData

  //vm对象上挂载 setData 方法,实现小程序方法
    vm.setData = setData 

  const propertyDefinition = {
    get () {
      return vm[SOURCE_KEY]
    },
    set (value) {
      vm[SOURCE_KEY] = value
    }
  }
    //小程序用法,可通过this.data.xx访问
  Object.defineProperties(vm, {
    data: propertyDefinition,
    properties: propertyDefinition
  })

  Object.keys(instanceData).forEach(key => {
    proxy(vm, SOURCE_KEY, key)
  })
}

虽然数据响应是uni-app自己实现的,但渲染依然使用了Vue框架的render函数,此时需小程序规范中的this.data.xx和Vue规范中的this.xx保持一致,通过代理的方式实现:

// mp/polyfill/state/proxy.js
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true
};

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

这里仅列出了主要的几步,中间涉及细节很多;部分无法通过Vue扩展机制实现的功能,只好修改Vue.js的内核源码,比如updateProperties支持、小程序wxsexternalClasses等功能在H5平台的支持,都需要定制部分 Vue.js runtime 源码。

结语

本文分享了uni-app将微信小程序自定义组件发行到H5平台的实现思路,希望对大家有所启发。

但这种方案,归根到底是为了解决多套项目并存时的业务重复开发的问题。

如果你是从头开发,我们建议直接选择业内成熟的跨端框架,既可以保持一套代码,更省力的维护,还可以借助框架的成熟生态(如跨端UI库插件市场),基于成熟轮子,快速完成业务的上线开发;

uni-app框架代码,包括小程序组件发行到H5平台的代码,全部开源在github,如果大家对本文逻辑有疑问,欢迎提交issue交流。

小程序的未来方向

序言:DCloud CTO 崔红保在 GMTC 全球大前端技术大会(深圳站)2019 分享了 《小程序的未来方向》,介绍了小程序技术架构,分析了通讯阻塞、混合渲染、同层渲染等深层次问题,这里是针对演讲整理的文字版,分享给大家,Enjoy~

简单介绍一下我自己:

分享大纲

简要介绍今天的分享大纲,罗马不是一天建成的,小程序也不是一天发明的;小程序这种介于H5和Native App之间的特殊应用形态,从探索到成熟,经历了哪些过程,我们首先带大家回顾梳理一下,然后从现有技术架构出发,分析小程序当下几个主要性能坑点,各家小程序引擎为解决这些坑点,做了哪些完善工作;比如大家知道小程序是以web渲染为主、原生渲染为辅,那引入原生渲染后,引发了哪些新的问题?为解决这些,微信提出了同层渲染的方案,同层渲染在技术层面上又是如何实现的?最后从当前已知问题出发,对于小程序未来的技术更迭,抛出一些我们认为的可能方向,供大家参考。

小程序历史

HTML5 于 2007 年在 W3C 立项,与 iPhone 发布同年。

乔布斯曾期待 HTML5 能帮助 iPhone 打造起应用生态系统。

但 HTML5 的发展速度并不如预期,它虽然成功地实现了打破 IE+Flash 垄断局面的目标,却没有达到承载优秀的移动互联网体验的地步。

于是在 iPhone 站稳脚跟后,发布了自己的 App Store,开启了移动互联网的原生应用时代。

大家知道现在手机端主要是iOS、Android两大系统,实际上在早期有3大系统竞争,还有一个就是诺基亚的Meego系统,MeeGo 采用 C + HTML5 的双模应用生态策略,然而 C 的开发难度太大,HTML5 体验又不行,所以后来 MeeGo就掉队了;与之对应,Android 依靠 Java 技术生态,在竞争中脱颖而出。

于是在移动互联网初期,应用生态被定了基调 —— 原生开发。

在那个时候,硬件不行,也没有其他办法,原生开发才能在低配硬件上带来商用体验。

但大家都在怀念 HTML,那种无需安装更新、即点即用,直达二级页面的特点,一直让人迷恋。

国内有一批做浏览器的厂商,尝试去改进 HTML5,比如百度在2013年的百度世界大会上发布了轻应用,方式是通过给 WebView 扩展原生能力,补充 JS API,让 HTML5 应用可以实现更多功能。

不过这类业务没有取得成功,HTML5 的问题不止是功能不足,性能体验是它更严重的问题,而体验问题,不是简单地扩展 JS 能力能搞定的。

这类业务发展的顶峰,是微信在2015年初发布的微信 JS SDK,作为国内事实上最大的手机浏览器,微信为它的浏览器内核扩充了大量 JS API,让开发者可以用 JS 调用微信支付、扫码等众多 HTML5 做不到的功能。

但微信团队对这套方案的体验仍然不满意,微信钱包栏目里打车、理财等很多应用虽然嵌入了 JS SDK,但每次点击要等半天白屏,让人用着很痛苦,他们在业内开始寻找新的解决方案。

与浏览器不同,Hybrid 应用是另一个细分领域,开发者使用JS编写应用,为了让 JS 应用更接近原生应用的功能体验,这个行业的从业者做出了很多尝试。我们DCloud公司是业内主流Hybrid App引擎提供方之一,我们提出了改进 HTML5 的“性工能”障碍的解决方案 —— 通过工具、引擎优化、开发模式调整,让开发者可以通过 JS 写出更接近原生 App 体验的应用。

多 WebView 模式,原生接管转场动画、下拉刷新、Tab 分页,预载 WebView……各种优化技术不停迭代,终于让 Hybrid 应用取得了性能体验的突破。

Hybrid 应用和普通的轻应用相比,还有一个巨大的差别:一个是 Client/Server,一个是 Browser/Server。简单来说,Hybrid 应用是 JS 编写的需要安装的 App,而轻应用是在线网页。

C/S 的应用在每次页面加载时,仅需要联网获取 JSON 数据;而 B/S 应用除了 JSON 数据外,还需要每次从服务器加载页面 DOM、样式、逻辑代码,所以 B/S 应用的页面加载很慢,体验很差。

可是这样的 C/S 应用虽然体验好,却失去了 HTML5 的动态性,仍然需要安装、更新,无法即点即用、直达二级页面。

那么 C/S 应用的动态性是否可以解决呢?对此,DCloud首先提出了流应用概念,把之前 Hybrid 应用里的运行于客户端的 JS 代码,先打包发布到服务器,制定流式加载协议,手机端引擎动态下载这些 JS 代码到本地,并且为了第一次加载速度更快,实现了应用的边下载边运行。

就像流媒体的边下边播一样,应用也可以实现边用边下。

在这套方案的保障下,终于解决了之前的各种难题:让 JS 应用功能体验达到原生,并且可即点即用、可直达二级页面。

这套技术,需要让客户端引擎提前预置在手机上,就像流媒体的普及,建立在 Flash 的装机量巨大的基础上,那么普及这个客户端引擎就变得很重要。

2015 年,360 和 DCloud 合作,在 360 手机助手里内嵌了这个客户端引擎,推出了业内第一个商用的小程序,360 称之为 360 微应用,这个实际上是国内小程序形态的最早原型。

之后,DCloud曾找过几个大厂探索过流应用的合作方式,并在部分厂商中发行上线,比如金立的应用市场、中国移动的MM市场等;但各大公司的利益诉求不同,流应用想借助大厂流量快速推广的愿望,困难重重。

接着就是微信小程序,最初的名字实际上是微信应用号,之后改名为小程序,2016年9月份内测,2017年1月正式发行,再之后阿里巴巴、手机厂商联盟、百度、今日头条,陆续推出了自己的小程序平台,小程序时代滚滚而来。

小程序火爆了,开发者的困扰也跟着来了,各家小程序规范各不相同,每个平台单独开发一套小程序,人力成本极大,DCloud看到开发者这些真实痛点,随之提供了小程序跨端开发框架,这就是uni-app

2018年9月,微信率先推出云开发,这个功能我们认为是小程序发展历史上的一个重要节点,它可以让前端工程师从前到后将所有业务闭环实现,减少前后端的沟通成本、人力成本、运维成本,属于开发模式的重大升级。和之前前端同学既可通过JS/CSS编写前端UI,又可通过node.js写后端业务,这种所谓全栈开发模式相比,云开发有更好的优势,因为前端同学对于DB优化、弹性扩容、攻击防护、灾备处理等方面还是有经验欠缺的,但云开发将这些都封装好了,真正做到仅专注业务实现,其它都委托云厂商服务。

小程序架构

这是一个比较通用的小程序架构,目前几家小程序架构设计大致都是这样的(快应用的区别是视图层只有原生渲染)。

大家知道小程序是一个逻辑、视图层分离的架构。

逻辑层就是上图左上角这块,小程序中开发的所有页面JS代码,最后都会打包合并到逻辑层,逻辑层除了执行开发者的业务JS代码外,还需处理小程序框架的内置逻辑,比如App生命周期管理。

视图层就是上图右上角这块,用户可见的UI效果、可触发的交互事件在视图层完成,视图层包含web组件、原生组件两种,也就是小程序是原生+web混合渲染的模式,这块后面会详细讲。

逻辑层最后运行在JS CORE或V8环境中;JS CORE既不是DOM环境,也不是node环境,你是无法使用JS中的DOM或BOM对象的,你能调用的仅仅是ECMAScript标准规范中所给出的方法。

那如果你要发送网络请求怎么办?window.XMLHttpRequest 是无法使用的(当然即使可以调用,在iOS的WKWebView中也存在更严格的跨域限制,会有问题)。这时候,网络请求就需要通过原生的网络模块来发送,JS CORE和原生之间呢,就需要这个JS Bridge来通讯。

架构引发的性能坑点

小程序这种架构,最大的好处是新页面加载可以并行,让页面加载更快,且不卡转场动画;但同时也引发了部分性能坑点,今天主要介绍 3 点:

逻辑层/视图层通讯阻塞

我们从swipeaction这个例子讲起,需求是用户在列表项上向左滑动,右侧隐藏的菜单跟随用户手势平滑移动

若想在小程序架构上实现流畅的跟手滑动,是很困难的,为什么?

我们再回顾一下上面的小程序架构,小程序的运行环境分为逻辑层和视图层,分别由2个线程管理,小程序在视图层与逻辑层两个线程间提供了数据传输和事件系统。这样的分离设计,带来了显而易见的好处:

环境隔离,既保证了安全性,同时也是一种性能提升的手段,逻辑和视图分离,即使业务逻辑计算非常繁忙,也不会阻塞渲染和用户在视图层上的交互

但同时也带来了明显的坏处:

  • 视图层(webview)中不能运行JS,而逻辑层JS又无法直接修改页面DOM,数据更新及事件系统只能靠线程间通讯,但跨线程通信的成本极高,特别是需要频繁通信的场景

基于这样的架构设计,我们回到swipeaction,分析一次touchmove的操作,小程序内部的响应过程:

  • 用户拖动列表项,视图层触发touchmove 事件,经Native层中转通知逻辑层(逻辑层、视图层不是直接通讯的,需Native中转),即下图中的⓵、⓶两步
  • 逻辑层计算需移动的位置,然后再通过 setData 传递位置数据到视图层,中间同样会由微信客户端(Native)做中转,即下图中的⓷、⓸两步

实际上,用户滑动过程中,touchmove的回调触发是非常频繁的,每次回调都需要4个步骤的通讯过程,高频率回调导致通讯成本大幅增加,极有可能导致页面卡顿或抖动。为什么会卡顿,因为通讯太过频繁,视图层无法在16毫秒内完成UI更新。

为解决这种通讯阻塞的问题,各家小程序都在逐步提供对应的解决方案,比如微信的WXS、支付宝的SJS、百度的Filter,但每家小程序支持情况不同,详细见下表。

另外,微信的关键帧动画、百度的animation-view Lottie动画,也是为减少频繁通讯的一种变更方式。

其实,通讯阻塞是业界普遍存在的一个问题,不止小程序,react nativeweex等同样存在通讯阻塞的问题。只不过react nativeweex的视图层是原生渲染,而小程序是web渲染。我们下面以weex为例来说明。

大家知道,weex底层使用的 JS-Native Bridge,这个 Bridge 使得 JS 和 Native 之间的通信会有固定的性能损耗。

继续以上述swipeaction为例,要实现列表项菜单的跟手滑动,大致需经如下流程:

  • 在UI视图上绑定 touch 事件(或 pan 事件)
  • 当手势触发时, Native UI层将手势事件通过 Bridge 传递给 JS逻辑层 , 这产生了一次 Native UI到 JS 逻辑的通信,即下图中的⓵、⓶两步
  • JS 逻辑在接收到事件后,根据手指移动的偏移量驱动界面变化,这又会产生一次 JS 到 Native UI的通信,即下图中的⓷、⓸两步

同样,手势回调事件触发的频率是非常高的,频繁的的通信带来的时间成本很可能导致界面无法在16ms中完成绘制,卡顿也就产生了。

weex为解决通讯阻塞,提供了BindingX解决方案,这是一种称之为Expression Binding的机制,简要介绍一下:
– 接收手势事件的视图,在移动过程中的偏移量以x,y两个变量表示
– 期望改变(跟随移动)的视图,变化的属性为translateXtranslateY,对应变化的偏移量以f(x),f(y)表达式表示
– 将”交互行为”以表达式的方式描述,并提前预置到Native UI层
– 交互触发时,Native UI根据其内置的表达式解析引擎,去执行表达式,并根据表达式执行的结果驱动视图变换,这个过程无需和JS逻辑通讯

伪代码 – 摘录自weex官网

{

   anchor: foo_view.ref                    // ----> 这是"产生手势的视图"的引用  
   props:
            [
                {
                    element: foo_view.ref, // ----> 这是"期望改变的视图"的引用
                    expression: f(x) = x,  // ----> 这是具体的表达式
                    property: translateX   // ----> 这是期望改变的属性
                },
                {
                    element: foo_view.ref,
                    expression: f(y) = y,  // ----> y 属性
                    property: translateY
                }
            ]
}

React Native 同样存在类似问题,为避免频繁的通信,React Native 生态也有对应方案,比如Animated组件及Lottie动画支持。以 Animated 组件为例,为实现流畅的动画效果,该组件采用了声明式的API,在 JS 端仅定义了输入与输出以及具体的 transform 行为,而真正的动画是通过 Native Driver 在 Native 层执行,这样就避免了频繁的通信。然而,声明式的方式能够定义的行为有限,无法胜任交互场景。

uni-app在App端同样面临通讯阻塞的问题,我们目前的方案是采用类似微信wxs的机制(内部叫renderjs),但放开了wxs中无法获取页面DOM元素的限制,比如下图中多个小球同时移动的canvas动画,uni-app在App端的实现方案是:

  • renderjs 中获取canvas对象
  • 基于web的canvas绘制动画,而非原生canvas绘制

Tips:大家需要注意,并不是所有场景都是原生性能更好,小程序架构下,如上多球同时移动的动画,原生canvas并不如在wxs(renderjs)中直接调用web canvas

下表总结了跨端框架在通讯阻塞方面的解决方案。

数据/组件差量更新

小程序架构存在通讯阻塞问题,厂商为解决这个问题,创造了wxs脚本语言及关键帧动画等方式,但这些都是厂商维度的优化方案。我们作为小程序开发者,在性能优化方面,又能做哪些工作呢?

小程序开发性能优化,核心就是setData的调用,你能做只有两件事情:
– 尽量少调用setData
– 每次调用setData,传递尽可能少的数据量,即数据差量更新

减少setData调用次数

假设我们有更改多个变量值的需求,示例如下:

change:function(){
    this.setData({a:1});
    ... //其它业务逻辑
    this.setData({b:2});
    ... //其它业务逻辑
    this.setData({c:3});
    ... //其它业务逻辑
    this.setData({d:4});
}

如上4次调用setData,会引发4次逻辑层、视图层数据通讯。这种场景,开发者需意识到setData有极高的调用代价,自己需手动调整代码,合并数据,减少数据通讯次数。

部分小程序三方框架已内置数据合并的能力,比如uni-app在 Vue runtime 上进行了深度定制,开发者无需关注setData的调用代价,可放心编写如下代码:

change:function(){
    this.a = 1;
    ... //其它业务逻辑
    this.b = 2;
    ... //其它业务逻辑
    this.c = 3;
    ... //其它业务逻辑
    this.d = 4;
}

如上4次赋值,uni-app运行时会自动合并成{"a":1,"b":2,"c":3,"d":4}一条记录,调用一次setData完成所有数据传递,大幅降低setData的调用频次,结果如下图:

减少setData调用次数,还有个注意点:后台页面(用户不可见的页面)应避免调用setData

数据差量更新

假设我们有一个 “列表页 + 上拉加载” 的场景,初始化列表项为 “item1 ~ item4”,用户上拉后要向列表追加4条新记录 “item5 ~ item8″,小程序代码如下:

page({
    data:{
        list:['item1','item2','item3','item4']
    },
    change:function(){
        let newData = ['item5','item6','item7','item8'];
        this.data.list.push(...newData); //列表项新增记录
        this.setData({
            list:this.data.list
        })
    }
})

如上代码,change方法执行时,会将list中的 “item1 ~ item8” 8个列表项通过setData全部传输过去,而实际上变化的数据只有”item5 ~ item8″。

开发者在这种场景下,应通过差量计算,仅通过setData传递变化的数据,如下是一个示例代码:

page({
    data:{
        list:['item1','item2','item3','item4']
    },
    change:function(){
        // 通过长度获取下一次渲染的索引
        let index = this.data.list.length;
        let newData = ['item5','item6','item7','item8'];
        let newDataObj = {};//变化的数据
        newData.forEach((item) => {
            newDataObj['list[' + (index++) + ']'] = item;//通过list下标精确控制变更内容
        });
        this.setData(newDataObj) //设置差量数据
    }
})

每次都手动计算差量变更数据是繁琐的,新手不理解小程序原理的话,也容易忽略这些性能点,给App埋下性能坑点。

此处建议开发者选择成熟的小程序三方框架,这些框架已经自动封装差量数据计算,对开发者更友好。比如uni-app借鉴了 westore JSON Diff库,在调用setData之前,会先比对历史数据,精确高效计算出有变化的差量数据,然后再调用setData,仅传输变化的数据,这样可实现传递数据量的最小化,提升通讯性能。如下是一个示例代码:

export default{
    data(){
        return {
            list:['item1','item2','item3','item4']
        }
    },
    methods:{
        change:function(){
            let newData = ['item5','item6','item7','item8'];
            this.list.push(...newData) // 直接赋值,框架会自动计算差量数据
        }
    }
}

Tips:如上change方法执行时,仅会将list中的”item5 ~ item8″4个新增列表项传输过去,实现了setData传输量的极简化

组件差量更新

下图是一个微博列表截图:

假设当前有200条微博,用户对某条微博点赞,需实时变更其点赞数据(状态);在传统模式下,一条微博的点赞状态变更,会将整个页面(Page)的数据全部通过setData传递过去,这个消耗是非常高的;而即使通过之前介绍,通过差量计算的方式获取变更数据,这个 Diff 遍历范围也很大,计算效率极低。

如何实现更高性能的微博点赞?这其实就是组件更新的典型场景。

合适的方式应该是,将每条微博封装成一个组件,用户点赞后,仅在当前组件范围内计算差量数据(可理解为Diff范围缩小为原来的1/200),这样效率才是最高的。

提醒大家注意,并不是所有小程序三方框架都已实现自定义组件,只有在基于自定义组件模式封装的框架,性能才会大幅提升;如果三方框架是基于老的template模板封装的组件开发,则性能并不会有明显改善,其 Diff 对比范围依然是Page页面级的。

混合渲染

大家知道,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 WebView 渲染的内置组件,他们是由原生客户端渲染的。

小程序中的原生组件,从使用方式上来说,主要分为三类:

  • 通过配置项创建的:选项卡、导航栏,还有下拉刷新
  • 通过组件名称创建的,比如:camera、canvas、input、live-player、live-pusher、map、textarea、video
  • 通过API接口创建的,比如:showModal、showActionSheet等

除了上面提到的这些之外,其它基本都是web渲染。所以说,小程序是混合渲染模式,web渲染为主,原生渲染为辅。

为什么要引入混合渲染

接下来的问题,为什么要引入原生渲染?以及为什么仅针对这几个组件提供了原生增强?其他组件为什么没有做原生实现?

这就需要我们针对每个组件单独进行分析思考,这里举了几个例子:
– tabs/navigationbar:避免页面白屏,提升新窗口进入时的用户体验
– Video:全屏后的滑动控制(声音、进度、亮度等)
– map:更流畅的双指缩放、位置拖动
– input:web端的input,键盘弹出时,只有完成按钮,无法让键盘显示发送下一个这样的按键

提到input控件的原生化,可以稍微发散一下。

小程序中原生input控件的通用做法是,未获取焦点时以web控件显示,但在获取焦点时,绘制一个原生input,盖在web input上方,此时用户看见的键盘即为原生input所对应的键盘,原生弹出键盘是可自定义按钮(如上图中下一步、send按钮)的。这种做法存在一个缺陷:web和原生,毕竟不同渲染引擎,在键盘弹出和关闭时,对应input的placeholder会闪烁。

在Android平台,还有一种做法是基于webkit改造,定制弹出键盘样式;这种方案,在键盘弹出和关闭时,input控件都是web实现的,故不存在placeholder闪烁的问题。

混合渲染引发的问题

原生组件虽然带来了更丰富的特性及更好的性能,但同时也引入了一些新的问题,比如:

1.层级问题:原生永远在最高层,无法通过z-index设置不同元素的层级,无法与 view、image 等内置组件相互覆盖,不支持在picker-viewscroll-viewswiper等组件中使用

2.通讯问题:比如一个长列表中内嵌视频组件,页面滚动时,需通知原生的视频组件一起滚动,通讯阻塞,可能导致组件抖动或拖影

3.字体问题:在Android手机上,调整系统主题字体,所有原生渲染的控件的字体都会变化,而web渲染的字体则不会变化。如下图,系统rom字体为一款“你的名字”的三方字体,设置后,小程序顶部标题字体变了,底部选项卡字体也变了,但小程序中间内容区字体不变,这就是比较尴尬的一种情况,一个页面,两种字体。

当然,并不是所有小程序都存在这种问题,部分小程序通过修改自带的webview内核,实现了webview也可以使用rom主题字体,比如微信、qq、支付宝;其他小程序(百度、头条),webview仍然无法渲染为rom主题字体。

混合渲染改进方案

既然混合渲染有这些问题,对应就会有解决方案,目前已有的方案如下。

方案1:创造层级更高的组件

既然其它组件无法覆盖到原生组件上,那就创造出一种新的组件,让这个新组件可以覆盖到video或map上。cover-view/cover-image就是基于这种需求创造出来的新组件;其实它们也是原生组件,只不过层级略高,可以覆盖在 map、video、canvas、camera等原生组件上。

目前除了字节跳动外,其它几家小程序均已支持cover-view/cover-image

cover-view/cover-image 在一定程度上缓解了分层覆盖的问题,但也有部分限制,比如严格的嵌套顺序。

方案2:消除分层,同层渲染

既然分层有问题,那就消除分层,从2层变成1层,所有组件都在一个层中,z-index岂不就可生效了?

这个小目标说起来简单,具体实现还是很复杂的,下个章节具体介绍。

同层渲染

抛开小程序当前架构实现,解决混合渲染最直接的方案,应该更换渲染引擎,全部基于原生渲染,video/map和image/view均为原生控件,层级相同,层级遮盖问题自然消失。这正是uni-app在App端的推荐方案。

uni-app在App端支持weex原生渲染,至于uni-app如何抹平weex和小程序的各项差异,这是另外一个话题,后续可单独分享。

回归到当前web渲染为主、原生渲染为辅的主流小程序现状,如何实现同层渲染?

基于我们的分析研究,这里简单讲解一下同层渲染实现的方案,和微信真实实现可能会有出入(目前仅微信一家实现了同层渲染)。

iOS平台

小程序在 iOS 端使用 WKWebView 进行渲染,WKWebView 在内部采用的是分层的方式进行渲染,一般会将多个DOM节点,合并到一个层上进行渲染,因此DOM节点和层之间不存在一一对应关系。但是,一旦将一个 DOM 节点的 CSS 属性设置为 overflow: scroll 后,WKWebView 便会为其生成一个 WKChildScrollView,且WebKit 内核已经处理了WKChildScrollView与其他 DOM 节点之间的层级关系,这时DOM节点就和层之间有一一对应关系了。

小程序 iOS 端的同层渲染可基于 WKChildScrollView 实现,主要流程如下:
– 创建一个 DOM 节点并设置其 CSS 属性为 overflow: scroll
– 通知原生层查找到该 DOM 节点对应的原生 WKChildScrollView 组件
– 将原生组件挂载到该 WKChildScrollView 节点上作为其子 View

Android平台

小程序在 Android 端采用 chromium 作为 WebView 渲染层,和iOS的WKWebView不同,是统一渲染的,不会分层渲染。但chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,可用来解析<embed>。Android 端的同层渲染可基于 <embed> 加 chromium 内核扩展来实现,大致流程如下:

  • 原生层创建一个原生组件(如video)
  • WebView 创建一个 <embed> 节点并指定其类型为video
  • chromium 内核创建一个 WebPlugin 实例,并生成一个 RenderLayer
  • 原生层将原生组件的画面绘制到 RenderLayer 所绑定的 SurfaceTexture 上
  • chromium 渲染该 RenderLayer

这个流程相当于给 WebView 添加了一个外置插件,且<embed>节点是真正的 DOM 节点,可将更多的样式作用于该节点上。

未来可能

如果要探讨小程序接下来的技术升级方向,我们认为应该在用户体验、开发效率两个方向上努力。

更优秀的用户体验

先说用户体验的问题,主要也是两个方面:
– 解决现有的性能坑点,比如前面分析的这几项,通讯阻塞、分层限制等,这里不再赘述
– 支持更多App的体验,更自由灵活的配置,比如高斯模糊

如果你也想快速搭建的自己的小程序引擎,并更优的解决如上体验问题,该怎么办?

这里放一个福利。

uni-app发行到App端,实际上就是一个完整的小程序引擎,DCloud会在近期将这个引擎完整开源,欢迎大家基于uni-app小程序SDK快速打造自己的小程序平台。

uni-app小程序SDK具备如下几个特征:
– 性能更高:支持native渲染,扩展wxs,更高的通讯性能
– 开放性更强:更灵活的配置,支持更多App的体验
– 开源不受限:无需签订任何协议,拿走就用
– 生态丰富:支持微信小程序自定义组件,支持所有uni-app插件,uni-app插件市场目前已有上千款成熟插件

开发效率

开发效率应该从跨端、跨云两个维度进行分析。

跨端开发

目前的小程序都带有明显的厂家属性,每个厂家各不相同。之前更遭,阿里内部有多套小程序(支付宝、淘宝、钉钉等),幸好阿里圆老板给力,目前已基本统一。但腾讯体系下,微信和QQ小程序依然是两队人马,两套规范。

小程序之前是手机端的,今年360出了PC端小程序。

接下来,会不会还有其它厂家推出自己的小程序?会不会有新的端的出现?比如面向电视的小程序、面向车载的小程序?

一切皆有可能。

逐水草而居是人类的本能,追求流量依然是互联网的制胜法宝。当前的小程序宿主,都是亿级流量入口,且各家流量政策不同。比如微信的流量最大的,但有各种限制;百度和头条系是支持广告投放的,通过广告投放,可以快速获得大量较为精准的用户;百度小程序还有个web化的功能,可以通过将web的搜索流量,转化成小程序的流量。

面对众多小程序平台及各自巨大的入口流量,开发者如何应对?

等待w3c的小程序标准统一,短期不太现实。当下,若想将业务快速触达多家小程序,借助跨端框架应该是唯一可行的方案。

跨云开发

开发商借助uni-app或其它跨端框架,虽然已可以开发所有前端应用。但仍然需要雇佣php或java等后台开发人员,既有后端人员成本,又有前/后端沟通成本。

腾讯、阿里、百度小程序虽陆续上线了云开发,但它们均只支持自己的小程序,无法跨端,分散的服务器对开发商更不可取。

故我们认为跨厂商的 serverless 是接下来的一个重点需求,开发者在一个云端存储所有业务数据及后端逻辑,然后将前端小程序发行到各家小程序平台,也就是“一云多端”模式。

总结

基于小程序的现状,我们也许可以总结一下小程序技术上的可能方向:
– 其它小程序拉齐与微信的差距,让开发者可以做出足够高性能的应用服务
– 所有小程序应拉齐和App的体验差距,虽然功能API方面仍有不足,但操作性能和交互体验,不应该弱于app
– 跨端框架 + serverless,让开发者更轻松,让企业更高效

OK,我的分享到此结束,若有错误,欢迎交流指正。

使用uni-app开发小程序,比直接原生开发小程序好在哪里

小程序原生开发有不少槽点:
1. 原生wxml开发对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程。所以大公司都会用框架开发
2. 微信定义的这套语法,wxml、wxs,以及wx:if等语法,私有化太强。不如正经学vue,学会了全端通用,而不是只为微信小程序
3. vue生态里有太多周边工具,可以提高开发效率,比如ide、校验器、三方库。。。而微信的开发者工具和专业编辑器相比实在不好用,个性化设置也非常少

作为前端工程师,除了微信小程序,还要开发web、其他小程序甚至App,人们不喜欢来回切换开发工具和变更语法思考方式。

uni-app自然可以解决这些问题,但开发者又经常有些顾虑:
1. 怕使用uni-app后,微信小程序里有的功能无法实现,受制于uni-app的更新
2. 怕性能不如原生WXML
3. 怕框架不成熟,跳到坑里
4. 担心社区生态不完善

本文从开发者关心的功能、性能、学习门槛、开发体验、生态、可扩展性等维度,逐个分析对比,给予说明。

1.功能实现

开发者最常问的问题:如果小程序迭代升级,新增了一批API,但uni-app框架未及时更新,该怎么办?

其实这是误解,uni-app不限制底层API 调用;在小程序端,uni-app支持直接编写微信原生代码。

类比传统web开发,如果vue、react等框架的使用,造成开发者无法操作浏览器提供的所有api,那这样的框架肯定是不成熟的。小程序开发也一样,uni-app框架中,同样可调用微信提供的所有原生代码。

故如果存在某些API(平台特有或新增API),uni-app尚未封装,开发者可直接在uni-app中编写微信原生API,即wx.开头的各种API。

举个例子,目前uni-app虽然尚未封装跨平台的广告(ad)组件,但开发者在小程序端依然可以使用微信<ad>组件来展现广告,代码示例如下:

 <view>
    <view class="title">微信官方banner广告</view>
    <view style="min-height: 50px;">
        <!-- uni-app尚未封装,但可直接使用微信原生的ad组件-->
        <ad unit-id="adunit-01b7axxxbf53d74e"></ad>
    </view>
    <view class="title">微信官方视频广告</view>
    <view style="min-height: 50px;">
        <!-- uni-app尚未封装,但可直接使用微信原生的ad组件-->
        <ad unit-id="adunit-9f340xxx64533" ad-type="video" ad-theme="white"></ad>
    </view>
</view>

小程序端运行效果如下:

包括微信小程序自定义组件、WXS、云开发这些复杂用法,在uni-app里一样全面支持。

所以,结论是:使用uni-app框架开发,在功能上和原生小程序开发没有区别,不会有任何限制。

2. 性能体验

开发者常问的第二个问题:三方框架,内部大多做了层层封装,这些封装是否会增加运行负载,导致性能下降?

同样是多虑了,uni-app不会导致性能下载,甚至对很多环节做了自动优化,很多场景下性能体验比微信原生开发更好。

类似使用vue.js开发web,不但不会造成性能比原生js差,反而由于虚拟dom和差量更新技术的运用,在大多数场景下,比开发者手动写代码操作dom的性能还好。

小程序中需要频繁的写setData代码来更新数据,这里很重要的就是差量数据更新。如果不做差量,代码性能不好,如果每处逻辑都判断差量数据更新,那代码写起来太麻烦了。

使用uni-app,底层自动差量数据更新,简单而高性能。

我们从优化理论、实测数据两个维度来仔细说明。

2.1 理论:框架优化方案

为提高性能体验,小程序从架构设计层面做了很多工作:
– 逻辑层、视图层分离,避免JS运算阻塞视图渲染
– 单独定义组件标签(wxml),减少DOM复杂度
– 精简样式(wxss),提升渲染性能
– 复杂组件原生化(video/map等),解决web组件的功能/体验缺失

通过这些规范约束,大幅提升了小程序的整体性能体验,但依然存在不少性能坑点,其中以setData最为频繁普遍。

这里引用微信官方的描述,简单介绍一下setData背后的工作原理:

小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。

为简化开发,微信将evaluateJavascript调用封装成了setData JS方法,实现视图层和逻辑层的数据传输,数据流示意图如下:

setData的执行会受到很多因素的影响,setData每次传递数据量过大或频繁被调用(见微信官方介绍),都可能引发性能体验问题。

幸运的是,uni-app在这两个方面都有优化。

2.1.1 减少 setData 传递数据量

假设当前页面有一个列表(初始值为a,b,c,d),现在要向列表后追加4个新列表项(e,f,g,h),我们分别以微信原生、uni-app 两种模式编写代码。

小程序原生代码:

page({
    data:{
        list:['a','b','c','d']
    },
    change:function(){
        let newData = ['e','f','g','h'];
        this.data.list.push(...newData);
        this.setData({
            list:this.data.list
        })
    }
})

如上微信原生代码,change方法执行时,会将list中的a,b,c,d,e,f,g,h8个列表项通过setData全部传输过去。

uni-app 代码:

export default{
    data(){
        return {
            list:['a','b','c','d']
        }
    },
    methods:{
        change:function(){
            let newData = ['e','f','g','h'];
            this.list.push(...newData)
        }
    }
}

如上uni-app代码,change方法执行时,仅会将list中的e,f,g,h4个新增列表项传输过去,实现了setData传输量的极简化。

uni-app借鉴了 westore JSON Diff库,在调用setData之前,会先比对历史数据,精确、高效计算出有变化的差量数据,然后再调用setData,仅传输变化的数据,这样就实现 setData 传递数据量的最小化,大幅提高通讯性能。

Tips:也许有些同学对传递数据从a,b,c,d,e,f,g,h8个列表项优化为e,f,g,h4个列表项,不以为然,但我们提醒,不要小看这个机制,上述只是demo示例。

  • 在实际列表场景中,每个列表项可能包含缩略图、标题、摘要、时间等各种信息,每个列表项数据都会更大(假设为1k);
  • 假设当前页面有20个列表项,连续上拉4次后,页面变成100条记录;如果再次上拉,页面变成120条记录时,情况会有不同
  • 上述微信原生的方式,将120条记录数据(120k)全部传输过去
  • 上述 uni-app 模式,仅会将新增的20条(101 ~ 120)记录数据(20k)传输过去,数据量是原生方式的1/6!
  • 当页面列表项数据越多,这个差别就越大,页面有200条记录时,uni-app传递数据量会变成微信原生数据传递量的1/10!

2.1.2 减少 setData 调用频次

假设我们有更改多个变量值的需求,我们分别以微信原生、uni-app 两种模式编写代码。

小程序原生代码:

change:function(){
    this.setData({a:1});
    this.setData({b:2});
    this.setData({c:3});
    this.setData({d:4});
}

如上四次调用setData,就会引发4次逻辑层、视图层数据通讯

uni-app 代码:

change:function(){
    this.a = 1;
    this.b = 2;
    this.c = 3;
    this.d = 4;
}

如上uni-app的代码,最后会被合并成{"a":1,"b":2,"c":3,"d":4}一条数据,然后仅调用一次setData完成所有数据传递,大幅降低了setData的调用频次。

uni-app之所以有这样的优势,是因为 uni-app 基于 Vue Runtime 深度定制实现,并借助了 Vue 的 nextTick 机制。

2.2 实测:性能对比数据

有了如上的理论分析,我们接着进行真机实测,用数据来对比。

测试模型如下:

  • 开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。

仿微博的列表是一个包含很多组件的列表,这种复杂列表对性能的压力更大,很适合做性能测试。

  • 界面如下:

  • 开发版本:使用微信原生、uni-app分别开发两套代码,uni-app使用cli方式默认安装。

  • 测试代码开源(Github仓库地址:https://github.com/dcloudio/test-framework),
    Tips:若有同学觉得测试代码写法欠妥,欢迎提交 PR 或 Issus,本项目下还有其它框架的测试代码,开发者可忽略

  • 测试机型:红米 Redmi 6 Pro、MIUI 10.2.2.0 稳定版(最新版)、微信版本 7.0.3(最新版)

  • 测试环境:每个框架开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。

从触发上拉加载到数据更新、页面渲染完成,需要准确计时。人眼视觉计时肯定不行,我们采用程序埋点的方式,制定了如下计时时机:

  • 计时开始时机:交互事件触发,框架赋值之前,如:上拉加载(onReachBottom)函数开头
  • 计时结束时机:页面渲染完毕(微信setData回调函数开头)

Tips:setData回调函数开头可认为是页面渲染完成的时间,是因为微信setData定义如下(微信规范):

字段 类型 必填 描述
data Object 这次要改变的数据
callback Function setData引起的界面更新渲染完毕后的回调函数

测试方式:从页面空列表开始,通过程序自动触发上拉加载,每次新增20条列表,记录单次耗时;固定间隔连续触发 N 次上拉加载,使得页面达到 20*N 条列表,计算这 N 次触发上拉到渲染完成的平均耗时。

测试结果如下:

列表条数 微信原生 uni-app
200 770 641
400 876 741
600 1111 910
800 1406 1113
1000 1690 1321

说明:以400条微博列表为例,从页面空列表开始,每隔1秒触发一次上拉加载(新增20条微博),记录单次耗时,触发20次后停止(页面达到400条微博),计算这20次的平均耗时,结果微信原生在这20次 触发上拉 -> 渲染完成 的平均耗时为876毫秒,uni-app是741毫秒。

这个数据,可能违反了很多人的直觉,uni-app 的性能竟然比微信原生还好!

当然,使用微信原生开发,也可以自己单独写代码优化setData。但每处业务都编写太多判断是不现实的,自然是用框架更舒心。

这个结果,和web开发类似,web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。

也恰恰是因为Vuereact框架的优秀,性能好,开发体验好,所以原生js开发已经逐渐减少使用了。

3.社区生态

3.1 周边轮子

小程序是脱离web自造生态,很多web生态中轮子无法使用。

微信小程序还是有周边生态的,而其他几家小程序平台的生态基本没建起来。

uni-app的周边生态非常丰富,在插件市场有近800个插件,详见 ext.dcloud.net.cn

首先uni-app兼容小程序的生态,各种自定义组件均可直接引入使用。在此基础上,uni-app的插件市场,有更多vue组件,同时可跨多端使用,并且性能优秀。

这使得uni-app的生态成为最丰富的小程序开发生态。

比如富文本解析、图表等组件,uni-app的插件性能均超过了wxparse、wx-echart等微信小程序组件。

如果开发者需要丰富和高性能的组件,更应该使用uni-app,而不是原生小程序开发。

3.2 活跃的QQ/微信群和论坛

uni-app官方有 70 个开发者QQ/微信交流群(大多2千人群,近10万开发者),三方群更多。

问答社区,每天有数百篇帖子。活跃度与微信小程序官方论坛相同,远超过其他小程序官方论坛。

uni-app三方培训活跃,腾讯课堂官方都为uni-app制作了课程,各种培训网站到处可见免费或收费的uni-app培训视频教程。

4.学习门槛、开发体验

首先微信原生的开发语法,既像React ,又像Vue,有点不伦不类,对于开发者来说,等于又要学习一套新的语法,大幅提升了学习成本,这一直被大家所诟病。

uni-app则对开发者更为友好,简单来说是 vue的语法 + 小程序的api。

它遵循Vue.js语法规范,组件和API遵循微信小程序命名,这些都属于通用技术栈,学习它们是前端必备技能,uni-app没有太多额外学习成本。

有一定 Vue.js 和微信小程序开发经验的开发者可快速上手 uni-app

没学过vue的同学,也不用掌握vue的全部,只需了解vue基础语法、数据绑定、列表渲染、组件等,其他如路由、loader、cli、node.js、webpack并不需要学。

因为HBuilderX工具搭配uni-app可以免终端开发,可视化创建项目、可视化安装组件和扩展编译器,也就是uni-app的学习门槛,比web开发的vue.js还低。

开发体验层面,微信原生开发相比uni-app有较大差距,主要体现在:

  • 更为强大的组件化开发能力:vue的组件开发比小程序自定义组件开发的体验要好很多
  • 应用状态管理:uni-app支持vuex
  • 使用 Sass 等 CSS 预处理器
  • 完整的 ES Next 语法支持
  • 自定义构建策略

开发工具维度,差距更大:
– 微信开发者工具被吐槽无数
uni-app的出品公司,同时也是HBuilder的出品公司,DCloud.io。HBuilder/HBuilderX系列是四大主流前端开发工具(可对比百度指数),其为uni-app做了很多优化,故uni-app的开发效率、易用性非微信原生开发可及。

这里可以输出一个结论:如果你需要工程化能力,那就直接忘了微信原生开发吧。

5.未来扩展性

虽然当前产品仅要求发布到微信小程序,但若有一天,老板和外来的一个和尚喝完咖啡,转身就要求覆盖阿里、百度、字节跳动等各家小程序平台,此时程序员该怎么办?

难道真的每个平台到处搬砖吗?

此时,uni-ap的跨端功能将成为程序员的自救神器,基于uni-app开发的小程序,无需修改,即可同时发布到多家小程序,甚至App、H5平台。这不是梦想,而是现实。大家可依次扫描如下8个二维码,亲自体验最全面的跨平台效果!。

6.结语

uni-app 微信
功能 相同 相同
性能 常规场景更优 需要自己编写复杂代码才能提高性能
社区生态 丰富,更多高性能组件 丰富
开发体验 纯vue体验,高效、统一;工程化能力强 语法私有化;工程化能力弱
多端能力 同时支持H5、多家小程序、跨平台App 只能用于微信小程序

结论:只开发微信小程序,也应该使用uni-app

阿里支付宝小程序IDE 0.70 Stable版发布,内置uni-app框架

随着微信、阿里、百度、头条、QQ纷纷推出小程序,开发者的开发维护成本持续上升,负担过重。这点已经成为共识。

为了减轻开发者的负担,阿里团队在官方的小程序开发者工具中整合了多端框架。

经过之前1个月的公测,10月10日,阿里小程序正式发布0.70版开发者工具,通过 uni-app 实现多端开发,成为本次版本更新的亮点功能!如下摘自阿里小程序官方更新日志:

我们这个版本内置支持 uni-app 跨平台小程序框架的研发,开发者可以实现一次编写后,生成多个平台体系的小程序代码,避免在不同平台维护不同代码,降低开发和维护成本(点击这里查看教程):

更多更新日志参考:https://docs.alipay.com/mini/ide/0.70-stable

本次阿里小程序工具集成 uni-app,会让 uni-app 继续快速爆发,取得更大的成功。

后续DCloud还将深化与阿里的合作,在serverless等领域给开发者提供更多优质服务。

使用多端框架开发各端应用,是多赢的模式。开发者减轻了负担,获得了更多新流量。而小程序平台厂商,也能保证自己平台上的各种应用可以被及时的更新。

DCloud助力,Vue官网有免费中文视频教程了!

Vue官网,是广大前端开发者学习Vue.js最重要的阵地。官网上有详细的文档、及部分视频教程。但之前这些视频都是英文的,对国内开发者的入门学习不太友好。

为了让更多开发者低门槛进入vue生态,DCloud与Vue官方合作,全新录制Vue中文视频教程,现已正式上线并更新到Vue官网,供大家免费观看学习。

更细致的是,在很多API文档旁,都提供了这个API对应视频讲解的链接。

该系列视频免费、且没有广告

除了观看视频讲解外,学习者还可点击右上角按钮,使用HBuilderX打开课程源码亲自动手编码,更深入的掌握Vue.js的具体用法。

在HBuilderX中可以方便的对Vue的语法进行代码提示转到定义,能在代码助手右侧看到API的详细文档链接。


通过HBuilderX右上角的预览,开发者可以实时了解自己代码的运行情况,还可以查看logdebug打断点。


Vue.js是我们中国人创造的世界级前端框架,在国内,它已经远远超过reactangular。下图为今年的百度指数对比:

欢迎广大开发者积极拥抱Vue,共同促进生态的繁荣发展!

​

uni-app 2.2发布,大幅优化H5端性能体验

背景

uni-app发布以来,已经服务了几十万开发者。让我们意外,或者说惊喜的是,有大量开发者用uni-app只编写H5版,并没有多端发布(可参考案例)。

这其实也符合uni-app的初衷,uni-app的定位并不是需要多端发布时才用uni-appuni-app是一个使用vue.js开发所有前端应用的统一框架。对于一个前端工程师来说,使用uni-app做多端效率更高,做单一端也没问题,并在各端有不少出彩的地方。

过去的版本迭代中,uni-app已经成为了更好的小程序开发框架,比使用原生微信开发更有优势。(见评测

uni-app2.2的新版中,我们大幅优化了H5版的性能,让使用uni-app开发的H5,性能体验和直接使用vue.js开发H5拉齐。

可能不少开发者有某种误解:多端框架要适配多端,所以性能肯定不如原生。我们想纠正一下:
1. 切忌想当然,多看数据评测。还不信就自己动手实验
2. 请问使用vue.js开发的web性能好,还是使用原生js开发web性能好?答案是:使用vue.js框架。为什么?因为它在底层会自动优化数据同步、虚拟dom,比大多数开发手动写的代码要更高效。同样的,使用uni-app也如此,框架底层的优化处理比大多数开发者手动写setdata或dom操作更高效。
3. 多端适配很多是在编译时做的,并不影响运行时的性能

优化难点

想优化H5端的性能,并不是一件容易的事。

“功能全面”和“小巧极速”,这是一对最难调和的冤家。

为了保障多端的一致性,uni-app实现了一套小程序的H5版Runtime,支持各种小程序的组件、API、配置。所以uni-app的H5版拥有比其他框架更好的跨端一致性。

但这也造成了老版的uni-app,输出H5端时,包体积过大(框架未压缩前有500k,部署gzip后162k)。

这确实是一个非常大的runtime,包含了几十个内置组件,数百个API。而且这些API仍然在快速增加中。

不能像其他框架一样因为功能少,所以体积小。我们不会用功能换性能,我们需要更好的方案。

优化方法

uni-app包含几十个内置组件、数百个API,是个“大而全”的框架;但开发者在开发具体应用时,未必能使用到所有的组件及API。若能根据项目具体情况,删掉没用到的组件及API,保留对项目有用的组件及API,便可精简框架、减少体积,这即是“摇树优化”的思路。

摇树优化(Tree-Shaking),顾名思义,摇晃树干,将枯死无用的枝条摇掉,仅保留有用的树枝。对应到框架层面理解,就是一个框架的众多组件和API,可以按需使用,把未引用的框架部分裁剪掉。Tree-Shaking 最早由 Rollup 提出,属于死码删除的一种形式。

常见的前端框架摇树,一般是基于明确的import引用关系。比如引用某UI库时,在A页面使用该UI库的search组件,此时需要写js代码import这个search组件,那么摇树分析就很容易。

uni-app和小程序一样,内置组件和API是不需要import的,这提升了开发的易用性,但此时却加大了摇树的难度,依靠简单的import分析无法实现摇树了。

幸好对DCloud团队而言,AST语法分析是看家本事,多年来HBuilder以js和vue语法提示著称。通过AST分析,uni-app新版可以精准判定这个项目使用了哪些组件和API。

不过这还不够,分析工程源码使用了什么组件和API之后,还得考虑框架各组件和API之间可能存在依赖和耦合关系,这需要进一步的计算和关系梳理,具体而言:

  • 组件:通过vue-template-compiler分析出来的AST,映射生成项目中使用到的组件清单,然后再基于Webpack插件将使用到的组件打包构建
  • API:编译器维护一个 api 依赖关系的 json 文件,该 json 文件描述每个 api 可能依赖的文件,然后 babel 查找到对应的 api 后,根据api 的依赖关系自动导入,重新编译

在工程师持续的加班奋战后,uni-app终于推出了全新的2.2版本,解决了这些难题,大幅降低了发行包体积,gzip后的框架体积,从162k降低到92k,仅相当于你在工程中引用了vue.jsvue-router、以及部分es6 polyfill库。(后续有详细比较)

除了大幅降低发行包体积,新版还调整了预载策略,可以进一步加快页面的渲染速度。

优化结果

搭建环境

我们使用vue-cli创建uni-app默认模板

vue create -p dcloudio/uni-preset-vue my-project

项目创建后,编译生成H5端的发行目录

npm run build:h5

然后配置nginx服务器,指定root目录并启用gzip压缩,示例如下:

server {
    ...
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_comp_level 4;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    ...
}

runtime 动态裁剪

然后通过 Chrome DevTools 的 Network 面板,查看优化前的首页网络请求包大小,结果如下:

然后启用H5平台的优化开关,重新查看首页的网络请求包大小,结果如下:

可以看到框架主库(chunk-vendors.js)从162k变为92.8k,体积压缩43%!

实际上,框架主库主要分为三个部分:

  • vue/vue-router基础库
  • es6 polyfill库
  • uni-app runtime(组件&API实现)

如果对这三个部分再拆开对比,我们会看到uni-app组件库优化比例更高:

vue/vue-router es6 polyfill库 uni-app runtime 累计
优化前 38k 43k 81k 162k
优化后 38k 26k 28.8k 92.8k

Tips:

  • uni-app runtime从81k瘦身为28.8k,裁剪比例达到64%
  • 新编译器对es6的使用也做了动态扫描,项目中用到的es6语法(包括uni-app runtime用到的es6语法),才会打包对应的polyfill实现,故es6 polyfill库从43k瘦身为26k

脚本执行时间

然后,我们再通过Chrome DevTools 的 Performance 面板,查看优化前后的性能数据,对比结果如下:

可以看出,最耗时的脚本执行时间,从227ms提升为154ms,时间提升达到32%。

如何使用

虽然内部实现比较复杂,但uni-app对外暴漏了简单的配置,开发者只需在配置文件中打开一个开关即可。具体在 manifest.json 中h5节点配置如下选项:

"h5" : {
    "optimization":{
        "treeShaking":{
            "enable":true //启用摇树优化
        }
    }
}

2.2版的其他优化

uni-app2.2版中,还开放了package.jsonvue-config.js的自定义,开发者可以自由的配置编译策略。

现在可以自定义支持所有小程序平台,包括钉钉小程序、高德小程序、抖音小程序…等。这样除了标准的8大平台(iOS、Android、H5、微信小程序、支付宝小程序、百度小程序、头条小程序、QQ小程序),这些生态的子生态也可以分版本条件编译。

同样,也支持对H5端进行多子端编译,比如微信里的内嵌的H5、App里内嵌的H5…都可以分开条件编译。

如此灵活的条件编译,对于一套工程的多端发布、共享复用、同步升级,有莫大的好处。即便是仅开发H5版,uni-app的多端条件编译也提供了更灵活和强大的工程化能力。

2.2版本还可以设置各种静态资源、js、小程序自定义组件的编译和拷贝策略。如果你之前的h5项目或小程序项目想转换至uni-app下,又不想挪动某些目录结构,那么在vue-config.js里配置策略即可。

使用uni-app开发H5和直接开发H5相比的优势

在与直接开发h5拉齐性能的基础之上,uni-app给开发者提供了更多优势:

  1. 开发效率
    uni-app提供了大量适合手机页面的基础组件(其实就是小程序组件)。还提供了扩展组件uni ui。插件市场更有600多款插件。

无论开发者想找一个电商的模板,还是找一个图表组件,都可以手到擒来。开发效率更胜以往。

  1. 多端编译
    我们开发H5时,经常需要给浏览器输出一份、给微信等超级App输出一份、给自家的App输出一份。甚至不同地域、不同用户画像,都会输出不同版本。以前,开发者只能对js部分进行条件编译,甚至不得不建多个仓库进行多版维护。

uni-app解决了这些烦恼,它的条件编译非常灵活强大:
– 不管是组件、js还是css,都可以按平台编译输出
– 还可以多个平台进行“与和或”的运算编译
– 除了文件中的代码,整个文件、甚至整个目录,都可以条件编译

例如微信、QQ等在支持x5内核的内置浏览器中,使用x5的视频同层渲染;或者在微信服务号中调用微信卡劵,这段代码只有build到 dist/h5-weixin 这个目录下的版本才会被编译进去,其他平台不会有这段代码

// #ifdef H5-WEIXIN
wx.openCard({
    cardList: [{
        cardId: '',
        code: ''
    }]// 需要打开的卡券列表
});
// #endif

后续计划

接下来,uni-app在H5端将提供服务端渲染机制(SSR)和PC宽屏界面适配优化,在追求性能极致和大一统的道路上继续前进!

相关代码全部托管在 github,欢迎大家 star 或提交 pr!