跳到主要内容

构建你的第一个应用

学习目标

在本教程的这一部分中,你将学习如何设置你的 Electron 项目并编写一个最简化的入门应用程序。在本节结束时,你应该能够从你的终端在开发模式下运行一个可工作的 Electron 应用程序。

设置你的项目

避免使用 WSL

如果你使用的是 Windows 机器,请在遵循本教程时不要使用 Windows Subsystem for Linux (WSL),因为在尝试执行应用程序时你会遇到问题。

初始化你的 npm 项目

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

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

此命令将提示你配置 package.json 中的一些字段。出于本教程的目的,需要遵循以下几条规则

  • 入口点 应为 main.js(你将很快创建该文件)。
  • 作者许可证描述 可以是任何值,但对于稍后的 打包 是必要的。

然后,将 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)。要执行此脚本,请将 electron . 添加到 package.json 的 scripts 字段中的 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 及更高版本支持 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 中,只有在 app 模块的 ready 事件触发后才能创建 BrowserWindow。你可以使用 app.whenReady() API 等待此事件,并在其 Promise 完成后调用 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"
}
]
}

当你从侧边栏选择“运行和调试”时,“Main + renderer”选项将出现,允许你设置断点并检查主进程和渲染器进程中的所有变量以及其他内容。

我们在 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 增强渲染器进程,以及如何在进程之间进行通信。