Skip to main content

Building your First App构建您的第一个应用程序

Learning goals学习目标

In this part of the tutorial, you will learn how to set up your Electron project and write a minimal starter application. 在本教程的这一部分中,您将学习如何设置您的Electron项目并编写一个最小的入门应用程序。By the end of this section, you should be able to run a working Electron app in development mode from your terminal.在本节结束时,您应该能够从终端以开发模式运行一个工作的Electron应用程序。

Setting up your project设置您的项目

Avoid 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.如果您在Windows计算机上,请不要在遵循本教程时使用Windows Linux子系统(WSL),因为您在尝试执行应用程序时会遇到问题。

Initializing your npm project初始化您的npm项目

Electron apps are scaffolded using npm, with the package.json file as an entry point. Electron应用程序使用npm构建,以package.json文件作为入口点。Start by creating a folder and initializing an npm package within it with npm init.首先创建一个文件夹,并使用npm init初始化其中的npm包。

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

This command will prompt you to configure some fields in your package.json. 此命令将提示您在package.json中配置一些字段。There are a few rules to follow for the purposes of this tutorial:在本教程中,有一些规则需要遵循:

  • entry point should be main.js (you will be creating that file soon).入口点应该是main.js(您将很快创建该文件)。
  • author, license, and description can be any value, but will be necessary for packaging later on.作者许可证描述可以是任何值,但在以后的打包中是必需的。

Then, install Electron into your app's devDependencies, which is the list of external development-only package dependencies not required in production.然后,将Electron安装到应用程序的devDependencies中,这是生产中不需要的外部开发包依赖项列表。

Why is Electron a devDependency?为什么Electron是devDependency?

This may seem counter-intuitive since your production code is running Electron APIs. 由于您的生产代码运行的是ElectronAPI,因此这看起来可能违反直觉。However, packaged apps will come bundled with the Electron binary, eliminating the need to specify it as a production dependency.然而,打包应用程序将与Electron二进制文件捆绑在一起,无需将其指定为生产依赖项。

npm install electron --save-dev

Your package.json file should look something like this after initializing your package and installing Electron. 初始化包并安装Electron后,您的package.json文件应该如下所示。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.现在,您还应该有一个包含Electron可执行文件的node_modules文件夹,以及一个指定要安装的确切依赖版本的package-lock.json锁文件。

package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "19.0.0"
}
}
Advanced Electron installation steps高级Electron安装步骤

If installing Electron directly fails, please refer to our Advanced Installation documentation for instructions on download mirrors, proxies, and troubleshooting steps.如果直接安装Electron失败,请参阅高级安装文档,了解有关下载镜像、代理和故障排除步骤的说明。

Adding a 添加.gitignore

The .gitignore file specifies which files and directories to avoid tracking with Git. .gitignore文件指定要避免使用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.您应该将GitHub的Node.js gitignore模板的副本放在项目的根文件夹中,以避免提交项目的node_modules文件夹。

Running an Electron app运行Electron应用程序

Further reading进一步阅读

Read Electron's process model documentation to better understand how Electron's multiple processes work together.阅读Electron的过程模型文档,以更好地了解Electron多个过程如何协同工作。

The main script you defined in package.json is the entry point of any Electron application. package.json中定义的脚本是任何Electron应用程序的入口点。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).该脚本控制主进程,主进程在Node.js环境中运行,负责控制应用程序的生命周期、显示本机接口、执行特权操作和管理渲染器进程(稍后将详细介绍)。

Before creating your first Electron app, you will first use a trivial script to ensure your main process entry point is configured correctly. 在创建第一个Electron应用程序之前,您将首先使用一个简单的脚本来确保主流程入口点配置正确。Create a main.js file in the root folder of your project with a single line of code:使用一行代码在项目的根文件夹中创建main.js文件:

main.js
console.log(`Hello from 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). 因为Electron的主进程是Node.js运行时,所以您可以使用electron命令执行任意Node.js代码(甚至可以将其用作REPL)。To execute this script, add electron . to the start command in the scripts field of your package.json. 要执行此脚本,请在package.json的脚本字段中的start命令中添加electronThis command will tell the Electron executable to look for the main script in the current directory and run it in dev mode.该命令将告诉Electron可执行文件在当前目录中查找主脚本,并在开发模式下运行。

package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^19.0.0"
}
}
npm run start

Your terminal should print out Hello from Electron 👋. 终端将打印出Hello from Electron 👋Congratulations, you have executed your first line of code in Electron! 祝贺您,您已经在Electron中执行了第一行代码!Next, you will learn how to create user interfaces with HTML and load that into a native window.接下来,您将学习如何使用HTML创建用户界面,并将其加载到本机窗口中。

Loading a web page into a BrowserWindow将网页加载到BrowserWindow

In Electron, each window displays a web page that can be loaded either from a local HTML file or a remote web address. 在Electron中,每个窗口都显示一个网页,可以从本地HTML文件或远程web地址加载。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文件中创建一个基本网页:

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- http://mdn.asprain.cn/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>

Now that you have a web page, you can load it into an Electron BrowserWindow. 现在您有了一个网页,您可以将其加载到ElectronBrowserWindow中。Replace the contents your main.js file with the following code. 用以下代码替换main.js文件的内容。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')

In the first line, we are importing two Electron modules with CommonJS module syntax:在第一行中,我们使用CommonJS模块语法导入两个Electron模块:

  • app, which controls your application's event lifecycle.,它控制应用程序的事件生命周期。
  • BrowserWindow, which creates and manages app windows.,创建和管理应用程序窗口。
Capitalization conventions资本化惯例

You might have noticed the capitalization difference between the app and BrowserWindow modules. 您可能已经注意到app和BrowserWindow模块之间的大小写差异。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).Electron在这里遵循典型的JavaScript约定,其中PascalCase模块是可实例化的类构造函数(例如BrowserWindow、Tray、Notification),而camelCase模块不是可实例化的(例如app、ipcRenderer、webContents)。

ES Modules in Electron

ECMAScript modulesECMAScript模块 (i.e. using import to load a module) are currently not directly supported in Electron. (即,使用import加载模块)目前在Electron中不直接支持。You can find more information about the state of ESM in Electron in electron/electron#21457.您可以在electron/electron#21457中找到有关Electron中ESM状态的更多信息。

Writing a reusable function to instantiate windows编写可重用函数来实例化windows

The createWindow() function loads your web page into a new BrowserWindow instance:createWindow()函数的作用是:将网页加载到新的BrowserWindow实例中:

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

Many of Electron's core modules are Node.js event emitters that adhere to Node's asynchronous event-driven architecture. Electron的许多核心模块都是遵守Node异步事件驱动架构的Node.js事件发射器The app module is one of these emitters.应用程序模块就是这些发射器之一。

In Electron, BrowserWindows can only be created after the app module's ready event is fired. 在Electron中,浏览器窗口只能在应用程序模块的ready事件触发后创建。You can wait for this event by using the app.whenReady() API and calling createWindow() once its promise is fulfilled.您可以使用app.whenReady()API等待此事件,并在其承诺实现后调用createWindow()

info

You typically listen to Node.js events by using an emitter's .on function.您通常通过使用发射器的.on函数来监听Node.js事件。

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

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. 然而,Electron公开了app.whenReady()作为专门针对ready事件的助手,以避免直接监听该事件时的微妙陷阱。See electron/electron#21972 for details.详见electron/electron#21972

At this point, running your Electron application's start command should successfully open a window that displays your web page!此时,运行Electron应用程序的start命令应该会成功打开一个显示网页的窗口!

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进程(简称为渲染器)。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.渲染器进程可以访问与典型前端web开发相同的JavaScript API和工具,例如使用webpack捆绑和缩小代码或使用React以构建用户界面。

Managing your app's window lifecycle管理应用程序的窗口生命周期

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. 与默认情况下强制执行这些约定不同,如果您希望遵循这些约定,Electron为您提供了在应用程序代码中实现这些约定的选择。You can implement basic window conventions by listening for events emitted by the app and BrowserWindow modules.您可以通过监听应用程序和浏览器窗口模块发出的事件来实现基本窗口约定。

Process-specific control flow过程特定控制流

Checking against Node's process.platform variable can help you to run code conditionally on certain platforms. 检查Node的process.platform变量可以帮助您在某些平台上有条件地运行代码。Note that there are only three possible platforms that Electron can run in: win32 (Windows), linux (Linux), and darwin (macOS).注意,只有三种可能的平台可以运行Electron:win32(Windows)、linux(linux)和darwin(macOS)。

Quit the app when all windows are closed (Windows & Linux)关闭所有窗口(windows和Linux)后退出应用程序

On Windows and Linux, closing all windows will generally quit an application entirely. 在Windows和Linux上,关闭所有窗口通常会完全退出应用程序。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.要在Electron应用程序中实现此模式,请监听应用程序模块的window-all-closed事件,如果用户不在macOS上,则调用app.quit()退出应用程序。

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

Open a window if none are open (macOS)如果没有打开窗口,则打开窗口(macOS)

In contrast, macOS apps generally continue running even without any windows open. 相比之下,macOS应用程序通常会继续运行,即使没有打开任何窗口。Activating the app when no windows are available should open a new one.当没有可用窗口时激活应用程序应打开一个新窗口。

To implement this feature, listen for the app module's activate event, and call your existing createWindow() method if no BrowserWindows are open.要实现此功能,请侦听应用程序模块的激活事件,如果没有打开浏览器窗口,则调用现有的createWindow()方法。

Because windows cannot be created before the ready event, you should only listen for activate events after your app is initialized. 由于无法在ready事件之前创建windows,因此您只能在应用程序初始化后侦听activate事件。Do this by only listening for activate events inside your existing whenReady() callback.通过只监听现有whenReady()回调中的激活事件来实现这一点。

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

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

Final starter code最终启动码

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

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

Optional: Debugging from VS Code可选:从VS Code进行调试

If you want to debug your application using VS Code, you need to attach VS Code to both the main and renderer processes. 如果希望使用VS Code调试应用程序,则需要将VS Code附加到主进程和渲染器进程。Here is a sample configuration for you to run. 下面是要运行的示例配置。Create a launch.json configuration in a new .vscode folder in your project:在项目中的新.vscode文件夹中创建启动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": "pwa-chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "pwa-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.当您从侧栏中选择“运行和调试”时,将出现“主+渲染器”选项,允许您设置断点并检查主进程和渲染器进程中的所有变量。

What we have done in the launch.json file is to create 3 configurations:我们在launch.json文件中所做的是创建3个配置:

  • Main is used to start the main process and also expose port 9222 for remote debugging (--remote-debugging-port=9222). 用于启动主进程,并公开用于远程调试的端口9222(--remote-debugging-port=9222)。This is the port that we will use to attach the debugger for the Renderer. 这是我们将用于为Renderer附加调试器的端口。Because the main process is a Node.js process, the type is set to pwa-node (pwa- is the prefix that tells VS Code to use the latest JavaScript debugger).因为主进程是一个Node.js进程,所以类型被设置为pwa-nodepwa-是告诉VS Code使用最新JavaScript调试器的前缀)。
  • 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. 因为主流程是创建流程的流程,我们必须“附加”到它("request": "attach"),而不是创建新流程。The renderer process is a web one, so the debugger we have to use is pwa-chrome.渲染器进程是web进程,因此我们必须使用的调试器是pwa-chrome
  • Main + renderer is a compound task that executes the previous ones simultaneously.是同时执行先前任务的复合任务
caution小心

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. 因为我们在Renderer中附加了一个进程,所以可能会跳过代码的第一行,因为调试器在执行它们之前没有足够的时间进行连接。You can work around this by refreshing the page or setting a timeout before executing the code in development mode.您可以通过在开发模式下执行代码之前刷新页面或设置超时来解决此问题。

Further reading进一步阅读

If you want to dig deeper in the debugging area, the following guides provide more information:如果您想深入调试区域,以下指南提供了更多信息:

Summary小结

Electron applications are set up using npm packages. 使用npm包设置Electron应用程序。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.Electron可执行文件应该安装在项目的devDependencies中,并且可以使用package.json文件中的脚本在开发模式下运行。

The executable runs the JavaScript entry point found in the main property of your package.json. 可执行文件运行package.jsonmain属性中的JavaScript入口点。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.该文件控制Electron的主进程,该进程运行Node.js实例,负责应用程序的生命周期、显示本机接口、执行特权操作和管理渲染器进程。

Renderer processes (or renderers for short) are responsible for display graphical content. 渲染器进程(简称渲染器)负责显示图形内容。You can load a web page into a renderer by pointing it to either a web address or a local HTML file. 通过将网页指向web地址或本地HTML文件,可以将网页加载到渲染器中。Renderers behave very similarly to regular web pages and have access to the same web APIs.呈现器的行为与常规网页非常相似,可以访问相同的web 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.在本教程的下一节中,我们将学习如何使用特权API增强渲染器进程,以及如何在进程之间进行通信。