楼层: 首页/ 软件技术/ 前端进阶/ 第 4 章 Electron:老牌桌面方案
电

第 4 章 Electron:老牌桌面方案

Electron — Chromium + Node.js

VS Code、Slack、Discord、Figma 都是 Electron 写的。它的思路是把 Chromium 和 Node.js 一起打包,你用纯 Web 技术写 UI,还能直接调 Node API。优点是生态成熟、不用学 Rust;缺点是包大、内存高。

主进程 / 渲染进程 / IPC

程Electron 的三层结构

主进程(main):Node.js 环境,一个 App 只有一个。管窗口生命周期、菜单、托盘、文件系统、系统 API。

渲染进程(renderer):Chromium 环境,每个窗口一个,跑你的前端页面。

preload 脚本:桥接层。通过 contextBridge 把主进程安全暴露的 API 给渲染进程用。不要在渲染进程直接开 Node 集成,安全风险大。

IPC:主进程和渲染进程通信的唯一通道。ipcMain.handle 接收,ipcRenderer.invoke 调用。

Electron 安全铁律

contextIsolation: true(隔离上下文)、nodeIntegration: false(渲染进程不能直接用 Node)、sandbox: true(沙箱)、配 CSP 内容安全策略。这几个开了,Electron 才是安全的。从网上抄老教程经常看到关闭这些选项,别照抄。

主进程 main.js 骨架

const { app, BrowserWindow, ipcMain } = require("electron"); const path = require("node:path"); function createWindow() { const win = new BrowserWindow({ width: 1000, height: 700, webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, // 必开 nodeIntegration: false, // 必关 }, }); win.loadURL("http://localhost:5173"); // 开发时连 Vite } app.whenReady().then(createWindow); // 接收渲染进程的调用 ipcMain.handle("file:open", async () => { return await openSomeFile(); });

preload.js —— 安全地暴露 API 给前端

const { contextBridge, ipcRenderer } = require("electron"); contextBridge.exposeInMainWorld("api", { openFile: () => ipcRenderer.invoke("file:open"), }); // 前端 React 里:window.api.openFile()

打包:electron-builder

# 打 Mac 安装包 electron-builder --mac dmg # 打 Windows 安装包 electron-builder --win nsis # 自动更新:配合 electron-updater,GitHub Releases 当更新源

IPC 两种模式:invoke/handle vs send/on

Electron 就是把网页包装成桌面 APP。主进程(Node 环境,一个 App 一个)管窗口/菜单/托盘/系统 API;渲染进程(Chromium,每窗口一个)跑你的 React 页面。两者唯一通道是 IPC。

// ① 请求-响应:invoke/handle(有返回值,Promise) // 主进程 ipcMain.handle("file:read", async (_e, path) => fs.readFile(path, "utf-8")); // preload contextBridge.exposeInMainWorld("api", { readFile: (p) => ipcRenderer.invoke("file:read", p) }); // ② 单向事件:send/on(无返回,主进程主动推) // 主进程:win.webContents.send("download-progress", 42) // preload:onProgress: (cb) => ipcRenderer.on("download-progress", (_e, v) => cb(v))

原生能力全家桶

模块能干啥
Menu应用菜单、右键上下文菜单、快捷键 accelerator。
Tray系统托盘图标、tooltip、菜单、点击/闪烁。
globalShortcut全局快捷键(App 在后台也能触发)。
dialogshowOpenDialog/showSaveDialog/showMessageBox 文件选择、弹窗。
Notification系统通知。
clipboard读写文本/图片到剪贴板。
screen屏幕尺寸、缩放比。
shell用系统默认浏览器打开链接、显示文件。
autoUpdater配合 electron-updater 检查/下载/安装更新。

安全铁律与打包分发

Electron 安全四条军规

contextIsolation: true(隔离渲染与 preload)、nodeIntegration: false(渲染进程不能直接 require)、sandbox: true(沙箱)、配 CSP。preload 只通过 contextBridge 暴露最小 API,绝不把 ipcRenderer 整个甩出去。照老教程把 nodeIntegration 打开,XSS 一来直接接管你电脑。

electron-builder 打包配置要点

// package.json build 段 { "appId": "com.example.app", "productName": "MyApp", "files": ["dist/**", "out/**"], "mac": { "target": ["dmg"], "category": "public.app-category.productivity" }, "win": { "target": ["nsis"] }, "linux": { "target": ["AppImage", "deb"] } } # electron-builder --mac / --win / --linux 出安装包 # Windows 要 EV 证书、Mac 要 Apple Developer ID + notarization 公证,否则 SmartScreen/ Gatekeeper 拦

完整案例:Electron + React + Vite + TS 写 Markdown 编辑器——主进程 BrowserWindow + preload 暴露 openFile/saveFile(走 dialog + fs),渲染进程 React 编辑器,托盘图标,electron-updater 自动更新。包体优化用 asar 打包 + 代码分割 + 移除 devDependencies。

本章面试题

Q1. 主进程和渲染进程有什么区别?为什么不能在渲染进程直接 require Node?

看答案

主进程是 Node 环境、管窗口和系统 API;渲染进程是 Chromium、跑 UI。渲染进程加载的网页不可信(XSS/第三方内容),直接给它 Node 权限等于把系统命令交出去,必须 nodeIntegration:false + preload contextBridge 白名单暴露。

Q2. preload 脚本起什么作用?

看答案

桥接主进程和渲染进程,在 contextIsolation 开启时通过 contextBridge.exposeInMainWorld 把主进程白名单 API 安全挂到 window 上,前端只能用这些显式暴露的方法。

Q3. ipcMain.handle/invoke 和 send/on 怎么选?

看答案

需要返回值用 handle/invoke(请求-响应、Promise);主进程主动单向推送用 send/on。