跳至主要内容

构建你的第一个应用

学习目标

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

设置你的项目

避免使用 WSL

如果您使用的是 Windows 机器,请在遵循本教程时不要使用Windows 子系统 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 文件或远程 Web 地址加载。对于此示例,您将加载本地文件。首先在项目根文件夹中的 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 的异步事件驱动架构的 Node.js 事件发射器。app 模块就是其中一个发射器。

在 Electron 中,只有在应用模块的 ready 事件触发后才能创建 BrowserWindow。您可以使用 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 增强渲染器进程,以及如何在进程之间进行通信。