跳到主要内容

进程模型

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 文件是渲染器进程的入口点。
  • 通过层叠样式表 (CSS) 添加 UI 样式。
  • 可以通过 <script> 元素添加可执行的 JavaScript 代码。

此外,这也意味着渲染器无法直接访问 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 全局变量,但由于contextIsolation默认值,您不能直接将任何变量从预加载脚本附加到 window

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 与渲染器进程建立通信通道。当需要从主进程 fork 子进程时,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')