简而言之,Electron 使开发人员能够使用 HTML、CSS 和 JavaScript 等 Web 技术构建桌面应用程序。Slack、Visual Studio Code 以及 WhatsApp 的桌面版本都有一个共同点:它们都是使用 Electron 构建的。随着这些强大的公司采用 Electron 取代本地桌面软件开发方法,Electron 已经成为开发桌面应用程序的可靠框架。
在本教程中,你将了解 Electron 的工作原理以及如何使用它来开发桌面应用程序。
- 使用 Web 技术从头开始构建
- 在主进程和渲染器进程之间进行通信
- 使用 Electron API 访问浏览器 API 中不可用的功能
- 显示桌面通知
了解 Electron
Electron 是一个开源的桌面应用程序开发框架,它允许开发者使用Web技术(如HTML、CSS和JavaScript)来构建跨平台的桌面应用程序。它最初由GitHub开发并命名为Atom Shell,后来改名为 Electron。
Electron 具有以下特性:
- 跨平台支持:Electron 可以在多个操作系统上运行,包括Windows、macOS 和 Linux。开发者只需编写一次代码,就可以将应用程序发布到不同的平台上。
- Web 技术栈:Electron 使用 Web 技术作为应用程序的构建基础,开发者可以使用熟悉的 HTML、CSS 和 JavaScript 来构建用户界面和处理应用逻辑。
- 原生功能访问:Electron 提供了一组丰富的 API,允许开发者访问操作系统的原生功能,如文件系统、系统托盘、对话框、剪贴板等。这使得开发者可以为应用程序添加丰富的功能和交互性。
- 自定义样式:开发者可以使用 CSS 和 JavaScript 来自定义应用程序的外观和行为,使其看起来和感觉上更像一个本地应用程序。
- 开放的生态系统:Electron 拥有一个活跃的开发者社区,提供了许多插件和扩展,可以增强应用程序的功能和性能。
- 自动更新:Electron 提供了自动更新机制,使得应用程序可以自动下载和安装最新的版本,无需用户手动更新。
- 调试和工具支持:Electron 集成了开发者工具,包括调试工具、性能分析器和错误报告工具,方便开发者进行调试和测试。
为什么使用 Electron
在 Electron 出现之前,如果一个应用程序需要在两个或更多不同的桌面操作系统(例如 Windows 和 Mac)上安装,你需要使用适用于每个平台的兼容语言(例如 C#或 Visual Basic 适用于 Windows,Objective-C 适用于 Mac)分别开发应用程序。如果开发人员决定选择 Java(使用 Java 开发跨平台桌面软件),这样的应用程序的用户需要在两个平台上安装 Java 运行时来运行应用程序。
然而,通过单个代码库,Electron 可以为所有平台生成安装程序,而不需要任何安装依赖项。因此,一个开发团队可以为目标平台开发一个应用程序。另一个主要优点是,如果你可以构建一个网站,那么你也可以使用 Electron 构建桌面应用程序,因此,现有的 Web 开发人员 / Web 开发团队可以轻松转变为桌面软件的开发人员。
Electron 应用程序的结构
从结构上看,Electron 由三个主要部分组成:
- Chromium:这是 Electron 结构中负责创建和显示网页的组件。Web 内容在 Electron 的渲染器进程中显示(稍后会详细介绍),由于 Chromium 环境,你可以像在普通的 Google Chrome 浏览器中一样访问所有浏览器 API 和开发工具。
- Node.js:这是 Electron 结构中的组件,它使你可以访问系统功能。Electron 在其主进程中运行 Node.js(稍后会详细介绍),使你可以访问 Node.js 提供的所有功能,例如与文件系统、操作系统等交互等等。
- 自定义 API:为了让开发人员创建常见的桌面体验并轻松使用本地功能,Electron 具有易于使用的 API 库,帮助你执行诸如创建和显示上下文菜单、显示桌面通知、使用键盘快捷键等任务。
主进程和渲染器进程
运行中的 Electron 应用程序维护两种类型的进程:主进程和一个或多个渲染器进程。
Electron 应用程序的入口点是主进程,它只是一个 Node.js 环境。这是所有与本机功能的交互发生的地方。
主进程负责创建网页。它通过创建一个 Electron 的 BrowserWindow
对象的新实例来实现这一点。这会创建一个在其自己的渲染器进程中运行的新网页。主进程可以创建多个网页,每个网页在其自己的渲染器进程中运行。
通常,Electron 应用程序使用默认的网页启动,这是应用程序的启动屏幕。如果应用程序需要,你可以创建更多的屏幕。
每个渲染器进程管理自己的网页,并且与其他渲染器进程和主进程本身完全隔离。因此,如果一个渲染器进程终止,它不会影响其他渲染器进程。渲染器进程也可以通过销毁其 BrowserWindow
实例从主进程终止。
渲染器进程只能访问浏览器 API,如 window
和 document
对象等。这是因为渲染器进程只是一个运行中的 Chromium 浏览器实例。但是,可以配置
渲染器进程以访问 Node.js API,如 process
和 require
。
上下文隔离
由于 Electron 允许在一个应用程序中同时运行多个渲染进程,为了确保安全性和稳定性,它引入了上下文隔离的概念。
上下文隔离是指在 Electron 应用程序中,每个渲染进程都运行在独立的 JavaScript 上下文中,不会直接访问其他渲染进程的变量、函数或 DOM 元素。这种隔离有助于避免不同渲染进程之间的干扰和冲突,提高了应用程序的稳定性和安全性。
具体来说,Electron 中的上下文隔离包括以下几个方面:
- 渲染进程隔离: 在 Electron 应用程序中,每个渲染进程(页面)都有自己的 JavaScript 上下文,彼此之间不会直接干扰。这意味着一个渲染进程中的代码不能直接访问其他渲染进程的全局变量或函数。
自 Electron 12 以来,默认情况下已启用上下文隔离,即 contextIsolation: true
,并且它是所有应用程序推荐的安全设置。
- 沙箱环境: 每个渲染进程都运行在一个类似于沙箱的环境中,限制了对操作系统和底层资源的直接访问。这有助于防止恶意代码对系统的不良影响。
从 Electron 20 开始,渲染进程默认启用了沙盒,即 nodeIntegration: false
,无需进一步配置。
- 进程间通信(IPC): 虽然渲染进程之间有隔离,但有时候它们需要相互通信。Electron 提供了一种叫做进程间通信(IPC)的机制,允许不同渲染进程之间安全地交换数据和消息。
使用 ipcMain
和 ipcRenderer
模块分别为主进程和渲染器进程,你可以从一个进程中发出事件,并在另一个进程中侦听事件。你还可以在进程之间传递数据。在本教程后面的练习中,你将使用这些模块在渲染器和主进程之间进行通信。
用于单向通信的 ipcRenderer.send API 也可用于双向通信。 这是在 Electron 7 之前通过 IPC 进行异步双向通信的推荐方式,Electron 7 之后推荐使用 ipcRenderer.invoke 作为处理渲染器进程中双向 IPC 的最佳实践。
- 主进程控制: Electron 应用程序由一个主进程和多个渲染进程组成。主进程通常用于执行底层任务,而渲染进程用于显示用户界面。主进程具有更高的权限,但也需要谨慎处理,以免影响安全性。
构建一个简单的 Electron 项目
现在是时候编写一些代码,亲身体验 Electron 了!在本教程中,你将创建一个简单的桌面应用程序,用于添加任务列表中的项目。目标是从头开始创建一个桌面应用程序并成功运行它。
先决条件
这些是开始使用 Electron 构建应用程序所需的内容:
- 对 HTML、CSS 和 JavaScript 的基本了解
- 在系统上安装 Node.js
- 对 Node.js 的基本了解
创建应用程序的框架
首先,从你首选的父目录运行以下命令,为项目创建一个文件夹,然后进入新文件夹:
mkdir hello-app
cd hello-app
因为 Electron 应用程序本质上是一个运行网页的 Node.js 应用程序,所以你需要运行以下命令来初始化应用程序并创建一个 package.json 文件:
npm init -y
接下来,在项目文件夹的根目录下创建一个 index.html 文件,并添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Electron</title>
</head>
<body>
<h1>Welcome to Electron</h1>
</body>
</html>
上面的 HTML 代码创建了一个简单的网页,其中标题为“Hello Electron”,正文中有一个带有文本“Welcome to Electron”的 h1 标签。
此时,你已经有了一个基本的 Node.js 应用程序。下一步是使用 Electron 将应用程序转换为桌面应用程序。
首先,安装 Electron 库。回到命令提示符,在项目的根目录中运行以下命令:
npm install --D electron
安装完成后,创建一个名为 main.js 的新文件。这将是应用程序的入口点:即主进程脚本。该脚本将执行以下操作:
- 为应用程序的主屏幕创建一个网页
- 在 Electron 应用程序启动时加载应用程序主屏幕
- 如果应用程序的窗口关闭但应用程序仍在运行,则在单击应用程序的图标时加载主屏幕
在你的新文件 main.js 中,首先导入必要的包,然后创建一个函数,该函数的作用是为应用程序的主屏幕创建一个新的网页:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
}
上面的代码块中,从 Electron 包中导入了 app
(Electron 应用程序对象)和 BrowserWindow
(用于创建和加载网页的 Electron 模块)。还导入了 path
模块,使你可以处理项目目录。
在导入之后,创建了 createWindow()
函数。该函数使用 BrowserWindow
对象创建一个新的 1200 像素宽、800 像素高的浏览器窗口,并从项目的根目录加载 index.html 文件。
接下来,在现有代码的下方添加对 createWindow()
函数的调用,以便在应用程序启动后立即调用该函数:
app.whenReady().then(() => {
createWindow()
}
只有在应用程序上发出ready
事件时才会调用createWindow()
。网页需要等待此事件,因为某些 API 只能在此事件发生后使用。
下一步是处理某些操作系统上的一个问题,即在所有窗口关闭后应用程序仍然保持活动状态。这通常发生在非 MacOS 平台上。为了解决这个问题,在 main.js 的现有代码下面添加以下代码:
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
该代码指示应用程序监听 window-all-closed
事件,该事件在主进程创建的所有窗口都关闭后触发。然后,它检查平台是否为 MacOS,如果不是,则显式退出应用程序,结束主进程,从而终止应用程序。
该文件的最后一步是确保当单击操作系统的应用程序坞中的应用程序图标时,应用程序启动,即使没有打开窗口。为了实现这一点,在文件的末尾添加以下代码:
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
此代码监听应用程序上的 activate
事件。当事件被触发时,该代码检查当前是否有属于应用程序的任何窗口处于打开状态。如果没有,则通过调用 createWindow()
函数加载主屏幕。
要让应用程序跑起来,得配置应用程序。
配置应用程序
你需要对 package.json 文件进行一些更改,以确保正确配置与 Electron 的工作方式。
打开 package.json 文件。将 main
键的值更改为 main.js
,如下所示:
"main": "main.js",
接下来,在 scripts
部分添加一个 start
脚本,如下所示:
"scripts": {
"start": "electron ."
}
保存并关闭文件。此时,你可以使用以下命令运行你的新的 Electron 应用程序:
npm start
这将启动应用程序并加载主屏幕:
创建一个简单的待办事项应用
为了解 Electron 的一些其他功能,你将创建一个基本的待办事项应用。
创建页面
首先,向应用程序的主屏幕添加一些基本内容。
打开 index.html 文件,在 body
元素中,h1
标签下面,将以下代码粘贴到两列网格的第一列中:
<body>
<div class="container">
<div class="row">
<div class="col-md-6">
<ul id="list" class="list-group">
<li class="list-group-item">Breakfirst</li>
<li class="list-group-item">Go to work</li>
</ul>
</div>
<div class="col-md-6">
</div>
</div>
</div>
</body>
如果应用程序当前正在运行,请按 Ctrl+C
关闭它,然后通过运行 npm start
重新启动它。
使用 Electron API 显示桌面通知
下面我们将在向任务列表添加新项目时显示通知,目的是演示渲染器进程和主进程之间的通信。
向任务列表中添加新项目
在你的 index.html 文件中,添加一个表单输入和一个按钮元素。用户将使用这些元素与任务列表交互以添加新项目。要添加这些元素,请将下面的代码粘贴到两列网格的第二列中:
// ...
<div class="col-md-6">
<input class="form-control" id="newTask" placeholder="Enter New Task" />
<br />
<button type="button" class="btn btn-primary" id="addTask">
Add Task
</button>
</div>
现在,在项目的根目录中创建一个名为 renderer.js 的新 JavaScript 文件,并将其导入到 index.html 文件中,如下所示:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.5.3/dist/css/bootstrap.css">
<script src="renderer.js"></script>
<title>Todo</title>
</head>
在 renderer.js 文件中,添加以下代码:
let list = document.getElementById('list');
let newTask = document.getElementById('newTask');
document.getElementById('addTask').addEventListener('click', () => {
list.insertAdjacentHTML('beforeend', `<li class='list-group-item'>${newTask.value}</li>`)
newTask.value = '';
});
上面的代码中,将一个 click
事件处理程序添加到你在 index.html 中添加的 button
元素中。当点击按钮时,输入字段的值将被插入到一个新的 <li>
元素中,并追加到任务列表中。
现在,退出应用程序并重新启动它。尝试通过在输入字段中输入并单击 Add Task 按钮来添加几个新项目。
为新添加的项目显示通知
向应用程序添加的最后一个功能是桌面通知。每当向列表中添加新项目时,都会显示一个通知。即使 Electron 可以使用渲染器进程中的HTML 5 通知 API,但在此示例中,我们将使用 Electron 原生通知模块。因此,渲染器进程需要与主进程进行通信才能使通知正常工作。
为了实现这一点,我们将使用 ipcRenderer
和 ipcMain
模块。
- 使用
ipcMain.on
监听事件
在主进程中,使用 ipcMain API 在 notification:show
通道上设置一个 IPC 监听器:
// main.js
ipcMain.on('notification:show', (_event, task) => {
const notification = new Notification({
title: 'New Task',
body: task
});
notification.show();
});
上面的代码块使用 ipcMain.on
模块侦听名为 notification:show
的事件。当该事件在渲染器进程中触发时,主进程将根据传递的任务创建一个新的通知,并显示在桌面上。
- 通过预加载脚本暴露
ipcRenderer.send
要将消息发送到上面创建的监听器,可以使用 ipcRenderer.send API。 默认情况下,渲染器进程没有权限访问 Node.js 和 Electron 模块。需要使用 contextBridge API 来选择要从预加载脚本中暴露哪些 API。
在预加载脚本中添加以下代码,向渲染器进程暴露一个全局的 window.electronAPI 变量。
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
showNotification: (task) => ipcRenderer.send('notification:show', task)
})
此时,我们将能够在渲染器进程中使用 window.electronAPI.showNotification()
函数。
- 重构渲染器进程
在渲染器进程中,ipcRenderer
模块用于将 notification:show
事件与 task
作为有效负载发送到主进程。将 renderer.js 文件重构以反映以下突出显示的更改:
// renderer.js
// ...
document.getElementById("addTask").addEventListener('click', () => {
list.insertAdjacentHTML('beforeend', `<li class="list-group-item">${newTask.value}</li>`);
window.electronAPI.showNotification(newTask.value);
newTask.value = '';
});
我们也可以在渲染器进程中调用ipcRenderer.invoke('notification:show', newTask.value)
,只要 main.js 中设置了contextIsolation: false
和nodeIntegration: true
。但这只是出于演示,并不是最佳实践。
最后,重新启动应用程序并尝试添加新的任务。你应该看到每当添加新任务时,都会显示一个桌面通知。
在 Electron 中,进程使用 ipcMain 和 ipcRenderer 模块,通过开发人员定义的“通道”传递消息来进行通信。本示例使用了单向模式进程间通信。点击这里了解更多 IPC 模式。
总结
通过本教程,你已经学会了使用 Electron 构建桌面应用程序的基础知识。你了解了 Electron 的基本情况以及如何使用它构建一个简单的任务列表应用程序。你还学习了如何在渲染器进程和主进程之间进行通信,并使用 Electron 通知模块显示桌面通知。
这只是 Electron 功能的冰山一角。它还提供了许多其他功能和 API,使你能够构建更复杂和功能丰富的桌面应用程序。继续学习和探索 Electron,发现你可以使用它来实现的更多想法和创意!
评论 (0)