那么有没有一种技术,能在效率、体验、成本、通用性上取得最大化的平衡,甚至打破传统的技术栈划分,进而实现三端融合同构呢?
带着上面的问题,雪球大前端FE团队今年着力于三端同构的建设,实现了RN/H5同构的落地方案,极大程度上抹平了Native、RN、H5的技术割裂。目前三端同构已经在雪球的多个业务的复杂场景下进行了应用实践。
对styledsystem进行了最简化的雪球定制实现,封装颜色token、屏幕适配、日夜间模式等,提升UI开发体验,样式代码量大幅降低,试过就再也不想写css。封装雪球同构组件库snowbox,提供开箱即用的丰富业务组件,提升开发效率并抹平三端差异。
将RN开发门槛从客户端降低为Web开发,初期可在浏览器以Web开发方式进行业务逻辑开发和接口联调等,无需启动模拟器或连接真机,中后期也可三端同步开发。
通过三端同构组件库snowbox和样式组件系统styledsystem实现RN业务代码的快速编写。
客户端部分:通过ReactNative生成双端代码,并为App提供动态化能力。同时借助RN/H5同构,改善RN开发方式,提高开发体验。
结合实际业务场景,从三方面详细展示同构的效果与优势,分别是实际业务页面的同构效果及H5的性能分析;采用同构技术栈后,如何改善传统RN开发方式和开发体验;介绍了同构组件库的功能和效果。
私募基金商品页需求是雪球三端同构方案的第一个落地尝试,针对页面复杂(二十余个复杂模块+6个二级页)、工期紧张、技术积累不足(开发同学没有RN开发经验)等等问题,在需求开发过程中设计并完善了同构方案,引入样式组件系统并封装同构组件库。
在使用RN/H5同构开发后的三端一致性演示视频如下,包括加载速度、复杂曲线的交互、日夜间主题切换、同构与原生交互、二级页效果等。
同构H5的性能
用ReactNative写Web会不会像Flutter一样无法在生产使用呢?
同设备同网络环境下同构H5页面和纯H5页面的技术指标对比:(测试采用m1,横向滑动展示js资源加载情况)
对于大部分同学来说RN开发非常不方便。RN的样式开发、元素审查要一直盯着手机屏幕,RN页面状态管理、网络请求查看、接口联调没有方便的官方工具,甚至没有最佳实践,大部分是通过打log来判断,所以开发调试困难、效率低。
在实现RN/H5同构后,大幅改善了RN的开发体验。
RN/H5同构后,需求开发初期,完全可以用常规H5的开发方式进行逻辑开发、UI初步编写和接口联调。使用vscode+chrome+snowbox组件库,即可快速完成页面基本逻辑和初步样式框架,降低开发难度。
样式初步-chrome选择iphonese宽度375,可直接与设计稿1:1对照。
页面状态数据查看-可通过ReactDevtool查看页面内部state、hooks、store数据查看
接口调试-直接在chrome网络调试tab进行接口联调开发,简单高效。
演示视频:
同时开启三端开发-开启两个terminal窗口分别运行RN调试命令yarndev和web调试命令yarnweb-dev。
UI微调-同时连接webandroidiOS,一次改动三端同步热更新,进行三端UI微调。
演示视频
在业务需求开发中,雪球FE团队联合设计团队对雪球Design设计组件进行三端同构的工程封装,并搭建文档网站,对同构进行了更详细的介绍。罗列每个组件的参数,为每个组件的增加了在线沙盒演示。
除了在RN开发中使用外,同构组件库也可在纯前端CRA、vite、next等项目中使用,实现了真正意义上的跨端组件封装。
组件参数文档
以往的RN业务代码:
我们对比结合市面多种方案和实践案例,最终受styledsystem启发,结合雪球实际需求,设计并实现了定制化的样式组件系统。封装屏幕适配、日夜主题适配等通用样式逻辑,将兼容三端差异的代码整合到样式组件中。
雪球样式组件系统具备以下优势:
我们采用两个基础组件来实现这个系统,分别是盒子组件Box和文字组件Txt,并通过Typescript进行规范和提示。
解析流程:
Box
盒子组件,相当于web的div和RN里的View。实现盒模型,定位,样式属性简写,颜色系统,主题切换,屏幕大小自适应等功能。
Txt
文字组件,支持字号、字重、颜色、雪球常用DIN字体等,封装行内占位等。
与每个样式单独写颜色色值不同,规范的设计系统,要有一套颜色系统的,将UI颜色和规范进行收敛控制,实现多主题切换,并用语义化的描述。比如雪球设计规范中,T010是指一级文字颜色,B010指一级背景颜色,并且同时包含日夜间主题的对应的颜色色号。
样式组件系统对该颜色进行封装,每种颜色值直接转变为一个前端变量,将前端编程语言与设计语言统一,使组件和设计系统可以被快速实现。比如直接写cl="T010"即可。样式组件系统会自动根据用户主题渲染为不同的颜色。
通过采用Typescript对两个组件types规范定义,包含每个props值的类型和枚举值,实现书写的提示和规范性。并进一步实现DesignToken。
屏幕适配我们采用以iphone8375宽度为标准,按宽度进行比例缩放。样式组件系统,所有的尺寸样式自带屏幕适配,无需给每个样式写屏幕适配代码。三端的屏幕方式有些差异,后文会详细介绍实现方式。
由于业务代码会在三端使用,部分属性在不同客户端会出现差异。比如RN没有行内元素的概念,在客户端内较难实现行内的Tag;比如字重500在安卓系统中不会生效;比如DIN字体,安卓客户端里需要使用DIN-medium与iOS不同等等。
以往遇到这种情况,需要单独做兼容,每个元素都要写一遍。采用样式组件系统后,将兼容代码进行封装,业务代码不用关心细微差异,降低代码量。
本节着重介绍同构H5的具体实现方案,先后介绍了同构H5关键库:ReactNativeWeb及其先进理念;如何对已有RN项目进行改造;同构项目的常见兼容处理方式,实现同构和代码共存;如何通过服务端渲染提高H5性能,以及如何处理服务渲染后的屏幕适配等等问题。
ReactNativeWeb由前Twitter前端主管Necolas开发,并使用该技术方案在2017年重构了Twitter网页(TwitterWebApp)并沿用至今。
ReactNativeWeb在国内知名度不高,但是放眼世界TwitterWeb、Expo、NativeBase、ReactNative官网等众多项目全部使用的ReactNativeWeb能力。
虽然是5年前就开始的项目,如今看依旧有非常前瞻性的理念,因为Necolas也是normalize.css库的作者,对CSS和前端工程化有独到的理解,其在ReactNativeWeb中开创性实现了诸如原子化CSS、css-in-js、前端组件化、Web的容器化封装、手势系统等等。具体可看参考文献中的第一个视频。
在div上能看到大量"r-"开头的单个css规则,是否令你联想起如今火热的Tailwindcss、UnoCSS?
原子化CSS处理
以往前端项目中,随着样式的增多,CSS逐渐冗余、重复打包、难以维护。现在很多库的前瞻性原子化CSS思路,其实ReactNativeWeb5年前已实现。
这种方式在使得CSS代码量不会随着页面的日益增多而无限扩展,而是会逐步趋于收敛,并且在服务端渲染后,能做到真正的CSS按需加载。
再加上客户端不支持CSS渲染,所以抛弃CSS的历史包袱之后,Web能跟客户端样式代码一致,开发才能真正的实现跨端组件封装,有助于我们实现三端同构的组件系统。
随着雪球前端对ReactNativeWeb的深入理解和使用,我们也积极参与ReactNativeWeb的开源建设,为ReactNativeWeb增加多个feature,核心开发者也展示在了ReactNativeWeb项目首页。
我们在已有的RN项目里引入ReactNativeWeb,并选取Next.js作为服务端渲染的项目脚手架,同时使用koa.js做node服务端自定义server。
Next.js是Vercel明星开发团队出品的成熟react服务端渲染框架,在开发体验、代码分割、服务端渲染、优化方面做的很好,开箱即用,所以我们使用Next.js来处理web的编译,通过服务端渲染能力解决ReactNativeWebcss-in-js方案的首屏渲染问题。
为了能复用雪球前端通用的node中间件,比如snb-lib-token等,所以服务端采用Next.js+自定义server的方式(custom-server)
雪球App里是为每个RN页面注册一个URL路由,该规则通过json动态下发,客户端读取后将RN页面注册到URL路由上,进行匹配和跳转,并且能控制RN、H5、原生的优先级逻辑。
同构改造还涉及对RN和H5的差异化处理,总结主要有三种方式:由代码处理、由Metro处理、由Next.js处理,分别以三端兼容代码、jsbridge、H5路由注册为例介绍。
Platform.OS有"iOS"|"android"|"windows"|"macos"|"web"5种,在同构项目中web其实还分为两种,一种是纯web,一种是node服务端渲染。所以我们进行封装增加了node标识。
OS:"iOS"|"android"|"windows"|"macos"|"web"|"node"
//只有web才引入weixin-js-sdkif(OS==='web'){wx=require('weixin-js-sdk');}//只有在服务端才进行mobx的服务端useStaticRendering设置if(OS==='node'){useStaticRendering(true);}2.由Metro打包工具处理差异-以jsbridge为例jsbridge等跟各端有关联,在客户端内需调用RN的方法,在web端需调用web的方法。
同构写法为通过native.js后缀区分,由ReactNativeMetro打包工具默认提供。
RNBridge├──index.js#由前端工具打包的文件├──index.native.js#由ReactNative自带打包工具(Metro)打包的文件3.由Next.js处理-以H5路由注册为例因为雪球的RN是按照路由注册,与web相通,所以同构H5实现对应的URL路由注册即可。
得益于Next.js的自动注册路由,服务会根据pages目录结构,生成对应的url路由。但是默认RN项目的页面目录也是pages,所以next.js会打包全部pages中的页面,部分没有经同构处理的页面会报错,需要控制只打包同构的页面。我们的处理方式是添加自定义标识".web.js",设置Next.js只处理"web.js"后缀的文件作为入口文件,实现跟RN原生代码的共存。
module.exports={//入口只选web.js后缀的pageExtensions:['web.jsx','web.js','web.tsx','web.ts'],};同一目录下的RN入口与同构入口:
.├──index.js├──index.web.jsindex.web.js中内容也很简单,使用封装好的Wrapper包装下RN入口文件即可,里面有全局的参数、样式等处理,把Web和node端也当做容器来处理,使H5保持跟RN一致。
我们发现复杂页面同构生成的H5,在iphone6等低端手机上渲染较慢,对同构页面进行lighthouse跑分也证实了这个问题。其中有一部分原因是因为ReactNativeWeb的样式解析采用的css-in-js方案,所以需要在js加载完后,动态计算后才能得到,在低端手机上必然会造成白屏问题。
对此我们的优化方案是采用服务端渲染,并修改Web的屏幕适配逻辑,提升同构网页性能。
采用RN同构服务端渲染后的性能提升:(测试采用m1,将cpu性能降低4倍)
RN服务端渲染演示
前端页面需做移动端适配,根据不同的屏幕宽度进行相应的比例缩放。
在客户端ReactNative中,我们使用的是Dimensions.get('window').width获取屏幕宽度,并按照比例对每个元素进行尺寸计算,得出适配大小。
但是在H5服务端渲染时,服务端的Dimensions获取不到用户的屏幕宽度,尺寸计算失败,导致服务端渲染的H5初始样式展示有问题,页面渲染会有抖动的现象。
根据前端项目的服务端渲染经验,我们做了两部分处理:
服务端渲染改造点还包括**「服务端样式提取」、「OS封装」、「Window封装」、「Wrapper通用参数处理」、「全局变量封装」**等,具体可移步组件库官网了解详情。
“持续集成”(CI)通常指的是持续地将代码更改集成到代码的主分支中。具体来说,CI服务通过自动化测试、构建和发布过程来帮助用户频繁地集成更改。对于基于pull-request的工作流,每次都会运行所有测试和lint检查,打包,下发。
雪球三端同构的CI由GitlabCI实现,包含commit校验、代码lint校验、单元测试、RN打包上传等等。
而持续部署(CD)分为两部分,分别是端侧RN发布和Web服务的部署。
端侧RN
采用GitlabCI+自研mPaaS实现。所有feature分支PR合并后自动打RN包,由自研mPaaS实现RN发布客户端版本控制、多阶段控制、代码上传、分包下发、切包等功能。
Web服务
采用DroneCI+自研RollingDocker部署。由sit、sep、staging、prod四个特定分支来承载Web服务的分环境部署。PR合并后触发DroneCI的Docker镜像构建,随后通过RollingDocker部署前端node服务。
同构的开发、测试、上线整体流程如下图所示:
CI流程中的关键环节是单元测试,因为涉及到客户端,所以同构代码的测试与以往前端测试有所不同,我们对RN开源项目的单元测试进行了对比调研。
RN组件库使用测试工具对比
方案对比
platform.os默认为iOS
2.Jest-expo-enzyme作为jestpreset配置,即集成了enzyme(也是react测试库)的api
可以模拟web、iOS和Android环境分别运行,platform.os正确
选用方案
Jest+ReactNativeTestingLibrary:测试库(基于react-test-renderer和ReactTestingLibrary)
代码覆盖率
近几年来,人工智能技术日益渗透到软件工程领域,比如微软Copilot基于大模型的代码自动生成系统。人工智能在设计稿转代码方面也有很多研究成果,2017年的关于图像转代码的Pix2Code论文掀起了业内激烈讨论的波澜,讲述如何从设计原型直接生成源代码。2018年微软AILab开源了草图转代码工具Sketch2Code,2019年阿里发布imgcook在双11大促中自动生成了79.34%的前端代码,京东Deco、codefun、58Picasso、DhiWise等产品相继出现。随着D2C在生产中的广泛应用,智能生成代码不再只是一个线下实验产品,而是真正产生了价值。
目前D2C大部分是应用于H5,如果能和三端同构结合,将会创造出更高的价值。并且由于同构组件对UI进行了高度的抽象,所以也有助于D2C代码智能生成的实现,同构生成三端代码。
我们对代码智能生成的开发工作划分了三个阶段,由浅入深,逐步实现最终的目标。
初期-编译器:利用imgcook/DhiWise/Deco/Picasso等平台对设计稿进行解析,生成描述代码DSL,开发编译器将描述代码DSL编译为前端代码。
中期-设计稿解析、可视化编辑器:自行开发设计稿图层解析服务,开发定制化的页面可视化编辑器。
后期-智能识别:智能识别设计稿/图片,自动分离/组合模块,合理布局,最终生成代码。
目前雪球FE在相对简单的后台类页面生成中,自主研发跑通了全流程,实现了后台类页面的智能代码生成。但在复杂的toC端设计稿转代码中,目前还在初期阶段。我们利用imgcook等工具对figma解析,识别设计稿,通过开发的DSL编译器自动生成的代码。
左边为设计稿,右边为自动生成的RN/H5同构页面,已初见雏形,后续还有很大的改善空间。
生成的代码示例:
展望未来,在丰富同构组件库、代码智能生成、多端同构等等方面还有很长的路要走,期待与大家交流和共创。