Electron 是什么

Electron 用 Chromium(渲染界面)+ Node.js(系统能力)构建跨平台桌面应用。前端技术栈(HTML/CSS/JS、Vue、React)可以直接用来做桌面软件,VS Code、Slack、飞书桌面版都是它的代表。

一、理解三个进程

Electron 应用由三部分协作:

部分 作用 能访问什么
主进程(main) 应用入口,创建窗口、管理系统事件 Node.js 全部能力(文件系统、网络、系统 API)
渲染进程(renderer) 页面 UI,运行在 Chromium 里 DOM、一部分受限 Node 能力
预加载脚本(preload) 在渲染进程加载前执行,桥接主进程与渲染进程 通过 contextBridge 暴露安全 API

安全模型:渲染进程不能直接碰 Node.js API,所有系统操作都要通过 IPC(进程间通信)找主进程完成。

二、初始化项目

1
2
3
mkdir my-electron-app && cd my-electron-app
npm init -y
npm install --save-dev electron

目录结构:

1
2
3
4
5
my-electron-app/
├── main.js # 主进程
├── preload.js # 预加载脚本
├── index.html # 渲染进程页面
└── package.json

package.json 里声明入口:

1
2
3
4
5
6
{
"main": "main.js",
"scripts": {
"start": "electron ."
}
}

三、主进程:创建窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

function createWindow() {
const win = new BrowserWindow({
width: 1000,
height: 700,
webPreferences: {
// 关键安全配置:开启上下文隔离,渲染进程无法直接访问 Node
contextIsolation: true,
// 使用 preload 脚本做桥接
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()

// macOS:点击 Dock 图标时重建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

// 除 macOS 外,全部窗口关闭时退出
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

四、IPC 通信:渲染进程请求主进程做事

场景:页面里点按钮,读取本机文件内容。

主进程监听(main.js 追加):

1
2
3
4
5
6
7
8
9
10
// 处理渲染进程发来的 read-file 请求
ipcMain.handle('read-file', async (event, filePath) => {
const fs = require('fs/promises')
try {
const content = await fs.readFile(filePath, 'utf-8')
return { ok: true, content }
} catch (e) {
return { ok: false, message: e.message }
}
})

preload 脚本暴露安全 API(preload.js):

1
2
3
4
5
6
7
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// 只暴露白名单方法,不直接暴露 ipcRenderer 本身
contextBridge.exposeInMainWorld('api', {
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath)
})

渲染进程调用(index.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<button id="btn">读取文件</button>
<pre id="result"></pre>

<script>
document.getElementById('btn').addEventListener('click', async () => {
// 通过 preload 暴露的 api 调用,而不是直接 require('fs')
const res = await window.api.readFile('/etc/hosts')
document.getElementById('result').textContent = res.ok
? res.content
: '读取失败:' + res.message
})
</script>
</body>
</html>

通信模式总结:

  • 渲染 → 主(请求-响应):ipcRenderer.invoke + ipcMain.handle
  • 渲染 → 主(单向通知):ipcRenderer.send + ipcMain.on
  • 主 → 渲染(推送):win.webContents.send + ipcRenderer.on(如后台任务进度)

主进程主动推送示例:

1
2
3
4
5
6
7
// 主进程
function sendProgress(win, percent) {
win.webContents.send('progress', percent)
}

// 渲染进程:preload 里加
// onProgress: (cb) => ipcRenderer.on('progress', (_e, p) => cb(p))

五、安全配置清单(生产环境必看)

1
2
3
4
5
6
webPreferences: {
contextIsolation: true, // 必须开启,隔离 Node 环境
nodeIntegration: false, // 禁止渲染进程直接使用 Node
preload: path.join(__dirname, 'preload.js'),
sandbox: true // 开启沙箱(Electron 20+ 默认)
}
  • 不要在渲染进程里 require(‘fs’)、require(‘child_process’),一律通过 preload 白名单走 IPC;
  • preload 只暴露必要的白名单方法,不要直接暴露整个 ipcRenderer;
  • 加载外部页面时(win.loadURL)要格外小心 XSS,本地页面也要转义用户输入。

六、打包发布

推荐用 electron-builder:

1
npm install --save-dev electron-builder

package.json 追加:

1
2
3
4
5
6
7
8
9
10
11
12
{
"build": {
"appId": "com.example.myapp",
"productName": "MyApp",
"files": ["main.js", "preload.js", "index.html"],
"win": { "target": "nsis" },
"mac": { "target": "dmg" }
},
"scripts": {
"build": "electron-builder"
}
}
1
npm run build

产物在 dist/ 目录:Windows 是 .exe 安装包,macOS 是 .dmg。

常见坑

  1. 渲染进程拿不到 Node 模块:先检查 contextIsolation/nodeIntegration 配置,再检查是否通过 preload 暴露;
  2. IPC 通道名拼错:invoke 和 handle 的名字必须完全一致;
  3. 打包后白屏:检查 files 配置是否把页面和 preload 打进去了;
  4. macOS 打包签名:正式分发需要 Apple Developer 证书,本地测试可跳过签名。

总结

Electron 开发的关键是先理解三进程模型,再遵守”一切系统操作走 IPC + preload 白名单”的安全规范。按”主进程建窗口 → preload 暴露 API → 渲染进程调用”的路径,就能搭出规范的桌面应用。