一、app和BrowserWindow
BrowserWindow浏览器窗口即构造器
ipc进程间通信:ipcMain和ipcRenderer
- 通过ipcMain.handle创建主进程处理程序
- 通过ipcRenderer.invoke在预处理脚本中暴露一个函数去触发处理程序
二、主进程和渲染进程
在 Electron 里,主进程 和渲染进程是两套不同职责的运行环境,大致可以这样理解:
主进程(Main Process)⭐
主进程本质是 Node 环境。
- 数量 :每个 Electron 应用通常只有 一个 主进程(入口一般是
main.js,或package.json里"main"指向的文件)。 - 角色:应用的「总控台」。负责创建窗口、处理系统级能力(菜单、托盘、全局快捷键等)、与 Node 原生模块打交道、管理应用生命周期(启动、退出)。
- 技术栈 :跑在 Node.js 环境里,可以直接用
require('fs')、path等 Node API。 - 典型 API :
app、BrowserWindow、ipcMain、dialog、Menu等。
可以理解为:不画界面(不直接画网页),但管窗口、管系统、管安全边界的那一侧。
你的项目里 :main.js(package.json → "main": "main.js")
渲染进程(Renderer Process)⭐
- 数量 :每个
BrowserWindow(以及 webview 等)通常对应 一个 渲染进程(多窗口 = 多个渲染进程)。 - 角色 :真正 显示页面 的地方:加载
index.html、执行里面的 JavaScript/CSS,和浏览器标签页类似。 - 技术栈 :本质是 Chromium 的渲染环境 (HTML/CSS/JS + Web API)。默认为了安全,不直接暴露完整 Node ;需要通过 preload +
contextBridge等方式,把 有限、明确 的能力暴露给页面脚本。 - 典型通信 :用
ipcRenderer(通常 经 preload 暴露 )和主进程的ipcMain发消息,完成「页面想读文件、想调系统对话框」等操作。
可以理解为:用户看到的网页那一侧,每个窗口一块「小浏览器」。
你的项目里 :index.html + renderer.js
桥梁:Preload(预加载脚本)⭐
preload 既不是主进程,也不是普通网页脚本 ,而是挂在 每个窗口 上、在页面加载前执行的一段 特殊脚本。
- 数量 :通常 每个窗口一份 (在
BrowserWindow的webPreferences.preload里配置,和你的窗口数一致)。 - 角色 :安全桥梁 。一端能接触 Electron/Node(如
ipcRenderer、contextBridge),另一端把 封装好的 API 交给页面里的window,让渲染进程 不必 开nodeIntegration也能间接用主进程能力。 - 技术栈 :跑在 隔离的上下文 里(和
renderer.js不是同一个 JS 世界),所以要用 contextBridge.exposeInMainWorld 暴露接口,而不是简单window.xxx = ...(否则页面可能篡改,不安全)。 - 典型写法 :
contextBridge+ipcRenderer.invoke/send;主进程用ipcMain.handle/on响应。
可以理解为:窗口里的「传话员」------页面只跟它说话,它再跟主进程/系统打交道。
你的项目里 :preload.js(在 main.js 里 preload: path.join(__dirname, 'preload.js'))
你项目里的例子:
|----------------------------------------------------------------------|--------------------------------------------|
| preload 做的事 | 页面怎么用 |
| exposeInMainWorld('versions', { node, chrome, electron, ping... }) | renderer.js 里调 versions.node() 等 |
| exposeInMainWorld('myfn', ...)、myObj | renderer.js 里调 myfn()、myObj.a |
| 内部 ipcRenderer.invoke('ping') | 页面只调 versions.ping(),不直接接触 ipcRenderer |
三者怎么协作(启动顺序)
pnpm start
→ main.js(主进程)创建 BrowserWindow,配置 preload,loadFile(index.html)
→ preload.js 先执行,contextBridge 挂到 window
→ index.html + renderer.js(渲染进程)跑起来,用 window 上暴露的 API
→ 需要系统能力时:renderer → preload(IPC)→ main(ipcMain)
一句话对比(含 preload)
|---------------|-------------|-------------------------|----------------------------|
| | 主进程 | Preload | 渲染进程 |
| 个数 | 一般 1 个 | 每窗口通常 1 个 | 每窗口通常 1 个 |
| 主要职责 | 窗口、系统、生命周期 | 安全暴露 API、转发 IPC | 界面、用户交互 |
| 环境 | Node 为主 | 隔离上下文 + 少量 Electron API | Chromium 为主 |
| 和用户界面 | 不直接渲染网页 | 不画界面,只搭桥 | 直接渲染网页 |
| 你的文件 | main.js | preload.js | index.html、renderer.js |
记忆口诀
- 主进程 = 后台老板(
main.js) - Preload = 传话员 + 安检(
preload.js,contextBridge) - 渲染进程 = 前台店面(
index.html+renderer.js)
页面 不要 直接 require('electron');该用的能力在 preload 里包一层,再通过 window.xxx 给 renderer.js 用,这就是现代 Electron 的推荐做法。
为什么要分两套?
主要是为了 安全与职责分离 :敏感能力(文件、原生模块)集中在主进程;渲染进程尽量像普通网页一样受限,再通过 IPC 做受控调用,减少被 XSS 等攻击后直接拿到整台机器权限的风险。
三、常用对象
contextBridge
解释:在隔离的上下文中创建一个安全的、双向的、同步的桥梁;把包装好的、安全的接口暴露给页面使用
作用:在「隔离的 preload 环境」和「页面脚本(渲染进程)」之间,安全地挂一小部分 API 到 window********上。
原因:渲染进程不能直接用node,preload可以访问node,但和页面又不是同一js上下文;contextBridge.exposeInMainWorld会 拷贝/封装 你暴露的函数和简单数据,挂到页面的 window 上,页面只能调用你允许的能力。