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受欢迎, 并且情况不太可能在未来改变.
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.
尽管底层技术相似, NW.js软件的体积要比Electron大.
Neutralinojs不打包Chromium, 而是使用操作系统上的WebView.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
Neutralionjs不支持较旧的Windows系统: https://github.com/neutralinojs/neutralinojs/issues/486
Tauri使用Rust和操作系统的WebView, 不支持Node.js模块, 需要用Rust编写API.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
WebView可想而知会带来兼容性问题, 因此不会被考虑为生产级方案.
除了桌面平台, Tauri还计划支持iOS和Android.
- 由于上下文隔离, 窗口的IPC变得非常麻烦.
- API缺乏统一性, 光是移除窗口的菜单栏就有多种方法.
- 过快的主版本迭代策略.
- 缺乏有用的测试框架.
Electron应用程序的主进程, 是整个应用程序的入口, 一个实例里只能有一个.
Electron应用程序里负责用户界面的进程, 由主进程启动, 一个实例里可以有多个.
渲染器进程在默认情况下不具备直接访问本机Node.js模块的能力,
所有Node.js模块都需要通过由preload脚本创建的上下文隔离接口来间接调用.
所有Node.js模块都需要通过由preload脚本创建的上下文隔离接口来间接调用.
可以在创建窗口时通过
nodeIntegration: true
选项启用渲染器进程的Node.js集成.默认情况下, preload脚本与渲染器进程的宿主环境是隔离的,
两者只能通过
两者只能通过
contextBridge
和 window.postMessage
进行通信.可以通过
contextIsolation: false
取消这一行为.contextBirdge.exposeInMainWorld
是在preload脚本里向渲染器进程的宿主环境暴露API的唯一方法.桥接时可用的数据类型受到限制:
除了基本的可结构化克隆的类型以外, 还允许发送函数, Promise, 错误等对象, 但仍然不能满足常见需求.
除了基本的可结构化克隆的类型以外, 还允许发送函数, 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
https://www.electronjs.org/docs/latest/tutorial/message-ports
适用于main和renderer, renderer和renderer之间的通信.
由于main和renderer之间可以通过ipcMain和ipcRenderer通信,
因此这种通信方式主要是用于renderer和renderer之间的通信.
由于main和renderer之间可以通过ipcMain和ipcRenderer通信,
因此这种通信方式主要是用于renderer和renderer之间的通信.
建立通信前, 首先要通过ipcMain和ipcRenderer传递MessagePort对象.
借助网络协议可以实现IPC, 例如HTTP和WebSocket.
考虑到在Electron里建立Main和Renderer(Web上下文)的通信是如此繁琐,
使用基于网络协议的作为IPC降低复杂性也是一种方法.
使用基于网络协议的作为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
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和基于
底层使用了同作者的conf模块, 支持JSON Schema和基于
package.json
的版本迁移.一个在内存中运行的文档数据库, 间歇性持久化到文件系统.
开发不积极.
纯JavaScript模块, 用JSON作为数据库.
这玩意说白了就是一个用于读取和写入JSON的类, 没有任何亮眼之处.
作者停止维护了.
将RxJS和PouchDB结合起来的一个高级数据库.
由于使用PouchDB的生态系统, 官方支持Electron.
由于使用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处理就能使用.
levelup模块的底层是native模块, 但它原生支持Electron, 因此不需要用electron-rebuild处理就能使用.
PouchDB并非为多进程场景而设计, 有很多基于内存的优化, 并且其Node.js底层LevelDB也不支持多进程.
因此在Electron使用时, 应该只在Main进程里创建它的实例, 然后由IPC暴露给Renderer访问.
因此在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没有使用价值.
在没有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处理后才能使用, 这可能带来依赖锁定问题.
better-sqlite3需要用electron-rebuild处理后才能使用, 这可能带来依赖锁定问题.
这是Ghost团队维护的SQLite3模块.
该模块自v5版本开始基于Node-API重写.
该模块自v5版本开始基于Node-API重写.
callback接口还设置了this, 导致无法使用箭头函数.
该库的每个Database实例只是一个连接, 不存在任何竞争条件.
当开始事务期间, 在实例上运行的所有的语句都会成为该事务的一部分.
因此, 总是需要创建一个新的实例来开始事务, 为了复用实例, 意味着需要创建池, 这使解决方案变得复杂.
当开始事务期间, 在实例上运行的所有的语句都会成为该事务的一部分.
因此, 总是需要创建一个新的实例来开始事务, 为了复用实例, 意味着需要创建池, 这使解决方案变得复杂.
这是VSCode使用的SQLite3库, 它是sqlite3的fork.
https://www.electronjs.org/docs/latest/tutorial/application-distribution
本质上, 应用程序分发是通过分发满足特定目录结构的预构建Electron二进制包来实现的.
例如, 在Windows上, 人工分发的方法是下载对应版本, 对应平台的预构建好的Electron二进制包,
在Electron目录下创建
之后, 通常还需要对Electron可执行文件改名和更改图标.
例如, 在Windows上, 人工分发的方法是下载对应版本, 对应平台的预构建好的Electron二进制包,
在Electron目录下创建
resources/app
目录, 然后将整个项目的文件拷贝到此目录下.之后, 通常还需要对Electron可执行文件改名和更改图标.
官方的electron-packager包用可来简化以上步骤.
Electron专用的一种类似tar的文件格式, 旨在将Electron工程打包成单个文件.
resources/app.asar
文件可以替代 resources/app
文件夹.一些本机模块在打包成asar文件后会弹出此错误.
在不使用asar打包的情况下, Electron可以正确找到并加载本机模块.
在不使用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文件忽略打包路径的特性, 虽然曾经有过
(拒绝的理由缺乏逻辑和事实支撑).
缺乏此特性导致electron-packager变成一个无用的包, 因为除非在命令行里大量添加ignore参数, 否则会将开发环境一同打包.
.electronignore
文件的PR, 但是被拒绝(拒绝的理由缺乏逻辑和事实支撑).
缺乏此特性导致electron-packager变成一个无用的包, 因为除非在命令行里大量添加ignore参数, 否则会将开发环境一同打包.
该项目在启用
检查源代码之后发现, 该程序只是将所有任务并行, 而这在electron v28意味着同时下载10个electron.
考虑到服务器的带宽和下载方的带宽, 这种毫无节制的并行只是在增加遇到网络错误的概率.
--all
选项时对I/O和CPU资源的使用明显高于它所执行任务所需的程度.检查源代码之后发现, 该程序只是将所有任务并行, 而这在electron v28意味着同时下载10个electron.
考虑到服务器的带宽和下载方的带宽, 这种毫无节制的并行只是在增加遇到网络错误的概率.
Windows安装包的开源解决方案.
由electron-builder支持.
Windows 8开始支持的UWP格式, 可发布到Microsoft Store.
electron-builder可使用MakeAppx工具将程序打包为appx.
微软于2018年开始支持的主要格式, 意在替代MSI, 用于Windows 10以上系统, 可发布到Microsoft Store.
- 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的测试仍然是实验性功能, 并且在很大程度上不可用:
然而, 截止Playwright v1.16, 基于Playwright的测试仍然是实验性功能, 并且在很大程度上不可用:
- 无法调试错误
- evaluate不支持导入Node.js模块
一个用于创建窗口覆盖层的库.
一站式开发和打包Electron程序.
一站式打包和分发Electron程序.
文档不一致到了让人觉得可悲的地步.
Electron使用与Node.js不同版本的ABI, 因此无法直接使用npm和yarn以当前Node.js版本为目标下载的native模块,
electron-rebuild是解决这一问题的工具.
electron-rebuild是解决这一问题的工具.
出于可维护性方面的考虑, 不建议使用需要electron-rebuild的native模块.
- electron-rebuild无法选择正确的ABI版本, 这使得依赖electron-rebuild的模块可能发展为一个难以升级的依赖项.
https://github.com/electron/electron-rebuild/issues/886 - 尽管属于官方仓库, electron-rebuild却经常不能跟上Electron的最新版本.