创建您的第一个应用程序
教程目录
这是 Electron 教程的第二部分。
学习目标
在本节,您会学习如何创建您的 Electron 项目,并且编写一个简单的入门程序。 到了本节末尾,您应该能够在终端开发环境运行一个 Electron 应用。
创建项目
避免使用 WSL
如果您用的是 Windows,在本教程中请不要使用 Windows Subsystem for Linux (WSL),否则您在运行应用时可能会遇到问题。
初始化 npm 项目
Electron 应用基于 npm 搭建,以 package.json 文件作为入口点。 首先创建一个文件夹,然后在其中执行 npm init
初始化项目。
- npm
- Yarn
mkdir my-electron-app && cd my-electron-app
npm init
mkdir my-electron-app && cd my-electron-app
yarn init
这条命令会帮您配置 package.json 中的一些字段。 为本教程的目的,有几条规则需要遵循:
- 入口点 应当是
main.js
(您很快就会创建它) - author (作者)、license (开源许可证) 和 description (描述) 可以为任意内容,不过在晚些的 打包应用程序 步骤中可能是需要的。
然后,将 Electron 安装为您项目的 devDependencies,即仅在开发环境需要的额外依赖。
为什么 Electron 是 devDependency?
您的应用需要运行 Electron API,因此这听上去可能有点反直觉。 实际上,打包后的应用本身会包含 Electron 的二进制文件,因此不需要将 Electron 作为生产环境依赖。
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
在初始化并且安装完 Electron 之后,您的 package.json 应该长下面这样。 文件夹中会出现一个 node_modules
文件夹,其中包含了 Electron 可执行文件;还有一个 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"
}
}
高级安装步骤
如果直接安装 Electron 失败,请参阅我们的 详细安装指导 文档,里面包含下载镜像、代理和故障排除等信息。
添加 .gitignore 文件
.gitignore 指定了哪些文件不需要 Git 追踪版本。 建议您复制一份 GitHub 的 Node.js gitignore 模板 到您项目的根目录,以避免将 node_modules
文件夹提交到版本控制系统中。
运行 Electron 应用
:::延伸阅读
参阅 Electron 进程模型 (process model) 相关文档来了解 Electron 的进程之间是如何协作的。
:::
您在 package.json 中指定的脚本文件 main 是所有 Electron 应用的入口点。 这个文件控制 主程序 (main process),它运行在 Node.js 环境里,负责控制您应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes),稍后会详细介绍。
在继续编写您的 Electron 应用之前,您将使用一个小小的脚本来确保主进程入口点已经配置正确。 在根目录的 main.js
文件中写一行代码:
main.js
console.log(`欢迎来到 Electron 👋`)
因为 Electron 的主进程是一个 Node.js 运行时,您可以使用 electron
命令执行任意 Node.js 代码(甚至将其用作 REPL)。 要执行这个脚本,在 package.json 的 scripts 字段中添加一个 start
命令,执行内容为 electron .
。 这个命令会告诉 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
- Yarn
npm run start
yarn run start
您的终端应该会输出 欢迎来到 Electron 👋
。 恭喜,您已经在 Electron 中执行了您的第一行代码! 接下来,您会学习如何用 HTML 创建用户界面,并将它们装载到原生窗口中。
将网页装载到 BrowserWindow
在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自远程 URL。 在本例中,您将会装载本地的文件。 在您项目的根目录中创建一个 index.html
文件,并写入下面的内容:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/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 (Line 1)
const { app, BrowserWindow } = require('electron')
在第一行中,我们使用 CommonJS 语法导入了两个 Electron 模块:
- app,它控制您的应用的事件生命周期。
- BrowserWindow,它负责创建和管理应用的窗口。
大小写惯例
您可能注意到了 app 和 BrowserWindow 两个模块名的大小写差异。 Electron 遵循 JavaScript 传统约定,以帕斯卡命名法 (PascalCase) 命名可实例化的类 (如 BrowserWindow, Tray 和 Notification),以驼峰命名法 (camelCase) 命名不可实例化的函数、变量等 (如 app, ipcRenderer, webContents) 。
在 Electron 中使用 ES 语法
Electron 目前对 ECMAScript 语法 (如使用 import
来导入模块) 的支持还不完善。 您可以在 electron/electron#21457 这个 issue 中查看 ESM 的适配进展。
将可复用的函数写入实例化窗口
createWindow()
函数将您的页面加载到新的 BrowserWindow 实例中:
main.js (Lines 3-10)
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
})
win.loadFile('index.html')
}
在应用准备就绪时调用函数
main.js (Lines 12-14)
app.whenReady().then(() => {
createWindow()
})
Electron 的很多核心模组是 Node.js 事件触发器,遵守 Node.js 的异步事件驱动架构。 app 模块就是其中一个。
在 Electron 中,只有在 app 模组的 ready 事件能触发后才能创建 BrowserWindows 实例。 您可以借助 app.whenReady() API 来等待此事件,并在该 API 的 promise 被 resolve 时调用 createWindow()
方法。
info
通常我们使用触发器的 .on
函数来监听 Node.js 事件。
+ app.on('ready').then(() => {
- app.whenReady().then(() => {
createWindow()
})
但是 Electron 暴露了 app.whenReady()
方法,作为其 ready
事件的专用监听器,这样可以避免直接监听 .on 事件带来的一些问题。 参见 electron/electron#21972 。
此时,运行 start
命令应该能成功地打开一个包含您网页内容的窗口!
您应用中的每个页面都在一个单独的进程中运行,我们称这些进程为 渲染器 (renderer) 。 渲染器也能访问前端开发常会用到的 API 和工具,例如用于打包并压缩代码的 webpack,还有用于构建用户界面的 React 。
管理应用的窗口生命周期
应用窗口在不同操作系统中的行为也不同。 Electron 允许您自行实现这些行为来遵循操作系统的规范,而不是采用默认的强制执行。 您可以通过监听 app 和 BrowserWindow 模组的事件,自行实现基础的应用窗口规范。
针对特定进程的控制流
可以检查 Node.js 的 process.platform 变量,帮助您在不同操作系统上运行特定代码。 请注意,Electron 目前只支持三个平台:win32
(Windows), linux
(Linux) 和 darwin
(macOS) 。
关闭所有窗口时退出应用 (Windows & Linux)
在 Windows 和 Linux 上,我们通常希望在关闭一个应用的所有窗口后让它退出。 若要在 Electron 中实现这一点,您可以监听 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()
回调即可。
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
完整实现代码
docs/fiddles/tutorial-first-app (23.0.0)Open in Fiddle
- main.js
- index.html
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();
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<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>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
可选:使用 VS Code 调试
如果您希望使用 VS Code 调试您的程序,您需要让 VS Code 监听主进程 (main process) 和渲染器进程 (renderer process) 。 下面为您提供了一个简单的配置文件。 请在根目录新建一个 .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
所做的其实是创建三个配置项:
Main
用来运行主程序,并且暴露出 9222 端口用于远程调试 (--remote-debugging-port=9222
) 。 我们将把调试器绑定到那个端口来调试renderer
。 因为主进程是 Node.js 进程,类型被设置为node
。Renderer
用来调试渲染器进程。 因为后者是由主进程创建的,我们要把它 “绑定” 到主进程上 ()"request": "attach"
,而不是创建一个新的。 渲染器是 web 进程,因此要选择chrome
调试器。Main + renderer
是一个 复合任务,可同时执行前两个任务。
注意事项
由于我们要将进程绑定到 Renderer
任务,您应用中的前几行代码可能会被跳过,因为调试器 (Debugger) 在执行代码之前没有足够的时间进行连接。 在开发环境中,您可以通过刷新页面或者使用 setTimeout 延迟运行代码,来避开这个问题。
延伸阅读
如果您希望深挖调试步骤,可以查看以下指南:
摘要
Electron 程序是通过 npm 包创建的。 您应将 Electron 依赖安装到 devDependencies
,然后在 package.json 中设置一个脚本来运行。
执行命令后,Electron 程序会运行您在 package.json 文件的 main
字段设置的入口文件。 这个入口文件控制着 Electron 的主进程,后者运行于 Node.js 实例,负责应用的生命周期、展示原生窗口、执行特殊操作和管理渲染进程。
渲染器进程(简称渲染器) 负责展示图形内容。 您可以将渲染的网页指向 web 地址或本地 HTML 文件。 渲染器和常规的网页行为很相似,访问的 web API 也相同。
在教程下一节,我们将会学习如何使用 API 给渲染器提权,以及如何在进程间通信。