共计 8051 个字符,预计需要花费 21 分钟才能阅读完成。
vivo 互联网前端团队 -Yang Kun
一、背景
在团队中,咱们因业务倒退,须要用到桌面端技术,如离线可用、调用桌面零碎能力。什么是桌面端开发?一句话概括就是: 以 Windows、macOS 和 Linux 为操作系统的软件开发 。对此咱们做了具体的技术调研,桌面端的开发方式次要有 Native、QT、Flutter、NW、Electron、Tarui。其各自优劣势如下表格所示:
咱们最终的桌面端技术选型是 Electron,Electron 是一个能够应用 Web 技术来开发跨平台桌面利用的开发框架。
其技术组成如下:
Electron = Chromium + Node.js + Native API
各技术能力如下图所示:
整体架构如下图所示:
Electron 是多过程架构,架构具备以下特点:
- 由一个主过程和 N 个渲染过程组成
- 主过程承当主导作用,用于实现各种跨平台和原生交互
- 渲染过程能够是多个,应用 Web 技术开发,通过浏览器内核渲染页面
- 主过程和渲染过程通过过程间通信来实现各种性能
这里说下 Electron 过程间通信技术原理:
electron 应用 IPC(interprocess communication)在过程之间进行通信,如下图所示:
其提供了 IPC 通信模块,主过程的 ipcMain 和渲染过程的 ipcRenderer。
从 electron 源码中能够看出,ipcMain 和 ipcRenderer 都是 EventEmitter 对象,源码如下图所示:
看到源码实现,是不是感觉 IPC 不难理解了。知其本质,方可熟能生巧。
看到这,咱们回顾上文技术表格,看到 Electron 利用包体积大,那体积大的根本原因是什么呢?
其实这和 chromium 的框架设计无关,其对很多性能都没有宏管制,导致很难把宏大简单的细节性能去除掉,也造成了基于 chromium 的开发框架,如 electron、nwjs 打出的包起步就是 100 多 M。
综上,electron 具备跨端、基于 Web、超强生态等长处,是桌面端开发的优良计划之一。下文将介绍 electron 利用开发实践经验,包含利用技术选型和罕用性能。
二、利用技术选型
2.1 编程语言 Typescript
理由如下:
- 针对开发者
- Javascript 的超集 – 无缝反对所有的 es2020+ 所有的个性,学习老本小
- 编译生成的 JavaScript 的代码放弃很好的可读性
- 可维护性明显增强
- 残缺的 OOP 的反对 – extends, interface,private,protect,public 等
- 类型即文档
- 类型的束缚,更少的单元测试的笼罩
- 更平安的代码
- 针对工具
- 更好的重构能力
- 动态剖析主动导包
- 代码谬误查看
- 代码跳转
- 代码提醒补齐
- 社区
大量的社区的类型定义文件 晋升开发效率
2.2 构建工具 Electron-Forge
理由:简略而又弱小,目前 electron 利用最好的构建工具之一。
这里提一下 electron-builder 其和 electron-forge 的介绍和区别,看下图所示:
两者最大的区别在于自由度,两者在能力上根本没什么差别了,从官网组织中的排序看,无意优先举荐 electron-forge。
2.3 Web 计划 Vue3 + Vite
咱们采纳的是 Vue3,同时应用 Vite 作为构建工具,具体长处,大家能够查看官网介绍,这套组合是目前支流的 Web 开发计划。
2.4 monorepo 计划 pnpm + turbo
目前的 monorepo 生态百花齐放,正确的实际办法应该是集大成法,也就是取各家之长,目前的趋势也是如此,各开源 monorepo 工具达成默契,专一本人善于的能力。
如 pnpm 善于依赖治理,turbo 善于构建工作编排。遂在 monorepo 技术选型上,我抉择了 pnpm 和 turbo。
pnpm 理由如下:
- 目前最好的包管理工具,pnpm 排汇了 npm、yarn、lerna 等支流工具的精髓,并去其糟粕。
- 生态、社区沉闷且弱小
- 联合 workspace 能够实现 monorepo 最佳设计和实际
- 在治理多我的项目的包依赖、代码格调、代码品质、组件库复用等场景下,表现出色
- 在框架、库的开发、调试、保护方面,表现出色
相比于 vue 官网,在应用 pnpm 上,我加了 workspace。
turbo 理由如下:
- 它是一个高性能构建零碎,领有增量构建、云缓存、并行执行、运行时零开销、工作管道、精简子集等个性
- 具备十分优良的工作编排能力,能够补救 pnpm 在工作编排上的短板
2.5 数据库 lowdb
electron 利用数据库有十分多的抉择如 lowdb、sqlite3、electron-store、pouchdb、dedb、rxdb、dexie、ImmortalDB 等。这些数据库都有一个个性,那就是无服务器。
electron 利用数据库技术选型思考因素次要有以下 3 点:
- 生态(使用者数量、保护频率、版本稳定度)
- 能力
- 性能
- 其余(和使用者技术匹配度)
咱们通过以下渠道进行了相干调研
- github 的 issues、commit、fork、star
- sourcegraph 关键字搜寻后果数
- 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
软件开发过程中,将一些流程和操作通过脚本来实现,能够无效地进步开发效率和幸福度。
依赖 node runtime 的优良抉择就两个:shelljs 和 zx,抉择 zx 的理由如下:
- 自带 fetch、chalk 等罕用库,应用方便快捷
- 多个子过程方便快捷、执行远端脚本、解析 md、xml 文件脚本、反对 ts,功能丰富且弱小
- 谷歌出品,大厂背景,生态十分沉闷
至此,技术选型就介绍完了,上面我将介绍 electron 利用的罕用性能。
三、构建
此局部次要介绍以下 5 点内容:
- 利用图标生成
- 二进制文件构建
- 按需构建
- 性能优化
- 跨平台兼容
3.1 利用图标生成
不同尺寸图标的生成有以下办法:
Windows
- 软件生成: icofx3
- 网页生成:https://tool.520101.com/diannao/ico/(opens new window)
MacOS
- 软件生成: icofx3
- 网页生成:https://tool.520101.com/diannao/ico/(opens new window)
- 命令行生成: 应用 sips 和 iconutil 生成
3.2 二进制文件构建
本章节内容是基于 electron-forge 论述的,不过原理是一样的。
在开发桌面端利用时,会有场景要用到第三方的二进制程序,比方 ffmpeg 这种。在构建二进制程序时,要关注以下两个留神项:
(1)二进制程序不能打包进 asar 中 能够在构建配置文件(forge.config.js)进行如下设置:
const os = require('os')
const platform = os.platform()
const config = {
packagerConfig: {
// 能够将 ffmpeg 目录打包到 asar 目录里面
extraResource: [`./src/main/ffmpeg/`]
}
}
(2)开发和生产环境,获取二进制程序门路办法是不一样的 能够采纳如下代码进行动静获取:
import {app} from 'electron'
import os from 'os'
import path from 'path'
const platform = os.platform()
const dir = app.getAppPath()
let basePath = ''
if(app.isPackaged) basePath = path.join(process.resourcesPath)
else basePath = path.join(dir, 'ffmpeg')
const isWin = platform === 'win32'
// ffmpeg 二进制程序门路
const ffmpegPath = path.join(basePath, `${platform}`, `ffmpeg${isWin ? '.exe' : ''}`)
3.3 按需构建
如何对跨平台二进制文件进行按需构建呢?
比方桌面利用中用到了 ffmpeg,它须要有 windows、mac 和 linux 的下载二进制。在打包的时候,如果不做按需构建,则会将 3 个二进制文件全部打到构建中,这样会让利用体积减少很多。
能够在 forge.config.js 配置文件中进行如下配置,即可实现按需构建,代码如下:
const platform = os.platform()
const config = {
packagerConfig: {extraResource: [`./src/main/ffmpeg/${platform}`]
},
}
通过 platform 变量来把对应零碎的二进制打到构建中,即可实现对二进制文件的按需构建。
3.4 性能优化
次要是构建速度和构建体积优化,构建速度这块不好优化。本文重点说下构建体积优化,这里拿 mac 零碎举例说明,在 electron 利用打包后,查看利用包内容,如下图所示:
能够看到有一个 app.asar 文件, 这个文件用 asar 解压后能够看到有以下内容:
能够看出 asar 中的文件,就是咱们构建后的我的项目代码,从图中能够看到有 node_modules 目录,这是因为在 electron 构建机制中,会主动把 dependencies 的依赖全部打到 asar 中。
所以联合上述剖析,咱们的优化措施有以下 4 点:
- 将 web 端构建所需的依赖全副放到 devDependencies 中,只将在 electron 端须要的依赖放到 dependencies
- 将和生产无关的代码和文件从构建中剔除
- 对跨平台应用的二进制文件,如 ffmpeg 进行按需构建(上文按需构建已介绍)
- 对 node_modules 进行清理精简
这里提下第 4 点,如何对 node_modules 进行清理精简呢?
如果是 yarn 装置的依赖,咱们能够在根目录应用上面命令进行精简:
yarn autoclean -I
yarn autoclean -F
如果是 pnpm 装置的依赖,第 4 点应该不起作用了。我在我的项目中应用 yarn 装置依赖,而后执行上述命令后,发现打包体积缩小了 6M,尽管不多,但也还能够。
至此,构建性能就介绍完了。
四、更新
本章节次要分为以下两个方面:
- 全量更新
- 增量更新
上面将顺次介绍上述两种更新
4.1 全量更新
通过下载最新的包或者 zip 文件,进行软件更新,须要替换所有的文件。
整体设计流程图如下:
依照流程图去实现,咱们须要做以下事件:
- 开发服务端接口,用来返回利用最新版本信息
- 渲染过程应用 axios 等工具申请接口,获取最新版本信息
- 封装更新逻辑,用来对接口返回的版本信息进行综合比拟,判断是否更新
- 通过 ipc 通信将更新信息传递给主过程
- 主过程通过 electron-updater 进行全量更新
- 将更新信息通过 ipc 推送给渲染过程
- 渲染过程向用户展现更新信息,若更新胜利,则弹出弹窗通知用户重启利用,实现软件更新
4.2 增量更新
通过拉取最新的渲染层打包文件,笼罩之前的渲染层代码,实现软件更新,此计划只需替换渲染层代码,无需替换所有文件。
依照流程图去实现,咱们须要做以下事件
- 渲染过程定时告诉主过程检测更新
- 主过程检测更新
- 须要更新,则拉取线上最新包
- 删除旧版本包,复制线上最新包,实现增量更新
- 告诉渲染过程,提醒用户重启利用实现更新
全量更新和增量更新各有劣势,少数状况下,采纳增量更新来进步用户更新体验,同时应用全量更新作为兜底更新计划。
至此,更新性能就介绍完了。
五、性能优化
分为以下 3 个方面:
- 构建优化
- 启动时优化
- 运行时优化
构建优化在上文内容中,曾经具体介绍过了,这里不再介绍,上面将介绍 启动时优化 和 运行时优化。
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(opens new window)
5.1.2 优先加载外围性能,非核心性能动静加载
伪代码如下:
export function share() {const kun = require('kun')
kun()}
5.2 运行时优化
- 对渲染过程 进行 Web 性能优化
- 对主过程进行轻量瘦身
5.2.1 对渲染过程 进行 Web 性能优化
用一个思维导图来残缺论述如何进行 Web 性能优化,如下图所示:
上图根本蕴含了性能优化的外围关键点和内容,大家能够以此作为参考,去做性能优化。
5.2.2 对主过程进行轻量瘦身
外围计划就是将运行时耗时、计算量大的性能交给新开的 node 过程去执行解决。
伪代码如下:
const {fork} = require('child_process')
let {app} = require('electron')
function createProcess(socketName) {
process = fork(`xxxx/server.js`, [
'--subprocess',
app.getVersion(),
socketName
])
}
const initApp = async () => {
// 其余初始化代码...
let socket = await findSocket()
createProcess(socket)
}
app.on('ready', initApp)
通过以上代码,将耗时、计算量大的性能,放在 server.js,而后再 fork 到新开 node 过程中进行解决。
至此,性能优化就介绍完了。
六、品质保障
品质保障的全流程措施如下图所示:
本章节次要介绍以下 3 个方面:
- 自动化测试
- 解体监控
- 解体治理
上面将会顺次介绍上述内容。
6.1 自动化测试
自动化测试是什么?
上图是做自动化测试一个残缺步骤,大家能够看图体会。
自动化测试次要分为 单元测试、集成测试、端到端测试,三者关系如下图所示:
个别状况下,作为软件工程师,咱们做到肯定的单元测试就能够了。而且从我目前教训来说,如果是写业务性质的我的项目,基本上不会编写测试相干的代码。自动化测试次要是用来编写库、框架、组件等须要作为独自个体提供给别人应用的。
electron 的测试工具举荐 vitest、spectron。具体用法参考官网文档即可,没什么特地的技巧。
6.2 解体监控
对于 GUI 软件,尤其桌面端软件来说,解体率十分重要,因而须要对解体进行监控。
解体监控原理如下图所示:
解体监控技巧
- 渲染过程解体后,提醒用户从新加载
- 通过 preload 对立初始化解体监控
- 主过程、渲染过程通过 process.crash() 进行模仿解体
- 对解体日志进行收集剖析
解体监控做好后,如果产生解体,该如何治理解体呢?
6.3 解体治理
解体治理难点:
- 定位出错栈艰难:Native 谬误栈,无操作上下文
- 调试门槛高:C++、IIdb/GDB
- 运行环境简单:机器型号、零碎、其他软件
解体治理技巧:
- 及时降级 electron
- 用户操作日志和零碎信息
- 复现和定位问题比治理重要
- 把问题交给社区解决,社区响应快
- 长于用 devtool 剖析和治理内存问题
七、平安
俗话说的好,平安大于天,保障 electron 利用的平安也是一项重要的事件,本章节将平安分为以下 5 个方面:
- 源码透露
- asar
- 源码爱护
- 利用平安
- 编码平安
上面将会顺次介绍上述内容。
7.1 源码透露
目前 electron 在源码平安做的不好,官网只用 asar 做了一下很没用的源码爱护,到底有多没用呢?
你只须要下载 asar 工具,而后对 asar 文件进行解压就能够失去外面的源码了,如下图所示:
通过图中操作即可看到语雀利用的源码。下面提到的 asar 是什么呢?
7.2 asar
asar 是一种将多个文件合并成一个文件的类 tar 格调的归档格局。Electron 能够无需解压整个文件,即可从其中读取任意文件内容。
asar 技术原理:
能够间接看 electron 源码,都是 ts 代码,容易浏览,源码如下图所示:
从图中能够看出,asar 的外围实现就是对 nodejs 的 fs 模块进行重写。
7.3 源码爱护
防止源码透露,依照从低到高的源码平安,能够分为以下水平
- asar
- 代码混同
- WebAssembly
- Language bindings
其中,Language bindings 是最高的源码安全措施,其实应用 C++ 或 Rust 代码来编写 electron 利用代码,通过将 C++ 或 Rust 代码编译成二进制代码后,破译的难度会变高。这里我说下如何应用 Rust 去编写 electron 利用代码。
计划:应用 napi-rs 作为工具去编写,如下图所示:
咱们采纳 pnpm-workspace 去治理 Rust 代码,应用 napi-rs,比方咱们写一个 sum 函数,rs 代码如下:
fn sum(a: f64, b: f64) -> f64 {a + b}
此时咱们加上 napi 装璜代码,如下所示:
use napi_derive::napi;
#[napi]
fn sum(a: f64, b: f64) -> f64 {a + b}
在通过 napi-cli 将上述代码编译成 node 能够调用的二进制代码。
编译后,在 electron 应用上述代码,如下所示:
import {sum as rsSum} from '@rebebuca/native'
// 输入 7
console.log(rsSum(2, 5))
napi-rs 的应用请浏览官网文档,地址是:https://napi.rs/(opens new window)
至此,language bindings 的论述就实现了。咱们通过这种形式,能够实现对重要性能的源码爱护。
7.4 利用平安
目前熟知的一个平安问题是克隆攻打,此问题的支流解决方案是将用户认证信息和利用设施指纹进行绑定,整体流程如如下图所示:
- 利用设施指纹生成:能够用上文论述的 napi-rs 计划去实现
- 用户认证信息和设施指纹绑定:应用服务端去实现
7.5 编码平安
次要有以下措施:
- 罕用的 web 平安,比方防 xss、csrf
- 设置 node 可执行环境
- 窗体开启平安选项
- 限度链接跳转
以上具体细节不再介绍,自行搜寻上述计划。除此之外,还有个官网举荐的最佳平安实际,有空能够看看,地址如下:https://www.electronjs.org/docs/latest/tutorial/security(opens new window)
至此,平安这块就介绍完了。
八、总结
本文介绍了咱们对桌面端技术的调研、确定技术选型,以及用 electron 开发过程中,总结的实践经验,如构建、性能优化、品质保障、平安等。心愿对读者在开发桌面利用过程中有所帮忙,文章不免有有余和谬误的中央,欢送读者在评论区交换。