Electron.js 详解、应用场景及完整案例
一、Electron.js 核心详解
1. 什么是 Electron.js?
Electron(原名为 Atom Shell)是由 GitHub 开发的开源框架,基于 Node.js 和 Chromium,允许开发者使用 HTML、CSS、JavaScript 构建跨平台(Windows、macOS、Linux)的桌面应用程序。
核心优势:
- 跨平台:一套代码运行在三大桌面系统,无需针对不同系统单独开发
- 技术栈友好:前端开发者无需学习 C++/Swift 等桌面开发语言,直接复用 Web 技术
- 原生能力:通过 Node.js 访问文件系统、系统API,通过 Electron 内置模块实现窗口管理、菜单、托盘等桌面特性
- 生态丰富:可复用 npm 生态(数十万包)和前端框架(React/Vue/Angular)
2. 核心架构
Electron 应用由 主进程 和 渲染进程 组成,通过 IPC(进程间通信)实现通信:
| 进程类型 | 作用 | 技术基础 | 核心模块 |
|---|---|---|---|
| 主进程(Main Process) | 管理应用生命周期(启动/退出)、窗口创建、系统资源访问 | Node.js + Electron API | BrowserWindow(窗口)、app(应用)、Menu(菜单)、ipcMain(IPC通信) |
| 渲染进程(Renderer Process) | 负责UI渲染,每个窗口对应一个独立渲染进程 | Chromium + Web 技术 | ipcRenderer(IPC通信)、remote(已废弃,改用 @electron/remote) |
关键原理:
- 主进程是应用入口,只能有一个,负责创建和管理渲染进程
- 渲染进程运行在 Chromium 沙箱中,默认无法直接访问系统资源,需通过 IPC 向主进程发送请求
- 预加载脚本(Preload Script):在渲染进程加载前执行,可桥接主进程和渲染进程,暴露安全的API给渲染进程
3. 核心模块速览
| 模块名 | 核心功能 |
|---|---|
app |
应用生命周期管理(启动、退出、激活、事件监听) |
BrowserWindow |
创建和控制应用窗口(大小、位置、样式、加载URL) |
ipcMain/ipcRenderer |
主进程与渲染进程的异步通信 |
Menu/MenuItem |
创建应用菜单(顶部菜单、右键菜单) |
Tray |
创建系统托盘图标和上下文菜单 |
dialog |
显示原生对话框(打开文件、保存文件、提示框) |
fs(Node.js 模块) |
文件系统读写(主进程可直接使用,渲染进程需通过IPC) |
二、Electron 应用场景
Electron 因跨平台、技术栈低门槛,被广泛用于以下场景:
1. 开发者工具
- VS Code:代码编辑器(最知名的 Electron 应用)
- Postman:API调试工具
- GitHub Desktop:Git 版本控制客户端
- Slack:团队协作工具
2. 生产力工具
- Notion:笔记与知识库工具
- Trello:项目管理工具
- Figma(桌面版):UI设计工具
- 企业内部办公系统(如审批、报表工具)
3. 媒体/娱乐应用
- Spotify(桌面版):音乐播放器
- 视频播放器(基于 Web 播放器+本地文件访问)
- 游戏客户端(2D 游戏或轻量 3D 游戏)
4. 本地数据处理工具
- 日志分析工具(读取本地日志文件+可视化)
- CSV/Excel 数据转换工具
- 本地数据库管理工具(如 SQLite 客户端)
5. 离线 Web 应用封装
- 将现有 Web 应用(如后台管理系统)封装为桌面应用,支持离线缓存
- 需访问本地资源的 Web 工具(如本地图片处理)
三、完整案例:本地文件阅读器
功能说明
- 支持通过对话框选择本地文本文件(.txt/.md)
- 读取文件内容并在界面展示
- 支持文件编码切换(UTF-8/GBK)
- 窗口大小可调整、支持最小化/关闭
技术栈
- 主框架:Electron 28(最新稳定版)
- 前端:原生 HTML/CSS/JavaScript(无框架依赖,便于理解)
- 文件编码:iconv-lite(处理 GBK 编码)
步骤 1:项目初始化
1.1 创建项目目录
mkdir electron-file-reader
cd electron-file-reader
1.2 初始化 npm 项目
npm init -y
1.3 安装依赖
# 安装 Electron(生产依赖)
npm install electron --save
# 安装 iconv-lite(处理文件编码)
npm install iconv-lite --save
1.4 配置 package.json
修改 package.json,添加 Electron 启动脚本和主进程入口:
{
"name": "electron-file-reader",
"version": "1.0.0",
"main": "main.js", // 主进程入口文件
"scripts": {
"start": "electron .", // 启动应用
"package": "electron-packager . FileReader --platform=all --arch=all --out=dist" // 打包(需先安装 electron-packager)
},
"devDependencies": {},
"dependencies": {
"electron": "^28.0.0",
"iconv-lite": "^0.6.3"
}
}
步骤 2:编写主进程代码(main.js)
主进程负责窗口创建、文件读取、IPC通信:
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
const fs = require('fs');
const path = require('path');
const iconv = require('iconv-lite');
let mainWindow;
// 创建主窗口
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 启用预加载脚本
preload: path.join(__dirname, 'preload.js'),
// 允许渲染进程使用 Node.js API(开发环境方便,生产环境建议禁用)
nodeIntegration: false,
contextIsolation: true // 启用上下文隔离,安全推荐
}
});
// 加载前端页面
mainWindow.loadFile('index.html');
// 打开开发者工具(开发环境)
mainWindow.webContents.openDevTools();
// 窗口关闭事件
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// 应用就绪后创建窗口
app.whenReady().then(createWindow);
// 所有窗口关闭时退出应用(Windows/Linux)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// macOS 激活应用时重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// IPC:监听渲染进程的"选择文件"请求
ipcMain.handle('select-file', async () => {
// 打开文件选择对话框
const result = await dialog.showOpenDialog(mainWindow, {
title: '选择文本文件',
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile'] // 仅允许选择单个文件
});
// 用户取消选择
if (result.canceled) return null;
// 返回选中的文件路径
return result.filePaths[0];
});
// IPC:监听渲染进程的"读取文件"请求
ipcMain.handle('read-file', async (event, filePath, encoding) => {
try {
// 读取文件原始字节(避免编码问题)
const buffer = fs.readFileSync(filePath);
// 根据选择的编码解码
const content = iconv.decode(buffer, encoding);
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
步骤 3:编写预加载脚本(preload.js)
预加载脚本在渲染进程加载前执行,用于暴露 IPC 通信接口(安全隔离):
const { contextBridge, ipcRenderer } = require('electron');
// 向渲染进程暴露安全的 API(window.electron 全局对象)
contextBridge.exposeInMainWorld('electron', {
// 选择文件
selectFile: () => ipcRenderer.invoke('select-file'),
// 读取文件
readFile: (filePath, encoding) => ipcRenderer.invoke('read-file', filePath, encoding)
});
步骤 4:编写前端界面(index.html)
前端负责UI展示和用户交互,通过 window.electron 调用主进程API:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Electron 本地文件阅读器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #f5f5f5;
}
.header {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
padding: 8px 16px;
cursor: pointer;
background: #2f54eb;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
}
button:hover {
background: #1d39c4;
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
}
.file-path {
margin: 10px 0;
color: #666;
font-size: 14px;
}
.content {
width: 100%;
height: calc(100vh - 120px);
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
overflow: auto;
white-space: pre-wrap; /* 保留换行和空格 */
font-size: 14px;
line-height: 1.6;
}
.error {
color: #f5222d;
}
</style>
</head>
<body>
<div class="header">
<button id="selectBtn">选择文件</button>
<select id="encodingSelect">
<option value="utf8">UTF-8</option>
<option value="gbk">GBK</option>
</select>
</div>
<div class="file-path" id="filePath">未选择文件</div>
<div class="content" id="content"></div>
<script>
// 获取DOM元素
const selectBtn = document.getElementById('selectBtn');
const encodingSelect = document.getElementById('encodingSelect');
const filePathEl = document.getElementById('filePath');
const contentEl = document.getElementById('content');
// 选择文件按钮点击事件
selectBtn.addEventListener('click', async () => {
try {
// 调用主进程的选择文件API
const filePath = await window.electron.selectFile();
if (!filePath) return;
// 显示文件路径
filePathEl.textContent = `当前文件:${filePath}`;
contentEl.textContent = '正在读取文件...';
// 获取选择的编码
const encoding = encodingSelect.value;
// 调用主进程的读取文件API
const result = await window.electron.readFile(filePath, encoding);
if (result.success) {
contentEl.textContent = result.content;
contentEl.classList.remove('error');
} else {
contentEl.textContent = `读取失败:${result.error}`;
contentEl.classList.add('error');
}
} catch (error) {
contentEl.textContent = `错误:${error.message}`;
contentEl.classList.add('error');
}
});
// 编码切换时重新读取文件
encodingSelect.addEventListener('change', async () => {
const filePath = filePathEl.textContent.replace('当前文件:', '');
if (filePath === '未选择文件') return;
contentEl.textContent = '正在重新读取文件...';
const encoding = encodingSelect.value;
const result = await window.electron.readFile(filePath, encoding);
if (result.success) {
contentEl.textContent = result.content;
contentEl.classList.remove('error');
} else {
contentEl.textContent = `读取失败:${result.error}`;
contentEl.classList.add('error');
}
});
</script>
</body>
</html>
步骤 5:运行应用
在项目根目录执行以下命令启动应用:
npm start
步骤 6:打包应用(可选)
6.1 安装打包工具
npm install electron-packager --save-dev
6.2 执行打包命令
npm run package
打包完成后,会在 dist 目录下生成对应系统(Windows/macOS/Linux)的可执行文件。
案例效果
- 启动后显示"未选择文件",点击"选择文件"按钮可打开系统文件对话框
- 选择
.txt或.md文件后,自动读取并展示内容 - 可通过下拉框切换编码(解决GBK文件乱码问题)
- 支持窗口大小调整、最小化/关闭,内容区域自动滚动
四、关键注意事项
1. 安全性
- 禁用
nodeIntegration: true(默认禁用),避免渲染进程被恶意脚本攻击 - 启用
contextIsolation: true(默认启用),隔离主进程和渲染进程的上下文 - 预加载脚本仅暴露必要的API,避免暴露敏感模块(如
fs直接给渲染进程)
2. 性能优化
- 避免创建过多窗口(每个窗口对应一个渲染进程,占用内存)
- 大文件读取采用流(
fs.createReadStream),避免同步读取导致UI阻塞 - 优化渲染进程的DOM操作,避免频繁重绘
3. 跨平台兼容性
- 文件路径:使用
path.join代替硬编码路径(Windows用\,macOS/Linux用/) - 系统托盘:macOS 托盘图标尺寸建议 22x22px,Windows 建议 32x32px
- 对话框样式:不同系统的原生对话框样式不同,无需手动适配
4. 打包与分发
- 打包工具:
electron-packager(简单打包)、electron-builder(更强大,支持自动签名、安装包生成) - 体积优化:剔除开发依赖(
npm prune --production)、使用asar压缩资源文件
五、扩展方向
- 支持更多文件格式(如 PDF、Office 文件,需集成对应解析库)
- 添加文件编辑和保存功能
- 实现最近打开文件列表(通过
app.getPath('recentDocuments')) - 支持暗黑模式(通过
nativeTheme模块) - 集成云同步功能(通过 Node.js 调用云存储API)

浙公网安备 33010602011771号