跳至主要内容

进程模型

Electron 从 Chromium 继承了它的多进程架构,这使得该框架在架构上与现代 Web 浏览器非常相似。本指南将扩展应用于 教程 中的概念。

为什么不是单进程?

Web 浏览器是极其复杂的应用程序。除了它们显示 Web 内容的主要能力外,它们还有许多次要职责,例如管理多个窗口(或标签页)以及加载第三方扩展程序。

在早期,浏览器通常使用一个进程来完成所有这些功能。虽然这种模式意味着每个打开的标签页的开销更少,但也意味着一个网站崩溃或挂起会影响整个浏览器。

多进程模型

为了解决这个问题,Chrome 团队决定每个标签页将在自己的进程中渲染,从而限制了有缺陷或恶意代码在网页上可能对整个应用程序造成的损害。然后,一个单独的浏览器进程控制这些进程,以及整个应用程序的生命周期。下面的来自 Chrome 漫画 的图表直观地展示了这种模型。

Chrome's multi-process architecture

Electron 应用程序的结构非常相似。作为应用程序开发人员,您控制两种类型的进程:主进程渲染进程。它们类似于上面概述的 Chrome 自己的浏览器进程和渲染进程。

主进程

每个 Electron 应用程序都有一个主进程,它充当应用程序的入口点。主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

窗口管理

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。

BrowserWindow 类的每个实例都会创建一个应用程序窗口,该窗口将在一个单独的渲染进程中加载网页。您可以使用窗口的 webContents 对象从主进程与该 Web 内容进行交互。

main.js
const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

const contents = win.webContents
console.log(contents)

注意:对于 Web 嵌入(例如 BrowserView 模块),也会创建渲染进程。webContents 对象也可以用于嵌入的 Web 内容。

因为 BrowserWindow 模块是一个 EventEmitter,所以您还可以添加各种用户事件的处理程序(例如,最小化或最大化您的窗口)。

当一个 BrowserWindow 实例被销毁时,它对应的渲染进程也会终止。

应用程序生命周期

主进程还通过 Electron 的 app 模块控制您的应用程序的生命周期。该模块提供了一套丰富的事件和方法,您可以使用它们来添加自定义应用程序行为(例如,以编程方式退出应用程序、修改应用程序坞或显示“关于”面板)。

作为一个实际的例子,快速入门指南 中显示的应用程序使用 app API 来创建一个更原生应用程序窗口体验。

main.js
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

原生 API

为了扩展 Electron 的功能,使其超越作为 Web 内容的 Chromium 包装器,主进程还添加了自定义 API 来与用户的操作系统进行交互。Electron 公开了各种模块,这些模块控制原生桌面功能,例如菜单、对话框和托盘图标。

有关 Electron 主进程模块的完整列表,请查看我们的 API 文档。

渲染进程

每个 Electron 应用程序都会为每个打开的 BrowserWindow(以及每个 Web 嵌入)生成一个单独的渲染进程。顾名思义,渲染器负责渲染 Web 内容。就所有意图和目的而言,在渲染进程中运行的代码应该根据 Web 标准(至少在 Chromium 的范围内)进行。

因此,单个浏览器窗口内的所有用户界面和应用程序功能都应使用您在 Web 上使用的相同工具和范式编写。

虽然本指南不包括解释每个 Web 规范,但要理解的最基本的是

  • HTML 文件是渲染进程的入口点。
  • UI 样式是通过级联样式表 (CSS) 添加的。
  • 可执行 JavaScript 代码可以通过 <script> 元素添加。

此外,这也意味着渲染器无法直接访问 require 或其他 Node.js API。为了直接在渲染器中包含 NPM 模块,您必须使用您在 Web 上使用的相同捆绑器工具链(例如,webpackparcel)。

警告

为了便于开发,渲染进程可以与完整的 Node.js 环境一起生成。在过去,这曾经是默认设置,但出于安全原因,此功能已被禁用。

此时,您可能想知道您的渲染进程用户界面如何与 Node.js 和 Electron 的原生桌面功能进行交互,如果这些功能只能从主进程访问。实际上,没有直接的方法来导入 Electron 的内容脚本。

预加载脚本

预加载脚本包含在渲染进程的 Web 内容开始加载之前执行的代码。这些脚本在渲染器上下文中运行,但由于可以访问 Node.js API,因此获得了更多权限。

可以在 BrowserWindow 构造函数的 webPreferences 选项中将预加载脚本附加到主进程。

main.js
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
// ...

因为预加载脚本与它们附加到的渲染器共享一个全局 Window 接口并且可以访问 Node.js API,所以它通过在 window 全局中公开任意 API 来增强您的渲染器,然后您的 Web 内容可以消耗这些 API。

虽然预加载脚本与它们附加到的渲染器共享一个 window 全局,但您无法直接将预加载脚本中的任何变量附加到 window,因为存在 contextIsolation 默认设置。

preload.js
window.myAPI = {
desktop: true
}
renderer.js
console.log(window.myAPI)
// => undefined

上下文隔离意味着预加载脚本与渲染器的主要世界隔离,以避免将任何特权 API 泄漏到您的 Web 内容代码中。

相反,使用 contextBridge 模块来安全地完成此操作

preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
desktop: true
})
renderer.js
console.log(window.myAPI)
// => { desktop: true }

此功能对于两个主要目的非常有用

  • 通过向渲染器公开 ipcRenderer 帮助程序,您可以使用进程间通信 (IPC) 从渲染器(反之亦然)触发主进程任务。
  • 如果您正在为托管在远程 URL 上的现有 Web 应用程序开发 Electron 包装器,则可以向渲染器的 window 全局添加自定义属性,这些属性可用于 Web 客户端端的桌面专用逻辑。

实用程序进程

每个 Electron 应用程序都可以使用 UtilityProcess API 从主进程生成多个子进程。实用程序进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。实用程序进程可用于托管例如:不可信服务、CPU 密集型任务或容易崩溃的组件,这些组件以前托管在主进程中,或者使用 Node.js child_process.fork API 生成的进程。实用程序进程与 Node.js child_process 模块生成的进程之间的主要区别在于实用程序进程可以使用 MessagePort 与渲染进程建立通信通道。Electron 应用程序始终可以使用 UtilityProcess API 代替 Node.js child_process.fork API,当需要从主进程派生子进程时。

特定于进程的模块别名(TypeScript)

Electron 的 npm 包还导出包含 Electron 的 TypeScript 类型定义子集的子路径。

  • electron/main 包含所有主进程模块的类型。
  • electron/renderer 包含所有渲染进程模块的类型。
  • electron/common 包含可在主进程和渲染进程中运行的模块的类型。

这些别名对运行时没有影响,但可用于类型检查和自动完成。

用法示例
const { app } = require('electron/main')
const { shell } = require('electron/common')