前段时间,我开始做一个叫taggr的副业,这是一个完全离线的、互动的照片探索应用。开发taggr需要我从最底层的应用程序的复杂性开始,尝试多种架构方法并探索每种方法的局限性。
在这篇文章中,我们将讨论用Electron构建桌面应用程序的不同架构方法的得失。我们将分析每种方法的缺点,并介绍一种旨在解决这些缺点的架构。
本文介绍的蓝图是我不断努力的结果,我想找到一种方法,使我这个单独的开发者能够通过利用标准的网络工具来管理应用程序的复杂性并满足性能要求。让我们深入了解一下
注意:你可以跟着这个GitHub仓库走。
Electron.js简介
在过去的几年里,JavaScript在浏览器领域的使用急剧增加,主要是在React、Vue和Angular等库和框架的帮助下。同样地,我们也看到了JavaScript在浏览器之外的发展,如Node.js、Deno和React Native。
Electron.js是这些框架中的一个。自2013年发布以来,Electron已经成长为构建跨平台桌面应用的最常用框架之一。VS Code、Slack、Twitch和许多其他流行的桌面应用程序都是用Electron构建的。
Electron如何工作
Electron在其二进制文件中嵌入了Chromium和Node.js,使网络开发者无需编写本地代码就能编写桌面应用程序。Electron实现了一个由主进程和渲染器进程组成的多进程模型,这与Chromium浏览器类似。
每个应用程序的窗口都是一个渲染进程,它将代码执行隔离在窗口级别。主进程负责应用程序的生命周期管理、窗口管理或渲染进程,以及系统菜单、通知和托盘图标等本地API。
每个应用程序都是由一个主进程和数量不等的渲染进程组成。渲染进程可用于JavaScript代码的执行,并且可以在没有用户界面的情况下进行隐藏。
注意:Electron并不是构建跨平台桌面应用程序的唯一选择。其他的替代方案提供了更少的资源消耗和更轻的可执行文件,但没有一个能分享到Electron的社区、学习资源或广泛的应用。
开始使用Electron
如果你还不熟悉Electron,它很容易上手,特别是因为Node.js和JavaScript的知识是可以转移的。
Electron提供了抽象和熟悉的语言,减少了进入市场的时间和开发成本。从本质上讲,Electron对桌面应用开发的作用类似于React Native对移动开发的作用。
Electron还可以管理应用程序更新的构建和部署,使跨平台的应用程序很容易保持在一个同步的版本。你可以通过自动更新和在运行时加载远程资产来实现这一点。
然而,Electron的好处并不是没有代价的。Electron带有Chromium和Node.js环境,导致Electron应用比其本地实现的同类应用消耗更多的资源。因此,人们对Electron的可行性意见不一。
此外,复杂的Electron应用也带来了与底层架构有关的性能和开发者体验的挑战。让我们通过分析三个不同的应用实例来深入考虑这些权衡。
应用程序的具体权衡
让我们来看看三个复杂程度不同的虚构的应用程序的高级架构。请记住,我们的应用分析并不是为了详尽无遗,而是为了挑出你可以用Electron构建的潜在应用。
低复杂度的应用
让我们从一个低复杂度的应用开始。在我们的例子中,我们将考虑把一个网页打包成一个桌面应用。例子可以包括即时通讯应用、数据分析仪表板和在线流媒体应用。
许多企业为其成功的基于网络的应用程序提供桌面版本,使我们的应用程序成为一个常见的用例。我们将使用Electron在Chromium上运行应用程序,消除不必要的polyfills,提供一个统一的UI,而不是一个异质的浏览器景观。
低复杂度的应用程序的主要特点。
- 代码将在网络应用和桌面应用之间共享
- 更新周期将在网络应用和桌面应用之间共享
- 桌面应用程序将加载与Web应用程序相同的资产,并在Chromium中渲染它们。
- 后台(如果适用)将保持不变
- 后台将以同样的方式从桌面和网络应用中访问。
- 依赖于浏览器支持的功能,如WebWorkers和WebGL,将在不改变的情况下跨平台运行。
- 我们将使用标准的网络开发工具
低复杂度应用程序的高级架构
作为一个架构范例,我们将使用Telegram聊天网络应用的桌面应用。Electron将作为现有网络应用的封装器,而不需要对后端进行任何改动。
对于这种类型的应用来说,设置Electron是很容易的!在网络应用程序代码库层面上不需要任何改变。
中等复杂度的应用
像Spotify这样的音乐流媒体应用,使用本地缓存提供离线流媒体支持,是一个典型的具有中等复杂程度的应用的例子。桌面应用可以使用Electron来建立一个本地缓存层。
与低复杂度的应用类似,中等复杂度的应用也可以补充网络应用。主要的区别是提供离线支持的能力。因此,这些应用在概念上与具有离线支持的渐进式网络应用(PWA)有关。
主要特点。
- 大部分代码可以在网络和桌面应用程序之间共享(即在UI层中)。
- 桌面应用程序将有一个本地缓存实现,它将拦截后端请求,填充缓存,并在离线时提供缓存的结果
- 我们需要使用高级别的Electron API来检查桌面应用是在线还是离线
- 更新周期不一定在网络和桌面之间共享。桌面将使用其离线UI从静态文件加载UI,并使用缓存创建一个自定义请求层
- 你可以利用标准的网络开发工具,但自定义请求模块除外,它必须为Electron开发和调整
高级别的架构
让我们想象一下,我们的流媒体应用程序播放一天中的歌曲。如果没有互联网连接,它将提供可用的缓存歌曲。
正如上面的模式所概述的,用户界面将从本地资产而不是CDN中提供,请求层必须被定制以支持缓存。虽然这个例子相对简单,但代码共享和缓存的要求最终会增加复杂性,需要定制Electron代码。
高复杂度的应用程序
对于最高级别的复杂性,让我们来看看像sharp这样的批量图像处理应用。该应用必须能够处理成千上万的图像,并且完全离线工作。
离线应用与前两个例子有明显不同。具体来说,典型的后端工作负载,如图像处理,将通过创建一个离线应用程序在Electron内执行。
主要特点。
- 我们的大部分代码将是为桌面应用程序定制的
- 该应用程序将有自己的发布周期
- 后台将在Electron内部运行(即从一个渲染过程中)。
- 可以使用标准的网络开发工具,但这取决于定义的架构
- 我们可能需要使用本地模块,如数据库访问、图像处理或机器学习。
- 可能需要从多个进程中访问低级别的Electron API,特别是用于进程间通信(IPC)。
高级别的架构
对于架构建议,让我们考虑上面描述的离线图像处理应用程序。
该架构按照Electron的文档对应用程序进行结构化,这带来了一些限制。首先,在一个隐藏的渲染器进程中运行长期存在的、CPU密集型的操作时,会有明显的性能下降。
请注意,你不应该在主进程中运行这些操作。这样做可能会阻塞主进程,导致你的应用程序冻结或崩溃。
此外,将业务逻辑和传输层与Electron APIs的耦合限制了重用标准的Web开发工具的选择。主进程和渲染器进程之间的通信使用IPC,在两个渲染进程之间通信时需要主进程的往返。
如果你的应用程序属于低度或中度复杂的类别,那么恭喜你!你的应用程序是一个很好的选择。许多在离线应用程序中出现的头痛问题并不适用于你。然而,如果你的应用需求属于高复杂度范围,那么还是有希望的!
高级架构建议
当我们考虑到离线应用程序中的问题,如性能下降、渲染进程之间的往返通信以及整体的开发者体验,我们需要一个专门的架构。
建议的架构建立在以下几个支柱上。
- 前台和后台之间共享的代码被提取到一个单一的模块中。
- UI代码是与Electron无关的,因此可以应用网络开发的最佳实践。
- UI和页面路由是使用受控组件和集中的应用状态建立的。
- 后台是由一个独立的Node.js进程运行的
- 前端和后端模块通过消息传递进行通信。
让我们详细了解一下每个模块吧
注意:堆栈中的某些部分纯粹是出于个人喜好而选择的,可以互换。例如,你可以把TypeScript换成JavaScript,把React换成Vue,把Redux换成MobX,或者把npm包换成代码共享而不是Yarn工作空间。只要尊重上述的支柱,你就可以在整个堆栈中自由选择。
共享模块
共享模块负责前端和后端模块所共享的代码和类型。它使你能够将这两个模块作为独立的实体来开发,同时仍然共享领域相关的代码和类型。
代码共享是通过Yarn工作空间实现的,这是一个简单的替代方案,可以将模块发布为npm包,并进行释放和版本管理。
主要特点。
- Typecript代码库
- 消息传递通信的类型:包含前端和后端都需要的有效载荷和消息处理程序
- 领域模型和实体
- 共享的实用程序,如日志和事件报告
前台模块
前台模块负责所有的UI。它包含了我们应用程序的组件和动画,但不包含业务逻辑。在生产中,Electron通过生成的静态文件为其提供服务。
主要特点。
- 可以访问共享模块的Typescript代码库
- 使用React来构建用户界面,以Create React App作为模板
- 使用Redux作为状态管理器,它确定地定义了用户界面的渲染状态
- 通过消息传递与后端进行通信:前端暴露了一个消息处理程序,它监听来自后端的消息并相应地修改Redux存储。
- 使用Storybook进行孤立的组件开发
带有Electron模块的后端
后台模块包含后台代码库和Electron设置代码。业务逻辑和长期运行的操作,如图像处理,将在一个单独的Node.js进程中运行,这样用户界面就不会受到性能下降的影响。
主要特点。
- Typescript代码库,可访问共享模块
- 后台作为一个分叉的Node.js进程运行,这提高了长期运行和计算昂贵任务的性能
- 访问本地的依赖性
- 执行一个预编译步骤,将本地的依赖关系与Electron的版本相匹配
- 包含必要的Electron配置和打包脚本
通信层
前端和后端使用进程间消息传递与 [node-ipc](https://github.com/RIAEvangelist/node-ipc).消息传递允许async 和基于事件的通信。
async 通讯最适合于短时的操作。前端可以等到后端处理消息的时候马上得到结果。
基于事件的通信更适合于长期的操作,如批处理。当任务在后端处理时,它发送的事件将修改Redux中前端的应用状态。后端可以异步地完成长期运行的任务,并定期更新UI显示的进度。
主要特点。
node-ipc作为通信库- 共享模块中完全类型化的消息有效载荷和处理程序
- 支持异步和基于消息的通信
总结
Electron是使用不同网络技术构建跨平台桌面应用程序的一个很好的选择。尽管Electron在低复杂度的应用中很容易使用,但随着复杂度的增加,性能和开发者经验的限制会浮现出来。
建议的架构旨在为高复杂度的应用程序提供一个良好的概念基础。当然,它可能需要根据使用情况进行扩展,但我发现它可以作为许多类型的应用程序的一个良好基础。
The postAdvanced Electron.js architectureappeared first onLogRocket Blog.