Electron

原名Atom Shell.

参考: 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缺乏统一性, 光是移除窗口的菜单栏就有多种方法.
    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, 可以加载一些开发工具.

专门用于将配置项持久化为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处理后才能使用, 这可能带来依赖锁定问题.

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 文件夹.

app.isPackaged 被广泛用于判断Electron应用程序是处于开发环境还是生产环境.

然而, 它的判断方式比较反直觉, 它是通过检查Electron可执行文件的文件名是否仍为electron来实现的.

该包用来自动化创建各个平台的应用程序.

  • 支持设置可执行文件图标.
  • 支持删除devDependencies以缩小打包体积.

electron-packager缺少按ignore文件忽略打包路径的特性, 虽然曾经有过 .electronignore 文件的PR, 但是被拒绝
(拒绝的理由缺乏逻辑和事实支撑).
缺乏此特性导致electron-packager变成一个无用的包, 因为除非在命令行里大量添加ignore参数, 否则会将开发环境一同打包.

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的最新版本.