使用 Electron 构建桌面应用

Electron 是一个基于 Node.js,并拥有一个 Chromium「外壳」的桌面应用开发框架。你可以调用所有 Node.js 和浏览器的 API,使用 JavaScript,HTML 和 CSS 等 Web 技术创建原生程序。根据官方的宣传语:「它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可。」
下面介绍如何使用 Electron 构建简单的桌面应用。你可以参照官方给出的入门程序:

1
git clone https://github.com/electron/electron-quick-start.git

安装

Electron 可以通过 npm install electron 来进行安装。在依赖包安装完成后,Electron 会开始下载它的「本体」—— 一个数十 MiB 大小的压缩包,包含 Electron 在不同平台下的可执行文件。这一步对于国内的开发者不太友好,因为下载的内容在 Amazon 云上,访问速度不佳;而且新版的 Electron 去除了下载进度条,导致即使出现问题,你也无法知晓卡在了哪一步。一种简单的解决方案是通过设定环境变量来使用淘宝源:

1
ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"

设置此环境变量后,再执行 npm install,下载就是正常的了。

开始构建

一个 Electron 应用应包含以下内容:

main.js

当你启动 Electron 程序时,这是最先被执行的脚本。在 main.js 中可以创建窗口对象以构建图形界面。一个典型的 main.js 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const { app, BrowserWindow } = require("electron");

function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadFile("index.html");
}

app.whenReady().then(() => {
createWindow();

app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});

app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});

mainWindow 即为 BrowserWindow 窗口对象。mainWindow 有很多参数可以设置,例如,可以将 widthheight 替换为

1
2
width: electron.screen.getPrimaryDisplay().workAreaSize.width,
height: electron.screen.getPrimaryDisplay().workAreaSize.height

来实现全屏。在创建了窗口对象后,可以使用

1
mainWindow.setAlwaysOnTop(true, "");

来使应用置顶(在 macOS 下,第二个参数将决定置顶的级别,例如,第二个参数取 "floating" 会使窗口置于大部分应用之前,但会被 KeyNote 等级别更高的应用挡住;而取 "main-menu" 则即使在演示 KeyNote 该窗口也能置于顶层)。 如果窗口设置了全屏置顶,就需要配合以下代码使用:

1
mainWindow.setIgnoreMouseEvents(true);

这样可以使窗口忽略鼠标事件,否则就不能正常操作其他窗口了。
process.platform !== "darwin" 的作用是,在 macOS 上,关闭所有窗口并不意味着退出程序 —— 你可以点击 Dock 中的程序图标唤醒它(即触发 activate 事件);而在 Windows 中,关闭所有窗口的默认行为是直接退出程序。

index.html

这是窗口的 HTML 文件。在 main.js 中,使用了 mainWindow.loadFile() 方法来加载页面内容。loadFileindex.html 也可以替换为任何自定义页面。
在创建窗口后,index.html 会在 Electron 创建的 Chromium 环境下加载,接下来的工作就是进行前端设计,来构建桌面应用的页面。
在这个 index.html 中,你可以使用 <script> 标签来加载 JavaScript 代码,就像平常的 HTML 页面一样。
前面 main.js 中有这样一串代码:

1
2
3
webPreferences: {
nodeIntegration: true
}

在设定 nodeIntegration: true 之后,这里的 JS 不仅可以访问 DOM,还能使用 Node.js 所有的 API。能前能后,想怎么玩都行。你甚至可以使用 jQuery,react.jsvue.js 等框架。不过需要注意的是,如果使用 jQuery,最好不要像这样加载

1
<script src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js"></script>

此时由于 module 的冲突,jQuery 并没有被定义为全局变量,也无法通过 window.$ 被访问和使用。
推荐的做法是通过 npm 安装 jQuery:

1
npm install jquery

然后在页面中用 CommonJS 模块的方式加载

1
window.jQuery = window.$ = require("jquery");

如果你需要更多的页面,可以创建 HTML 文件并使用 new BrowserWindow() 加载这些页面。

dialog.showMessage() 方法

还要注意的是,alert() 会导致渲染进程阻塞(在全屏状态下使用会有严重问题),应该使用

1
2
3
4
const { dialog } = require("electron").remote;
dialog.showMessageBox({
message: msg
});

像这样调用 dialog.showMessage() 方法显示消息,msg 参数就是消息的内容。这是异步的,对应的同步方法是 dialog.showMessageBoxSync()。它和 alert() 一样会阻止其它代码的执行。
相关内容可以查看:
https://github.com/electron/electron/pull/17298
https://github.com/electron/electron/issues/20855

页面间通讯

此外,Electron 提供了在不同页面间进行通讯的 API,在父页面使用

1
mainWindow.webContents.send(channel, messageContent);

发送消息,其中 channel 是任意字符串,表示通信频道,messageContent 是消息内容。在子页面使用

1
2
3
4
const { ipcRenderer } = require("electron");
ipcRenderer.on(channel, (event, message) => {
console.log(message);
});

监听消息即可。

禁止拖拽文件

在旧版的 Electron 中,将一个 HTML 文件拖拽进页面中,会导致页面跳转。这一行为有些不太友好(因为 Electron 没有浏览器的返回键),需要通过设置 addEventListener 来禁用掉这一行为。不过这也被官方注意到,并且修复了:
https://github.com/electron/electron/pull/12655
现在应该不会遇到由于拖拽造成的问题了。

打包

在设计完成后,使用命令 npx electron . 即可运行。如果是全局安装的 electron,则执行 electron .

如果需要将应用打包,可以使用 electron-builder(用 npm 安装),在 Windows 系统下就会默认打包成.exe 格式,macOS 则为.dmg。当然,在 macOS 上打包 Windows 版本的可执行文件也不是问题,具体可以看文档。macOS 10.15 出现了一个小 bug,不过影响不大,将 electron-builder 升级到最新就能解决:
https://github.com/electron-userland/electron-builder/issues/4305#issuecomment-559138959

发挥你的想象力,便能使用 Electron 构建功能丰富的跨平台应用啦!


参考文章:
使用 electron 构建跨平台 Node.js 桌面应用经验分享
Electron API 演示

官方文档:Electron 文档