下面是 「contextBridge」 的学习记录,风格和上一篇一致,并尽量对照你当前项目里的 preload.js / renderer.js。
一、本节在教程里的位置
- 上一节 :主进程
app+BrowserWindow开窗口、加载页面。 - 这一节 :渲染进程里,预加载脚本 如何把能力安全地 交给页面里的 JS(你的
renderer.js、以后接 Vue3 的代码)。 - 核心问题 :页面不能直接乱用 Node / 整包
ipcRenderer,那怎么和主进程通信?------用 contextBridge****搭一座「受控的桥」。
二、一句话理解 contextBridge
|-----------|---------------------------------------------------------------------------------|
| 项目 | 说明 |
| 叫什么 | 上下文桥接器 / 安全桥梁 API |
| 跑在哪 | 渲染进程 里的 预加载脚本(preload) |
| 干什么 | 在 隔离的预加载环境 和 页面的主世界(Main World) 之间,只暴露你允许的那一小部分 API |
| 典型写法 | contextBridge.exposeInMainWorld('electron', { doThing: () => ... }) |
| 页面怎么用 | window.electron.doThing()(你项目里甚至可以直接写 versions.chrome(),因为已经挂到 window 上了) |
通俗比喻 :主进程是「后台办公室」,页面是「前台大厅」。contextBridge 是安检口旁的传话窗口------你只能递进去规定格式的纸条(白名单 API),不能把整个后台钥匙(Node、完整 ipc)塞给大厅里随便一个人。
三、两个「世界」------先搞懂词
Main World(主世界)
- 是什么 :你
index.html里引用的renderer.js、以后 Vue 打包出来的业务代码,默认都在这里跑。 - 特点 :更像普通网页环境;在开启 上下文隔离(contextIsolation) 后,不能直接
require('electron')、不能随便碰 Node。
Isolated World(隔离世界)
- 是什么 :preload 脚本 跑的环境(Electron 12+ 默认开启
contextIsolation时就是这样)。 - 特点 :能
require('electron'),能用ipcRenderer,但和页面 JS 不是同一个全局对象,彼此默认看不见。
contextBridge 在中间做什么?
[ 隔离世界 preload ] --contextBridge--> [ 主世界 renderer / Vue ]
能碰 ipc、Node 只能用你暴露的 window.xxx
没有这座桥:要么关隔离(不安全),要么页面完全碰不到主进程(做不了桌面能力)。
四、常用方法(先记两个就够)
|-------------------------------------------------|---------------------------------------|-------------|
| 方法 | 用途 | 你现阶段 |
| exposeInMainWorld(apiKey, api) | 把 API 挂到 window[apiKey],给页面 / Vue 用 | 主力,你已在用 |
| exposeInIsolatedWorld(worldId, apiKey, api) | 挂到指定 world(如 1000+ 自建隔离世界) | 进阶,先了解即可 |
| executeInMainWorld | 实验功能,在主世界执行函数 | 先跳过 |
你项目里的对应关系:
contextBridge.exposeInMainWorld('versions', {
num1: () => 1,
process: () => process,
// ...
ping: () =>ipcRenderer.invoke('ping')
})
contextBridge.exposeInMainWorld('myfn', () => '自定义函数')
contextBridge.exposeInMainWorld('myObj', { a:1,b:2,c:3 })
页面侧:
information.innerText = `... ${versions.chrome()} ...`
information.innerText = information.innerText + `, myfn (${myfn()})`
information.innerText = information.innerText + `, myObj (${myObj.a}, ...)`
等价于 window.versions、window.myfn、window.myObj------桥已经帮你挂到 window 上了。
五、expose 的 api 能长什么样?
官方允许的结构(简化记忆):
- 基本类型 :
string、number、boolean - 函数 :会被代理到另一侧(跨世界调用)
- 数组、普通对象 :会复制 + 冻结(immutable),一边改了另一边不会跟着变
- 可以嵌套 :
nestedAPI.evenDeeper.fn这种
和直接改全局变量的区别 :不是把 preload 里的对象「共享引用」给页面,而是复制/代理一份安全的快照或通道。
六、函数参数、返回值:什么能过桥?
桥上传的是拷贝/克隆后的数据,不是随便什么都能传。记两类就够:
|---------------------------|----------|---------------------------|
| 类型 | 能不能用 | 备注 |
| string / number / boolean | ✅ | 最省心 |
| 普通 Object / Array | ✅ | 键和值都要是「简单/支持的类型」 |
| Function | ✅ | 会被代理;不要指望传 class 构造函数 |
| Promise / Error | ✅ | 有细微差异(错误栈、自定义属性可能变) |
| Symbol | ❌ | 过不了桥 |
你写的 process: () => process:能调用 ,但返回的是复杂对象,实际过桥的规则要按「可克隆类型」来;学习阶段可以观察控制台行为,生产环境更推荐只暴露 process.versions****这类简单字段 ,不要整包 process。
七、安全红线(必背)
不要把整个 ipcRenderer 暴露出去
// ❌ 危险:页面里任意代码都能 send 任意频道
contextBridge.exposeInMainWorld('ipc', ipcRenderer) // 对面收到可能是空对象,且是安全雷
// ✅ 正确:只暴露白名单方法
contextBridge.exposeInMainWorld('electron', {
onMyEventName: (callback) =>
ipcRenderer.on('MyEventName', (_e, ...args) => callback(args)),
ping: () => ipcRenderer.invoke('ping'), // 你项目里这种「单个频道」更好
})
原则 :页面只能做你设计好的几件事 (读版本、调 ping),不能「随便给主进程发任何消息」。
慎暴露 Node 全局 / 本地能力
文档示例用 crypto 做 sha256sum 可以,但 Node API 往往连着文件系统、网络、进程 ------远程内容或不可信页面时,暴露越少越安全。
和 main.js 的配合
你在 preload 里写了 ipcRenderer.invoke('ping'),主进程里还需要 ipcMain.handle('ping', ...) 才算闭环;否则调用会挂起或报错。这是「桥 + IPC」完整链路,不单是 contextBridge 的事。
八、和你当前项目的对照小结
|-----------------|------------------------------------------------------|
| 文件 | 角色 |
| main.js | webPreferences.preload 指定预加载脚本路径 |
| preload.js | 隔离世界里 require('electron'),用 contextBridge 暴露 API |
| renderer.js | 主世界里像用普通全局 API 一样用 versions / myfn / myObj |
| index.html | CSP 仍是 script-src 'self',业务脚本走本地文件 |
你已经练到的三点:
- 暴露对象 (
versions、myObj) - 暴露单个函数 (
myfn) - 暴露封装好的 IPC (
ping→invoke)
九、对接 Vue3 时怎么想(预习)
- Vue 组件里同样用 window.versions 或你在 preload 里挂的 window.electron(推荐统一命名,例如都叫
window.electronAPI)。 - 更干净的做法:在 Vue 里包一层
src/utils/electron.ts,内部读window.electronAPI,组件不直接碰全局。 - 不要 在 Vue 里
require('electron');不要 关contextIsolation图省事------用 bridge 才是正道。
十、一张心智图

十一、背这一句就够复盘
contextBridge****是预加载脚本在「隔离世界」里用的工具,只把经过你设计的 API 挂到页面的 window****上;函数走代理,数据走复制/冻结;永远不要整包暴露 ipcRenderer****或 Node 全家桶。