Skip to main content

构建你的第一个应用

学习目标

¥Learning goals

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

¥In this part of the tutorial, you will learn how to set up your Electron project and write a minimal starter application. By the end of this section, you should be able to run a working Electron app in development mode from your terminal.

设置你的项目

¥Setting up your project

避免 WSL

如果你使用的是 Windows 计算机,请在遵循本教程时不要使用 Linux 的 Windows 子系统 (WSL),因为你在尝试执行该应用时会遇到问题。

¥If you are on a Windows machine, please do not use Windows Subsystem for Linux (WSL) when following this tutorial as you will run into issues when trying to execute the application.

初始化你的 npm 项目

¥Initializing your npm project

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

¥Electron apps are scaffolded using npm, with the package.json file as an entry point. Start by creating a folder and initializing an npm package within it with npm init.

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

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

¥This command will prompt you to configure some fields in your package.json. There are a few rules to follow for the purposes of this tutorial:

  • 入口点应该是 main.js(你很快就会创建该文件)。

    ¥entry point should be main.js (you will be creating that file soon).

  • 作者、许可证和描述可以是任何值,但稍后对于 packaging 是必需的。

    ¥author, license, and description can be any value, but will be necessary for packaging later on.

然后,将 Electron 安装到应用的 devDependency 中,这是生产中不需要的外部仅开发包依赖的列表。

¥Then, install Electron into your app's devDependencies, which is the list of external development-only package dependencies not required in production.

为什么 Electron 是一个 devDependency?

这可能看起来违反直觉,因为你的生产代码正在运行 Electron API。然而,打包的应用将与 Electron 二进制文件打包在一起,从而无需将其指定为生产依赖。

¥This may seem counter-intuitive since your production code is running Electron APIs. However, packaged apps will come bundled with the Electron binary, eliminating the need to specify it as a production dependency.

npm install electron --save-dev

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

¥Your package.json file should look something like this after initializing your package and installing Electron. You should also now have a node_modules folder containing the Electron executable, as well as a package-lock.json lockfile that specifies the exact dependency versions to install.

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 失败,请参阅我们的 高级安装 文档,了解有关下载镜像、代理和故障排除步骤的说明。

¥If installing Electron directly fails, please refer to our Advanced Installation documentation for instructions on download mirrors, proxies, and troubleshooting steps.

添加 .gitignore

¥Adding a .gitignore

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

¥The .gitignore file specifies which files and directories to avoid tracking with Git. You should place a copy of GitHub's Node.js gitignore template into your project's root folder to avoid committing your project's node_modules folder.

运行 Electron 应用

¥Running an Electron app

进一步阅读

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

¥Read Electron's process model documentation to better understand how Electron's multiple processes work together.

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

¥The main script you defined in package.json is the entry point of any Electron application. This script controls the main process, which runs in a Node.js environment and is responsible for controlling your app's lifecycle, displaying native interfaces, performing privileged operations, and managing renderer processes (more on that later).

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

¥Before creating your first Electron app, you will first use a trivial script to ensure your main process entry point is configured correctly. Create a main.js file in the root folder of your project with a single line of code:

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

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

¥Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code with the electron command (you can even use it as a REPL). To execute this script, add electron . to the start command in the scripts field of your package.json. This command will tell the Electron executable to look for the main script in the current directory and run it in dev mode.

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 创建用户界面并将其加载到原生窗口中。

¥Your terminal should print out Hello from Electron 👋. Congratulations, you have executed your first line of code in Electron! Next, you will learn how to create user interfaces with HTML and load that into a native window.

将网页加载到 BrowserWindow 中

¥Loading a web page into a BrowserWindow

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

¥In Electron, each window displays a web page that can be loaded either from a local HTML file or a remote web address. For this example, you will be loading in a local file. Start by creating a barebones web page in an index.html file in the root folder of your project:

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://web.nodejs.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 文件的内容替换为以下代码。我们将分别解释每个高亮的块。

¥Now that you have a web page, you can load it into an Electron BrowserWindow. Replace the contents of your main.js file with the following code. We will explain each highlighted block separately.

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

导入模块

¥Importing modules

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

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

¥In the first line, we are importing two Electron modules with CommonJS module syntax:

  • app,它控制应用的事件生命周期。

    ¥app, which controls your application's event lifecycle.

  • BrowserWindow,创建和管理应用窗口。

    ¥BrowserWindow, which creates and manages app windows.

Module capitalization conventions

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

¥You might have noticed the capitalization difference between the app and BrowserWindow modules. Electron follows typical JavaScript conventions here, where PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray, Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents).

Typed import aliases

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

¥For better type checking when writing TypeScript code, you can choose to import main process modules from electron/main.

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

有关详细信息,请参阅 进程模型文档

¥For more information, see the Process Model docs.

Electron 中的 ES 模块

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

¥ECMAScript modules (i.e. using import to load a module) are supported in Electron as of Electron 28. You can find more information about the state of ESM in Electron and how to use them in our app in our ESM guide.

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

¥Writing a reusable function to instantiate windows

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

¥The createWindow() function loads your web page into a new BrowserWindow instance:

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

win.loadFile('index.html')
}

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

¥Calling your function when the app is ready

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

Electron 的许多核心模块都是 Node.js 事件触发器,遵循 Node 的异步事件驱动架构。应用模块就是这些触发器之一。

¥Many of Electron's core modules are Node.js event emitters that adhere to Node's asynchronous event-driven architecture. The app module is one of these emitters.

在 Electron 中,只有在应用模块的 ready 事件被触发后才能创建 BrowserWindows。你可以使用 app.whenReady() API 等待此事件,并在其 promise 履行后调用 createWindow()

¥In Electron, BrowserWindows can only be created after the app module's ready event is fired. You can wait for this event by using the app.whenReady() API and calling createWindow() once its promise is fulfilled.

信息

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

¥You typically listen to Node.js events by using an emitter's .on function.

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

然而,Electron 将 app.whenReady() 公开为专门针对 ready 事件的助手,以避免直接监听该事件时出现微妙的陷阱。详情请参见 electron/electron#21972

¥However, Electron exposes app.whenReady() as a helper specifically for the ready event to avoid subtle pitfalls with directly listening to that event in particular. See electron/electron#21972 for details.

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

¥At this point, running your Electron application's start command should successfully open a window that displays your web page!

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

¥Each web page your app displays in a window will run in a separate process called a renderer process (or simply renderer for short). Renderer processes have access to the same JavaScript APIs and tooling you use for typical front-end web development, such as using webpack to bundle and minify your code or React to build your user interfaces.

管理应用的窗口生命周期

¥Managing your app's window lifecycle

应用窗口在每个操作系统上的行为有所不同。如果你希望遵循这些约定,Electron 不会默认强制执行这些约定,而是让你可以选择在应用代码中实现它们。你可以通过监听应用和 BrowserWindow 模块发出的事件来实现基本的窗口约定。

¥Application windows behave differently on each operating system. Rather than enforce these conventions by default, Electron gives you the choice to implement them in your app code if you wish to follow them. You can implement basic window conventions by listening for events emitted by the app and BrowserWindow modules.

特定于流程的控制流程

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

¥Checking against Node's process.platform variable can help you to run code conditionally on certain platforms. Note that there are only three possible platforms that Electron can run in: win32 (Windows), linux (Linux), and darwin (macOS).

所有窗口关闭后退出应用(Windows 和 Linux)

¥Quit the app when all windows are closed (Windows & Linux)

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

¥On Windows and Linux, closing all windows will generally quit an application entirely. To implement this pattern in your Electron app, listen for the app module's window-all-closed event, and call app.quit() to exit your app if the user is not on macOS.

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

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

¥Open a window if none are open (macOS)

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

¥In contrast, macOS apps generally continue running even without any windows open. Activating the app when no windows are available should open a new one.

要实现此功能,请监听应用模块的 activate 事件,并在没有打开 BrowserWindows 的情况下调用现有的 createWindow() 方法。

¥To implement this feature, listen for the app module's activate event, and call your existing createWindow() method if no BrowserWindows are open.

由于无法在 ready 事件之前创建窗口,因此你应该仅在应用初始化后监听 activate 事件。通过仅监听现有 whenReady() 回调中的激活事件来执行此操作。

¥Because windows cannot be created before the ready event, you should only listen for activate events after your app is initialized. Do this by only listening for activate events inside your existing whenReady() callback.

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

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

最终的起始代码

¥Final starter code

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 进行调试

¥Optional: Debugging from VS Code

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

¥If you want to debug your application using VS Code, you need to attach VS Code to both the main and renderer processes. Here is a sample configuration for you to run. Create a launch.json configuration in a new .vscode folder in your project:

.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"
}
]
}

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

¥The "Main + renderer" option will appear when you select "Run and Debug" from the sidebar, allowing you to set breakpoints and inspect all the variables among other things in both the main and renderer processes.

我们在 launch.json 文件中所做的是创建 3 个配置:

¥What we have done in the launch.json file is to create 3 configurations:

  • Main 用于启动主进程,并公开端口 9222 用于远程调试(--remote-debugging-port=9222)。这是我们用来连接 Renderer 调试器的端口。由于主进程是 Node.js 进程,因此类型设置为 node

    ¥Main is used to start the main process and also expose port 9222 for remote debugging (--remote-debugging-port=9222). This is the port that we will use to attach the debugger for the Renderer. Because the main process is a Node.js process, the type is set to node.

  • Renderer 用于调试渲染器进程。因为主进程是创建进程的进程,所以我们必须对其进行 "attach" ("request": "attach"),而不是创建一个新进程。渲染器进程是一个 Web 进程,所以我们必须使用的调试器是 chrome

    ¥Renderer is used to debug the renderer process. Because the main process is the one that creates the process, we have to "attach" to it ("request": "attach") instead of creating a new one. The renderer process is a web one, so the debugger we have to use is chrome.

  • Main + renderer 是同时执行前面的 复合任务

    ¥Main + renderer is a compound task that executes the previous ones simultaneously.

提醒

由于我们要附加到 Renderer 中的进程,因此可能会跳过代码的第一行,因为调试器在执行代码之前没有足够的时间进行连接。你可以通过在开发模式下执行代码之前刷新页面或设置超时来解决此问题。

¥Because we are attaching to a process in Renderer, it is possible that the first lines of your code will be skipped as the debugger will not have had enough time to connect before they are being executed. You can work around this by refreshing the page or setting a timeout before executing the code in development mode.

进一步阅读

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

¥If you want to dig deeper in the debugging area, the following guides provide more information:

概括

¥Summary

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

¥Electron applications are set up using npm packages. The Electron executable should be installed in your project's devDependencies and can be run in development mode using a script in your package.json file.

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

¥The executable runs the JavaScript entry point found in the main property of your package.json. This file controls Electron's main process, which runs an instance of Node.js and is responsible for your app's lifecycle, displaying native interfaces, performing privileged operations, and managing renderer processes.

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

¥Renderer processes (or renderers for short) are responsible for displaying graphical content. You can load a web page into a renderer by pointing it to either a web address or a local HTML file. Renderers behave very similarly to regular web pages and have access to the same web APIs.

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

¥In the next section of the tutorial, we will be learning how to augment the renderer process with privileged APIs and how to communicate between processes.