进程模型¶
Electron 从 Chromium 继承了多进程架构,这使得该框架在架构上与现代 Web 浏览器非常相似。本指南将扩展 教程 中应用的概念。
为什么不是单进程?¶
Web 浏览器是极其复杂的应用程序。除了显示 Web 内容这一主要能力外,它们还有许多次要职责,例如管理多个窗口(或标签页)以及加载第三方扩展。
在早期,浏览器通常使用单个进程来处理所有这些功能。虽然这种模式意味着每个打开的标签页开销更小,但也意味着某个网站崩溃或挂起会影响整个浏览器。
多进程模型¶
为了解决这个问题,Chrome 团队决定让每个标签页在自己的进程中渲染,从而限制网页上存在缺陷或恶意的代码对整个应用可能造成的损害。然后,单个浏览器进程控制这些进程,以及整个应用程序生命周期。下面这张来自 Chrome Comic 的图可视化了这一模型:

Electron 应用程序的结构非常相似。作为应用程序开发者,你控制两种类型的进程:主进程 和 渲染进程。它们类似于上述 Chrome 自身的浏览器进程和渲染进程。
主进程¶
每个 Electron 应用程序都有一个主进程,它充当应用程序的入口点。主进程运行在 Node.js 环境中,这意味着它能够 require 模块并使用所有 Node.js API。
窗口管理¶
主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。
每个 BrowserWindow 类实例都会创建一个应用程序窗口,该窗口在单独的渲染进程中加载一个 Web 页面。你可以使用窗口的 webContents 对象从主进程与该 Web 内容交互。
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
[!NOTE] 对于 Web 嵌入(例如
BrowserView模块)也会创建一个渲染进程。嵌入式 Web 内容也可以访问webContents对象。
由于 BrowserWindow 模块是一个 EventEmitter,你还可以为各种用户事件添加处理程序(例如最小化或最大化窗口)。
当 BrowserWindow 实例被销毁时,其对应的渲染进程也会被终止。
应用生命周期¶
主进程还通过 Electron 的 app 模块控制你的应用程序生命周期。该模块提供大量事件和方法,可用于添加自定义应用程序行为(例如以编程方式退出应用程序、修改应用程序 Dock 或显示关于面板)。
作为一个实用示例,教程初始代码 中展示的应用程序使用 app API 来创建更原生的应用程序窗口体验。
// 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 上相同的打包工具链(例如 webpack 或 parcel)。
:::warning
渲染进程可以以完整的 Node.js 环境派生,以便于开发。历史上,这曾经是默认行为,但由于安全原因,该功能已被禁用。
:::
此时,你可能会想知道,如果这些功能只能从主进程访问,渲染进程中的用户界面如何与 Node.js 和 Electron 的原生桌面功能交互。事实上,没有直接方式导入 Electron 的内容脚本。
预加载脚本¶
预加载脚本包含在渲染进程的 Web 内容开始加载之前执行的代码。这些脚本在渲染进程上下文中运行,但由于可以访问 Node.js API,因此被授予更多权限。
A preload script can be attached to the main process in the BrowserWindow constructor's
webPreferences option.
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
// ...
由于预加载脚本与渲染进程共享全局 Window 接口,并且可以访问 Node.js API,因此它可以通过在 window 全局对象中暴露任意 API 来增强渲染进程,供你的 Web 内容使用。
尽管预加载脚本与其所附加的渲染进程共享 window 全局对象,但由于 contextIsolation 的默认设置,你不能直接将预加载脚本中的任何变量附加到 window 上。
```js title='preload.js' @ts-nocheck window.myAPI = { desktop: true }
上下文隔离意味着预加载脚本与渲染进程的主世界隔离,以避免将任何特权 API 泄漏到你的 Web 内容代码中。
相反,请使用 contextBridge 模块来安全地实现这一点:
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
desktop: true
})
```js title='renderer.js' @ts-nocheck console.log(window.myAPI) // => { desktop: true }
此功能对于以下两个主要用途非常有用:
- 通过向渲染进程暴露 [`ipcRenderer`][ipcrenderer] 辅助函数,你可以使用进程间通信(IPC)从渲染进程触发主进程任务(反之亦然)。
- 如果你正在为托管在远程 URL 上的现有 Web 应用开发 Electron 封装器,你可以向渲染进程的 `window` 全局对象添加自定义属性,用于 Web 客户端侧的仅限桌面逻辑。
## 工具进程 {#the-utility-process}
每个 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) {#process-specific-module-aliases-typescript}
Electron 的 npm 包还导出了包含 Electron TypeScript 类型定义子集的子路径。
- `electron/main` 包含所有主进程模块的类型。
- `electron/renderer` 包含所有渲染进程模块的类型。
- `electron/common` 包含可在主进程和渲染进程中运行的模块的类型。
这些别名不会影响运行时,但可用于类型检查和自动补全。
```js title="Usage example"
const { shell } = require('electron/common')
const { app } = require('electron/main')
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 el/electron