构建你的第一个应用
学习目标
在本教程的这一部分,你将学习如何设置你的 Electron 项目并编写一个最小的入门应用程序。在本节结束时,你应该能够从终端以开发模式运行一个可用的 Electron 应用。
设置你的项目
如果你使用的是 Windows 机器,请不要在遵循本教程时使用 Windows Subsystem for Linux (WSL),因为在尝试执行应用程序时会遇到问题。
初始化你的 npm 项目
Electron 应用是使用 npm 进行脚手架搭建的,其中 package.json 文件是入口点。首先创建一个文件夹并在其中初始化一个 npm 包,使用 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 API。但是,打包后的应用将包含 Electron 二进制文件,从而无需将其指定为生产依赖项。
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
在初始化包并安装 Electron 后,你的 package.json 文件应该看起来像这样。现在你应该还有一个 node_modules 文件夹,其中包含 Electron 可执行文件,以及一个 package-lock.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 失败,请参阅我们的 高级安装文档,了解有关下载镜像、代理和故障排除步骤的说明。
添加 .gitignore
.gitignore 文件指定了要避免使用 Git 进行跟踪的文件和目录。你应该将 GitHub 的 Node.js gitignore 模板 的副本放入项目根文件夹,以避免提交项目的 node_modules 文件夹。
运行 Electron 应用
阅读 Electron 的进程模型 文档,以更好地理解 Electron 的多个进程如何协同工作。
在 package.json 中定义的 main 脚本是任何 Electron 应用程序的入口点。此脚本控制主进程,它运行在 Node.js 环境中,负责控制应用的生命周期、显示原生界面、执行特权操作以及管理渲染进程(稍后会详细介绍)。
在创建你的第一个 Electron 应用之前,你将首先使用一个简单的脚本来确保你的主进程入口点已正确配置。在项目根文件夹中创建一个 main.js 文件,其中包含一行代码:
console.log('Hello from Electron 👋')
由于 Electron 的主进程是一个 Node.js 运行时,你可以使用 electron 命令执行任意 Node.js 代码(你甚至可以用它作为 REPL)。要执行此脚本,请将 electron . 添加到 package.json 的 scripts 字段的 start 命令中。此命令将指示 Electron 可执行文件查找当前目录中的主脚本并在开发模式下运行它。
{
  "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
- Yarn
npm run start
yarn run start
你的终端应该会打印出 Hello from Electron 👋。恭喜你,你已经在 Electron 中执行了第一行代码!接下来,你将学习如何使用 HTML 创建用户界面并将它们加载到原生窗口中。
将网页加载到 BrowserWindow 中
在 Electron 中,每个窗口都显示一个网页,该网页可以从本地 HTML 文件或远程 Web 地址加载。在此示例中,你将加载一个本地文件。首先,在项目根文件夹中创建一个最简单的网页 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 文件内容。我们将单独解释每个高亮显示的块。
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })
  win.loadFile('index.html')
}
app.whenReady().then(() => {
  createWindow()
})
导入模块
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 28 开始,支持 ECMAScript 模块(即使用 import 加载模块)。有关 ESM 在 Electron 中的状态以及如何在我们的应用中使用它们的更多信息,请参阅 我们的 ESM 指南。
编写一个可重用的函数来实例化窗口
createWindow() 函数将你的网页加载到一个新的 BrowserWindow 实例中:
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })
  win.loadFile('index.html')
}
当应用准备就绪时调用你的函数
app.whenReady().then(() => {
  createWindow()
})
Electron 的许多核心模块都是 Node.js 事件发射器,遵循 Node 的异步事件驱动架构。app 模块就是其中一个事件发射器。
在 Electron 中,只有在 app 模块的 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 事件之前无法创建窗口,因此你应该只在你现有的 whenReady() 回调函数中监听 activate 事件。为此,只需在现有的 whenReady() 回调中监听 activate 事件。
app.whenReady().then(() => {
  createWindow()
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})
最终的入门代码
- main.js
- index.html
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()
  }
})
<!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 附加到主进程和渲染器进程。这是一个示例配置供你运行。在项目中的新 .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 增强渲染器进程以及如何在进程之间进行通信。