Electron 开发详解
Electron (原名 Atom Shell) 是一个由 GitHub 开发的开源框架,它允许你使用 Web 技术 (HTML, CSS, JavaScript) 来构建跨平台的桌面应用程序。通过将 Chromium 渲染引擎和 Node.js 运行时集成到一个单一的项目中,Electron 使得前端开发者只需掌握一套技术栈,就能创建出功能强大、拥有原生外观和感觉的桌面应用,并能够访问操作系统底层功能。
核心思想:借助 Chromium (渲染视图) 和 Node.js (处理操作系统交互) 的能力,Electron 让 Web 技术栈能够构建全功能的跨平台桌面应用。
一、Electron 简介与优势
1.1 什么是 Electron?
Electron 可以被简单理解为一个微型浏览器套壳,它包含了:
- Chromium:提供渲染用户界面的能力,这意味着你可以使用 HTML、CSS 和 JavaScript 来构建应用的 UI。
- Node.js:提供访问操作系统底层 API 的能力,例如文件系统、网络、进程管理等。
- 原生 API 集成:Electron 提供了一套 API,用于访问操作系统特定的功能,如菜单、通知、窗口管理等。
1.2 Electron 的优势
- 跨平台:只需编写一次代码,即可在 Windows、macOS 和 Linux 上运行。
- Web 技术栈:前端开发者可以利用熟悉的 HTML、CSS 和 JavaScript 技能快速上手。
- 丰富的生态系统:受益于庞大的 Web 和 Node.js 生态系统,有无数的库和工具可用。
- 强大的功能:Node.js 赋予了应用强大的后端能力与系统级交互。
- 社区活跃:拥有庞大且活跃的开发者社区和 GitHub 的持续支持。
1.3 知名应用
许多知名的桌面应用都是基于 Electron 构建的,例如:
- VS Code
- Slack
- Discord
- Skype (新版)
- GitHub Desktop
- Notion
二、Electron 架构:主进程与渲染进程
Electron 应用的核心是一个多进程架构,类似于 Web 浏览器:
graph TD
subgraph "Main Process (Node.js)"
A[主进程入口文件: main.js] --> B(创建和管理 BrowserWindow 实例)
B --> C(管理应用生命周期)
B --> D(处理原生 GUI 事件: 菜单, Dock)
B --> E(访问操作系统 API: 文件系统, 网络)
E --> F[IPCMain: 与渲染进程通信]
end
subgraph "Renderer Process (Chromium)"
G[渲染进程入口文件: index.html] --> H(加载 Web 页面)
H --> I(运行前端 JavaScript: React, Vue, Angular)
H --> J(渲染 UI: HTML, CSS)
J --> K[IPCRenderer: 与主进程通信]
end
E <--> K
F <--> K
2.1 主进程 (Main Process)
- 角色:负责管理应用程序的生命周期、创建和管理窗口、处理系统事件、与原生操作系统 API 交互。
- 环境:运行在一个 Node.js 环境中,因此拥有 Node.js 的所有功能。
- 文件:通常只有一个主进程,其入口文件通常是
main.js或类似名称。 - 权限:拥有完全访问操作系统底层资源的权限。
- 实例:
BrowserWindow实例 (new BrowserWindow()),用于创建新的渲染进程窗口。
2.2 渲染进程 (Renderer Process)
- 角色:负责渲染每个窗口中的用户界面。每个
BrowserWindow实例都运行在一个独立的渲染进程中。 - 环境:运行在一个 Chromium 环境中,与普通 Web 页面类似,拥有 DOM、BOM 和 Web API。
- 权限:默认情况下,出于安全考虑,渲染进程无法直接访问 Node.js API (可以通过
contextBridge或nodeIntegration改变)。
2.3 进程间通信 (IPC - Inter-Process Communication)
由于主进程和渲染进程运行在不同的线程中,它们需要通过 IPC 机制进行通信。
ipcMain:主进程模块,监听来自渲染进程的消息,并向渲染进程发送消息。ipcRenderer:渲染进程模块,向主进程发送消息,并监听来自主进程的消息。
通信示例:
主进程 (main.js)
1 | const { app, BrowserWindow, ipcMain } = require('electron'); |
预加载脚本 (preload.js)
预加载脚本在渲染进程内容加载之前运行,并可以安全地暴露 Node.js API 给渲染进程的上下文。
1 | const { contextBridge, ipcRenderer } = require('electron'); |
渲染进程 (renderer.js 或直接写在 index.html 中)
1 |
|
三、Electron 开发环境搭建与 Hello World
3.1 目录结构推荐
1 | . |
3.2 最小化 Hello World 示例
创建项目文件夹并初始化
npm:1
2
3mkdir my-electron-app
cd my-electron-app
npm init -y安装 Electron:
1
npm install --save-dev electron
编辑
package.json:
添加一个启动脚本,指向主进程文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"name": "my-electron-app",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "main.js", # 指向主进程文件
"scripts": {
"start": "electron .", # 启动 Electron 应用
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"electron": "^最新版本" # 例如 ^28.0.0
}
}创建
main.js(主进程文件):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
30const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js') // 使用预加载脚本增强安全性
}
})
mainWindow.loadFile('index.html')
// 可选:打开开发者工具
// mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// 在 macOS 上,当所有窗口都关闭后,通常在点击 Dock 图标时重新创建一个窗口。
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})创建
index.html(渲染进程 UI 文件):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<meta charset="UTF-8">
<title>Hello Electron!</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>Hello from Electron!</h1>
<p>Current Node.js version: <span id="node-version"></span></p>
<p>Current Chromium version: <span id="chrome-version"></span></p>
<p>Current Electron version: <span id="electron-version"></span></p>
<script src="renderer.js"></script>
</body>
</html>创建
preload.js(预加载脚本):1
2
3
4
5
6
7
8const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
// 你也可以在这里暴露其他 IPC 通信方法
})创建
renderer.js(渲染进程脚本):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// contextBridge 暴露的对象在 window.versions 上
const information = document.getElementById('info')
if (information) { // 检查元素是否存在
information.innerText = `This app is using Node.js ${versions.node()},
Chromium ${versions.chrome()}, and Electron ${versions.electron()}.`
}
const setVersionText = (id, text) => {
const element = document.getElementById(id);
if (element) {
element.innerText = text;
}
};
setVersionText('node-version', window.versions.node());
setVersionText('chrome-version', window.versions.chrome());
setVersionText('electron-version', window.versions.electron());创建
index.css(可选,渲染进程样式文件):1
2
3
4
5
6body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: auto;
max-width: 38rem;
padding: 2rem;
}运行应用:
1
npm start
你将看到一个显示 “Hello from Electron!” 和版本信息的桌面窗口。
四、安全最佳实践
由于 Electron 应用同时拥有 Web 内容和对操作系统的访问权限,安全性至关重要。
- 启用
contextIsolation:默认开启。防止预加载脚本中的 Node.js 环境与渲染进程的 JavaScript 环境混淆。 - 使用
preload.js和contextBridge:通过contextBridge暴露受限的 API 给渲染进程,避免直接在渲染进程中启用nodeIntegration。 - 禁用
nodeIntegration:默认关闭。防止在渲染进程中直接访问 Node.js API。 - 禁用
webview中的nodeIntegration:如果使用webview标签加载外部内容,务必禁用其nodeIntegration。 - 限制导航:使用
webContents.on('will-navigate')和webContents.on('new-window')阻止或限制应用导航到非信任的 URL。 - 处理内容沙箱:Chromium 的沙箱机制有助于隔离不信任的内容。
- 内容安全策略 (CSP):在
index.html中设置 CSP 来限制允许加载的资源。 - 避免加载远程内容:优先加载本地文件 (
file://),避免加载远程 URL (http://,https://),除非你完全信任这些内容。
五、打包和分发
完成开发后,你需要将 Electron 应用程序打包成可分发的安装包。常用的工具有:
electron-builder:功能强大、配置灵活,支持多种平台和输出格式 (exe, dmg, deb, snap 等)。electron-packager:相对简单,只负责将应用打包成可执行文件,不创建安装包。
使用 electron-builder 示例:
安装
electron-builder:1
npm install --save-dev electron-builder
在
package.json中添加配置: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
33
34
35
36
37
38
39
40{
"name": "my-electron-app",
"version": "1.0.0",
"description": "...",
"main": "main.js",
"scripts": {
"start": "electron .",
"pack": "electron-builder --dir", # 只打包不创建安装程序
"dist": "electron-builder" # 生成安装程序
},
"build": {
"appId": "com.yourcompany.yourapp",
"productName": "MyElectronApp",
"copyright": "Copyright © 2025 ${author}",
"directories": {
"output": "dist"
},
"files": [
"**/*",
"!node_modules/*/{electron,electron-builder,electron-packager}*" # 排除开发依赖
],
"win": {
"target": ["nsis"],
"icon": "build/icon.ico"
},
"mac": {
"category": "public.app-category.utilities",
"target": ["dmg"],
"icon": "build/icon.icns"
},
"linux": {
"target": ["AppImage"],
"icon": "build/icons"
}
},
"devDependencies": {
"electron": "^最新版本",
"electron-builder": "^最新版本"
}
}运行打包命令:
1
npm run dist
这将在
./dist目录下生成针对不同操作系统的安装包。
六、集成前端框架
Electron 应用的渲染进程本质上就是一个 Web 页面,因此你可以自由地集成任何前端框架,如 React、Vue、Angular 等。
- 通常做法:使用 Create React App、Vue CLI、Angular CLI 等工具创建一个前端项目,将其构建输出 (如
build或dist目录) 作为 Electron 渲染进程的启动内容。
开发流程:
- 创建前端项目:
create-react-app my-react-app - 构建前端项目:
npm run build(通常会生成build目录) - 配置 Electron 加载:在
main.js中,将mainWindow.loadFile('index.html')修改为mainWindow.loadFile(path.join(__dirname, 'build', 'index.html'))。 - 开发体验优化: 在开发模式下,可以配置 Electron 加载前端开发服务器的 URL (如
localhost:3000),实现热重载。同时需要确保前端项目和 Electron 项目可以并行启动。1
2
3
4
5
6// main.js (开发模式)
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000'); // React Dev Server
} else {
mainWindow.loadFile(path.join(__dirname, 'build', 'index.html'));
}
七、总结
Electron 为 Web 开发者打开了桌面应用开发的大门。它通过结合 Chromium 的强大渲染能力和 Node.js 的系统级访问能力,提供了一个灵活、高效的跨平台解决方案。理解主进程与渲染进程的架构、IPC 通信机制以及安全最佳实践是开发高质量 Electron 应用的关键。通过合理的项目结构、前端框架集成和高效的打包分发工具,你可以将你的 Web 应用带到桌面,提供更加无缝和强大的用户体验。
