当前位置: 首页 > Web前端 > HTML

Electron应用开发优秀实践

时间:2023-03-28 15:16:10 HTML

vivo互联网前端团队-杨坤1.背景在团队中,由于业务发展需要使用到桌面技术,比如离线可用性,调用桌面系统能力等。什么是桌面开发?一句话概括:Windows、macOS和Linux的软件开发。我们对此做了详细的技术研究,桌面开发方式主要有Native、QT、Flutter、NW、Electron、Tarui。它们各自的优缺点如下表所示:我们最终选择的桌面技术是Electron,它是一个可以利用Web技术开发跨平台桌面应用的开发框架。其技术构成如下:Electron=Chromium+Node.js+NativeAPI技术能力如下图所示:整体架构如下图所示:Electron是一个多进程架构,具有以下特点:主进程和N个渲染进程组成主进程起主导作用,用于完成各种跨平台和原生交互。可以有多个渲染进程,使用Web技术开发,通过浏览器内核渲染页面。主进程和渲染进程通过进程间通信完成各种功能这里先说一下Electron进程间通信技术的原理:Electron使用IPC(进程间通信)进行进程间通信,如下图:它提供了一个IPC通信模块,主进程ipcMain和渲染进程ipcRenderer。从electron源码可以看出ipcMain和ipcRenderer都是EventEmitter对象,源码如下图所示:看到源码实现,是不是觉得IPC不难理解。了解其本质,才能游刃有余。看到这里,我们查看上面的技术表,发现Electron应用程序包体积较大。尺寸大的根本原因是什么?其实这和chromium的框架设计有关。它对很多功能没有宏观控制,这使得去除大而复杂的细节功能变得困难。也导致基于chromium的开发框架,如electron、nwjs。包100多M起步。综上所述,Electron具有跨端、基于Web、超级生态等优点,是桌面开发的优秀解决方案之一。下面将介绍电子应用开发的实践经验,包括应用技术选择和常用功能。2.应用技术选择2.1编程语言Typescript理由如下:Javascript开发者的超集-无缝支持所有es2020+所有特性,学习成本低,编译生成的JavaScript代码保持良好的可读性和可维护性显着增强对完整OOP的支持-extends,interface,private,protect,public等类型,即文档类型约束,单元测试覆盖率更小,代码更安全,工具重构能力更强,静态分析,自动打包代码引入错误检查代码跳转代码提示完成大量社区类型定义文件,提高开发效率2.2构建工具Electron-Forge理由:简单强大,目前最好的electron应用构建工具之一。先提一下electron-builder和electron-forge的介绍和区别,如下图:两者最大的区别在于自由度,两者在能力上基本没有区别。从官方组织的排名来看,有意的Electron-forge是首选。2.3Web解决方案Vue3+Vite我们使用Vue3和Vite作为构建工具。具体优势可以查看官网介绍。这种组合是目前主流的Web开发方案。2.4Monorepo解决方案pnpm+turbo当前monorepo生态蓬勃发展,正确的实践方法应该是综合法,即取长补短。目前的趋势也是如此。开源的monorepo工具已经达成默契,各显神通。比如pnpm擅长依赖管理,turbo擅长构建任务编排。所以在monorepo技术的选择上,我选择了pnpm和turbo。选择pnpm的理由如下:目前最好的包管理工具,pnpm吸取了npm、yarn、lerna等主流工具的精华,去其糟粕。生态、社区活跃且强大结合workspace可以完成monorepo的最佳设计和实践在管理多项目包依赖、代码风格、代码质量、组件库复用等方面,在framework和library方面表现出色开发、调试、维护性能方面,相比vue官网,我在使用pnpm的时候加了一个workspace。使用Turbo的原因如下:它是一个高性能构建系统,具有增量构建、云缓存、并行执行、零开销运行时、任务管道和减少子集等特性。短板上2.5数据库lowdbelectron应用数据库有lowdb、sqlite3、electron-store、pouchdb、dedb、rxdb、dexie、ImmortalDB等很多选择。这些数据库都有一个特点,就是Serverless。Electron应用数据库技术选型主要包括以下三点:生态(用户数量、维护频率、版本稳定性)能力和性能其他(与用户技术匹配)我们通过以下渠道进行了相关研究githubissues,Commit,fork,starsourcegraph关键字搜索结果、npm包下载、版本发布官网和博客给出四个最优选择,分别是lowdb、sqlite3、nedb、electron-store。原因如下:lowdb:生态、能力、性能三个方面的优秀表现。json形式的存储结构,支持lodash、ramda等api操作,有利于备份和调用sqlite3:生态、能力、性能三方面表现优异。Nodejs关系型数据库首选nedb:能力和性能三方面性能优秀,缺点是基本不维护,但是基础还是有的,尤其是操作是MongoDB的一个子集,是一个优秀的熟悉MongoDB的用户的选择。electron-store:优秀的生态性能,轻量级的持久化方案,简单易用我们使用的数据库选择是lowdb方案。PS:再提一下pouchdb。如果需要将本地数据同步到远程数据库,可以使用pouchdb,可以轻松完成与couchdb的同步。2.6脚本工具在zx软件开发过程中,一些流程和操作都是通过脚本完成的,可以有效提高开发效率和幸福感。依赖节点运行时的优秀选择只有两个:shelljs和zx。选择zx的原因如下:自带fetch、chalk等常用库,使用起来方便快捷。多个子进程,方便快捷,执行远程脚本,解析md,xml文件脚本,支持ts,功能丰富强大Google出品,大厂背景,生态非常活跃目前为止,引入技术选型结束,下面介绍一下electron应用的常用功能。3.构建这部分主要介绍以下5点:应用程序图标生成二进制文件构建按需构建性能优化跨平台兼容性3.1应用程序图标生成不同大小的图标生成有以下方法:Windows软件生成:icofx3网页生成:https://tool.520101.com/diannao/ico/(opensnewwindow)MacOS软件生成:icofx3网页生成:https://tool.520101.com/diannao/ico/(opensnewwindow)命令行生成:使用sips和iconutil生成3.2二进制文件构建本章基于electron-forge,但原理是一样的。在开发桌面应用程序时,有使用第三方二进制程序的场景,例如ffmpeg。在构建二进制程序时,要注意以下两项:(1)二进制程序不能打包成asar。您可以在构建配置文件(forge.config.js)中设置以下设置:constos=require('os')constplatform=os.platform()constconfig={packagerConfig:{//您可以打包ffmpegasar目录外的目录extraResource:[`./src/main/ffmpeg/`]}}(2)开发环境和生产环境,获取二进制程序路径的方法不同。以下代码可用于动态获取:import{app}from'electron'importosfrom'os'importpathfrom'path'constplatform=os.platform()constdir=app.getAppPath()letbasePath=''if(app.isPackaged)basePath=path.join(process.resourcesPath)elsebasePath=path.join(dir,'ffmpeg')constisWin=platform==='win32'//ffmpeg二进制程序路径constffmpegPath=path.join(basePath,`${platform}`,`ffmpeg${isWin?'.exe':''}`)3.3按需构建如何按需构建跨平台二进制文件?例如,ffmpeg用于桌面应用程序,这需要为windows、mac和linux下载二进制文件。打包时,如果不做按需构建,3个二进制文件都会被打入构建中,这会增加很多应用程序的体积。您可以在forge.config.js配置文件中进行如下配置,完成按需构建。代码如下:constplatform=os.platform()constconfig={packagerConfig:{extraResource:[`./src/main/ffmpeg/${platform}`]},}将对应系统的binary放入通过平台变量进行构建,即可完成二进制文件的按需构建。3.4性能优化主要是优化buildspeed和buildvolume,buildspeed不容易优化。本文重点介绍构建体积优化。这里我们以mac系统为例。electron应用打包后,查看应用包内容,如下图:可以看到有一个app.asar文件,用asar解压后可以看到里面有如下内容:可以看到asar中的文件就是我们构建后的项目代码。从图中我们可以看到有一个node_modules目录。这是因为在electron构建机制中,所有依赖的依赖都会被自动打入asar中。所以结合上面的分析,我们的优化措施有以下四点:将web端构建需要的依赖全部放到devDependencies中,只把electron端需要的依赖放到dependencies中,去掉没有的代码和文件从build开始做production剔除跨平台二进制文件,比如ffmpeg进行按需构建(上面的按需构建已经介绍过了),清理简化node_modules这里是第四点,如何清理起来并简化node_modules?如果是yarn安装的依赖,我们可以在根目录下使用如下命令简化:yarnautoclean-Iyarnautoclean-F如果是pnpm安装的依赖,第4点应该不行。我在项目中使用yarn安装依赖,执行上面的命令后发现打包体积减少了6M。虽然不多,但还是可以的。至此,构造函数就介绍完了。4.更新本章主要分为以下两个方面:完全更新增量更新下面依次介绍以上两种更新4.1完全更新下载最新的压缩包或zip文件对软件进行更新,需要替换所有文件.总体设计流程图如下:按照流程图实现,需要做如下工作:开发服务端接口,返回应用的最新版本信息。渲染进程使用axios等工具请求接口获取最新版本信息,封装更新逻辑,对接口返回的版本信息进行综合比较,判断是否更新。更新信息通过ipc通信传递给主进程。主进程通过electron-updater执行完整更新。更新信息通过ipc推送给渲染进程。渲染进程向用户显示更新信息。如果更新成功,会弹出一个弹窗告诉用户重启应用程序,完成软件更新。4.2增量更新拉取最新的渲染层包文件,覆盖之前的渲染层代码,完成软件更新。该方案只需要替换渲染层代码,无需替换所有文件。根据流程图,我们需要做以下事情。渲染进程定时通知主进程进行检测和更新。主进程检测到更新需要更新,然后拉取最新在线包,删除旧版本包,复制最新在线包,完成增量更新通知渲染。进程,提示用户重启应用程序以完成更新。全量更新和增量更新各有优势。大多数情况下,增量更新用于提升用户更新体验,全量更新作为自下而上的更新方案。至此,更新功能介绍完毕。5、性能优化分为以下三个方面:构建优化启动优化运行时优化构建优化上面已经详细介绍,这里不再介绍。下面将分别介绍启动优化和运行时优化。5.1启动优化使用v8-compile-cache缓存编译代码先加载核心功能,非核心功能使用多进程动态加载,多线程技术使用asar封装:加快启动速度,增加视觉过渡:loading+骨架屏5.1.1使用v8-compile-cache缓存编译代码使用V8缓存数据,为什么要这样做?因为electorn使用V8引擎来运行js,所以V8运行js时需要先解析编译,然后再执行代码。其中,解析和编译过程消耗大量时间,往往会导致性能瓶颈。V8缓存功能可以缓存编译后的字节码,节省下一次解析编译的时间。主要使用v8-compile-cache来缓存编译后的代码。方法很简单:在需要缓存的地方加一行require('v8-compile-cache')。其他使用方法可以参考这个链接文档https://www.npmjs。com/package/v8-compile-cache(opensnewwindow)5.1.2先加载核心函数,再动态加载非核心函数。伪代码如下:exportfunctionshare(){constkun=require('kun')kun()}5.2运行时优化优化渲染进程的web性能轻量化主进程5.2.1优化渲染的web性能process包含性能优化的核心要点和内容,可以以此作为性能优化的参考。5.2.2主进程轻量化核心解决方案是将耗时和计算量大的功能交给新开的节点进程执行。伪代码如下:const{fork}=require('child_process')let{app}=require('electron')functioncreateProcess(socketName){process=fork(`xxxx/server.js`,['--subprocess',app.getVersion(),socketName])}constinitApp=async()=>{//其他初始化代码...letsocket=awaitfindSocket()createProcess(socket)}app.on('ready',initApp)通过上面的代码,将耗时和计算量大的函数放到了server.js中,然后fork到新开的node进程中进行处理。至此,性能优化介绍完毕。6.质量保证质量保证的全过程措施如下图所示:本章主要介绍以下三个方面:自动化测试崩溃监控崩溃治理下面将依次介绍以上内容。6.1自动化测试什么是自动化测试?上图是自动化测试的一个完整步骤,大家看图就明白了。自动化测试主要分为单元测试、集成测试和端到端测试。三者的关系如下图所示:一般来说,作为软件工程师,我们可以做一定量的单元测试。而且以我目前的经验来看,如果是写业务项目,基本不会写测试相关的代码。自动化测试主要用于编写库、框架、组件等需要作为单个个体提供给他人的。Electron的测试工具推荐vitest和spectron。具体使用请参考官网文档,没有什么特别的技巧。6.2死机监控对于GUI软件,尤其是桌面软件,死机率非常重要,因此需要对死机进行监控。崩溃监控原理如下图所示:崩溃监控技巧渲染进程崩溃后,通过preload提示用户重新加载崩溃监控主进程,渲染进程通过process.crash()模拟崩溃来收集并分析崩溃日志。崩溃监控做好最后,如果发生崩溃,如何治理崩溃?6.3崩溃治理崩溃治理难点:错误栈定位难:原生错误栈,无运行上下文调试门槛高:C++、IIdb/GDB运行环境复杂:机器模型、系统、其他软件崩溃治理技巧:及时升级electron用户操作日志和系统信息复制和定位问题比治理更重要。问题交给社区解决。社区响应速度快,擅长使用devtool分析管理内存问题。Things,本章将安全分为以下五个方面:源码泄露asar源码保护应用安全编码安全下面依次介绍以上内容。7.1源码泄露目前Electron在源码安全方面做得并不好。官方只是用asar做了一个很没用的源码保护。有多没用?你只需要下载asar工具,然后解压asar文件就可以得到里面的源码,如下图:通过图中的操作可以看到语雀应用的源码。上面说的asar是什么?7.2asarasar是一种类似于tar的存档格式,可将多个文件合并为一个文件。Electron可以从中读取任意文件内容而无需解压整个文件。Asar技术原理:可以直接看electron源码,是ts代码,易读。源码如下图所示:从图中可以看出,asar的核心实现是重写了nodejs的fs模块。7.3源代码保护为避免源代码泄露,按照源代码安全性从低到高,分为以下几个等级:asar代码混淆WebAssemblyLanguagebindings其中,Languagebindings是最高的源代码安全措施。事实上,使用C++或Rust代码编写电子应用程序代码,通过将C++或Rust代码编译成二进制代码后,就变得更难破译了。下面我就来说说如何使用Rust来编写electron的应用代码。解决方案:使用napi-rs作为工具来编写,如下图所示:我们使用pnpm-workspace来管理Rust代码,使用napi-rs,例如我们写一个sum函数,rs代码如下:fnsum(a:f64,b:f64)->f64{a+b}此时我们添加napi装饰代码,如下图:usenapi_derive::napi;#[napi]fnsum(a:f64,b:f64)->f64{a+b}将以上代码编译成二进制代码,node可以通过napi-cli调用。编译后在electron中使用以上代码,如下图:import{sumasrsSum}from'@rebebuca/native'//output7console.log(rsSum(2,5))使用请阅读官方文档napi-rs,地址为:https://napi.rs/(opensnewwindow)至此,语言绑定的描述就完成了。这样我们就可以完成重要功能的源代码保护。7.4应用安全一个众所周知的安全问题是克隆攻击。该问题的主流解决方案是将用户认证信息与应用设备指纹进行绑定。整体流程如下图所示:应用设备指纹生成:上面可以描述napi-rs实现用户认证信息与设备指纹绑定的方案:使用服务端实现7.5编码安全主要有以下几个措施:常见的web安全,如anti-xss,csrf设置节点可执行环境形式打开安全选项限制链接跳转到上面具体细节就不介绍了,自行搜索上面的解决方法。另外官方还有一个推荐的bestsecuritypractice,大家有时间可以看看,地址如下:https://www.electronjs.org/docs/latest/tutorial/security(opensnewwindow)至此,security的介绍到此结束。8.总结本文介绍了我们对桌面技术的研究,技术选型的确定,以及在用electron开发过程中总结的构建、性能优化、质量保证、安全等实践经验,希望对读者有所帮助在开发桌面应用程序的过程中。文章难免存在不足和错误。欢迎读者在评论区交流。