导读: 随着移动互联网的迅猛发展,目前市面上「端」的形态多种多样,iOS、Android 、H5、微信小程序等各种端大行其道,同一个业务需求往往又需要在多端上去实现,针对不同端去编写多套代码的成本显然非常高。雪球大前端团队将今年在跨端能力建设上的演进和推广工作整理成系列文章。
本文为第三部分,介绍今年雪球大前端团队在三端同构建设上相关工作:
RN / H5 同构的方案及效果
样式组件系统的优势及实现
同构H5的实现及服务端渲染 SSR
同构的 CI/CD、单元测试及开发测试流程
同构的 D2C 代码智能生成
雪球前端的业务场景除了雪球 App 外,还有重要的一环是微信私域 H5。各需求优先在客户端 App 内实现后再同步到微信 H5,至少要投入 iOS、安卓、H5 三端的开发资源。在人力资源有限的情况下,经常会出现多需求和多开发岗位相互交叉的错综复杂关系,多岗位协同难免会造成效率降低和资源浪费。
那么有没有一种技术,能在效率、体验、成本、通用性上取得最大化的平衡,甚至打破传统的技术栈划分,进而实现三端融合同构呢?
放眼业界,各大公司不约而同也在探索跨端融合。Facebook 最早推出 React Native 支持开发双端,谷歌后来居上推出 Flutter 并不断迭代,阿里则以闲鱼为代表在推动大前端的融合,字节&快手都在大力推进自己的跨双端开发框架,比如 lynx 等。目前市面较成熟的方案是双端的融合。其中 React Native 是中小型公司较好的选择,能以较低成本为客户端提供双端同构和动态化能力。
但 RN 开发有门槛难上手、代码繁琐、多端需单独适配,并且在雪球的业务场景下,优先用 RN 开发完客户端功能后,还要再次投入资源开发微信中的 H5 。
带着上面的问题,雪球大前端 FE 团队今年着力于三端同构的建设,实现了 RN / H5 同构的落地方案,极大程度上抹平了 Native、RN、H5 的技术割裂。目前三端同构已经在雪球的私募基金、公募基金、雪盈、社区等多个业务的复杂场景下进行了应用实践。
RN / H5 同构方案最适合以 App 开发为主,并需要把 App 内功能同步到微信 H5 生态的业务场景。对于想实现 App 动态化、提升人效的中小型公司有借鉴意义,也提供了可落地的实操指南。
一套代码 iOS / Android / H5 三端运行,通过 RN 为客户端提供动态化能力,并能同构生成微信 H5。三端代码一致,告别三端单独开发或者兼容适配。
对 styled system 进行了最简化的雪球定制实现,封装颜色 token、屏幕适配、日夜间模式等,提升 UI 开发体验,样式代码量大幅降低,试过就再也不想写 css。封装雪球同构组件库snowbox,提供开箱即用的丰富业务组件,提升开发效率并抹平三端差异。
将 RN 开发门槛从客户端降低为 Web 开发,初期可在浏览器以 Web 开发方式进行业务逻辑开发和接口联调等,无需启动模拟器或连接真机,中后期也可三端同步开发。
通过三端同构组件库 snowbox 和样式组件系统 styled system 实现 RN 业务代码的快速编写。
客户端部分:通过 React Native 生成双端代码,并为 App 提供动态化能力。同时借助 RN/H5 同构,改善 RN 开发方式,提高开发体验。
H5 部分:通过 React Native Web 将 RN 代码编译为 H5,并实现服务端渲染,将雪球客户端里的 RN 功能和体验完整复刻同步到微信体系。
结合实际业务场景,从三方面详细展示同构的效果与优势,分别是实际业务页面的同构效果及 H5 的性能分析;采用同构技术栈后,如何改善传统RN开发方式和开发体验;最后介绍了同构组件库的功能和效果。
私募基金商品页需求是雪球三端同构方案的第一个落地尝试,针对页面复杂(二十余个复杂模块+6 个二级页)、工期紧张、技术积累不足等等问题,在需求开发过程中设计并完善了同构方案,引入样式组件系统并封装同构组件库。
同构页面在 APP 中打开速度媲美原生,同时能实现线上热更新,开发人效相比于传统开发提升一倍以上,高效响应业务变化。并且能同步部署 H5,将原本客户端内才有的丰富功能和优秀体验复刻到微信生态,同时赋能业务,客户经理可以很方便在微信内分享传播,客户也能在微信中使用 H5 完成跟雪球客户端内一致的业务全流程。
在使用 RN/H5 同构开发后的三端一致性演示视频如下,包括加载速度、复杂曲线的交互、日夜间主题切换、同构与原生交互、二级页效果等。
同构 H5 的性能
用 React Native 写 Web 会不会像 Flutter 一样无法在生产使用呢?
同设备同网络环境下同构 H5 页面和纯 H5 页面的技术指标对比:(测试采用 m1,横向滑动展示 js 资源加载情况)
项目技术栈lighthouse首次加载 js总 js组件库页RN 同构 SSR96205k205k私募商品页RN 同构 SSR80270k270k私募持仓页react ssr82208k208k私募首页react ssr68227k227k投顾商品页vue65340k485k基金首页vue51340k359k基金持仓页vue74340k369k
可以看到,使用 RN/H5 进行三端同构技术后,页面性能相比于纯 web 开发页面基本持平,在代码分割按需加载方面也没有劣势,不像 Flutter web 那样动辄几 MB 的 js 资源,完全可以在生产环境使用。目前也已在雪球微信服务号、雪球私募、雪球基金及投顾等场景广泛使用。
对于大部分同学来说 RN 开发非常不方便。RN 的样式开发、元素审查要一直盯着手机屏幕,RN 页面状态管理、网络请求查看、接口联调没有方便的官方工具,甚至没有最佳实践,大部分是通过打 log 来判断,所以开发调试困难、效率低。
在实现 RN / H5 同构后,大幅改善了 RN 的开发体验。
RN / H5 同构后,需求开发初期,完全可以用常规 H5 的开发方式进行逻辑开发、UI 初步编写和接口联调。使用 vscode + chrome + snowbox 组件库 ,即可快速完成页面基本逻辑和初步样式框架,降低开发难度。
样式初步 - chrome 选择 iphone se 宽度 375,可直接与设计稿 1:1 对照。
页面状态数据查看 - 可通过 React Devtool 查看页面内部 state、hooks、store 数据查看
接口调试 - 直接在 chrome 网络调试 tab 进行接口联调开发,简单高效。
演示视频:
页面基本逻辑&UI 初步开发完成后,可以进入客户端内同构开发&样式微调阶段。
同时开启三端开发 - 开启两个 terminal 窗口分别运行 RN 调试命令 yarn dev 和 web 调试命令 yarn web-dev。
UI 微调 - 同时连接 web android iOS,一次改动三端同步热更新,进行三端 UI 微调。
演示视频
在业务需求开发中,雪球 FE 团队联合设计团队对雪球 Design 设计组件进行三端同构的工程封装,并搭建文档网站,对同构进行了更详细的介绍。罗列每个组件的参数,为每个组件的增加了在线沙盒演示。
除了在 RN 开发中使用外,同构组件库也可在纯前端 CRA、vite、next 等项目中使用,实现了真正意义上的跨端组件封装。
https://snowbox.js.org/
视频展示
组件参数文档
前端工程师开发中很大一部分时间是在写 css 样式,以往 Web 和 RN 的样式写法很繁琐,比如写样式时,要给每个元素想一个有意义的 classname;css js 两个文件要来回切换;css 样式需隔离,css 样式冗余,属性重复定义,css 体积不断增大 ;RN 样式写法繁琐冗余,每个元素都要重复设置日夜间主题颜色、布局方式、文字大小、屏幕适配;RN 双端样式有细微差异等等。
以往的 RN 业务代码:
我们对比结合市面多种方案和实践案例,最终受 styled system 启发,结合雪球实际需求,设计并实现了定制化的样式组件系统。封装屏幕适配、日夜主题适配等通用样式逻辑,将兼容三端差异的代码整合到样式组件中。
雪球样式组件系统具备以下优势:
4.2 样式组件的核心实现
我们采用两个基础组件来实现这个系统,分别是盒子组件 Box 和 文字组件 Txt,并通过 Typescript 进行规范和提示。
如下图例子所示,分别将盒模型中边距、颜色、大小、定位等属性定义成属性和Token,用代码化的语言,将组件的每一部分属性进行描述,也就是最方便的声明式 UI。
解析流程:
盒子组件,相当于 web 的 div 和 RN 里的 View 。实现盒模型,定位,样式属性简写,颜色系统,主题切换,屏幕大小自适应等功能。
文字组件, 支持字号、字重、颜色、雪球常用 DIN 字体等,封装行内占位等。
与每个样式单独写颜色色值不同,规范的设计系统,要有一套颜色系统的,将 UI 颜色和规范进行收敛控制,实现多主题切换,并用语义化的描述。比如雪球设计规范中,T010是指一级文字颜色,B010指一级背景颜色,并且同时包含日夜间主题的对应的颜色色号。
样式组件系统对该颜色进行封装,每种颜色值直接转变为一个前端变量,将前端编程语言与设计语言统一,使组件和设计系统可以被快速实现。比如直接写 cl="T010"即可。样式组件系统会自动根据用户主题渲染为不同的颜色。
通过采用 Typescript 对两个组件 types 规范定义,包含每个 props 值的类型和枚举值,实现书写的提示和规范性。并进一步实现 Design Token。
屏幕适配我们采用以 iphone8 375 宽度为标准,按宽度进行比例缩放。样式组件系统,所有的尺寸样式自带屏幕适配,无需给每个样式写屏幕适配代码。三端的屏幕方式有些差异,后文会详细介绍实现方式。
由于业务代码会在三端使用,部分属性在不同客户端会出现差异。比如 RN 没有行内元素的概念,在客户端内较难实现行内的 Tag; 比如字重 500 在安卓系统中不会生效;比如 DIN 字体,安卓客户端里需要使用 DIN-medium与iOS不同等等。
以往遇到这种情况,需要单独做兼容,每个元素都要写一遍。采用样式组件系统后,将兼容代码进行封装,业务代码不用关心细微差异,降低代码量。
本节着重介绍同构 H5 的具体实现方案,先后介绍了同构 H5 关键库:React Native Web及其先进理念;如何对已有 RN 项目进行改造;同构项目的常见兼容处理方式,实现同构和代码共存;如何通过服务端渲染提高H5性能,以及如何处理服务渲染后的屏幕适配等等问题。
React Native Web 由前 Twitter 前端主管 Necolas 开发,并使用该技术方案在 2017 年重构了 Twitter 网页 (Twitter Web App) 并沿用至今。
React Native Web 在国内知名度不高,但是放眼世界 Twitter Web、Expo、NativeBase、React Native 官网等众多项目全部使用的 React Native Web 能力。
虽然是 5 年前就开始的项目,如今看依旧有非常前瞻性的理念,因为 Necolas 也是 normalize.css 库的作者,对 CSS 和前端工程化有独到的理解,其在 React Native Web 中开创性实现了诸如原子化 CSS、css-in-js、前端组件化、Web 的容器化封装、手势系统等等。具体可看参考文献中的第一个视频。
在 div 上能看到大量"r-"开头的单个 css 规则,是否令你联想起如今火热的 Tailwind css、UnoCSS?
原子化 CSS 处理
以往前端项目中,随着样式的增多,CSS 逐渐冗余、重复打包、难以维护。现在很多库的前瞻性原子化 CSS 思路,其实 React Native Web 5 年前已实现。
这种方式在使得 CSS 代码量不会随着页面的日益增多而无限扩展,而是会逐步趋于收敛,并且在服务端渲染后,能做到真正的 CSS 按需加载。
再加上客户端不支持 CSS 渲染,所以抛弃 CSS 的历史包袱之后,Web 能跟客户端样式代码一致,开发才能真正的实现跨端组件封装,有助于我们实现三端同构的组件系统。
后来 Necolas 加入 Facebook,从事 React 相关开发。近期 Facebook 对 React Native Web 这种原子化 CSS 的处理方式进行封装,取名 stylex,Facebook 和 Whatsapp 等官网都使用这种 CSS 处理方式重写。stylex目前还未开源,不过其基本理念来源于 React Native Web ,可见 React Native Web 的前瞻性,再加上 React 是 Facebook 出品,在可预见的未来,这种处理方式可能会成为 React 的默认样式写法与解析方法。
随着雪球前端对 React Native Web 的深入理解和使用,我们也积极参与 React Native Web 的开源建设,为 React Native Web 增加多个 feature,核心开发者也展示在了 React Native Web 项目首页。
我们在已有的 RN 项目里引入 React Native Web,并选取 Next.js 作为服务端渲染的项目脚手架,同时使用 koa.js 做 node 服务端自定义 server。
Next.js 是 Vercel 明星开发团队出品的成熟 react 服务端渲染框架,在开发体验、代码分割、服务端渲染、优化方面做的很好,开箱即用,所以我们使用 Next.js 来处理 web 的编译,通过服务端渲染能力解决 React Native Web css-in-js 方案的首屏渲染问题。
为了能复用雪球前端通用的 node 中间件,比如 snb-lib-token 等, 所以服务端采用 Next.js + 自定义 server 的方式(custom-server)
雪球 App 里是为每个 RN 页面注册一个 URL 路由,该规则通过json动态下发,客户端读取后将 RN 页面注册到 URL 路由上,进行匹配和跳转,并且能控制 RN、H5、原生的优先级逻辑。
同构 H5 的路由跟 RN 的路由注册保持一致,实现同一个 URL 在客户端内打开 RN 页面,在微信中打开 H5。这样做还有一个优势,由于保持了 RN / H5 路由一致,H5 还会为客户端 RN 页做兜底,比如 RN 未覆盖到的低版本客户端或者 RN 包出问题时,点击 URL 会打开对应的同构 H5 页面做降级。
同构改造还涉及对 RN 和 H5 的差异化处理,总结主要有三种方式:由代码处理、由 Metro 处理、由 Next.js 处理,分别以三端兼容代码、jsbridge、H5 路由注册为例介绍。
开发需对平台进行逻辑判断,比如微信分享的 jssdk 在客户端打包时无需引入,比如端上的逻辑无需在 node 上执行。
Platform.OS 有 "iOS" | "android" | "windows" | "macos" | "web" 5 种,在同构项目中 web 其实还分为两种,一种是纯 web,一种是 node 服务端渲染。所以我们进行封装,增加了 node 标识。
OS : "iOS" | "android" | "windows" | "macos" | "web" | "node"
// 只有web才引入weixin-js-sdk
if (OS === 'web') { wx = require('weixin-js-sdk');
}
// 只有在服务端 才进行mobx 的 服务端 useStaticRendering 设置
if (OS === 'node') {
useStaticRendering(true);
}
jsbridge 等功能跟各端有关联,在客户端内需调用端内方法, 在 web 端需调用 web 的方法。
同构写法为通过 native.js 后缀区分,由 React Native Metro 打包工具默认提供。
RNBridge├── index.js # 由前端工具打包的文件├── index.native.js # 由 React Native 自带打包工具(Metro) 打包的文件
因为雪球的 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.js
index.web.js 中内容也很简单,使用封装好的 Wrapper 包装下 RN 入口文件即可,里面有全局的参数、样式等处理,把Web和node端也当做容器来处理,使 H5 保持跟 RN 一致。
import { Wrapper } from 'snowbox';import Page from '.';export default Wrapper(Page);
服务端渲染(SSR),是指由服务侧(server side)完成页面的 DOM 结构拼接和样式生成,降低页面渲染的白屏时间。
我们发现复杂页面同构生成的 H5,在 iphone6 等低端手机上渲染较慢,对同构页面进行 lighthouse 跑分也证实了这个问题。其中有一部分原因是因为React Native Web 的样式解析采用的 css-in-js 方案,所以需要在 js 加载完后,动态计算后才能得到,在低端手机上必然会造成白屏问题。
对此我们的优化方案是采用服务端渲染,并修改Web的屏幕适配逻辑,提升同构网页性能。
采用 RN 同构服务端渲染后的性能提升:(测试采用 m1,将 cpu 性能降低 4 倍)
lighthouse
项目服务端渲染非服务端私募新商品页8067xueqiu.com/rn9893组件库展示页9686
RN 服务端渲染演示
前端页面需做移动端适配,根据不同的屏幕宽度进行相应的比例缩放。
在客户端 React Native 中,我们使用的是 Dimensions.get('window').width 获取屏幕宽度,并按照比例对每个元素进行尺寸计算,得出适配大小。
但是在 H5 服务端渲染时,服务端的 Dimensions 获取不到用户的屏幕宽度,尺寸计算失败,导致服务端渲染的 H5 初始样式展示有问题,页面渲染会有抖动的现象。
根据前端项目的服务端渲染经验,我们做了两部分处理:
服务端渲染改造点还包括「服务端样式提取」、「OS 封装」、「Window 封装」、「Wrapper 通用参数处理」、「全局变量封装」等,具体可移步组件库官网了解详情。
“持续集成”(CI) 通常指的是持续地将代码更改集成到代码的主分支中。具体来说,CI 服务通过自动化测试、构建和发布过程来帮助用户频繁地集成更改。对于基于 pull-request 的工作流,每次都会运行所有测试和 lint 检查,打包,下发。
雪球三端同构的 CI 由 Gitlab CI 实现,包含 commit 校验、代码 lint 校验、单元测试、RN 打包上传等等。
而持续部署( CD )分为两部分,分别是端侧 RN 发布和 Web 服务的部署。
端侧 RN
采用 Gitlab CI + 自研 mPaaS 实现。所有 feature 分支 PR 合并后自动打 RN 包,由自研 mPaaS 实现 RN 发布客户端版本控制、多阶段控制、代码上传、分包下发、切包等功能。
Web 服务
采用 Drone CI + 自研 Rolling Docker 部署。由 sit、sep、staging、prod 四个特定分支来承载 Web 服务的分环境部署。PR 合并后触发 Drone CI 的 Docker 镜像构建,随后通过 Rolling Docker 部署前端 node 服务 。
同构的开发、测试、上线整体流程如下图所示:
CI 流程中的关键环节是单元测试,因为涉及到客户端,所以同构代码的测试与以往前端测试有所不同,我们对 RN 开源项目的单元测试进行了对比调研。
RN 组件库使用测试工具对比
组件库地址工具及类库react-nativehttps://github.com/facebook/react-nativeJest react-test-rendererReact Native Elementshttps://reactnativeelements.com/docs/testingJest React Native Testing Librarytamaguihttps://github.com/tamagui/tamagui/blob/master/jest.config.jsJestnativebasehttps://docs.nativebase.io/testingJest jest-expo
方案对比
2.Jest-expo-enzyme 作为 jest preset 配置,即集成了 enzyme(也是 react 测试库)的 api
选用方案
Jest + React Native Testing Library:测试库(基于 react-test-renderer 和 React Testing Library)
代码覆盖率
jest 集成了代码覆盖率计算的功能,通过jest --coverage可以获得代码覆盖率相关结果,以下是 test case 的示例结果,通常认为 80%以上是一个比较理想的结果,测试率报告可以作为完善用例的参考。
近几年来,人工智能技术日益渗透到软件工程领域,比如微软 Copilot 基于大模型的代码自动生成系统。人工智能在设计稿转代码方面也有很多研究成果,2017 年的关于图像转代码的 Pix2Code 论文掀起了业内激烈讨论的波澜,讲述如何从设计原型直接生成源代码。2018 年微软 AI Lab 开源了草图转代码 工具 Sketch2Code,2019 年阿里发布 imgcook 在双 11 大促中自动生成了 79.34% 的前端代码,京东 Deco、codefun、58 Picasso、DhiWise 等产品相继出现。随着 D2C 在生产中的广泛应用,智能生成代码不再只是一个线下实验产品,而是真正产生了价值。
目前 D2C 大部分是应用于 H5,如果能和三端同构结合,将会创造出更高的价值。并且由于同构组件对 UI 进行了高度的抽象,所以也有助于 D2C 代码智能生成的实现,同构生成三端代码。
我们对代码智能生成的开发工作划分了三个阶段,由浅入深,逐步实现最终的目标。
初期-编译器:利用 imgcook/DhiWise/Deco/Picasso 等平台对设计稿进行解析,生成描述代码 DSL,开发编译器将描述代码 DSL 编译为前端代码。
中期-设计稿解析、可视化编辑器:自行开发设计稿图层解析服务,开发定制化的页面可视化编辑器。
后期-智能识别:智能识别设计稿/图片,自动分离/组合模块,合理布局,最终生成代码。
目前雪球 FE 在相对简单的后台类页面生成中,自主研发跑通了全流程,实现了后台类页面的智能代码生成。但在复杂的 toC 端设计稿转代码中,目前还在初期阶段。我们利用 imgcook 等工具对 figma 解析,识别设计稿,通过开发的 DSL 编译器自动生成的代码。
左边为设计稿,右边为自动生成的 RN/H5 同构页面,已初见雏形,后续还有很大的改善空间。
生成的代码示例:
export default function Block_0() { return ( <Box col br={8} w={351} h={202} > <Box p="12"> <Box col ml={16}> <Txt f={28} fw="500" c> +21.49 </Txt> <Box m=微信小程序 > <Txt f={12} fw="400"> 今年以来 </Txt> </Box> </Box> <Txt f={16} fw="500"> % </Txt> </Box> </Box> );}
微信生态除了 H5 之外,小程序也是非常关键的一环。目前雪球的小程序还是单独开发,后续我们计划适配小程序,把样式组件系统的 UI 编写方式引入小程序开发,同时也会向 Taro 、Uni-App 等方案学习交流,进一步实现 iOS、安卓、H5、小程序、PC、VR 等多端同构的愿景。
软件工程没有银弹,只有根据具体案例制定相匹配的解决方案。RN / H5 三端同构方案是基于实际业务需求,综合考虑效率、体验、成本、通用性的,针对雪球现状定制化的解决方案。此方案最适合以 App 开发为主,并需要把 App 内的功能同步到微信 H5 生态的业务场景。对于想实现 App 动态化、提升人效的中小型公司有借鉴意义,也提供了可落地的实操指南,通过技术改造来显著提升开发体验,使技术真正为我们所用,并进一步赋能业务。
展望未来,在丰富同构组件库、代码智能生成、多端同构等等方面还有很长的路要走,期待与大家交流和共创。
Nicolas Gallagher - Twitter Lite, React Native, and Progressive Web Apps https://youtu.be/tFFn39lLO-U
Theme UI : https://theme-ui.com/
styled-system: https://styled-system.com/
Shopify/Restyle: https://github.com/Shopify/restyle
NativeBase: https://docs.nativebase.io/utility-first
React 18 keynote: https://youtu.be/FZ0cG47msEk
闲鱼宗心:这一年,我对终端组织与技术架构的思考:https://mp.weixin.qq.com/s/BGGsuYrlojMfTqfTo71VZg
人机协同时代,AI助力90.4%双11前端模块自动生成: https://www.imgcook.com/blog
作者:雪球大前端团队
来源:微信公众号:雪球工程师团队
出处
:https://mp.weixin.qq.com/s/3DRsUeZs5Z6TrMVlPrLiuA