预加载脚本 Preload
本教程共 45 篇 · 第 9 篇 · 更新于 2026-08-03
9. 预加载脚本 Preload
本节目标
- 理解 preload 脚本的定义与执行时机。
- 明白它在
contextIsolation下如何充当安全的桥梁。 - 清楚 preload 能访问什么、不能访问什么。
- 学会用
contextBridge把有限能力暴露给渲染进程。 - 知道 preload 与渲染进程「同页面、不同世界」的关系。
主进程是一个拥有完整系统权限的 Node.js 环境,渲染进程默认却跑在网页沙箱里、碰不到 Node.js。这中间有一道墙。preload(预加载脚本)就是 Electron 架在墙上的那座「受控小桥」。
本章讲清楚 preload 是什么、什么时候跑、能做什么、不能做什么,以及它和渲染进程到底是啥关系。
9-1 preload 是什么
preload 是一段你在 BrowserWindow 构造时通过 webPreferences.preload 指定的脚本。它在渲染进程加载网页之前运行,作用类似 Chrome 扩展的 content script。
它特殊的地方在于:既看得到网页的 DOM,又能拿到一部分 Node.js 与 Electron 的受限 API。正是这种「两边都能看一点」的位置,让它成为打通主、渲两进程的天然通道。
// 主进程 main.js
const { app, BrowserWindow } = require('electron/main')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 指定 preload 脚本路径
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(createWindow)
__dirname 指向当前脚本所在目录,path.join 用来拼接跨平台都正确的路径。这两个是 Node.js 基础概念,写 Electron 会经常用到。
9-2 执行时机很关键
preload 在「网页加载之前」注入。也就是说,网页里的 JS 还没跑,preload 已经先跑完了。这让 preload 有机会在页面看到世界之前,先准备好要暴露给页面的全局对象。
因为 preload 先执行,渲染进程里后续的业务代码才能直接调用 window.xxx 这些被暴露出来的接口。时序上一定是:preload 注册 → 网页脚本使用。
// preload.js(Preload 脚本)
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
})
上面的 preload 把 Electron 的版本信息,通过 contextBridge 暴露成 window.versions。注意这些值是函数,页面调用时才返回,避免把敏感对象直接挂出去。
9-3 在隔离世界里的 preload
自 Electron 12 起,contextIsolation 默认开启。开启后,preload 运行在一个「隔离世界(Isolated World)」,它看到的 window 和网页看到的 window 不是同一个对象。
这意味着:你在 preload 里写 window.hello = 'wave',网页里读 window.hello 会是 undefined。两边各有一份 window,互不可见。这正是安全设计——网页里的恶意脚本摸不到 preload 持有的强大能力。
那 preload 怎么把东西交给网页?靠 contextBridge.exposeInMainWorld。它专门负责把 preload 这边的接口,「安全地」映射到网页能看到的那个 window 上。
9-4 preload 能做什么
preload 能做的事,可以分成三类:
- 读取受限信息:比如
process.versions、部分 Electron 渲染端模块。从 Electron 20 起,渲染进程默认开启沙箱,preload 只能用一个被裁剪过的require,能引入的模块很有限。 - 用
contextBridge暴露 API:把主进程能力包装成函数,挂到window上给网页调用,这是 preload 最重要的职责。 - 做 DOM 操作:preload 能触达 DOM,可以给页面提前注入节点、绑定事件,相当于一个轻量的页面初始化层。
第一条尤其容易踩坑。沙箱下 require 只认 electron 以及 node:events、node:timers、node:url 这类少数内置模块,想在 preload 里直接 require('node:fs') 读文件是行不通的。需要文件系统能力,就把活儿交给主进程,preload 只负责转发请求。
// preload.js(Preload 脚本)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
// 把一次 IPC 调用包装成简单函数
ping: () => ipcRenderer.invoke('ping')
})
渲染进程里就能这样用:
// 渲染进程 renderer.js
// 普通脚本里不能直接用顶层 await,包一层 async 函数
async function callPing () {
const res = await window.electronAPI.ping()
console.log(res) // 输出主进程返回的字符串
}
callPing()
对应的主进程要用 ipcMain.handle('ping', () => 'pong') 接住这次调用,否则 invoke 返回的 Promise 会一直挂着不结束。
9-5 preload 不能做什么
preload 不是万能的,有几条硬边界:
不能把整个 ipcRenderer 直接暴露出去。一旦网页拿到完整 ipcRenderer,它就能发任意 IPC 消息,等于把主进程大门的钥匙交了出去。必须像上面那样,一个函数只对应一个明确的动作。
不能传函数、Symbol、带原型的自定义类过桥。结构化克隆只认纯数据。你暴露的函数会被代理,但类构造函数、Symbol 都会丢失。
不能假设网页和你共享同一个 window。隔离世界下这根本不成立,所以别再用老写法 window.myAPI = {...},请统一用 contextBridge。
9-6 与渲染进程的关系
一句话概括:preload 和渲染进程加载的是同一个页面,但它们处在两个不同的 JavaScript 世界。preload 是「特权访客」,渲染进程是「普通访客」。
渲染进程(你写的页面业务代码)默认没有 Node.js,也碰不到 Electron 内部。它只能通过 preload 故意留给它的那几个函数,间接请求主进程帮忙。这种「最小权限」的设计,让即使页面被注入恶意脚本,危害也被限制在 preload 暴露的接口范围内。
// 渲染进程 index.html 内引用的 renderer.js
const information = document.getElementById('info')
information.innerText =
`本机 Chrome v${window.versions.chrome()}, ` +
`Node v${window.versions.node()}, ` +
`Electron v${window.versions.electron()}`
记住:preload 负责「给能力」,渲染进程负责「用能力」。分工清晰,代码才好维护。
9-7 preload 的加载与多个窗口
每个 BrowserWindow 都可以有自己的 preload,也可以多个窗口共用同一个 preload 文件。共享同一个 preload 能减少重复代码,但前提是所有窗口需要的接口一致。若不同窗口职责差别很大,分文件写 preload 会更清晰。
preload 里暴露的 API,是对「当前这个窗口的渲染进程」生效的。窗口 A 的 preload 挂的 window.electronAPI,窗口 B 默认也有(如果 B 用了同一个 preload)。但两边各自是独立的运行时,A 里调用函数改的是 A 对应的主进程交互,不会直接影响 B。
需要提醒:preload 在网页加载前注入,所以务必保证你暴露的接口名稳定。渲染进程业务代码依赖 window.electronAPI.xxx,一旦 preload 改名,页面就会报「找不到函数」。把接口名当作契约,前后端都遵守,是团队协作时的基本纪律。
9-8 常见误区
- 误区一:在 preload 里写
window.myAPI = {...}。隔离世界下网页读不到,请改用contextBridge.exposeInMainWorld。 - 误区二:为了省事把整个
ipcRenderer暴露给页面。这是严重的安全隐患,应按动作逐一封装。 - 误区三:以为 preload 能直接
require任意 npm 包。沙箱下require被裁剪,复杂依赖请走打包或放主进程。
9-9 本章小结
preload 是 Electron 安全模型里的关键一环:它在网页加载前运行,处在隔离世界,通过 contextBridge 把少量受控能力交给渲染进程。理解「执行时机、隔离边界、最小暴露」这三点,你就真正掌握了 preload。