阅读 17129

桌面端混合开发总结

不知不觉在阿里已经入职满一年时间了,在这一年时间里主要接触了低代码(Low Code)、小程序、移动端 H5、监控以及 PC 桌面端(CEF & Electron)等开发技术。在这些技术中, PC 桌面端混合开发技术相对沉淀多一些,接下来会跟大家分享一些桌面端开发的思考,希望对大家的日常开发有所启迪。

温馨提示:如果对桌面端技术不了解可以先查看 2019 前端年度总结 / 桌面端开发。除此之外,本文所讲解的桌面端开发技术并不是采用新颖的 Flutter 技术,如果你对阿里内部 Flutter 技术的建设非常感兴趣,则可以查看阿里集团内如何进行 Flutter 体系化建设

基本概念

首先讲解一些桌面端开发的基础概念,这里有些说明并不会贯穿全文,其中一些说明会有助于理解后续的框架说明。

Hybrid 混合开发

本文所讲解的桌面端开发主要采用了 Hybrid 混合的开发模式(一种在原生应用容器中嵌入 Web 浏览器的开发模式),这种模式混合了 Natiive 和 Web 开发的优点。在某些高性能的业务场景中(例如 IM 聊天、音视频、直播等)可以采用 Native 进行开发,而在一些功能迭代相对频繁的业务场景中则可以采用 Web 进行开发(开发成本低)。除此之外, Web 开发在跨平台、动画、动态化、富文本编排等方面能力相对出色。

CEF 容器

本文所讲解的 Hybrid 混合开发主要采用了 CEF(Chromium Embedded Framework) 技术,它主要的特点如下:

  • 可以在 Native 原生应用中嵌入兼容 HTML5 的 Google Chromium 浏览器
  • 可以创建轻量级的应用程序容器(简称 CEF 容器),主要用于承载使用 Web 技术进行开发的用户界面
  • 可以支持跨平台(Mac、Linux & Windows)开发,并且方便定制和融合其他类型的 Native 应用
  • 会实时追踪 Google Chromium 的版本升级,能使 Web 开发使用一些相对教新的特性
  • 采用 C 和 C++ 语言进行开发设计,可以和 Google Chromium 浏览器进行紧密结合
  • 特定版本的 CEF 可以支持在 Windows XP 系统上运行桌面端应用

需要注意上述所说的 CEF 容器可以简单理解为在原生应用环境中嵌入 Google Chromium 浏览器。除此之外,桌面端的应用可以做到多窗口的开发模式,每一个窗口至少可以承载一个 Web 前端应用(简称子应用)。多个子应用之间如果想进行通信则可以利用 URL 的 query 参数(实时性要求低)或者 Native 桥接技术中的发布订阅机制(实时性要求高)。

温馨提示:很多同学对于 Google Chromium 和 Google Chrome 的差异相对迷惑,值得 Goolge 一下。

JSBridge & Native API

前端开发的环境受限于浏览器的沙箱隔离,操作系统中一些涉及到安全问题的系统能力会被限制使用。在桌面端的混合开发中难免需要使用 Web 前端去调起特定的系统能力,此时就需要一种桥接 Web 和 Native 的技术,这种技术被称为 Bridge,具体如下图所示:

温馨提示:图片由同事提供,没有找到具体出处。

可以把上图的左侧部分(自绘引擎开发框架)简单当作 Native 开发,右侧部分(泛 Web 容器框架)当作 Hybrid 混合开发。可以发现 Web 前端如果想绕过沙箱限制调起原生视图组件和系统能力,需要通过 Native 提供的 Bridge 技术。 在 CEF 容器中原生应用可以侵入 JavaScript 语法环境,因此可以通过向 JavaScript 环境(例如 window 全局对象)注射 API 的形式进行桥接,这里的 API 简称 Native API。

温馨提示:除了桌面端的混合开发模型,移动端也存在类似的混合模型,其中的 Web 前端通常被称作 H5 技术。除此之外,桥接技术是 Web 前端间接通过 Native 去调用系统能力,因此会存在一些性能损耗。

JS API

阿里的桌面端除了开发自己的应用以外,也会提供服务平台供外部的 ISV 进行三方应用扩展。此时需要将 Native API 进行能力开放(会涉及到一些调用的版本和鉴权匹配处理),这些开放的 API 简称 JS API,相对 Native API 而言在能力上会更加抽象,除此之外,也需要做到跨平台、跨应用等,具体如下图所示:

温馨提示:在阿里内部理论上会对你所能想到的任何技术进行收口处理, JS API 也不例外。

HotFix

在原生应用的桌面端技术中,想要修复线上版本的 Bug 或进行新功能迭代往往需要伴随着新版本的发布。HotFix 是在不发布应用版本的前提下修复线上 Bug 且可以做到用户无感知(也可以通过提示用户手动更新补丁包的方式),大致实现流程如下:

温馨提示:图片来自大前端时代下的热修复平台建设

在混合开发中如果想要修复 Web 前端应用的 Bug 会相对简单一些,至少可以省略桌面端的重启步骤。同时离线的 Web 前端资源可以通过资源下发的形式进行更新,而 CDN 形式的资源则只需要推送新的资源版本即可(通常做这些事情的时候会进行逐级灰度,确保更新的稳定性)。

桌面端开发

在阿里的一年时间里,对于桌面端的开发主要经历了一下几个阶段:

  • 了解现有的桌面端框架
  • 在已有的框架中开发新的子应用
  • 对于桌面端框架的优化探索
  • 设计并实践新的桌面端框架

了解桌面端框架

在前期对桌面端技术进行了简单的科普之后,对于现有的桌面端框架进行了深入了解,大致如下图所示:

该框架主要包含了容器层、通用层和应用层三个层级,特性大致如下:

  • 所有的子应用放在同一个仓库中进行开发维护
  • Webpack 多入口配置、Webpack 多进程处理以及开发态配置
  • 新增子应用时使用的技术栈会受到框架的限制
  • 采用 TypeScript 进行业务开发,鲁棒性好
  • 采用 Rxjs (Action 事件流)结合发布订阅进行事件处理
  • 没有采用 React Router,简单页面的切换采用状态管理的方式进行处理

开发新的子应用

刚开始进行客户端子应用开发时虽然意识到了框架结构的复杂性,但是并没有想到在框架上可以做一些优化工作,只是在原有框架的基础上进行了应用层的局部优化处理。由于受到了框架的限制,无法采用 React Hook 以及 JavaScript 的一些新特性,代码的设计相对无法做到扁平化的结构方式。当时思考了一些子应用层面的代码结构层次优化,具体如下图所示:

业务层

从桌面端框架和以上思维导图可以看出,这里对原有的应用层做了一些简单的变动。原有业务层本身的功能没有层级划分,是一种没有层级思维模式的开发方式。在前期延续旧的子应用思维模式时发现代码的结构无法适应需求带来的频繁变化,因此思考了之后决定采用业务层分层的设计模式:

  • 布局:可快速响应业务需求变化,重组业务模块,具备良好的可扩展性
  • 容器:采用高阶函数封装,单纯对业务模块进行应用数据和行为的分发管控
  • 业务模块:对业务的模块化抽离,模块之间会设计特定的通信数据

这样的层级结构设计带来的好处是需求一旦发生排布变化,只需要在布局层做出相应的调整即可。

MVC 约束

建设公共的数据层(Model),用于驱动业务层(View)更新,而业务层中的容器则担任了控制器(Controller)的作用。除此之外,当业务变得极其复杂之后,数据层会变得相对难以维护,这里采用了分类的控制方式:

  • 视图数据:经过数据适配层处理后的渲染视图数据
  • 通信数据:用于业务模块之间的联动处理
  • 请求状态:用于缓存请求参数、游标 / 分页响应信息以及请求状态标识(默认视图、Loading 以及请求错误等处理)

需要注意视图数据会随着业务的复杂度增加而增加,当视图数据和视图的关联越来越复杂后容易导致重复渲染问题,此时可通过 immutability-helper 配合 React.PureComponentshouldComponentUpdate 进行处理(更多关于 React 的优化建议可查看官方文档 Optimizing Performance )。

温馨提示:很多同学书写的代码会强依赖后端的接口数据,一旦后端的数据失控很容易导致前端页面白屏。为了提升代码的健壮性和可维护性,在数据层中新增后端数据处理的适配层,可以做到后端数据和前端视图的解耦处理。在适配的过程中可以使用 ECMAScript proposal Stage-4 支持的 Optional Chaining 语法进行数据处理(如果编译环境不支持这种教新的语法,则可以采用 Lodash 的 get 工具方法)。

桌面端框架优化探索

在开发子应用的过程中,逐渐体会到旧客户端框架的一些痛点:

  • 复杂的 Webpack 多入口配置以及冗余的开发态配置
  • 尽管使用了 parallel-webpack,应用编译速度缓慢,降低了开发效率
  • 没有 Hot Reload 能力,降低了开发效率
  • 无法使用 React 新版本的特性,例如 React Hook
  • 无法使用 JavaScript & TypeScript 的新版本特性,例如 Optional Chaining
  • 业务层 / 容器的高阶函数形成的地狱嵌套使得代码臃肿复杂,不利于代码的维护性
  • 简单子应用的 Rxjs 发布订阅以及 Action 流机制不利于代码维护
  • 公共服务层以及物料层中的业务组件等通用性代码没有做到抽离解耦
  • ...

大致设想了一些框架的局部优化处理:

从上图可以看出,在原有框架的基础上新增了 Monorepo 结构,该框架的特点如下:

  • 解耦 Webpack 配置,将多入口配置优化成单入口配置,提升编译效率
  • 应用层可以做到部分解耦,可以配置一些新环境使用新特性
  • Native API、业务组件以及公共服务等通用能力可以进行抽离解耦

当时考虑了一些通用能力的建设,但是子应用仍然无法做到真正的解耦。除此之外, Monorepo 本身并不能够减少应用的认知和维护成本,因此没有采用这套框架体系。该框架的设计思考奠定了解耦子应用的思维模式。

温馨提示:Monorepo 的开发工具包括 NxLerna 等。想要了解关于 Monorepo 的具体实践,可以查看 Vue CLI 3结合 Lerna 进行 UI 框架设计

技术沉淀

桌面端框架优化实践

为了彻底做到子应用的解耦,新的子应用不在原有应用的仓库中进行开发和维护,而是采用了集团的工程化脚手架能力进行独立的开发和维护。除此之外,还在脚手架的基础上新增了桌面端子应用的通用模板能力,可通过 CLI 命令一键快速生成:

大致介绍一下该工程化的能力建设:

  • 集团脚手架:简单理解为 react-scripts,可支持应用、组件以及小程序等多种开发模式
  • 工程化监控:用于监控并上报脚手架在开发使用过程中的编译性能以及本身的报错信息
  • Webpack 定制插件:用于定制桌面端子应用的特殊编译需求
  • 子应用开发模板:集成了桌面端的开发的基本能力
  • 模板拉取:可通过 CLI 命令一键获取并生成通用的开发模板类型

其中子应用开发模板的结构大致如下所示:

这里主要讲解一下通用能力建设:

  • ErrorBoundary:视图白屏处理(显示兜底的出错视图),并进行白屏的监控上报处理
  • Hook:多端通用的自定义 Hook 能力
  • Request:多端通用的请求库,能处理缓存和 Loading 优化(特定请求时间内不产生 Loading,请求数据缓存等)
  • Utils:通用的工具函数

需要注意这里的子应用开发模板并没有做到动态插件化的能力,而是相对固定的一个桌面端开发模板。

温馨提示:这里所说的多端是指小程序、H5、PC 客户端(Windows、Linux、Mac) 等,这是一个常规建设命题。

子应用解耦之后,桌面端混合开发优化后的框架如下图所示:

优化后的框架特性如下:

  • 新增工程化监控能力,可快速感知各个子应用的开发效能
  • Webpack 配置交由工程化脚手架维护
  • 提升编译的速度,增加 Hot Reload 能力
  • 使用最新的 React 、JavaScript 以及 TypeScript 特性
  • 将子应用试点优化的多层级开发结构改造成扁平化的开发结构(去除高阶函数的容器能力)
  • 请求缓存、Loading 优化、视图的 ErrorBoundary 容错处理等体验提升
  • 视图白屏监测能力
  • 真正解耦子应用,可做到客户端应用的定制化

除此之外,在旧有框架的基础上新增了构建层。由于框架的优化解耦了各个子应用,因此需要将所有子应用的构建产物进行合并处理(成为客户端安装包中的 Web 前端离线产物),合并产物所包含的各个子应用还需要和客户端进行版本映射。因此增加构建层用于处理各个子应用的版本映射关系以及云构建协同处理,这里采用了 Jenkins、 Node 以及 Shell 进行各个子应用的在线构建和产物合并处理。

未来规划

桌面端框架的优化实践虽然做了一些工作,开发效率也得到了极大的提升,但同时也增加了认知和开发协同成本。除此之外,在构建层做的太薄,除了构建本身以外还有很多可发挥的空间,这里就客户端的未来建设做出一些粗略的规划。

文档中心

建立桌面端开发的文档中心,可以更好的帮助开发者在一无所知的情况下快速了解当前桌面端的技术和业务背景,文档中心未来大致会分为以下几个内容:

  • 开发指南
  • 业务说明
  • 技术规划
  • 规范文档
  • 业务进展

其中开发指南是重点,大概会包含以下一些内容:

  • 开发简介
  • 基础概念
  • 总体框架
  • 项目模板
  • 开发工具
  • 开发调试
  • 版本规范
  • 在线构建
  • 线上故障

构建优化

构建层除了云构建本身以外,还可以做到子应用开发规范的收口处理,具体如下图所示:

其中会新增在线检测能力,主要包含:

  • 规范:版本、工程模板等是否符合开发规范
  • 框架:相对重要的库版本检测
  • 资源大小:检测资源构建的大小
  • 能力:检测是否接入监控、业务打点以及国际化能力等
  • 代码质量检测:检测 Optional Chaining 等容易导致白屏的语法...

流程协同主要是提升构建的效率,在一些研发流程相关的闭环中可以快速进行构建处理。比如在 Gitlab 中提交特定 commit 信息的代码或者 publish 特定分支即可触发构建(这一块和 Github Actions 类似),当然这里的触发层不仅仅涉及 Gitlab,还会涉及到很多阿里内部的研发协同平台。

研发服务

研发服务是可以打通整个研发链路的工具服务平台。例如构建优化框架中最顶层的虚线框就是其中非常重要的一个环节,除此之外也可以接入工程化能力、研发效能数据大盘、业务监控数据大盘、流程协同以及 HotFixed 能力等。

研发服务是未来打通桌面端(也可以包括小程序、H5 等应用)整个研发链路的基础服务。在工程化层面可以集成类似于 vue ui 的能力,将现有的子应用模板进行灵活的插件化集成,从而可以快速适应各个业务场景的研发诉求。除此之外,从创建应用、需求评审关联、需求变更、分支管理、需求开发、云构建、缺陷关联、灰度以及发布等整体的研发链路都可以在研发服务平台完成,从而做到工程体系、流程协同、构建等一体化。

参考文献

文章分类
前端
文章标签