跳转到主要内容

构建你的第一个应用

学习目标

在本教程的这一部分中,你将学习如何设置你的 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 中的一些字段。为了本教程的目的,需要遵循一些规则

  • entry point 应该为 main.js(你很快就会创建该文件)。
  • authorlicensedescription 可以是任何值,但对于稍后 打包 而言是必需的。
使用常规 node_modules 文件夹安装依赖项

Electron 的打包工具链要求 node_modules 文件夹以 npm 安装 Node 依赖项的方式物理地存在于磁盘上。默认情况下,Yarn Berrypnpm 都使用替代的安装策略。

因此,如果你正在使用这些包管理器,则必须在 Yarn 中设置 nodeLinker: node-modules 或在 pnpm 中设置 nodeLinker: hoisted

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

为什么 Electron 是一个开发依赖项?

这可能看起来违反直觉,因为你的生产代码正在运行 Electron API。在底层,Electron 的 JavaScript API 绑定到一个包含其实现的二进制文件。Electron 的打包步骤处理此二进制文件的捆绑,无需将其指定为生产依赖项。

npm install electron --save-dev
警告

为了正确安装 Electron,你需要确保其 postinstall 生命周期脚本能够运行。这意味着避免在 npm 上使用 --ignore-scripts 标志,并在其他包管理器上允许列出 electron 以运行构建脚本。

这可能会在 Electron 的未来版本中发生变化。有关更多详细信息,请参阅 electron/rfcs#22

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

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 字段中。此命令将告诉 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 模块

ECMAScript 模块(即使用 import 加载模块)在 Electron 28 中得到支持。你可以在我们的 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 中,BrowserWindows 只能在 app 模块的 ready 事件触发后创建。你可以使用 app.whenReady() API 并调用 createWindow() 一旦其 promise 得到满足,来等待此事件。

信息

你通常使用发射器的 .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 事件,并调用 app.quit() 以退出您的应用程序(如果用户不在 macOS 上)。

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