跳转到主要内容

进程模型

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

为什么不使用单进程?

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

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

多进程模型

为了解决这个问题,Chrome 团队决定每个标签页将在自己的进程中渲染,从而限制了 Web 页面上的错误或恶意代码对整个应用程序造成的损害。单个浏览器进程随后控制这些进程,以及应用程序的整个生命周期。下图来自 Chrome Comic 可视化了这种模型

Chrome's multi-process architecture

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

主进程

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

窗口管理

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

BrowserWindow 类的每个实例都会创建一个应用程序窗口,该窗口在单独的渲染进程中加载 Web 页面。您可以使用窗口的 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 模块控制您的应用程序的生命周期。该模块提供了一组大型事件和方法,您可以使用它们来添加自定义应用程序行为(例如,以编程方式退出您的应用程序,修改应用程序 Dock,或显示“关于”面板)。

作为一个实际的例子,教程启动代码 中显示的应用程序使用 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 样式通过 Cascading Style Sheets (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 全局,但由于 contextIsolation 默认设置,您无法直接将任何变量从预加载脚本附加到 window

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

Context Isolation 意味着预加载脚本与渲染器的主世界隔离,以避免将任何特权 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 { shell } = require('electron/common')
const { app } = require('electron/main')