---
URL: "/zh-CN/blog/2024-12/electron_preload.html"
LLMS_URL: "/zh-CN/blog/2024-12/electron_preload.md"
lastUpdated: true
commentabled: true
recommended: true
title: "Electron开发中，为什么官方建议在预加载脚本中使用 ipcRenderer"
description: "Electron开发中，为什么官方建议在预加载脚本中使用 ipcRenderer"
date:

pageClass: "blog-page-class"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: Electron开发中，为什么官方建议在预加载脚本中使用 ipcRenderer
description: Electron开发中，为什么官方建议在预加载脚本中使用 ipcRenderer
date: 2024-12-25 12:18:00
pageClass: blog-page-class
---

# Electron开发中，为什么官方建议在预加载脚本中使用 ipcRenderer #

> 在开发 Electron 应用时，我需要在渲染进程中与主进程进行通信，于是使用了官方提供的 `ipcRenderer` 模块。这个模块可以直接在渲染进程中调用，使用起来非常方便。但当我深入研究官方文档时，发现他们强烈建议通过预加载脚本（preload script）来使用 ipcRenderer，并通过 `contextBridge` 将特定的功能暴露给渲染进程。这样做似乎增加了代码复杂度，但背后的原因是什么呢？直接在渲染进程中使用 `ipcRenderer` 是否存在潜在的安全隐患？在什么情况下，我们必须遵循这种建议呢？


## ipcRenderer 在渲染进程中的使用 ##

在早期版本的 Electron 中，开发者可以直接在渲染进程中引入并使用 `ipcRenderer` 模块：

```ts
// 渲染进程中直接使用 ipcRenderer
const { ipcRenderer } = require('electron');

// 发送消息到主进程
ipcRenderer.send('message-channel', { message: 'Hello, Main Process!' });

// 接收主进程的响应
ipcRenderer.on('reply-channel', (event, data) => {
  console.log('Received from Main:', data);
});
```

虽然这样使用简单直接，但随着 Electron 发展，这种方式暴露出了一些安全性和结构性的问题。

## 官方建议的原因 ##

### 安全性问题 ###

渲染进程通常需要加载用户界面资源（如 HTML、CSS 和 JavaScript），这些资源可能来自本地文件或外部网络。如果渲染进程中的页面存在安全漏洞（如 XSS 攻击），恶意代码可以通过 `ipcRenderer` 访问主进程的功能，例如文件系统、进程控制等。这将导致整个应用程序的安全性受到威胁。

**解决方法：使用预加载脚本和 `contextBridge` 限制访问范围**

通过在预加载脚本中暴露有限的接口，开发者可以控制渲染进程能够调用的功能，从而降低安全风险。

```ts
// preload.ts
const { contextBridge, ipcRenderer } = require('electron');

// 暴露有限接口
contextBridge.exposeInMainWorld('api', {
  sendMessage: (channel, data) => {
    const validChannels = ['message-channel'];
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data);
    }
  },
  onMessage: (channel, callback) => {
    const validChannels = ['reply-channel'];
    if (validChannels.includes(channel)) {
      ipcRenderer.on(channel, (event, ...args) => callback(...args));
    }
  },
});
```

在渲染进程中只能通过 `window.api` 调用：

```ts
// renderer.ts
window.api.sendMessage('message-channel', { message: 'Hello, Main Process!' });
window.api.onMessage('reply-channel', (data) => {
  console.log('Received from Main:', data);
});

这种设计确保了即使渲染进程被攻击，也无法直接访问 `ipcRenderer`。

### 沙盒模式的兼容性 ###

Electron 提供了“沙盒模式”（`sandbox: true`），让渲染进程运行在受限的 V8 环境中，无法直接使用 Node.js 模块。开启沙盒模式后，`ipcRenderer` 将无法直接使用，必须通过预加载脚本桥接 Node.js 环境与渲染进程。

**沙盒模式配置示例**

```ts
// 主进程
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    sandbox: true, // 开启沙盒模式
  },
});
```

通过这种方式，Electron 应用可以在保留灵活性的同时提升安全性。

### 模块隔离与代码结构 ###

直接在渲染进程中使用 ipcRenderer 会使应用的模块划分不清晰，通信逻辑容易分散在各个角落。通过预加载脚本统一管理与主进程的通信接口，可以简化代码逻辑，增强可维护性。

**对比**

- 直接使用 ipcRenderer：
  各功能模块都可能直接调用 ipcRenderer，通信逻辑分散。
- 通过预加载脚本：
  所有与主进程交互的接口集中在 preload.ts，渲染进程只需调用暴露的 API，逻辑更清晰。


## 补充知识：什么是预加载脚本 ##

预加载脚本（preload.ts）是在 Electron 应用启动时，由主进程指定并加载到渲染进程中的脚本。它运行在一个特殊的环境中，既可以访问 Node.js API，又可以操作渲染进程的全局对象（如 window）。

## 特点 ##

- 独立于渲染进程页面的代码。
- 可以桥接 Node.js 环境与浏览器环境。
- 是实现 contextBridge 的基础。


## 实践总结：如何构建安全的 IPC 通信 ##

### 主进程监听消息 ###

```ts
const { ipcMain } = require('electron');

ipcMain.handle('message-channel', async (event, data) => {
console.log('Received from Renderer:', data);
return { reply: 'Hello, Renderer!' }; // 回复消息
});
```

### 预加载脚本暴露接口 ###

```ts
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
sendMessage: async (channel, data) => {
  const validChannels = ['message-channel'];
  if (validChannels.includes(channel)) {
    return ipcRenderer.invoke(channel, data);
  }
},
});
```

### 渲染进程调用接口 ###

```ts
window.api.sendMessage('message-channel', { msg: 'Hello, Main!' }).then((response) => {
console.log('Response from Main:', response);
});
```

> 尽管 ipcRenderer 可以直接在渲染进程中使用，但出于 安全性、沙盒兼容性 和 代码结构化 的考虑，官方建议在预加载脚本中使用它。通过合理设计预加载脚本和接口，开发者可以构建一个安全、模块化的 Electron 应用，同时为未来的兼容性做好准备。

