3.contextBridge桥梁

下面是 「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.versionswindow.myfnwindow.myObj------桥已经帮你挂到 window 上了


五、expose 的 api 能长什么样?

官方允许的结构(简化记忆):

  • 基本类型stringnumberboolean
  • 函数 :会被代理到另一侧(跨世界调用)
  • 数组、普通对象 :会复制 + 冻结(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 全局 / 本地能力

文档示例用 cryptosha256sum 可以,但 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',业务脚本走本地文件 |

你已经练到的三点:

  1. 暴露对象versionsmyObj
  2. 暴露单个函数myfn
  3. 暴露封装好的 IPCpinginvoke

九、对接 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 全家桶。

相关推荐
bug-100868 小时前
hooks,mixin,pinia,vuex
前端·vue.js
lqj_本人8 小时前
鸿蒙electron跨端框架PC墨案写作实战:把 Markdown 正文区做成桌面写作的中心
华为·electron·harmonyos
阿正的梦工坊8 小时前
【Typescript】04-数组元组枚举与字面量类型
javascript·ubuntu·typescript
神奇小汤圆8 小时前
自己用 ai 写了个链接 mysql 数据库的 mcp 工具
javascript
ai安歌8 小时前
鸿蒙PC:鸿蒙electron跨端框架PC影像巡检台实战:把图片管理做成可复核的本地工作流
华为·electron·harmonyos
Shirley~~8 小时前
npm包发布与 dist-tag 管理指南
前端·npm·node.js
Csvn8 小时前
前端可视化入门:Canvas、SVG 与 D3.js 基础
前端·d3.js
bug-100868 小时前
vue2和vue3的路由变化
前端·vue.js
百数平台8 小时前
功能更新——百数详情页“数据简报”与“关联标签页”配置指南
java·服务器·前端