:::info 跟随教程
这是 Electron 教程的第 3 部分。
:::
学习目标¶
在本教程的这一部分中,你将了解什么是预加载脚本,以及如何安全地将特权 API 暴露到渲染进程中。你还将学习如何使用 Electron 的进程间通信(IPC)模块在主进程和渲染进程之间进行通信。
什么是预加载脚本?¶
Electron 的主进程是一个 Node.js 环境,具有完整的操作系统访问权限。除了 Electron 模块,你还可以访问 Node.js 内置模块,以及通过 npm 安装的任何包。另一方面,出于安全原因,渲染进程运行网页,默认不运行 Node.js。
为了将 Electron 的不同进程类型连接起来,我们需要使用一种称为 preload 的特殊脚本。
使用预加载脚本增强渲染进程¶
BrowserWindow 的预加载脚本运行在一个可以访问 HTML DOM 以及 Node.js 和 Electron API 有限子集的上下文中。
:::info 预加载脚本沙盒化
从 Electron 20 开始,预加载脚本默认沙盒化,并且不再能够访问完整的 Node.js 环境。实际上,这意味着你拥有一个 require 函数,它只能加载 electron 模块(渲染进程模块),此外还有一个精简的 process 对象,以及 globalThis 的 global 别名。没有可用的 Node.js 内置模块或 Node.js 全局变量。
如需了解更多信息,请查看进程沙盒化指南。
:::
预加载脚本会在渲染进程中网页加载之前注入,类似于 Chrome 扩展的内容脚本。要为渲染进程添加需要特权访问的功能,你可以通过 contextBridge API 定义 global 对象。
为了演示这个概念,你将创建一个预加载脚本,将你的应用的 Chrome、Node 和 Electron 版本暴露到渲染进程中。
添加一个新的 preload.js 脚本,将 Electron 的 process.versions 对象的选定属性通过 versions 全局变量暴露到渲染进程中。
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
// we can also expose variables, not just functions
})
要将此脚本附加到你的渲染进程,请在 BrowserWindow 构造函数中将其路径传递给 webPreferences.preload 选项:
```js {2,8-10} title="main.js" const { app, BrowserWindow } = require('electron')
const path = require('node:path')
const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } })
win.loadFile('index.html') }
app.whenReady().then(() => { createWindow() })
:::info
这里用到了两个 Node.js 概念:
- [`__dirname`][dirname] 字符串指向当前执行脚本的路径(在本例中,是你的项目根文件夹)。
- [`path.join`][path-join] API 将多个路径段连接在一起,创建一个在所有平台上都能工作的组合路径字符串。
:::
此时,渲染进程可以访问 `versions` 全局变量,因此让我们在窗口中显示这些信息。可以通过 `window.versions` 或简单地通过 `versions` 访问此变量。创建一个 `renderer.js` 脚本,使用 [`document.getElementById`][] DOM API 替换 `id` 属性为 `info` 的 HTML 元素的显示文本。
```js title="renderer.js" @ts-nocheck
const information = document.getElementById('info')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
然后,修改你的 index.html,添加一个 id 属性为 info 的新元素,并附加你的 renderer.js 脚本:
```html {18,20} title="index.html"
Hello from Electron renderer!
👋
按照上述步骤操作后,你的应用应该看起来类似这样:

代码应该看起来像这样:
```fiddle docs/fiddles/tutorial-preload
进程间通信¶
正如上文所述,Electron 的主进程和渲染进程具有不同的职责,且不能互换。这意味着无法从渲染进程直接访问 Node.js API,也无法从主进程访问 HTML 文档对象模型(DOM)。
解决此问题的方法是使用 Electron 的 ipcMain 和 ipcRenderer 模块进行进程间通信(IPC)。要从你的网页向主进程发送消息,你可以使用 ipcMain.handle 设置一个主进程处理器,然后在你的预加载脚本中暴露一个调用 ipcRenderer.invoke 以触发该处理器的函数。
为了说明这一点,我们将向渲染进程添加一个名为 ping() 的全局函数,该函数将从主进程返回一个字符串。
首先,在你的预加载脚本中设置 invoke 调用:
```js {1,7} title="preload.js" const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', { node: () => process.versions.node, chrome: () => process.versions.chrome, electron: () => process.versions.electron, ping: () => ipcRenderer.invoke('ping') // we can also expose variables, not just functions })
:::caution IPC 安全
注意,我们将 `ipcRenderer.invoke('ping')` 调用包装在一个辅助函数中,而不是通过 context bridge 直接暴露 `ipcRenderer` 模块。你**绝不**应该通过 preload 直接暴露整个 `ipcRenderer` 模块。这会让渲染进程能够向主进程发送任意 IPC 消息,从而成为恶意代码的强大攻击向量。
:::
然后,在主进程中设置你的 `handle` 监听器。我们在加载 HTML 文件_之前_这样做,以确保在你从渲染进程发出 `invoke` 调用之前,处理程序已经准备就绪。
```js {1,15} title="main.js"
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
})
一旦你设置好了发送方和接收方,现在就可以通过你刚刚定义的 'ping' 通道从渲染进程向主进程发送消息。
```js title='renderer.js' @ts-expect-error=[2] const func = async () => { const response = await window.versions.ping() console.log(response) // prints out 'pong' }
func() ```
:::info
如需对使用 ipcRenderer 和 ipcMain 模块的更深入说明,请查看完整的 进程间通信 指南。
:::
总结¶
preload 脚本包含在网页加载到浏览器窗口之前运行的代码。它可以访问 DOM API 和 Node.js 环境,并且通常用于通过 contextBridge API 向渲染进程暴露特权 API。
由于主进程和渲染进程具有非常不同的职责,Electron 应用通常使用 preload 脚本来设置进程间通信(IPC)接口,以便在两种进程之间传递任意消息。
在教程的下一部分中,我们将向你展示用于为应用添加更多功能的资源,然后教你如何向用户分发你的应用。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 el/electron