Electron

原名Atom Shell.
自Electron 28开始, 支持ESM.
参考: https://github.com/Elanis/web-to-desktop-framework-comparison
NW.js(原名node-webkit)是Electron的主要竞争对手, 二者同样是Chromium+Node.js架构的跨平台桌面应用程序解决方案.
NW.js比Electron创立早2年, 但Electron现已远比NW.js受欢迎, 并且情况不太可能在未来改变.
  • Electron分离了Main进程了Renderer进程.
    Renderer进程内分为两个上下文, 分别执行Node.js代码和浏览器端代码.
Electron的模型分离了关注点, 但IPC异常繁琐.
  • NW.js基于Chrome Apps架构.
    分为Node上下文和Web上下文两个环境,
    Node上下文是Node.js模块的运行环境, Web上下文是NW.js脚本的运行环境.
有分离上下文和混合上下文两种模式, 后者会使得Node模块的运行环境带有浏览器和NW.js特有的API.
NW.js的模型更直接, 但很难分离关注点.
  • Electron的入口点是Main进程的JavaScript脚本.
  • NW.js入口点主要是HTML文件, 但也可以是JavaScript脚本.
  • Electron有很多商业公司用户, 最著名的项目是VSCode.
  • 尽管其起步较早, NW.js现存的用户量比较少, 已知微信小程序的IDE使用了NW.js.
Neutralinojs不打包Chromium, 而是使用操作系统上的WebView.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
Tauri使用Rust和操作系统的WebView, 不支持Node.js模块, 需要用Rust编写API.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
除了桌面平台, Tauri还计划支持iOS和Android.
  • 由于上下文隔离, 窗口的IPC变得非常麻烦.
  • API缺乏统一性, 光是移除窗口的菜单栏就有多种方法.
  • 过快的主版本迭代策略.
  • 缺乏有用的测试框架.
Electron应用程序的主进程, 是整个应用程序的入口, 一个实例里只能有一个.
Electron应用程序里负责用户界面的进程, 由主进程启动, 一个实例里可以有多个.
渲染器进程在默认情况下不具备直接访问本机Node.js模块的能力,
所有Node.js模块都需要通过由preload脚本创建的上下文隔离接口来间接调用.
可以在创建窗口时通过 nodeIntegration: true 选项启用渲染器进程的Node.js集成.
默认情况下, preload脚本与渲染器进程的宿主环境是隔离的,
两者只能通过 contextBridgewindow.postMessage 进行通信.
可以通过 contextIsolation: false 取消这一行为.
contextBirdge.exposeInMainWorld 是在preload脚本里向渲染器进程的宿主环境暴露API的唯一方法.
桥接时可用的数据类型受到限制:
除了基本的可结构化克隆的类型以外, 还允许发送函数, Promise, 错误等对象, 但仍然不能满足常见需求.
桥接还具有以下特性:
  • 对preload来说, API暴露是一次性的, 不能重复暴露, API的变化也不能同步到宿主环境.
  • 对宿主环境来说, 远程函数内部会显示成native code.
  • 暴露的对象会被冻结, 因此对宿主环境来说, API不可写.
  • 对不支持的类型, 可能会出现传 undefined /对象属性拷贝/报错这三种不同的结果.
renderer进程可以使用ipcRenderer向main进程发出请求.
这种ipc支持两种模式:
  • send: 仅向ipcMain发送消息
  • invoke: request-reply模式
由于上下文隔离, renderer进程里调用 ipcRenderer.send/invoke 是很不方便的.
main进程可以向renderer进程发出请求, 但前提是先得到renderer进程的WebContents对象.
WebContents对象有以下两种取得方式:
  • 由renderer首先向main发出请求, main接收到的 event.sender 就是WebContents对象.
  • main保留创建窗口时的BrowserWindow, 然后使用 BrowserWindow.webContents.
由于上下文隔离, 在renderer进程里注册事件监听器是很不方便的.
标准MesssagePort接口.
https://www.electronjs.org/docs/latest/tutorial/message-ports
适用于main和renderer, renderer和renderer之间的通信.
由于main和renderer之间可以通过ipcMain和ipcRenderer通信,
因此这种通信方式主要是用于renderer和renderer之间的通信.
建立通信前, 首先要通过ipcMain和ipcRenderer传递MessagePort对象.
借助网络协议可以实现IPC, 例如HTTP和WebSocket.
考虑到在Electron里建立Main和Renderer(Web上下文)的通信是如此繁琐,
使用基于网络协议的作为IPC降低复杂性也是一种方法.
  • IPC接口会暴露给同一网络的其他程序
  • 性能比其他方法差.
由于ABI不同, Electron不能直接使用Node.js的native模块, 模块必须专门为Electron编译.
https://www.electronjs.org/docs/latest/tutorial/devtools-extension
Electron有限度地支持Chrome扩展程序API, 可以加载一些开发工具.
在Windows和macOS上, electron自带的API可以用于设置开机启动:
https://www.electronjs.org/docs/latest/api/app#appsetloginitemsettingssettings-macos-windows
然而, 相关功能的实现充满了可悲的bug:
  • https://github.com/electron/electron/issues/32657
  • https://github.com/electron/electron/issues/33308
该实现不支持自定义args.
专门用于将配置项持久化为JSON文件的方案.
底层使用了同作者的conf模块, 支持JSON Schema和基于 package.json 的版本迁移.
一个在内存中运行的文档数据库, 间歇性持久化到文件系统.
开发不积极.
纯JavaScript模块, 用JSON作为数据库.
这玩意说白了就是一个用于读取和写入JSON的类, 没有任何亮眼之处.
作者停止维护了.
将RxJS和PouchDB结合起来的一个高级数据库.
由于使用PouchDB的生态系统, 官方支持Electron.
下载量与Github Stars数不成比例的典型代表, 实际的用户量并不多.
尽管作者对这个项目付出很多努力, 但该项目对于新用户来说仍然具有陡峭的学习曲线.
这个项目还依赖了太多作者无力控制的软件包, 导致该项目变得非常脆弱:
  • RxJS
  • PouchDB
  • pouchdb-find(RxCollection的find方法直接使用了此包, 因此共享相同的缺陷)
  • mquery(复制了代码)
从v10版本起, RxDB开始为脱离PouchDB而做准备.
无论从项目的流行程度, 可维护性, 还是其依赖项的可靠性考虑, RxDB都不值得使用.
在Node.js里, PouchDB通过levelup模块利用LevelDB作为存储底层,
levelup模块的底层是native模块, 但它原生支持Electron, 因此不需要用electron-rebuild处理就能使用.
PouchDB并非为多进程场景而设计, 有很多基于内存的优化, 并且其Node.js底层LevelDB也不支持多进程.
因此在Electron使用时, 应该只在Main进程里创建它的实例, 然后由IPC暴露给Renderer访问.
pouchdb-find是用于实现Mongo式查询语法的官方扩展.
这个包存在很多问题, 包括但不限于:
  • 在有索引的情况下反而无法得到正确结果:
    https://github.com/pouchdb/pouchdb/issues/7810
    尽管相关issue被关闭, 但我仍然在v7.2.2的包里遇到了相同的问题.
  • 丢弃选择器:
    https://github.com/pouchdb/pouchdb/issues/6572
    https://github.com/pouchdb/pouchdb/issues/6371
依赖该包进行查询显然是不可靠的.
在没有Mongo支持的情况下, PouchDB没有使用价值.
上一个版本是2020年发布的, 期间的bug没有被修复, 有很多关键bug的issue被维护者忽视.
其类型定义 @types/pouchdb 也是旧的(适配的是PouchDB v6.4).
RxDB的作者在RxDB v10的发行注记中也认为PouchDB没有得到良好的维护.
https://github.com/pouchdb/pouchdb/issues/3224
作为一个不支持Node-API(N-API)的native模块,
better-sqlite3需要用electron-rebuild处理后才能使用, 这可能带来依赖锁定问题.
这是Ghost团队维护的SQLite3模块.
该模块自v5版本开始基于Node-API重写.
callback接口还设置了this, 导致无法使用箭头函数.
为了获得Promise包装, 需要安装第三方包装器node-sqlite.
该库的每个Database实例只是一个连接, 不存在任何竞争条件.
当开始事务期间, 在实例上运行的所有的语句都会成为该事务的一部分.
因此, 总是需要创建一个新的实例来开始事务, 为了复用实例, 意味着需要创建池, 这使解决方案变得复杂.
这是VSCode使用的SQLite3库, 它是sqlite3的fork.
https://www.electronjs.org/docs/latest/tutorial/application-distribution
本质上, 应用程序分发是通过分发满足特定目录结构的预构建Electron二进制包来实现的.
例如, 在Windows上, 人工分发的方法是下载对应版本, 对应平台的预构建好的Electron二进制包,
在Electron目录下创建 resources/app 目录, 然后将整个项目的文件拷贝到此目录下.
之后, 通常还需要对Electron可执行文件改名和更改图标.
官方的electron-packager包用可来简化以上步骤.
Electron专用的一种类似tar的文件格式, 旨在将Electron工程打包成单个文件.
resources/app.asar 文件可以替代 resources/app 文件夹.
一些本机模块在打包成asar文件后会弹出此错误.
在不使用asar打包的情况下, Electron可以正确找到并加载本机模块.
使用 --asar.unpack=*.{node,dll} 替换electron-packager的 --asar 可以解决此问题.
参考:
  • https://github.com/electron/packager/issues/424
  • https://github.com/electron/asar/issues/75
app.isPackaged 被广泛用于判断Electron应用程序是处于开发环境还是生产环境.
然而, 它的判断方式比较反直觉, 它是通过检查Electron可执行文件的文件名是否仍为electron来实现的.
该包用来自动化创建各个平台的应用程序.
  • 支持设置可执行文件图标.
  • 支持删除devDependencies以缩小打包体积.
electron-packager缺少按ignore文件忽略打包路径的特性, 虽然曾经有过 .electronignore 文件的PR, 但是被拒绝
(拒绝的理由缺乏逻辑和事实支撑).
缺乏此特性导致electron-packager变成一个无用的包, 因为除非在命令行里大量添加ignore参数, 否则会将开发环境一同打包.
该项目在启用 --all 选项时对I/O和CPU资源的使用明显高于它所执行任务所需的程度.
检查源代码之后发现, 该程序只是将所有任务并行, 而这在electron v28意味着同时下载10个electron.
考虑到服务器的带宽和下载方的带宽, 这种毫无节制的并行只是在增加遇到网络错误的概率.
Windows安装包的开源解决方案.
由electron-builder支持.
由社区包electron-wix-msi支持.
Windows 8开始支持的UWP格式, 可发布到Microsoft Store.
electron-builder可使用MakeAppx工具将程序打包为appx.
微软于2018年开始支持的主要格式, 意在替代MSI, 用于Windows 10以上系统, 可发布到Microsoft Store.
  • Electron社区还不存在MSIX的打包方案.
  • 打包解决方案提供商Advanced Installer支持将Electron项目打包为MSIX.
  • 微软官方提供的MSIX Packaging Tool可用来将EXE或MSI安装包重新打包为MSIX:
    https://docs.microsoft.com/en-us/windows/msix/packaging-tool/create-app-package
Electron有一个官方维护的基于ChromeDriver的测试库Spectron, 但最终由于缺乏维护者于2022年初被弃用.
Spectron的潜在替代方案是Playwright.
然而, 截止Playwright v1.16, 基于Playwright的测试仍然是实验性功能, 并且在很大程度上不可用:
  • 无法调试错误
  • evaluate不支持导入Node.js模块
一个用于创建窗口覆盖层的库.
一站式开发和打包Electron程序.
一站式打包和分发Electron程序.
文档不一致到了让人觉得可悲的地步.
Electron使用与Node.js不同版本的ABI, 因此无法直接使用npm和yarn以当前Node.js版本为目标下载的native模块,
electron-rebuild是解决这一问题的工具.
出于可维护性方面的考虑, 不建议使用需要electron-rebuild的native模块.
  • electron-rebuild无法选择正确的ABI版本, 这使得依赖electron-rebuild的模块可能发展为一个难以升级的依赖项.
    https://github.com/electron/electron-rebuild/issues/886
  • 尽管属于官方仓库, electron-rebuild却经常不能跟上Electron的最新版本.