2.预加载(preload)
学习记录使用预加载脚本教程第 3 部分一、本节位置与目标项目内容教程位置第 3 部分前面建项目、开窗口后面加功能、打包、发布学习目标理解预加载脚本用contextBridge安全暴露 API用IPC让主进程与渲染进程通信本节要解决的核心问题主进程很强Node 系统渲染进程很像网页默认不能乱用 Node——中间怎么安全地搭桥二、三种进程能力对比先建立表格进程环境能做什么不能做什么主进程完整 Node.js Electron生命周期、窗口、文件、系统 API不能直接操作页面 DOM渲染进程类似浏览器网页DOM、Web API、Vue/React UI默认不能直接require(fs)等 Node预加载脚本介于两者之间在页面加载之前注入有限 Node/Electron用contextBridge暴露白名单 API不是给用户随便写业务 UI 的地方分工原则背下来主进程↔渲染进程职责不可互换渲染器要「特权能力」→ 走preload contextBridge IPC主进程要「页面信息」→ 也走IPC不要指望直接摸 DOM三、什么是预加载脚本预加载脚本包含在浏览器窗口加载网页之前运行的代码。 其可访问 DOM 接口和 Node.js 环境并且经常在其中使用contextBridge接口将特权接口暴露给渲染器。由于主进程和渲染进程有着完全不同的分工Electron 应用通常使用预加载脚本来设置进程间通信 (IPC) 接口以在两种进程之间传输任意信息。通俗理解预加载脚本 窗口里的「安检员 传话员」在HTML/页面脚本加载之前就先运行类似 Chrome 扩展的 Content Script 注入时机能接触一部分Node / Electron API通过contextBridge.exposeInMainWorld把你允许的能力挂到页面的window上主世界 worldId0Electron 20预加载默认沙盒化从 Electron 20 起preload默认沙盒化不再是「完整 Node 随便用」只有polyfill 过的require只能加载有限模块常见可用Electron 模块、部分 Node 模块、部分全局对象学习笔记不要假设 preload 里能require任意 npm 包以官方「进程沙盒化」文档为准。四、实战一把版本号暴露给页面4.1 流程四步main.js 指定 preload 路径 ↓ preload.js 用 contextBridge 暴露 versions ↓ index.html 引入 renderer.js ↓ renderer.js 读 versions.xxx() 更新 DOM4.2 关键代码与职责① main.js —— 挂上 preloadwebPreferences: { preload: path.join(__dirname, preload.js), }概念作用__dirname当前正在执行的脚本所在目录你的main.js所在文件夹一般是项目根path.join(...)拼跨平台路径避免手写\//你项目里已配置webPreferences: { preload: path.join(__dirname, preload.js), },② preload.js —— 暴露 API目标主世界 world 0contextBridge.exposeInMainWorld(versions, { node: () process.versions.node, chrome: () process.versions.chrome, electron: () process.versions.electron, })你项目在教程基础上还多了练习代码myfn、myObj、num1、process等——有助于理解「能暴露函数、对象、嵌套」但process: () process整包暴露要谨慎安全与克隆规则生产环境更推荐只暴露需要的字段。③ renderer.js —— 当普通网页 JS 用versions.chrome() // 等价 window.versions.chrome()你当前实现const information document.getElementById(info) information.innerText ... ${versions.chrome()} ... // myfn、myObj 等④ index.htmlp idinfo/p占位script src./renderer.js/scriptCSPscript-src self→ 只加载本地脚本符合安全基线五、实战二IPC —— 主进程与渲染进程说话5.1 为什么需要 IPC需求正确做法页面按钮 → 读本地文件渲染器invoke→ 主进程handle读文件主进程 → 通知页面更新webContents.send preload 里封装on渲染器直接require(fs)❌ 默认不应这样做5.2 教程里的ping / pong完整链路sequenceDiagram participant R as renderer.js (world 0) participant P as preload.js (world 999) participant M as main.js (主进程) R-P: window.versions.ping() P-M: ipcRenderer.invoke(ping) M--P: return pong P--R: Promise resolve pong步骤文件代码要点1 暴露封装好的调用preload.jsping: () ipcRenderer.invoke(ping)2 主进程注册处理main.jsipcMain.handle(ping, () pong)3 页面发起调用renderer.jsawait window.versions.ping()你preload 已写好pingping: () ipcRenderer.invoke(ping)待补全对照教程main.js在app.whenReady()里、createWindow()之前注册const { ipcMain } require(electron) app.whenReady().then(() { ipcMain.handle(ping, () pong) createWindow() })renderer.js里异步测试const func async () { const response await window.versions.ping() console.log(response) // pong } func()5.3 IPC 安全必背红线// ❌ 永远不要这样 contextBridge.exposeInMainWorld(ipc, ipcRenderer) // ✅ 只暴露白名单方法 ping: () ipcRenderer.invoke(ping)原因整包ipcRenderer会让页面代码能向主进程发任意频道的消息等于把后台钥匙交给前台恶意脚本危害极大。Web 类比像只提供api.getUser()而不是把整个fetch 管理员 Token 挂在window上。六、和你已学知识的串联前面学过的本节怎么用app/BrowserWindowwebPreferences.preload把脚本绑到窗口contextBridge.exposeInMainWorld注入到world 0给renderer.js/ VueexposeInIsolatedWorld本节教程不用日常开发也极少用Web 的window.SDK≈exposeInMainWorld(versions, {...})Web 的fetch调后端≈invoke调主进程七、对接 Vue3 的预习笔记main.js → 创建窗口、ipcMain.handle preload.js → contextBridge.exposeInMainWorld(electronAPI, {...}) src/main.ts (Vue) → window.electronAPI.ping()建议统一一个名字如electronAPIVue 里可再包一层 composableuseElectron()组件不直接散落window.xxx。八、本节文件职责清单复习用文件角色本节要点main.js主进程preload路径ipcMain.handlepreload.js预加载隔离世界contextBridge封装invokeindex.html页面结构CSP#info引 rendererrenderer.js渲染逻辑主世界用versionsawait ping()九、官方摘要 你的复盘句官方摘要Preload 在网页加载之前运行常用contextBridge把特权接口交给渲染器。主/渲染分工不同靠preload IPC传信息。你的复盘句背这句主进程管系统和窗口渲染进程管界面preload 在页面加载前用contextBridge只暴露白名单 API跨进程办事用 IPC且只封装invoke/on绝不暴露整个ipcRenderer。