Electron 入门与架构

一、Electron 是什么

Electron 是一个使用 Web 技术(HTML、CSS、JavaScript)构建跨平台桌面应用的框架。它由 GitHub 于 2013 年创建(最初叫 Atom Shell),目前由 OpenJS Foundation 维护。

用 Web 技术写桌面应用

1
Electron = Chromium(渲染) + Node.js(系统能力) + 原生 API(窗口/菜单/托盘)

这意味着:如果你会写前端,就能写桌面应用。代码写一次,打包成 Windows、macOS 和 Linux 三个平台的原生安装包。

知名产品

  • VS Code(最著名的 Electron 应用)
  • SlackDiscordWhatsApp Desktop
  • Figma(桌面版)
  • NotionObsidian
  • PostmanTypora
  • GitHub Desktop

Electron vs 其他方案

方案技术栈体积性能生态
ElectronHTML/CSS/JS + Node.js较大(~150MB)较高内存最成熟
TauriHTML/CSS/JS + Rust极小(~5MB)低内存成长中
FlutterDart中等移动端为主
QtC++最好学习成本高
NW.jsHTML/CSS/JS + Node.js同 Electron社区较小

二、核心架构

Electron 应用由两种进程组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──────────────────────────────────────────┐
│ 主进程(Main Process) │
│ - 启动应用、创建窗口 │
│ - 访问原生 API(Menu、Tray、dialog) │
│ - 控制应用生命周期 │
│ - 只能有一个主进程 │
└────────────┬─────────────────────────────┘
│ IPC (ipcMain / ipcRenderer)

┌────────┴────────┐
│ 预加载脚本 │
│ preload.js │
│ (contextBridge)│
└────────┬────────┘

┌────────┴────────┐ ┌─────────────────┐
│ 渲染进程 1 │ │ 渲染进程 2 │
│ Renderer │ │ Renderer │
│ (Chromium) │ │ (Chromium) │
│ 每个窗口一个 │ │ 每个窗口一个 │
└─────────────────┘ └─────────────────┘

主进程(Main Process)

  • 应用入口,运行在 Node.js 环境中
  • 负责创建窗口、管理应用生命周期
  • 可以调用所有 Node.js API 和 Electron 原生模块
  • 每个应用只有一个主进程(main.jsmain.ts

渲染进程(Renderer Process)

  • 每个 BrowserWindow 对应一个渲染进程
  • 运行在 Chromium 沙箱中,类似浏览器环境
  • 可以访问 DOM、CSS、window 对象
  • 默认不能直接访问 Node.js API(出于安全考虑)

预加载脚本(Preload Script)

  • 在主进程和渲染进程之间架桥
  • 使用 contextBridge 安全地暴露有限 API 给渲染进程
  • 运行在渲染进程中,但可以访问部分 Node.js API

IPC(进程间通信)

主进程和渲染进程之间通过 ipcMainipcRenderer 通信:

1
2
3
4
5
6
7
8
9
渲染进程                 主进程
│ │
│── ipcRenderer.invoke ──→ ipcMain.handle
│ │
│←── Promise resolve ──── return value
│ │
│── ipcRenderer.send ──→ ipcMain.on
│ │
│←── event.reply ──────── webContents.send

三、快速开始

创建项目

1
2
3
mkdir my-app && cd my-app
npm init -y
npm install 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
// main.js
const { app, BrowserWindow } = require('electron');

function createWindow() {
const win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true, // 开启上下文隔离(安全)
},
});

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();
});

HTML 页面

1
2
3
4
5
6
7
8
9
10
11
<!-- index.html -->
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Electron App</title></head>
<body>
<h1>Hello Electron</h1>
<button id="btn">版本信息</button>
<p id="info"></p>
<script src="renderer.js"></script>
</body>
</html>

预加载脚本

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

contextBridge.exposeInMainWorld('electronAPI', {
getVersion: () => ipcRenderer.invoke('get-version'),
getPlatform: () => ipcRenderer.invoke('get-platform'),
});

渲染进程

1
2
3
4
5
6
// renderer.js
document.getElementById('btn').addEventListener('click', async () => {
const version = await window.electronAPI.getVersion();
const platform = await window.electronAPI.getPlatform();
document.getElementById('info').textContent = `${platform} v${version}`;
});

主进程处理 IPC

1
2
3
4
5
6
7
8
9
10
// main.js
const { ipcMain } = require('electron');

ipcMain.handle('get-version', () => {
return process.versions.electron;
});

ipcMain.handle('get-platform', () => {
return process.platform;
});

启动

1
2
3
4
5
6
7
// package.json
{
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
1
npm start

四、上下文隔离与安全

Electron 的安全模型经历了几个阶段的变化。新项目的推荐配置:

1
2
3
4
5
6
7
8
9
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: false, // 禁用渲染进程的 Node.js
contextIsolation: true, // 开启上下文隔离
preload: path.join(__dirname, 'preload.js'), // 使用预加载暴露有限 API
sandbox: true, // 启用沙箱
webSecurity: true, // 启用同源策略
},
});
1
2
3
4
5
6
7
// 安全:通过 contextBridge 暴露指定 API
contextBridge.exposeInMainWorld('api', {
readFile: (path) => ipcRenderer.invoke('read-file', path),
});

// ❌ 不安全:直接启用 nodeIntegration
// nodeIntegration: true // 渲染进程可以直接 require('fs')

五、应用生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const { app } = require('electron');

app.on('ready', () => {
// 应用初始化完成,创建窗口
});

app.on('window-all-closed', () => {
// 所有窗口关闭
// macOS 通常不退出,直到 Cmd+Q
if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
// macOS:点击 dock 图标时触发
// 通常在窗口已关闭时重新创建
});

app.on('before-quit', () => {
// 应用即将退出,可以清理资源
});

app.whenReady().then(() => {
// 推荐替代 app.on('ready')
});

六、常见问题

Q1: Electron 应用体积为什么这么大

Electron 内置了完整 Chromium 和 Node.js(约 150MB)。可选的优化方式:

  • 使用 electron-builderasar 压缩
  • 删除不需要的 Chromium 组件
  • 使用 NSIS(Windows)/ DMG(macOS)压缩

Q2: 和浏览器的区别

能力浏览器Electron
DOM 操作
文件系统✅(通过 IPC + 主进程)
原生窗口✅ BrowserWindow
系统托盘✅ Tray
通知✅ Web Notification✅ Notification API
自动更新✅ autoUpdater
菜单栏✅ Menu

Q3: contextIsolation 是什么

contextIsolation: true 将渲染进程的 JavaScript 上下文与预加载脚本的上下文隔离,防止渲染进程直接访问 Node.js API。这是 Electron 12+ 的默认值,必须保持开启

七、推荐学习路径

  1. 理解主进程与渲染进程的架构和 IPC 通信
  2. 创建一个基本的窗口应用(本文)
  3. 掌握 BrowserWindow 的配置和生命周期
  4. 使用 ipcMain/ipcRenderer 实现进程通信
  5. 了解 contextBridge 的安全暴露模式
  6. 学习 Menu、Tray、dialog 等原生 API