Skip to main content

构建你的第一个应用

按照教程进行操作

这是 Electron 教程的第二部分

🌐 This is part 2 of the Electron tutorial.

  1. 先决条件
  2. 构建你的第一个应用
  3. 使用预加载脚本
  4. 添加功能
  5. 打包你的应用
  6. 发布与更新

学习目标

🌐 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:

  • entry point 应该是 main.js(你很快就会创建该文件)。
  • authorlicensedescription 可以是任意值,但以后在 封装 中将是必要的。
使用常规的 node_modules 文件夹安装依赖

Electron 的打包工具链要求 node_modules 文件夹必须像 npm 安装 Node 依赖那样实际存在于磁盘上。默认情况下, Yarn Berrypnpm 都使用了替代的安装策略。

🌐 Electron's packaging toolchain requires the node_modules folder to be physically on disk in the way that npm installs Node dependencies. By default, Yarn Berry and pnpm both use alternative installation strategies.

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

🌐 Therefore, you must set nodeLinker: node-modules in Yarn or nodeLinker: hoisted in pnpm if you are using those package managers.

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

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

为什么 Electron 是开发依赖?

这可能看起来不合常理,因为你的生产代码正在运行 Electron API。在底层,Electron 的 JavaScript API 绑定到包含其实现的二进制文件。Electron 的打包步骤处理了这个二进制文件的打包,消除了将其指定为生产依赖的必要性。

🌐 This may seem counter-intuitive since your production code is running Electron APIs. Under the hood, Electron's JavaScript API binds to a binary that contains its implementations. The packaging step for Electron handles the bundling of this binary, eliminating the need to specify it as a production dependency.

npm install electron --save-dev
warning

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

🌐 In order to correctly install Electron, you need to ensure that its postinstall lifecycle script is able to run. This means avoiding the --ignore-scripts flag on npm and allowlisting electron to run build scripts on other package managers.

这在未来的 Electron 版本中可能会发生变化。更多详情请参见 electron/rfcs#22

🌐 This is likely to change in a future version of Electron. See electron/rfcs#22 for more details.

在初始化你的 package 并安装 Electron 之后,你的 package.json 文件应该类似如下。你现在还应该有一个 node_modules 文件夹,其中包含 Electron 可执行文件,以及一个 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 代码(你甚至可以将其用作 交互式编程环境)。要执行此脚本,请在 package.json 的 scripts 字段中将 electron . 添加到 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 浏览器窗口 中。将你的 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 和 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).

类型化导入别名

为了在编写 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')

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

Electron 中的 ES 模块

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

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

🌐 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 异步事件驱动架构的 Node.js 事件触发器 模块。app 模块就是这些事件触发器之一。

🌐 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 中,BrowserWindow 只能在应用模块的 ready 事件触发后创建。你可以使用 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.

info

通常,你可以使用触发器的 .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!

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

🌐 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 并不会默认强制执行这些约定,而是允许你在应用代码中选择是否遵循它们。你可以通过监听由 app 和 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 应用中实现这一模式,请监听 app 模块的 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 事件,如果没有打开的 BrowserWindow,就调用你现有的 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
  • Renderer 用于调试渲染进程。因为主进程是创建该进程的,所以我们必须“附加”到它("request": "attach"),而不是创建一个新的进程。渲染进程是一个网页进程,因此我们需要使用的调试器是 chrome
  • Main + renderer 是一个可以同时执行前面任务的 复合任务
caution

因为我们正在附加到 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。

在教程的下一部分,我们将学习如何使用特权 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.