今年因为业务需要,开启了 electron 相关的一系列技术建设,也沉淀了一个脚手架 electron-lab。本文依据 electron 脚手架的搭建原理,并和 electron-forge 进行对比。
Electron 脚手架怎么搭
Electron 有两个部分,一部分是 node.js 编写的主进程;另一部分是渲染进程,也就是一个普通的浏览器进程。
1. 主进程入口文件
Electron 通过读取 package.json 中的 main 字段指向的 js 文件作为入口。
{
"name" : "test",
"version" : "1.0.0",
"main" : "src/index.js"
}
因此,如果要在渲染进程使用 ts,一个主进程的“打包”工具也是很重要的。
2. 渲染进程开发
渲染进程由主进程唤起,在主进程初始化窗口之后,可以通过 loadURL/loadFile
两个方法加载一个 .html
入口文件来作为应用入口。
example:
mainWinodw.loadURL("http://127.0.0.1:8000");
如果是一个非常简单的应用,可以直接 load 一个 html 就可以启动一个渲染窗口(因为 electron 可以控制浏览器版本,倒是可以比较轻松地使用新的语言特性)。但是现在的前端开发中,热更新、插件等是基本配置,所以我们一般会启动一个开发服务器。注意最后构建的时候,比如把URL变成一个本地的资源地址,例如:
// 开发时
mainWinodw.loadURL("http://127.0.0.1:8000");
// 打包时
mainWinodw.loadURL("file:///User/xxx/app.asar/index.html")
因此,为了让开发时和打包时不用频繁修改入口文件URL,通常会注入一个变量。例如在 electron-forge 中,是通过 webpack.DefinePlugin 实现的:
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
mainWinodw.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
3. 构建 app
应用开发、主进程渲染进程分别打包之后,下一步就是使用打包工具,构建出一个 app。这一步可以使用的工具有 electron-builder、electron-packager、electron-forge 等。
需要注意的是,electron 对依赖的处理方式比较特殊,任何包含在 package.json:dependencies 的依赖都会打包到产物中。这通常是给主进程的 node.js 用的,因为前端的依赖都会在 webpack 中打包。如果没有关注 dependencies 的内容,会使最后产物非常大。
在我处理这个问题后,相同的代码,包体积从 126.4MB 下降到 93.4MB,下降比例为 26.1%
经过以上三个步骤之后,一个简单的研发流程就可以实现了,但是还有很多痛点是目前存在的工具无法解决的。在经历了一轮折磨之后,我开始了一个新的脚手架的探索之路。
脚手架痛点
1. 主进程热启停
主进程是一个 nodejs 进程,无法像常规的前端一样使用市面上常见的热更新工具。因此,在编写代码时,需要不断中断进程,然后重新 start 项目。这带来了很多不必要的手动操作。目前 electron-forge 没有这个能力。
2. 多窗口
如果一个应用要打开多个窗口,必须存在多个入口文件。这需要设计一个规则,用来规定如果去创建一个 mpa 应用。但是 electron-forge 实际上只暴露了部分的配置,因此无法达到我定制多入口的需求。
但是实际上这算是个伪需求,大部分的多窗口都可以使用前端模态框来解决,多窗口还带来了额外的管理负担。
3. 依赖管理
同上所说,electron 打包时会自动包含 dependencies 中的包。因此,如果开发时不注意错误地添加了依赖,难以主动发现错误。
4. 保留目录结构
node 和前端代码最大的不同在于:
- node 可以使用 fork 来唤起一个新的 nodejs 实例,将类似解压的高性能操作扔给子进程,避免主进程卡死。
- node 需要使用 require 或者 fs.readFile 来引用其他文件。例如
webPreferences.prelaod
在前端隔离 context 时预加载的脚本
另外,我们还需要保留原有的代码结构以用于错误分析。如果经过 webpack 打包,则会报错于 index.js 某万行代码且是 webpack 打包后的代码,在没有 source-map 的场景下,几乎无法定位错误。
因此,使用 webpack 打包主进程是一个不好的选择。
实际上我很好奇为什么 electron-forge 这么做,难道老外都能一看看懂编译后的代码吗。
5. 蚂蚁生态
6. 前端的新框架
electron-forge 是基于 webpack 的打包工具,和现在次世代技术难以兼容,例如 vite、swc、esbuild、umi 等。
当然 mfsu@4 支持了纯 webpack
6. 打包 app 成本
经过上述分析,可以看出:electron-forge 是一个复杂的工具。既是一个研发脚手架,也是一个打包工具,这导致了他对两者的支持都不好。例如,它限制了只能在某个系统构建该系统的包。
虽然这是可以避免兼容性问题的方法,但是对于没有该问题的应用,实在难受
electron-lab 和 electron-forge 比对
和 umi/bigfish 一样,electron-lab 也专注于 electron 开发的最佳实践。electron-lab 做的工作就是找到一个最好的方法,让用户可以简单地开发客户端产品。
特性 | electron-lab | electron-forge | 说明 |
---|---|---|---|
支持 webpack | ✅ | ✅ | |
支持插件 | ❌ | ✅ | |
主进程热更新 | ✅ | ❌ | |
支持 umi、vite | ✅ | ❌ | electron-lab 更像是一个调度器,针对普通的 webpack 脚手架和 umi 脚手架做了封装,最终暴露出相同的 api。 |
支持依赖分析 | ✅ | ❌ | electron-lab 支持在打包时对依赖进行分析,如果缺失依赖,直接抛出错误;对于多余的依赖,会给予提示。 |
保留主进程结构 | ✅ | ❌ | electron-lab 主进程使用 father-build 的魔改版打包,直接使用 babel 对每个主进程文件进行编译。 |
打包 app 优化 | ✅ | ❌ | electron-lab 增加了额外的打包信息。比如本次构建的 commit 号、版本号和 md5 等等。另外还会自动添加 electron 的国内镜像地址到 .npmrc,加快打包速度。 |
electorn-lab 和 umi 结合
这有一个 demo:https://github.com/electron-laboratory/demo-umi
和 umi 结合是一件非常简单的事情,因为 electron-lab 本身就像一个进程的调度器,调度各个工具来完成开发过程。在一个 umi 项目里,只需要做以下修改:
1. 安装 electron-lab 并修改 package.json
yarn add electron-lab@beta
package.json 中:
{ "scripts" : { "start" : "el start --engine=umi", "build": "el build --engine=umi" } }
package.json 中
{"main":".el/main/index.ts"}
el 是 electron-lab 命令太长了,简化一点。
2. 添加主进程文件
在 src 目录中添加 main 目录和 main/index.ts 入口文件。
3. 修改一些 umi 配置
// config/config.ts
{
outputPath: './.el/renderer', // 输出到 el 临时目录中
history: { type: 'hash' }, // 因为最终产物是文件,只能使用 hash 路由
publicPath: './', // 因为产物是文件,因此使用相对路径引入
mfsu: {}, // 又到了👴最爱的 mfsu 环节
}
现状下一步规划
electron-lab 的发展和进步离不开业务的发展和产品的迭代,目前的主要的产品为蚂蚁链 Morse 的基础版安装器和客户端,已经在内部打通了研发和测试流程,产品也平稳运行了一个多月。
OB 业务也存在 electron 产品,但是目前是自己做了一个 bigfish + 脚本的脚手架,存在对一个完善的脚手架的需求;其他业务可以在接入 umi 后持续去针对优化。
未来将会持续跟进以下问题的优化:
- 签名。因为签名涉及主体问题,会在未来提供解决方案
- 热更新。热更新需要一定的服务器资源支持,目前正在寻找中
- 发布。支持自动发布到 cdn 或者 oss,正在寻找资源解决(只针对内部)
最后,求个 star!https://github.com/electron-laboratory/electron-lab