Skip to main content

进程模型

🌐 Process Model

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

🌐 Electron inherits its multi-process architecture from Chromium, which makes the framework architecturally very similar to a modern web browser. This guide will expand on the concepts applied in the Tutorial.

为什么不是单一进程呢?

🌐 Why not a single process?

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

🌐 Web browsers are incredibly complicated applications. Aside from their primary ability to display web content, they have many secondary responsibilities, such as managing multiple windows (or tabs) and loading third-party extensions.

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

🌐 In the earlier days, browsers usually used a single process for all of this functionality. Although this pattern meant less overhead for each tab you had open, it also meant that one website crashing or hanging would affect the entire browser.

多进程模型

🌐 The multi-process model

为了解决这个问题,Chrome 团队决定每个标签页在自己的进程中渲染,从而限制网页上的错误或恶意代码对整个应用造成的伤害。然后,一个浏览器进程控制这些进程,以及整个应用的生命周期。下面来自 Chromium漫画 的图展示了这个模型:

🌐 To solve this problem, the Chrome team decided that each tab would render in its own process, limiting the harm that buggy or malicious code on a web page could cause to the app as a whole. A single browser process then controls these processes, as well as the application lifecycle as a whole. This diagram below from the Chrome Comic visualizes this model:

Chrome's multi-process architecture

Electron 应用的结构非常相似。作为应用开发者,你可以控制两类进程:mainrenderer。它们类似于上文提到的 Chrome 自身的浏览器和渲染进程。

🌐 Electron applications are structured very similarly. As an app developer, you control two types of processes: main and renderer. These are analogous to Chrome's own browser and renderer processes outlined above.

主进程

🌐 The main process

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

🌐 Each Electron app has a single main process, which acts as the application's entry point. The main process runs in a Node.js environment, meaning it has the ability to require modules and use all of Node.js APIs.

窗口管理

🌐 Window management

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

🌐 The main process' primary purpose is to create and manage application windows with the BrowserWindow module.

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

🌐 Each instance of the BrowserWindow class creates an application window that loads a web page in a separate renderer process. You can interact with this web content from the main process using the window's webContents object.

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)
note

网页嵌入(例如 BrowserView 模块)也会创建一个渲染进程。嵌入的网页内容也可以访问 webContents 对象。

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

🌐 Because the BrowserWindow module is an EventEmitter, you can also add handlers for various user events (for example, minimizing or maximizing your window).

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

🌐 When a BrowserWindow instance is destroyed, its corresponding renderer process gets terminated as well.

应用生命周期

🌐 Application lifecycle

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

🌐 The main process also controls your application's lifecycle through Electron's app module. This module provides a large set of events and methods that you can use to add custom application behavior (for instance, programmatically quitting your application, modifying the application dock, or showing an About panel).

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

🌐 As a practical example, the app shown in the tutorial starter code uses app APIs to create a more native application window experience.

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

🌐 Native APIs

为了将 Electron 的功能扩展到不仅仅是为网页内容提供 Chromium 封装,主进程还添加了与用户操作系统交互的自定义 API。Electron 提供了各种模块,用于控制本地桌面功能,例如菜单、对话框和托盘图标。

🌐 To extend Electron's features beyond being a Chromium wrapper for web contents, the main process also adds custom APIs to interact with the user's operating system. Electron exposes various modules that control native desktop functionality, such as menus, dialogs, and tray icons.

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

🌐 For a full list of Electron's main process modules, check out our API documentation.

渲染器进程

🌐 The renderer process

每个 Electron 应用都会为每个打开的 BrowserWindow(以及每个网页嵌入)生成一个单独的渲染进程。顾名思义,渲染器负责呈现网页内容。在所有实际用途上,在渲染进程中运行的代码应该遵循网页标准(至少在 Chromium 能做到的范围内)。

🌐 Each Electron app spawns a separate renderer process for each open BrowserWindow (and each web embed). As its name implies, a renderer is responsible for rendering web content. For all intents and purposes, code run in renderer processes should behave according to web standards (insofar as Chromium does, at least).

因此,单个浏览器窗口中的所有用户界面和应用功能都应该使用与在网络上相同的工具和范式来编写。

🌐 Therefore, all user interfaces and app functionality within a single browser window should be written with the same tools and paradigms that you use on the web.

虽然本指南无法解释每一个网页规范,但至少需要理解的基本内容是:

🌐 Although explaining every web spec is out of scope for this guide, the bare minimum to understand is:

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

此外,这也意味着渲染进程无法直接访问 require 或其他 Node.js API。为了在渲染进程中直接引入 NPM 模块,你必须使用与在网页上相同的打包工具链(例如 webpackparcel)。

🌐 Moreover, this also means that the renderer has no direct access to require or other Node.js APIs. In order to directly include NPM modules in the renderer, you must use the same bundler toolchains (for example, webpack or parcel) that you use on the web.

warning

渲染器进程可以启用完整的 Node.js 环境,以便开发。历史上,这曾是默认设置,但出于安全原因,此功能已被禁用。

🌐 Renderer processes can be spawned with a full Node.js environment for ease of development. Historically, this used to be the default, but this feature was disabled for security reasons.

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

🌐 At this point, you might be wondering how your renderer process user interfaces can interact with Node.js and Electron's native desktop functionality if these features are only accessible from the main process. In fact, there is no direct way to import Electron's content scripts.

预加载脚本

🌐 Preload scripts

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

🌐 Preload scripts contain code that executes in a renderer process before its web content begins loading. These scripts run within the renderer context, but are granted more privileges by having access to Node.js APIs.

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

🌐 A preload script can be attached to the main process in the BrowserWindow constructor's webPreferences option.

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

因为预加载脚本与渲染器共享全局 Window 接口并且可以访问 Node.js API,它可以通过在 window 全局中暴露任意 API 来增强你的渲染器,然后你的网页内容就可以使用这些 API。

🌐 Because the preload script shares a global Window interface with the renderers and can access Node.js APIs, it serves to enhance your renderer by exposing arbitrary APIs in the window global that your web contents can then consume.

虽然预加载脚本与它们附加的渲染器共享一个 window 全局变量,但由于 contextIsolation 的默认设置,你不能直接将预加载脚本中的任何变量附加到 window

🌐 Although preload scripts share a window global with the renderer they're attached to, you cannot directly attach any variables from the preload script to window because of the contextIsolation default.

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

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

🌐 Context Isolation means that preload scripts are isolated from the renderer's main world to avoid leaking any privileged APIs into your web content's code.

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

🌐 Instead, use the contextBridge module to accomplish this securely:

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

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

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

🌐 This feature is incredibly useful for two main purposes:

  • 通过将 ipcRenderer 助手暴露给渲染器,你可以使用进程间通信 (IPC) 从渲染器触发主进程任务(反之亦然)。
  • 如果你正在为托管在远程 URL 上的现有 Web 应用开发 Electron 封装器,你可以在渲染器的 window 全局对象上添加自定义属性,这些属性可以用于 Web 客户端的仅桌面逻辑。

实用进程

🌐 The utility process

每个 Electron 应用都可以使用 实用程序进程 API 从主进程派生多个子进程。实用进程运行在 Node.js 环境中,这意味着它能够 require 模块并使用所有 Node.js API。实用进程可以用于托管例如:不受信任的服务、CPU 密集型任务或易崩溃组件,这些组件之前通常会托管在主进程中或使用 Node.js child_process.fork API 派生的进程中。实用进程与 Node.js child_process 模块派生的进程之间的主要区别在于,实用进程可以使用 MessagePort 与渲染进程建立通信通道。当需要从主进程派生子进程时,Electron 应用总是可以优先选择 实用程序进程 API 而非 Node.js child_process.fork API。

🌐 Each Electron app can spawn multiple child processes from the main process using the UtilityProcess API. The utility process runs in a Node.js environment, meaning it has the ability to require modules and use all of Node.js APIs. The utility process can be used to host for example: untrusted services, CPU intensive tasks or crash prone components which would have previously been hosted in the main process or process spawned with Node.js child_process.fork API. The primary difference between the utility process and process spawned by Node.js child_process module is that the utility process can establish a communication channel with a renderer process using MessagePorts. An Electron app can always prefer the UtilityProcess API over Node.js child_process.fork API when there is need to fork a child process from the main process.

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

🌐 Process-specific module aliases (TypeScript)

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

🌐 Electron's npm package also exports subpaths that contain a subset of Electron's TypeScript type definitions.

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

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

🌐 These aliases have no impact on runtime, but can be used for typechecking and autocomplete.

Usage example
const { shell } = require('electron/common')
const { app } = require('electron/main')