跳到主要内容

构建你的第一个应用

跟着教程一起操作

学习目标

在本部分教程中,你将学习如何设置 Electron 项目并编写一个最小化的入门应用。完成本节后,你应该能够从终端以开发模式运行一个工作的 Electron 应用。

设置你的项目

避免使用 WSL

如果你在 Windows 机器上,请在按照本教程操作时避免使用 适用于 Linux 的 Windows 子系统 (WSL),因为尝试执行应用时会遇到问题。

初始化你的 npm 项目

Electron 应用使用 npm 构建,package.json 文件作为入口点。首先创建一个文件夹并在其中使用 npm init 初始化一个 npm 包。

mkdir my-electron-app && cd my-electron-app
npm init

该命令会提示你配置 package.json 中的一些字段。出于本教程的目的,需要遵循一些规则

  • 入口点 (entry point) 应该是 main.js (你很快就会创建这个文件)。
  • 作者 (author)许可证 (license)描述 (description) 可以是任何值,但在后续的打包中是必需的。

然后,将 Electron 安装到你的应用的 devDependencies 中,这是仅用于开发的不需要在生产环境中使用的外部包依赖项列表。

为什么 Electron 是 devDependency?

这看起来可能违反直觉,因为你的生产代码正在运行 Electron API。但是,打包的应用将捆绑 Electron 二进制文件,因此无需将其指定为生产依赖项。

npm install electron --save-dev

初始化包并安装 Electron 后,你的 package.json 文件应如下所示。你现在应该还有一个包含 Electron 可执行文件的 node_modules 文件夹,以及一个指定要安装的确切依赖项版本的 package-lock.json 锁文件。

package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
高级 Electron 安装步骤

如果直接安装 Electron 失败,请参阅我们的高级安装文档,了解有关下载镜像、代理和故障排除步骤的说明。

添加 .gitignore

.gitignore 文件指定要避免使用 Git 跟踪的文件和目录。你应该将 GitHub 的 Node.js gitignore 模板 复制一份放到你的项目根文件夹中,以避免提交项目的 node_modules 文件夹。

运行 Electron 应用

延伸阅读

阅读Electron 的进程模型文档,以更好地了解 Electron 的多进程如何协同工作。

你在 package.json 中定义的 main 脚本是任何 Electron 应用的入口点。这个脚本控制着主进程,主进程运行在 Node.js 环境中,负责控制应用的生命周期、显示原生界面、执行特权操作以及管理渲染器进程 (稍后会详细介绍)。

在创建你的第一个 Electron 应用之前,你将首先使用一个简单的脚本来确保主进程入口点配置正确。在项目根文件夹中创建一个 main.js 文件,只包含一行代码

main.js
console.log('Hello from Electron 👋')

由于 Electron 的主进程是一个 Node.js 运行时,你可以使用 electron 命令执行任意 Node.js 代码 (甚至可以将其用作 REPL)。要执行此脚本,请在 package.json 的 scripts 字段中将 electron . 添加到 start 命令中。此命令将指示 Electron 可执行文件在当前目录中查找主脚本,并在开发模式下运行它。

package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
npm run start

你的终端应该打印出 Hello from Electron 👋。恭喜,你已经在 Electron 中执行了你的第一行代码!接下来,你将学习如何使用 HTML 创建用户界面并将其加载到原生窗口中。

将网页加载到 BrowserWindow 中

在 Electron 中,每个窗口都显示一个网页,可以从本地 HTML 文件或远程网址加载。在此示例中,你将加载一个本地文件。首先在项目根文件夹中创建一个最简单的 index.html 文件

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://mdn.org.cn/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>

现在你有了网页,可以将其加载到 Electron BrowserWindow 中。将 main.js 文件的内容替换为以下代码。我们将分别解释每个高亮的代码块。

main.js
const { app, BrowserWindow } = require('electron')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

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

导入模块

main.js (第 1 行)
const { app, BrowserWindow } = require('electron')

在第一行中,我们使用 CommonJS 模块语法导入了两个 Electron 模块

  • app,用于控制应用的事件生命周期。
  • BrowserWindow,用于创建和管理应用窗口。
模块大小写命名约定

你可能已经注意到 app 和 BrowserWindow 模块之间的大小写差异。Electron 在这里遵循典型的 JavaScript 约定,PascalCase 模块是可实例化的类构造函数 (例如 BrowserWindow、Tray、Notification),而 camelCase 模块则不可实例化 (例如 app、ipcRenderer、webContents)。

类型化导入别名

为了在使用 TypeScript 编写代码时获得更好的类型检查,你可以选择从 electron/main 导入主进程模块。

const { app, BrowserWindow } = require('electron/main')

更多信息请参阅进程模型文档

Electron 中的 ES 模块

自 Electron 28 起,Electron 支持ECMAScript 模块 (即使用 import 加载模块)。你可以在我们的 ESM 指南中找到有关 Electron 中 ESM 现状以及如何在应用中使用它们的更多信息。

编写一个可重用函数来实例化窗口

createWindow() 函数将你的网页加载到一个新的 BrowserWindow 实例中

main.js (第 3-10 行)
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

当应用就绪时调用你的函数

main.js (第 12-14 行)
app.whenReady().then(() => {
createWindow()
})

Electron 的许多核心模块都是 Node.js 事件发射器,它们遵循 Node 的异步事件驱动架构。app 模块就是其中之一。

在 Electron 中,BrowserWindow 只能在 app 模块的 ready 事件触发后创建。你可以通过使用 app.whenReady() API 等待此事件,并在其 Promise fulfilled 后调用 createWindow()

信息

你通常通过使用发射器的 .on 函数来监听 Node.js 事件。

+ app.on('ready', () => {
- app.whenReady().then(() => {
createWindow()
})

然而,Electron 提供了 app.whenReady() 作为专门用于 ready 事件的辅助方法,以避免直接监听该事件时可能出现的细微陷阱。详见 electron/electron#21972

此时,运行 Electron 应用的 start 命令应该能成功打开一个显示你网页的窗口!

你的应用在窗口中显示的每个网页都将在一个单独的进程中运行,称为渲染器进程 (简称渲染器)。渲染器进程可以访问你在典型的前端 Web 开发中使用的相同 JavaScript API 和工具,例如使用 webpack 打包和压缩代码,或使用 React 构建用户界面。

管理应用窗口的生命周期

应用窗口在不同的操作系统上有不同的行为。Electron 没有默认强制执行这些约定,而是让你选择是否在应用代码中实现它们。你可以通过监听 app 和 BrowserWindow 模块发出的事件来实现在线窗口约定。

进程特定的控制流

检查 Node 的 process.platform 变量可以帮助你在特定平台上条件性地运行代码。注意,Electron 可以运行的平台只有三种:win32 (Windows)、linux (Linux) 和 darwin (macOS)。

关闭所有窗口时退出应用 (Windows & Linux)

在 Windows 和 Linux 上,关闭所有窗口通常会完全退出应用。要在你的 Electron 应用中实现此模式,请监听 app 模块的 window-all-closed 事件,并在用户不在 macOS 上时调用 app.quit() 退出应用。

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

如果没有窗口打开则打开一个新窗口 (macOS)

相比之下,macOS 应用通常即使没有窗口打开也会继续运行。当没有可用窗口时激活应用应该打开一个新窗口。

要实现此功能,请监听 app 模块的 activate 事件,如果没有任何 BrowserWindow 打开,则调用你现有的 createWindow() 方法。

由于窗口无法在 ready 事件之前创建,因此你只应该在应用初始化后监听 activate 事件。为此,只需在你现有的 whenReady() 回调中监听 activate 事件即可。

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

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

最终入门代码

const { app, BrowserWindow } = require('electron/main')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.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()
}
})

可选:从 VS Code 调试

如果你想使用 VS Code 调试你的应用,你需要将 VS Code 附加到主进程和渲染器进程。这里有一个供你运行的示例配置。在项目的 .vscode 文件夹中创建一个 launch.json 配置文件

.vscode/launch.json
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}

当你从侧边栏选择“运行和调试”时,将出现“主进程 + 渲染器”选项,允许你在主进程和渲染器进程中设置断点、检查所有变量等。

我们在 launch.json 文件中创建了 3 个配置

  • Main 用于启动主进程并暴露 9222 端口用于远程调试 (--remote-debugging-port=9222)。这是我们将用于附加 Renderer 调试器的端口。由于主进程是一个 Node.js 进程,其类型设置为 node
  • Renderer 用于调试渲染器进程。由于主进程是创建此进程的那个,因此我们必须“附加”到它 ("request": "attach"),而不是创建一个新的。渲染器进程是 Web 进程,因此我们必须使用 chrome 调试器。
  • Main + renderer 是一个复合任务,它同时执行前面的任务。
注意

因为我们在 Renderer 中附加到一个进程,所以在执行代码之前,调试器可能没有足够的时间连接,从而导致代码的前几行被跳过。你可以通过刷新页面或在开发模式下执行代码之前设置一个超时来解决这个问题。

延伸阅读

如果你想深入了解调试领域,以下指南提供了更多信息

总结

Electron 应用使用 npm 包进行设置。Electron 可执行文件应该安装在你项目的 devDependencies 中,并且可以使用 package.json 文件中的脚本以开发模式运行。

可执行文件会运行 package.json 中 main 属性指定的 JavaScript 入口点。此文件控制 Electron 的主进程,主进程运行 Node.js 实例,负责应用的生命周期、显示原生界面、执行特权操作以及管理渲染器进程。

渲染器进程 (简称渲染器) 负责显示图形内容。你可以通过指向 Web 地址或本地 HTML 文件将网页加载到渲染器中。渲染器的行为与普通网页非常相似,并且可以访问相同的 Web API。

在教程的下一部分中,我们将学习如何使用特权 API 增强渲染器进程以及如何在进程之间进行通信。