查看原文
其他

一文读懂跨平台技术的前世今生

李伟涛等 京东设计中心JDC 2022-12-07

过去十数年间,跨平台开发技术始终是开发者们探索的方向,"Write Once, Run Everywhere" 是无数开发者追逐的目标。放眼今朝,应用运行的平台不但没有减少,反而呈更加碎片化增长的趋势。开发者除了需要考虑兼容 Web、iOS、Android 平台外,还需要考虑兼容各家小程序乃至各种 IOT 设备等。

不难看出,能降低开发与维护成本,让业务快速落地的跨平台开发技术将在未来很长一段时间里持续发光发热。本文首先将带大家回顾业界内主要的跨平台技术的发展历史和迭代趋势,并介绍公司内部针对不同业务场景所诞生的各种跨平台容器技术,最后会阐述 Taro 作为一款跨平台开发框架对“一码多端”的思考与实践。

跨平台技术发展史

跨平台技术分类

按技术本质分类,业界的跨平台技术可以按渲染管线的不同来大致划分为以下三类:

  • Hybrid:基于 WebView 渲染,通过 JS Bridge 把一部分系统能力开放给 JS 调用。如 Cordova。

  • 原生渲染:使用 JS 开发,通过中间层桥接后使用原生控件来渲染 UI 界面。如 React Native。

  • 自建渲染:利用 Skia 重新实现渲染管线,不依赖原生控件。如 Flutter。

Hybrid

Hybrid 是最早期的跨平台技术,一方面基于 WebView 让开发者可以快速开发跨平台应用,另一方面 WebView 的性能无法与原生开发相比,尤其在 10 年前那个手机硬件配置的背景下,当时跨平台意味着糟糕的体验。

随着手机硬件的提升和 Hybrid 技术的发展,通过预载、缓存、同层渲染等手段,性能与体验已经有相当大的提升。或许在某些交互复杂的场景仍然无法与原生相比,然而开发简单、生态丰富等特点让它在很多场景都有一战之力,因此未来 Hybrid 仍会是主流的跨平台技术之一。

在此也简单提一下小程序。小程序是带着浓重商业气息的跨平台方案,为了隔离渲染环境,划分了在 JS 引擎执行的逻辑层和在 WebView 渲染的渲染层,本质还是基于 WebView 渲染的 Hybrid 技术。同时它也使用了很多手段优化性能与体验,如预载、每个页面对应一个 WebView 等,总体来说也有着不错的跨平台效果。

React Native

2015 年 Facebook 推出了 React Native,它迅速吸引了大批开发者,逐步成为一大主流的跨平台技术。开发者使用 JS 进行开发,RN 保持着 React 虚拟 DOM 节点和 Native UI 控件的映射关系,每次渲染都通过 Bridge 传递虚拟 DOM 树的数据来调用原生控件进行渲染,从而摆脱了 WebView 冗长的渲染管线,拥有比 Hybrid 更佳的布局和渲染性能。

Bridge 交互的双方(JS 和 Native)没有共享内存,靠 JSON 序列化数据进行通信,同时 Bridge 限制调用频率、只允许异步调用等特点对 UI 渲染来说是致命的。在滑动、动画、手势操作等更新频率较高的场景里,低效 Bridge 通信无法让 JS 更新及时反应在 UI 上,往往会出现白屏或卡顿现象。另外,RN 没有完全屏蔽原生平台,对 iOS 和 Android 的实现细节还有所依赖,开发者需要对 Native 有一定的了解才能写好 RN。

针对性能问题,RN 新版本对渲染架构进行了重构,使用 JSI、Fabric、Turbo Modules 等概念以减少对 Bridge 的依赖,支持同步渲染等能力。能否获得社区和商业公司的的认可还需待时间验证。

Flutter

2018 年 Google 正式发布 Flutter,是为 Fuchsia 操作系统的配套的开发方式。与 Hybrid 和 RN 不同,Flutter 没有选择使用 JS,而是选择了 Dart 作为开发语言。生产环境中 Dart 通过 AOT 编译成对应平台的指令,执行效率远远高于 JS,也不需要 Bridge 通信,性能比 RN 更好且接近原生。同时 Flutter 基于跨平台的 Skia 图形库自建了渲染引擎,最大程度地保证了跨平台渲染的一致性。

从选用 Dart 作为开发语言可以看出,Flutter 首要目标是 Android 和 iOS 平台的统一。相比 Hybrid 和 RN 开发者会有更高的上手学习成本,同时生态相对匮乏。此外,不支持动态化更新也是 Flutter 的痛点之一,虽然社区有探索出一些方案,但相对地会降低 Flutter 的性能,对此只能见仁见智了。

不过 Flutter 的野心不仅在统一 Android 和 iOS,还包括 Web、桌面端和嵌入式设备,相信随着 Flutter 核心的迭代和社区生态的发展,Flutter 仍会在跨平台技术里占主要地位。

跨平台技术发展趋势

目前跨平台技术领域仍然是百花齐放的状态,以开发效率、性能、一致性是对跨平台技术为主要评价标准,则各技术流派是对此三者间取舍的艺术。Hybrid 拥有很高的开发效率和一致性,然而存在性能问题。RN 为了解决 WebView 的性能问题选择了桥接原生渲染,但却舍弃了 CSS3 等 Web 特性,降低了开发效率。Flutter 使用 Dart 和自建渲染很好地解决了性能和一致性问题,但同时也降低了开发效率。

当然,业界除了以上三种主流的跨平台技术外,还有很多的一些探索。例如类似 RN 但同时支持 React 和 Vue 的 Hippy;使用 JS 开发,基于 Flutter 渲染的 Weex 2.0;使用 JS 开发,也支持大部分 CSS 标准,基于 Flutter 渲染的 Karken 等。总体来说,跨端技术在往兼容 Web 开发方式(提高开发效率)和自建渲染管线(提高性能和一致性)的方向发展演进。

在京东 App 里也存在着不同的跨平台技术方案,它们分别面向着不同特点的业务场景。在类似京东的超级 App 里如何统一这些技术方案的开发范式,让业务组件、通用逻辑在不同方案中复用起来是我们当前面对的一大难题。我们可以把以上这些技术统一称作跨平台容器技术。在跨平台容器之上,架设跨平台框架,统一开发语法、组件和 API 等规范,真正实现一码多端。

京东跨平台技术现状

近年来,京东主站 APP 的业务形态呈现越来越多样化,而为了应对不同的业务形态诉求,主站 APP 内种也是存在多种跨平台技术。

JDHybrid

关键词:灵活性、时效性。

JDHybrid 是一个移动端高性能 Hybrid 容器框架,致力于提升 H5 在京东 APP 内的加载与渲染性能,在电商场景下,我们通常会应对大量的营销活动场景,例如 618、双 11 等大促活动,为了保证活动会场的灵活性与时效性,H5 是目前的最优选择,而 JDHybrid 方案的出现则是为 H5 带来了性能及体验的保障,详见 Hybrid 

而在开发方式 JDHybrid 与普通 H5 应用基本无异,对 Web 前端开发非常友好。

京东小程序

关键词:连接线上线下、独立。

京东小程序是京东官方推出的小程序平台,为开发者提供一种快速开发方式,连接线上线下购物能力,帮助商家、开发者以全新的方式连接消费者。京东小程序是一种全新的开放模式,在手机京东APP上使用,可以被便捷地获取和传播,为终端用户提供更好的使用体验。同时,京东小程序也可以作为独立引擎赋能给其他 APP 及终端屏,实现更广的跨端统一。

在开发方式上,京东小程序与业界主流小程序方案基本一致,采用类 Web 的开发方式,对 Web 前端开发以及熟悉小程序的开发来说基本没有上手难度。

JDReact/JDFlutter

关键词:体验,效率。

JDReact/JDFlutter 是京东内部基于开源社区的 React Native/Flutter 方案,针对内部业务诉求改造而来的方案,在React Native、Flutter 上进行了深度二次开发和功能扩展,不仅打通了Android/iOS/Web三端平台,而且对京东移动端基础业务能力进行了SDK级别的封装,提供了统一、易于开发的API。

从开发效率上来说 JDReact 拥有接近 Web 的开发体验和效率,而 JDFlutter 对于熟悉 Dart 及 Flutter 的人来说开发体验和效率也很高,不过因为使用 Dart 开发的缘故,对于 Web 前端开发来说 JDFlutter 还是有一定的上手难度;从性能上来说,得益于二者的实现机制, JDReact/JDFlutter 开发应用的理论性能要强于 H5 及小程序(基于 WebView),更接近原生性能。所以,JDReact/JDFlutter 会更适合对性能有一定要求,而对时效性没有过多要求的业务。

MCube

关键词:稳定性、性能。

在京东 APP 里,消费者购物的关键环节包括搜索、商品详情页、购物车、结算下单到订单等,在整个购物链路中属于价值非常高的部分,因此被称为黄金流程,黄流对稳定性与性能的要求非常高,而同时为了兼顾研发效率以及动态化诉求,所以诞生出一套原生动态化方案 MCube,它是一个提供完整的跨端原生页面动态展示的解决方案,使得业务可以基于 MCube 做到一次开发,随时上线,多端复用的效果。(更多可以详见 Mcube 介绍文章 


在开发方式上,Mcube 采用 XML DSL 方式进行开发,这对于 Web 前端开发来说会有一定的学习成本。

综上,我们京东内部为了应对不同的业务诉求,存在多跨平台技术共存的情况,这从某种程度上来说是合理的,但是多个跨平台技术共存对我们的研发效率和学习成本上都造成了不小的影响,所以我们需要一个跨平台开发框架来实现对诸多跨平台技术的研发模式统一。

Taro 做了什么

在业界有很多面向跨端需求的解决方案,各个方案之间实现的原理各有不同,也是面向不同痛点的开发者需求研发的。在这其中 Taro 是目前支持平台最多,也是最为开放的跨端解决方案。在当前业界中,Taro 的流行程度和社区生态方面都非常领先,从学习成本、核心能力、可拓展性角度来看,Taro 具备一定的优势和先进性,是大多数开发者的不二之选。

支持编译小程序平台

小程序是体现 Taro 核心优势的一个方面,在所有小程序平台中,或许是通过社区开发者自发的贡献,亦或是小程序平台官方提供的端平台插件,大众开发者熟悉或不熟悉的小程序平台,都能够通过 Taro 得到支持。同时针对开发者的个性化需求,在 Taro 社区生态中,也提供各开发场景所需的插件供开发者选配,构架最适合项目的开发工作流。

“编译时” or “运行时”

选择“编译时”还是“运行时”是很多跨端技术解决方案都需要面对的问题,在小程序跨端技术被业界关注之初,为了降低运行时性能的损耗,大家都不约而同的选择了通过在编译时基于 AST 作词法语法分析,完成代码能力的转换。对于 Taro 来说,在 3.x 以前的版本选择了“编译时”,通过这样的架构也能为开发者提供不错的开发体验。

但是随着框架的不断迭代,这类方案需要面对的问题逐渐凸显,语法限制问题牵扯开发者心智使得框架的学习门槛增加,研发也需要付出额外的成本;同时随着写法的多样性不断提升,框架维护的难度也在不断攀升,极大的阻拦了新人贡献者参与到社区的生态当中;小程序平台的增长也成为不得不考虑的重要因素,架构已经不足以支撑框架大步向前迈进……这些问题都推动 Taro 框架不断向前,架构也逐渐从“编译时”走向“运行时”。

框架层面的设计理念改变,给 Taro 社区生态注入了更多的活力。而对开发者来说,在小程序中提供完整的 React 支持,能够大幅提升开发体验,同时也极大的降低了框架的学习门槛。


全新的架构在帮助开发者更好的支持 React 的同时,也提供了一个契机支持更多的前端框架,比如对于 Vue、Preact 等等框架来说,同样有着庞大的开发者受众,为他们提供跨端技术的支持能够为 Taro 提供更多的业务和研发场景。在抛开框架层面 DSL 限制的同时,Taro 也不再限制开发者使用的语言,理论上我们支持任何可以最终编译到 JavaScript 的语言构建 Taro 项目,比如 TypeScript、CoffeeScript 等等。

插件化能力支持

Taro 对于自己的定位是一个「开放式跨端跨框架解决方案」,在支持多端统一开发的特性同时,更重要的是成为一个开放式的解决方案,所以我们希望通过支持插件化能力为 Taro 拓展更多平台提供基础支持,也能够将我们现有的能力解耦,为支持更多的平台和能力发展提供框架层面的基础支持。

在当前的框架架构中,插件大致分为三种类型:框架插件、端平台插件、其它能力插件,其中框架类型用于实现对于 React、Vue 等前端框架的支持;端平台插件则提供了编译到平台或者端的能力支持,小程序端平台通过插件横向或纵向拓展,很大程度上节省了维护成本;而其它能力插件则通过 Taro 内核暴露出来的编译时、运行时的生命周期,为 Taro 生态提供有力的支持。


插件化支持,为 Taro 添上了很多想象的空间,我们希望开发者可以根据 Taro 提供的 API 开发一个插件就能实现自己去为 Taro 扩展更多平台与前端框架的支持,例如未来有些新的平台推出小程序,或者有人希望能在 Taro 中使用 Angular 等更多的前端框架,那么就可以通过 Taro 的开放式机制来自行扩展,而不用等待 Taro 官方来进行支持,Taro 将只作为一个跨端适配的平台,所有的可能性都可以让社区自己去自由发掘。

性能体验优化

运行时性能主要分为两个部分,一是更新性能,二是初始化性能。

初始化性能则是一个不小的痛点。原生小程序或编译型框架的初始数据可以直接用于渲染,但 Taro 在初始化时会把框架的渲染数据转化为小程序的渲染数据,多了一次 setData 开销。为了解决这个问题,Taro 从服务端渲染受到启发,在 Taro CLI 将页面初始化的状态直接渲染为无状态的 wxml,在框架和业务逻辑运行之前执行渲染流程。我们将这一技术称之为预渲染(Prerender),经过 Prerender 的页面初始渲染速度通常会和原生小程序一致甚至更快。

对于更新性能而言,Taro 将 diff 的工作交给了开发者使用的框架(React/Nerv/Vue),而框架 diff 之后的数据也会通过 Taro 按路径去最小化更新。因此开发者可以根据使用框架的特性进行更多更细微的性能优化。但如果页面结构比较复杂,应用更新的性能就会下降。为此我们引入了一个基础组件 CustomWrapper,它的作用是创建一个原生自定义组件。对后代节点的 setData 将由此自定义组件进行调用,达到局部更新的效果,从而提升更新性能。

开发者可以使用 CustomWrapper 去包裹遇到更新性能问题的组件:

<CustomWrapper> <GoodsList> <Item /> <Item /> // ... </GoodsList> </CustomWrapper>

除此以外,Taro 还为更多开发者遇到个各种情况提供了标准的解决方案,比如专为解决长列表渲染问题提供的虚拟列表组件,还有解决超大项目编译效率问题的依赖预编译特性等等,可以前往 Taro 官网了解更多哦。

支持编译 Web 端应用

框架层面支持一个类小程序的 Web 应用,需要做的事情大致可以归纳为三类:路由、标准组件库、标准 API。在 Taro 中也是如此,我们通过这三个方面的工作对齐不同框架的组件和生命周期,为 Taro 项目提供 Web 端编译的能力,让开发者可以在开发小程序的同时,构建项目的 Web 版本。

Web 端生态

Web 端场景和小程序中会有很大的不同,很多时候我们在设计能力时都需要平衡不同端之间的特性,在保证各端统一的同时,给开发者流出足够的空间发挥各个端平台的优势能力。而由于 Web 端的繁荣生态,除了类小程序形态的单页面应用,TabBar 等基础配置能力,我们还期待能够支持多页面应用,服务端渲染等等能力,提供更多 Web 应用的业务研发场景。

这些 Taro 都在生态中逐一实现,有的是 postcss 插件,比如 postcss-pxtransform、postcss-html-transform 等等提供了标准化尺寸、小程序标签转换的能力;有的是 babel 插件,比如 babel-plugin-transform-taroapi 会移除不需要的 API 代码;也有 tarojs-plugin-platform-nextjs 这样社区写的 Taro 插件,结合 Web 生态提供易用的服务端渲染方案……

跨框架组件库

作为一个跨框架的跨端解决方案,Taro 也需要一个跨框架可用的标准化组件库。在需要支持多框架,避免冗余工作的同时,我们也不希望自己再次回到刀耕火种的石器时代,于是抱着寻找一个好用的方案为出发点开始寻找,而作为一个系列的技术规范,Web Components 为各大主流的浏览器所支持,仿佛理所当然的 Web Components 成为了那个标准答案。

为了更好的使用 Web Components,又能够兼容我们已有的 React Component 组件库,我们同时还选择了 Stencil 帮助我们完成这次转换。当然在通过 Stencil 提供的 Web Components 组件和 React、Vue 框架结合的过程中,依旧会遇到很多问题,但是通过提供对应的适配器,大部分问题都可以兼容适配,在这里就不一一展开,具体问题与解决方案在 Taro 官方博客都可以找到对应文章解析,可自行前往查阅。

支持编译其它端或平台

在 Taro 3.x 不断发展的过程中,我们也在不断推出小程序端侧插件的同时,为开发者提供了很多小程序之外的选择。

支持编译原生应用

支持编译原生应用,在 Taro 早期版本中一直都是借助于 React Native 实现,在 Taro 3.x 中也不例外。通过更贴合 React Native 生态体系的编译工具 metro 用于打包,尽管这会导致一些问题,比如 webpackChain 在 rn 编译时不再支持,不过好在这些问题都有替代的解决方案。

其它方面。譬如路由、组件和 API 等等在 Taro 3 标准的运行时结合 expo 生态体系改造后也更加灵活,可以按项目需要集成依赖;React Native App 接入方案也更加灵活,不再锁定版本,开发者可以根据需要选择快速开发,或者更加灵活的配置方案……

支持编译鸿蒙应用

对于 Taro 来说,我们以插件的形式适配鸿蒙并非难事,当然为了抹平鸿蒙和其他平台的差异,我们需要针对处理的问题还有不少,比方说支持 React & Vue 语法,支持标准的组件和 API 等等,支持语法通过编写框架的运行时就可以实现,而组件和 API 则需要通过 OH 提供的能力来实现。

最终我们就可以看到我们写的代码可通过 webpack 打包成应用,在前端框架层通过 Taro 提供的运行时与 UI 视图交互数据和事件,加上 OH 提供的基础能力就可以鸿蒙端的适配渲染流程。

Taro 和华为以及开放原子基金会一直以来都有合作,通过加入 OpenHarmony 并成立 CrossPlatformUI Sig,为鸿蒙跨端能力的支持。而目前在 Taro canary 版本中已经支持了鸿蒙,并且稳定的运行了很长一段时间,相信在不久的将来就会与大家见面。

支持 Mcube

对于公司内部的开发者,Taro 也在通过和 Mcube 的合作为开发者提供新的使用场景。调研通过编译时的方式,将 Taro 项目编译为 Mcube 支持的格式,并以 Mcube 标准适配小程序和 Web 端。

插件发布初期预计支持,通过将使用 React 编写的 Taro 项目编译到 Mcube 平台,敬请期待!

未来的发展

随着 IOT 设备的普及,未来肯定会存在更多的“平台”,面对不同场景会诞生各种跨平台容器技术。因此做好一款跨平台框架,对上统一语法、组件、API 标准,对下对接各种跨平台容器,将是 Taro 框架未来的主要使命。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存