首页 / Electron 入门教程 / 预加载脚本 Preload

Electron 入门教程

预加载脚本 Preload

本教程共 45 篇 · 第 9 篇 · 更新于 2026-08-03

Electronpreload预加载脚本contextBridge渲染进程

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:eventsnode:timersnode: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。