1 介绍
1.1 定义
01.什么是Electron
a.跨平台桌面应用框架
Electron是基于Node.js和Chromium构建的开源框架,允许使用Web技术开发跨平台桌面应用程序
b.核心定位
旨在让开发者使用HTML、CSS和JavaScript等Web技术栈来构建功能完整的桌面应用
c.技术背景
由GitHub发起并维护,最初名为Atom Shell,为Atom编辑器而开发,后发展成独立的桌面应用框架
02.桌面应用开发新范式
a.Web技术栈
基于熟悉的Web技术(HTML、CSS、JavaScript),降低桌面应用开发门槛
支持现代前端框架(React、Vue、Angular等)
b.跨平台特性
一套代码,多平台运行(Windows、macOS、Linux)
原生操作系统API访问能力
c.生态系统
丰富的Node.js生态系统和NPM包管理器
庞大的社区支持和第三方库
03.发展历程
a.起源阶段(2013-2014)
作为Atom编辑器的核心框架开始开发
项目名称为Atom Shell
b.独立阶段(2014-2015)
从Atom项目中独立出来
改名为Electron,正式开源发布
c.发展阶段(2016-至今)
被众多知名应用采用(Visual Studio Code、Slack、Discord等)
社区快速发展,功能不断完善
1.2 核心概念
01.主进程(Main Process)
a.定义
每个Electron应用只有一个主进程,负责创建和管理应用程序的生命周期
主进程拥有完整的Node.js环境,可以访问所有Node.js API和操作系统API
b.职责范围
创建和销毁渲染进程
处理应用生命周期事件
管理窗口、菜单、系统托盘等UI元素
处理文件系统操作和网络请求
控制应用程序的整体行为
c.技术特点
运行在Node.js环境中
可以使用所有Node.js模块
具有对操作系统的完全访问权限
02.渲染进程(Renderer Process)
a.定义
每个BrowserWindow实例都会创建一个独立的渲染进程
渲染进程负责渲染用户界面,类似于浏览器的渲染引擎
b.职责范围
渲染HTML页面和CSS样式
执行JavaScript代码
处理用户交互事件
显示用户界面
c.技术特点
基于Chromium的渲染引擎
支持现代Web标准
默认运行在沙箱环境中
通过预加载脚本可以扩展功能
03.进程间通信(IPC)
a.定义
主进程和渲染进程之间通过IPC(Inter-Process Communication)机制进行通信
包括同步和异步通信方式
b.通信方式
渲染进程到主进程:ipcRenderer.send()、ipcRenderer.invoke()
主进程到渲染进程:webContents.send()、webContents.invoke()
双向通信:ipcMain.handle()、ipcRenderer.invoke()
c.技术特点
基于事件驱动的通信模型
支持同步和异步调用
数据序列化传输
04.安全沙箱
a.定义
渲染进程默认运行在沙箱环境中,限制了对系统资源的访问
提供额外的安全保护层
b.限制内容
无法直接访问Node.js API
限制文件系统访问
网络请求受控制
禁用原生模块
c.绕过方式
通过预加载脚本(preload script)暴露安全的API
使用contextBridge进行安全的通信
1.3 优缺点
01.主要优势
a.开发效率高
使用熟悉的Web技术栈,学习成本低
一套代码支持多个平台
快速原型开发和迭代
b.生态系统丰富
可以直接使用NPM上的海量包
前端框架和工具链支持完善
活跃的社区和丰富的文档
c.功能强大
完整的Node.js API访问
原生操作系统功能调用
自动更新和打包分发机制
d.跨平台兼容
Windows、macOS、Linux全平台支持
平台特定的功能适配
一致的API体验
02.存在挑战
a.应用体积大
需要打包整个Chromium和Node.js运行时
即使简单的应用也有几十MB大小
相比原生应用体积明显偏大
b.内存占用高
每个渲染进程都相当于一个完整的浏览器实例
多进程架构导致内存消耗较大
对低配置设备不够友好
c.性能开销
相比原生应用有一定的性能损失
启动时间相对较长
渲染性能可能不如原生应用
d.安全考虑
Web技术栈带来了新的安全挑战
需要谨慎处理用户输入和内容安全
需要及时更新依赖库修复安全漏洞
03.适用性分析
a.适合场景
Web应用桌面化(如Slack、Discord)
开发工具和IDE(如Visual Studio Code、Atom)
内容管理工具(如WordPress Desktop)
数据可视化和分析工具
b.不适合场景
对性能要求极高的应用
资源受限的环境
简单的轻量级工具
需要大量系统级操作的应用
c.替代方案
原生开发(C++、C#、Swift等)
Tauri(更轻量的跨平台方案)
Flutter Desktop
Web技术+WebView(各平台原生实现)
1.4 使用场景
01.办公生产力工具
a.文档编辑器
富文本编辑器(如Typora、Notion Desktop)
代码编辑器(如Visual Studio Code、Atom)
笔记管理工具(如Joplin、Boostnote)
b.协作工具
团队通讯软件(如Slack、Microsoft Teams)
项目管理工具(如Trello Desktop、ClickUp)
设计协作工具(如Figma Desktop、Sketch)
c.办公套件
电子表格工具
演示文稿制作
任务管理应用
02.开发工具
a.集成开发环境
代码编辑器和IDE
调试工具和分析器
版本控制客户端(如GitHub Desktop)
b.API工具
接口测试工具(如Postman、Insomnia)
数据库管理工具(如TablePlus、DataGrip)
服务器管理工具
c.构建和部署工具
持续集成客户端
容器管理界面
监控和日志查看器
03.多媒体应用
a.图像处理
图片编辑器(如GIMP、Paint.NET的Electron版本)
图标设计工具
截图和录屏工具(如ShareX、LICEcap)
b.音视频处理
音频播放器(如iTunes、Spotify Desktop)
视频播放器(如VLC、PotPlayer的Electron版本)
视频编辑和转码工具
c.创意工具
动画制作软件
3D建模工具
游戏开发工具
04.企业应用
a.企业管理系统
客户关系管理(CRM)系统
企业资源计划(ERP)系统
人力资源管理(HRM)系统
b.数据可视化
商业智能工具
数据分析平台
报表生成器
c.行业专用工具
医疗信息系统
金融交易终端
教育培训平台
1.5 架构原理
01.多进程架构
a.进程模型
主进程:负责应用生命周期管理和系统调用
渲染进程:负责UI渲染和用户交互
工作进程:可选的附加进程,用于后台任务
b.进程间通信机制
IPC(Inter-Process Communication)通道
基于事件驱动的消息传递
同步和异步调用支持
c.安全隔离
渲染进程沙箱环境
有限的系统资源访问权限
通过上下文隔离预加载脚本
02.渲染引擎
a.Chromium集成
基于Blink渲染引擎
支持现代Web标准
V8 JavaScript引擎集成
b.Web组件
HTML5、CSS3、ES6+支持
WebAssembly集成
音视频处理能力
c.性能优化
硬件加速渲染
内存管理和垃圾回收
多线程JavaScript执行
03.Node.js集成
a.运行时环境
完整的Node.js运行时支持
NPM包管理器集成
原生模块支持
b.系统API访问
文件系统操作
网络请求处理
操作系统API调用
c.模块系统
CommonJS模块规范
ES模块支持
原生addon开发
04.架构层次
a.应用层
用户界面和业务逻辑
前端框架集成
状态管理
b.框架层
Electron API封装
进程管理和IPC
安全沙箱实现
c.系统层
操作系统API调用
原生窗口管理
系统事件处理
1.6 技术特性
01.跨平台兼容性
a.操作系统支持
Windows 7及以上版本
macOS 10.10及以上版本
各种Linux发行版
b.平台特性适配
原生菜单和快捷键
系统通知和托盘
文件关联处理
c.构建目标
每个平台的原生安装包
代码签名和公证
自动更新机制
02.Web技术集成
a.现代Web标准
HTML5、CSS3、ES6+
WebGL和Canvas 2D
WebAudio和WebRTC
b.前端框架支持
React、Vue、Angular等
构建工具集成(Webpack、Vite等)
热重载和开发调试
c.第三方库集成
NPM包管理器
前端UI组件库
工具库和插件
03.原生功能访问
a.文件系统操作
文件读写和目录管理
文件监控和变更通知
拖拽文件支持
b.系统集成
系统通知和提醒
全局快捷键注册
系统托盘和菜单
c.硬件访问
摄像头和麦克风
打印机和扫描仪
USB设备通信
04.开发工具支持
a.调试功能
Chrome DevTools集成
主进程调试支持
远程调试能力
b.性能分析
内存使用分析
CPU性能监控
渲染性能统计
c.开发者体验
热重载功能
实时错误报告
丰富的开发API
1.7 与其他桌面技术对比
01.对比原生开发
a.开发效率
Electron开发周期短,学习成本低
原生开发性能更好,但开发复杂度高
Web技术栈人才更容易招聘
b.性能表现
原生应用性能更优,资源占用更少
Electron应用体积更大,内存占用更高
对于简单应用差异不明显
c.跨平台成本
Electron一套代码多平台运行
原生开发需要多套代码
平台特定功能适配成本
02.对比其他Web技术方案
a.vs NW.js
Electron功能更完善,社区更活跃
NW.js更接近传统Web开发模式
都基于Chromium和Node.js
b.vs Cordova/PhoneGap
Electron针对桌面应用,Cordova针对移动应用
Electron性能更好,功能更丰富
开发体验和调试能力更佳
c.vs PWA(渐进式Web应用)
Electron功能更强大,系统集成更好
PWA更轻量,无需安装
选择取决于具体需求
03.对比新兴桌面技术
a.vs Tauri
Tauri体积更小,性能更好
Electron生态系统更成熟
Tauri使用Rust后端,安全性更高
b.vs Flutter Desktop
Flutter性能接近原生
Electron开发门槛更低
Flutter移动端生态更完善
c.vs React Native Desktop
React Native Desktop仍在发展中
Electron技术更成熟稳定
社区支持和第三方库更丰富
04.选择建议
a.选择Electron的情况
团队熟悉Web技术栈
需要快速跨平台开发
依赖丰富的Web生态
对应用体积不敏感
b.选择原生开发的情况
对性能要求极高
需要深度系统集成
开发周期充裕
有足够的原生开发资源
c.考虑新兴方案的情况
应用体积和性能是关键因素
可以接受新技术学习成本
特定语言偏好(如Rust、Dart)
长期技术路线规划
2 核心组件
2.1 汇总:4个核心组件
00.总结
a.主进程(Main Process)
整个应用程序的控制中心,负责创建和管理所有其他进程
b.渲染进程(Renderer Process)
负责渲染用户界面,每个BrowserWindow对应一个渲染进程
c.IPC通信机制
进程间通信系统,实现主进程和渲染进程之间的数据交换
d.安全沙箱
渲染进程的安全隔离环境,限制对系统资源的访问
2.2 主进程(Main Process)
01.核心职责
a.应用生命周期管理
创建应用程序实例
处理启动和退出事件
管理应用状态和配置
b.窗口管理
创建和管理BrowserWindow实例
处理窗口显示、隐藏、关闭等操作
管理多窗口应用
c.系统集成
处理菜单、系统托盘、全局快捷键
管理文件关联和协议注册
处理系统通知和提醒
d.进程调度
创建和销毁渲染进程
管理进程间的通信
监控进程状态
02.主要API模块
a.app模块
应用程序实例和控制
生命周期事件处理
应用信息和路径获取
---
const { app } = require('electron');
// 应用就绪事件
app.whenReady().then(() => {
console.log('应用程序已准备就绪');
createMainWindow();
});
// 退出前事件
app.on('before-quit', (event) => {
console.log('应用程序即将退出');
// 清理资源
});
// 获取应用版本
console.log('应用版本:', app.getVersion());
// 获取用户数据目录
console.log('用户数据目录:', app.getPath('userData'));
---
b.BrowserWindow模块
窗口创建和管理
窗口事件处理
窗口状态控制
---
const { BrowserWindow } = require('electron');
function createMainWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
},
show: false, // 先不显示,等ready-to-show事件
icon: path.join(__dirname, 'assets/icon.png')
});
// 加载应用页面
mainWindow.loadFile('index.html');
// 窗口准备显示时显示
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// 窗口关闭事件
mainWindow.on('closed', () => {
mainWindow = null;
});
// 开发环境打开开发者工具
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
return mainWindow;
}
---
c.Menu模块
应用菜单和上下文菜单
菜单项创建和管理
菜单事件处理
---
const { Menu } = require('electron');
function createApplicationMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '新建文件',
accelerator: 'CmdOrCtrl+N',
click: () => {
// 发送消息到渲染进程
mainWindow.webContents.send('new-file');
}
},
{
label: '打开文件',
accelerator: 'CmdOrCtrl+O',
click: () => {
// 打开文件对话框
const result = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] }
]
});
}
},
{ type: 'separator' },
{
label: '退出',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
}
]
},
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤销' },
{ role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' }
]
},
{
label: '视图',
submenu: [
{ role: 'reload', label: '重新加载' },
{ role: 'forceReload', label: '强制重新加载' },
{ role: 'toggleDevTools', label: '开发者工具' },
{ type: 'separator' },
{ role: 'resetZoom', label: '实际大小' },
{ role: 'zoomIn', label: '放大' },
{ role: 'zoomOut', label: '缩小' },
{ type: 'separator' },
{ role: 'togglefullscreen', label: '全屏' }
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// 创建右键菜单
function createContextMenu() {
const contextMenu = Menu.buildFromTemplate([
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' },
{ type: 'separator' },
{ role: 'selectAll', label: '全选' }
]);
return contextMenu;
}
---
03.主进程事件处理
a.应用生命周期事件
ready、window-all-closed、before-quit
activate、browser-window-focus
b.窗口相关事件
window-created、window-closed
web-contents-created
c.系统集成事件
open-file、open-url
certificate-error
2.3 渲染进程(Renderer Process)
01.进程特性
a.沙箱环境
默认运行在安全沙箱中
限制对Node.js API的直接访问
隔离的JavaScript执行环境
b.Web技术栈
基于Chromium渲染引擎
支持HTML5、CSS3、JavaScript
集成V8 JavaScript引擎
c.多进程支持
每个BrowserWindow对应一个渲染进程
进程间相互隔离
一个进程崩溃不影响其他进程
02.渲染进程API
a.渲染器全局对象
window对象扩展
document对象增强
navigator对象补充
b.安全限制
禁用eval()和Function构造器
限制远程脚本加载
禁用Node.js集成
c.预加载脚本
在渲染进程加载前执行
可以访问Node.js API
提供安全的API暴露
---
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的API到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作API
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
// 系统对话框API
showOpenDialog: (options) => ipcRenderer.invoke('show-open-dialog', options),
showSaveDialog: (options) => ipcRenderer.invoke('show-save-dialog', options),
// 应用信息API
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
getPlatform: () => process.platform,
// 事件监听API
onMenuAction: (callback) => ipcRenderer.on('menu-action', callback),
removeMenuActionListener: (callback) => ipcRenderer.removeListener('menu-action', callback),
// 窗口控制API
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window')
});
---
03.WebContents对象
a.页面控制
加载URL和文件
导航历史管理
页面缩放和滚动
b.内容渲染
HTML和CSS渲染
JavaScript执行
多媒体内容支持
c.事件处理
页面加载事件
用户交互事件
错误和异常处理
---
// 主进程中操作WebContents
mainWindow.webContents.on('did-finish-load', () => {
console.log('页面加载完成');
});
mainWindow.webContents.on('page-title-updated', (event, title) => {
console.log('页面标题变更:', title);
});
mainWindow.webContents.on('dom-ready', () => {
console.log('DOM结构已准备就绪');
});
// 注入JavaScript代码
mainWindow.webContents.executeJavaScript(`
console.log('从主进程注入的代码');
document.body.style.backgroundColor = '#f0f0f0';
`);
// 拦截导航请求
mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
console.log('即将导航到:', navigationUrl);
// 阻止外部链接导航
if (navigationUrl.startsWith('http')) {
event.preventDefault();
require('electron').shell.openExternal(navigationUrl);
}
});
2.4 IPC通信
01.通信基础
a.IPC模块介绍
ipcMain:主进程中的IPC处理模块
ipcRenderer:渲染进程中的IPC通信模块
基于事件驱动的通信机制
b.消息类型
同步消息:阻塞等待响应
异步消息:非阻塞通信
广播消息:一对多通信
c.数据传输
JSON序列化传输
大数据传输优化
错误处理机制
02.主进程通信
a.ipcMain模块
监听渲染进程消息
处理和响应请求
发送消息到渲染进程
---
const { ipcMain, dialog } = require('electron');
// 处理文件读取请求
ipcMain.handle('read-file', async (event, filePath) => {
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
// 处理文件写入请求
ipcMain.handle('write-file', async (event, filePath, content) => {
try {
const fs = require('fs').promises;
await fs.writeFile(filePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// 处理打开文件对话框
ipcMain.handle('show-open-dialog', async (event, options) => {
const result = await dialog.showOpenDialog(options);
return result;
});
// 处理保存文件对话框
ipcMain.handle('show-save-dialog', async (event, options) => {
const result = await dialog.showSaveDialog(options);
return result;
});
// 处理窗口控制请求
ipcMain.handle('minimize-window', (event) => {
const window = BrowserWindow.fromWebContents(event.sender);
if (window) window.minimize();
});
ipcMain.handle('maximize-window', (event) => {
const window = BrowserWindow.fromWebContents(event.sender);
if (window) {
if (window.isMaximized()) {
window.unmaximize();
} else {
window.maximize();
}
}
});
// 发送菜单操作到渲染进程
function sendMenuAction(action, data = {}) {
const windows = BrowserWindow.getAllWindows();
windows.forEach(window => {
window.webContents.send('menu-action', action, data);
});
}
---
b.错误处理和重试
异步操作错误处理
通信超时处理
消息重试机制
---
// 带超时的IPC处理
function handleWithTimeout(channel, handler, timeout = 5000) {
ipcMain.handle(channel, async (event, ...args) => {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`操作超时: ${channel}`));
}, timeout);
handler(event, ...args)
.then(result => {
clearTimeout(timer);
resolve(result);
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
});
}
// 使用示例
handleWithTimeout('heavy-computation', async (event, data) => {
// 模拟耗时操作
await new Promise(resolve => setTimeout(resolve, 3000));
return { result: data * 2 };
});
---
03.渲染进程通信
a.ipcRenderer模块
发送消息到主进程
接收主进程消息
监听和移除监听器
---
// 渲染进程中使用IPC通信
class ElectronAPI {
constructor() {
this.api = window.electronAPI;
}
// 文件操作方法
async readFile(filePath) {
return await this.api.readFile(filePath);
}
async writeFile(filePath, content) {
return await this.api.writeFile(filePath, content);
}
// 对话框方法
async showOpenDialog(options = {}) {
const defaultOptions = {
properties: ['openFile'],
filters: [
{ name: '所有文件', extensions: ['*'] }
]
};
return await this.api.showOpenDialog({ ...defaultOptions, ...options });
}
async showSaveDialog(options = {}) {
return await this.api.showSaveDialog(options);
}
// 应用信息方法
async getAppVersion() {
return await this.api.getAppVersion();
}
getPlatform() {
return this.api.getPlatform();
}
// 事件监听方法
onMenuAction(callback) {
this.api.onMenuAction(callback);
}
removeMenuActionListener(callback) {
this.api.removeMenuActionListener(callback);
}
// 窗口控制方法
minimizeWindow() {
return this.api.minimizeWindow();
}
maximizeWindow() {
return this.api.maximizeWindow();
}
closeWindow() {
return this.api.closeWindow();
}
}
// 创建全局API实例
const electronAPI = new ElectronAPI();
// 在Vue组件中使用
export default {
data() {
return {
content: '',
version: '',
platform: ''
};
},
async mounted() {
// 获取应用版本
this.version = await electronAPI.getAppVersion();
this.platform = electronAPI.getPlatform();
// 监听菜单操作
electronAPI.onMenuAction((event, action, data) => {
this.handleMenuAction(action, data);
});
},
methods: {
async openFile() {
const result = await electronAPI.showOpenDialog({
filters: [
{ name: '文本文件', extensions: ['txt', 'md', 'js'] }
]
});
if (result.canceled) return;
const filePath = result.filePaths[0];
const fileResult = await electronAPI.readFile(filePath);
if (fileResult.success) {
this.content = fileResult.content;
} else {
alert('文件读取失败: ' + fileResult.error);
}
},
async saveFile() {
const result = await electronAPI.showSaveDialog({
filters: [
{ name: '文本文件', extensions: ['txt'] }
]
});
if (result.canceled) return;
const filePath = result.filePath;
const fileResult = await electronAPI.writeFile(filePath, this.content);
if (fileResult.success) {
alert('文件保存成功');
} else {
alert('文件保存失败: ' + fileResult.error);
}
},
handleMenuAction(action, data) {
switch (action) {
case 'new-file':
this.newFile();
break;
case 'open-file':
this.openFile();
break;
case 'save-file':
this.saveFile();
break;
default:
console.log('未知菜单操作:', action);
}
},
newFile() {
this.content = '';
}
}
};
2.5 进程管理
01.进程模型
a.单主进程架构
应用只有一个主进程
主进程负责整体协调
渲染进程由主进程创建和管理
b.多渲染进程
每个窗口对应一个渲染进程
进程间相互隔离
支持进程重启
c.工作进程扩展
可选的后台工作进程
用于CPU密集型任务
不阻塞主进程
02.进程生命周期
a.进程创建
BrowserWindow创建时自动创建渲染进程
可配置进程参数
预加载脚本注入时机
b.进程监控
进程状态监控
崩溃检测和处理
进程资源使用统计
c.进程销毁
窗口关闭时销毁对应进程
强制终止进程
资源清理机制
---
// 进程管理示例
class ProcessManager {
constructor() {
this.processes = new Map();
this.setupProcessMonitoring();
}
createWindow(options) {
const window = new BrowserWindow({
...options,
webPreferences: {
...options.webPreferences,
webSecurity: true,
allowRunningInsecureContent: false,
experimentalFeatures: false
}
});
// 记录进程信息
this.processes.set(window.id, {
window,
processType: 'renderer',
createdAt: Date.now(),
pid: window.webContents.getProcessId()
});
// 监听进程事件
window.webContents.on('crashed', (event, killed) => {
this.handleProcessCrash(window.id, killed);
});
window.webContents.on('unresponsive', () => {
this.handleProcessUnresponsive(window.id);
});
window.webContents.on('responsive', () => {
this.handleProcessResponsive(window.id);
});
window.on('closed', () => {
this.processes.delete(window.id);
});
return window;
}
setupProcessMonitoring() {
// 定期检查进程状态
setInterval(() => {
this.checkProcessHealth();
}, 30000); // 每30秒检查一次
}
checkProcessHealth() {
this.processes.forEach((processInfo, windowId) => {
const window = processInfo.window;
// 检查进程是否响应
if (window.webContents.isDestroyed()) return;
// 检查内存使用
const memoryUsage = process.getProcessMemoryInfo();
console.log(`窗口${windowId}内存使用:`, memoryUsage);
// 检查CPU使用
const cpuUsage = process.getCPUUsage();
console.log(`窗口${windowId}CPU使用:`, cpuUsage);
});
}
handleProcessCrash(windowId, killed) {
console.log(`进程${windowId}崩溃,是否被杀死:`, killed);
const processInfo = this.processes.get(windowId);
if (!processInfo) return;
// 记录崩溃信息
this.logCrashInfo(processInfo);
// 尝试恢复进程
this.recoverProcess(processInfo);
}
handleProcessUnresponsive(windowId) {
console.log(`进程${windowId}无响应`);
const processInfo = this.processes.get(windowId);
if (!processInfo) return;
// 显示用户提示
dialog.showMessageBox(processInfo.window, {
type: 'warning',
title: '应用程序无响应',
message: '当前窗口无响应,是否要重新加载?',
buttons: ['重新加载', '等待', '关闭']
}).then((result) => {
switch (result.response) {
case 0: // 重新加载
processInfo.window.reload();
break;
case 1: // 等待
// 继续等待
break;
case 2: // 关闭
processInfo.window.close();
break;
}
});
}
handleProcessResponsive(windowId) {
console.log(`进程${windowId}已恢复响应`);
}
recoverProcess(processInfo) {
// 备份窗口状态
const bounds = processInfo.window.getBounds();
const isMaximized = processInfo.window.isMaximized();
// 创建新窗口
const newWindow = this.createWindow({
width: bounds.width,
height: bounds.height,
x: bounds.x,
y: bounds.y,
show: false
});
// 加载相同内容
newWindow.webContents.once('did-finish-load', () => {
newWindow.show();
if (isMaximized) {
newWindow.maximize();
}
});
newWindow.loadURL(processInfo.window.webContents.getURL());
// 关闭崩溃的窗口
processInfo.window.close();
}
logCrashInfo(processInfo) {
const crashLog = {
timestamp: new Date().toISOString(),
windowId: processInfo.window.id,
processId: processInfo.pid,
url: processInfo.window.webContents.getURL(),
uptime: Date.now() - processInfo.createdAt,
memoryUsage: process.getProcessMemoryInfo(),
platform: process.platform,
electronVersion: process.versions.electron
};
// 将崩溃日志写入文件
const fs = require('fs');
const logPath = require('path').join(app.getPath('userData'), 'crash-logs.json');
try {
let logs = [];
if (fs.existsSync(logPath)) {
logs = JSON.parse(fs.readFileSync(logPath, 'utf-8'));
}
logs.push(crashLog);
// 只保留最近100条日志
if (logs.length > 100) {
logs = logs.slice(-100);
}
fs.writeFileSync(logPath, JSON.stringify(logs, null, 2));
} catch (error) {
console.error('写入崩溃日志失败:', error);
}
}
getProcessInfo(windowId) {
return this.processes.get(windowId);
}
getAllProcesses() {
return Array.from(this.processes.entries()).map(([id, info]) => ({
id,
...info
}));
}
}
// 使用进程管理器
const processManager = new ProcessManager();
2.6 安全沙箱
01.沙箱机制
a.隔离环境
渲染进程运行在受限环境中
禁止直接访问Node.js API
限制文件系统访问
b.安全策略
内容安全策略(CSP)
脚本执行限制
资源加载控制
c.权限控制
预加载脚本权限控制
API访问权限管理
跨域请求限制
02.安全配置
a.WebPreferences配置
contextIsolation:上下文隔离
nodeIntegration:Node.js集成
sandbox:沙箱模式
webSecurity:Web安全
---
// 安全的WebPreferences配置
const secureWebPreferences = {
// 启用上下文隔离
contextIsolation: true,
// 禁用Node.js集成
nodeIntegration: false,
// 启用沙箱模式
sandbox: true,
// 启用Web安全
webSecurity: true,
// 禁用远程模块
enableRemoteModule: false,
// 禁用实验性功能
experimentalFeatures: false,
// 禁用不安全的本地文件访问
allowRunningInsecureContent: false,
// 设置预加载脚本
preload: path.join(__dirname, 'preload.js'),
// 安全的会话存储
partition: 'persist:secure-session',
// 禁用Blink特性
enableBlinkFeatures: '',
disableBlinkFeatures: 'Auxclick'
};
const window = new BrowserWindow({
webPreferences: secureWebPreferences
});
---
b.预加载脚本安全
安全API暴露
参数验证
错误处理
---
// 安全的预加载脚本示例
const { contextBridge, ipcRenderer } = require('electron');
const { validateFilePath, sanitizeHTML } = require('./security-utils');
// 安全的文件API
const secureFileAPI = {
async readFile(filePath) {
// 验证文件路径
if (!validateFilePath(filePath)) {
throw new Error('无效的文件路径');
}
// 通过IPC调用主进程读取文件
return await ipcRenderer.invoke('secure-read-file', filePath);
},
async writeFile(filePath, content) {
// 验证文件路径和内容
if (!validateFilePath(filePath)) {
throw new Error('无效的文件路径');
}
if (typeof content !== 'string') {
throw new Error('内容必须是字符串');
}
return await ipcRenderer.invoke('secure-write-file', filePath, content);
}
};
// 安全的系统API
const secureSystemAPI = {
async showOpenDialog(options) {
// 验证和过滤选项
const safeOptions = {
...options,
properties: options.properties || ['openFile'],
filters: options.filters || []
};
return await ipcRenderer.invoke('secure-show-open-dialog', safeOptions);
}
};
// 暴露安全API到渲染进程
contextBridge.exposeInMainWorld('secureAPI', {
...secureFileAPI,
...secureSystemAPI,
// 添加安全工具函数
sanitizeHTML: sanitizeHTML,
validateURL: (url) => {
try {
new URL(url);
return true;
} catch {
return false;
}
}
});
---
c.内容安全策略
CSP头部设置
脚本源限制
资源加载控制
---
// 在主进程设置CSP
function setupContentSecurityPolicy() {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self';" +
"script-src 'self' 'unsafe-inline';" +
"style-src 'self' 'unsafe-inline';" +
"img-src 'self' data: https:;" +
"connect-src 'self' https://api.example.com;" +
"font-src 'self';" +
"object-src 'none';" +
"media-src 'self';" +
"frame-src 'none';"
]
}
});
});
}
// 或者在HTML中设置CSP
const cspMetaTag = `
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
font-src 'self';
object-src 'none';
media-src 'self';
frame-src 'none';
">
`;
03.安全最佳实践
a.输入验证
文件路径验证
用户输入过滤
参数类型检查
b.权限最小化
只暴露必要的API
限制文件访问范围
控制网络请求
c.错误处理
安全的错误信息
避免敏感信息泄露
统一的错误处理机制
---
// 安全工具函数
const path = require('path');
const os = require('os');
// 验证文件路径是否安全
function validateFilePath(filePath) {
if (!filePath || typeof filePath !== 'string') {
return false;
}
// 检查路径遍历攻击
if (filePath.includes('..') || filePath.includes('~')) {
return false;
}
// 获取绝对路径
const absolutePath = path.resolve(filePath);
const userDataPath = path.join(app.getPath('userData'));
const tempPath = os.tmpdir();
// 只允许访问用户数据目录和临时目录
return absolutePath.startsWith(userDataPath) || absolutePath.startsWith(tempPath);
}
// HTML内容清理
function sanitizeHTML(html) {
if (!html || typeof html !== 'string') {
return '';
}
// 移除危险标签和属性
return html
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
.replace(/on\w+="[^"]*"/gi, '')
.replace(/javascript:/gi, '')
.replace(/data:text\/html/gi, '');
}
// 验证URL是否安全
function validateURL(url) {
if (!url || typeof url !== 'string') {
return false;
}
try {
const urlObj = new URL(url);
// 只允许HTTP和HTTPS协议
return ['http:', 'https:'].includes(urlObj.protocol);
} catch {
return false;
}
}
module.exports = {
validateFilePath,
sanitizeHTML,
validateURL
};
02.IPC安全通信
a.消息验证
参数类型检查
数据范围验证
恶意内容过滤
b.访问控制
源验证
权限检查
操作审计
---
// 安全的IPC处理
function setupSecureIPC() {
// 安全的文件读取
ipcMain.handle('secure-read-file', async (event, filePath) => {
// 验证发送者来源
const webContents = event.sender;
const url = webContents.getURL();
if (!url.startsWith('file://')) {
throw new Error('不允许的外部来源');
}
// 验证文件路径
if (!validateFilePath(filePath)) {
throw new Error('不安全的文件路径');
}
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
// 安全的对话框
ipcMain.handle('secure-show-open-dialog', async (event, options) => {
// 验证选项
const safeOptions = {
properties: ['openFile'],
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
]
};
// 合并用户选项,但限制安全性
const finalOptions = {
...safeOptions,
title: options.title || '选择文件',
buttonLabel: options.buttonLabel || '打开'
};
const result = await dialog.showOpenDialog(finalOptions);
return result;
});
// 操作审计
ipcMain.on('audit-log', (event, operation, details) => {
const logEntry = {
timestamp: new Date().toISOString(),
source: event.sender.getURL(),
operation,
details,
pid: process.pid
};
// 写入审计日志
const fs = require('fs');
const logPath = path.join(app.getPath('userData'), 'audit.log');
const logLine = JSON.stringify(logEntry) + '\n';
fs.appendFileSync(logPath, logLine);
});
}
---
3 环境搭建
3.1 开发环境准备
01.核心工具链
a.Node.js运行时
Electron开发的基础环境,推荐使用LTS版本确保稳定性
支持现代JavaScript特性和丰富的NPM生态系统
提供完整的文件系统、网络和系统API访问能力
---
# 使用nvm管理Node.js版本
nvm install --lts
nvm use --lts
nvm alias default node
# 验证安装
node --version
npm --version
---
b.包管理工具
npm:Node.js默认包管理器,与Node.js一同安装
yarn:Facebook开发的替代包管理器,性能更优
pnpm:节省磁盘空间的包管理器,支持符号链接
---
# 全局安装yarn(可选)
npm install -g yarn
# 验证yarn安装
yarn --version
---
c.开发编辑器
Visual Studio Code:微软开发,插件生态丰富
WebStorm:JetBrains出品,功能强大的IDE
Sublime Text:轻量级编辑器,启动速度快
---
# 推荐的VS Code插件
{
"recommendations": [
"ms-vscode.vscode-typescript-next",
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-json"
]
}
---
02.环境配置优化
a.Node.js版本管理
使用nvm在不同项目间切换Node.js版本
配置项目级别的Node.js版本要求
确保开发环境和生产环境的一致性
---
# .nvmrc文件内容
18.17.0
# 自动切换到指定版本
nvm use
# 设置默认版本
nvm alias default 18.17.0
---
b.NPM配置优化
配置国内镜像源加速下载
设置默认初始化配置
配置私有仓库(如需要)
---
# 设置淘宝镜像源
npm config set registry https://registry.npmmirror.com
# 验证镜像源
npm config get registry
# 全局配置
npm config set init-author-name "Your Name"
npm config set init-author-email "[email protected]"
npm config set init-license "MIT"
npm config set init-version "1.0.0"
---
c.开发工具集成
Git版本控制系统配置
ESLint和Prettier代码规范工具
Chrome DevTools扩展插件
---
# Git全局配置
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
git config --global core.autocrlf input
# 创建全局.gitignore
echo "node_modules/
dist/
.DS_Store
*.log" > ~/.gitignore
---
03.调试环境设置
a.Chrome开发者工具
内置的强大调试工具
支持断点调试、性能分析、网络监控
可安装React、Vue等框架专用调试工具
---
// 主进程中开启DevTools
const { BrowserWindow } = require('electron');
function createWindow() {
const window = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
devTools: true
}
});
// 开发环境自动打开DevTools
if (process.env.NODE_ENV === 'development') {
window.webContents.openDevTools();
}
return window;
}
---
b.VS Code调试配置
支持主进程和渲染进程调试
集成断点调试和变量监视
支持热重载和自动重启
---
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": ["."],
"outputCapture": "std"
}
]
}
---
c.性能监控工具
Chrome Performance面板分析渲染性能
Memory面板检测内存泄漏
Network面板监控网络请求
---
// 性能监控代码示例
const performanceMonitor = {
start() {
this.startTime = performance.now();
console.log('性能监控开始');
},
mark(label) {
const time = performance.now() - this.startTime;
console.log(`${label}: ${time.toFixed(2)}ms`);
},
end() {
const totalTime = performance.now() - this.startTime;
console.log(`总耗时: ${totalTime.toFixed(2)}ms`);
}
};
---
3.2 项目初始化
01.脚手架工具选择
a.Electron Forge
官方推荐的脚手架工具
提供完整的项目模板和构建工具链
支持TypeScript、React、Vue等模板
---
# 全局安装Electron Forge CLI
npm install -g @electron-forge/cli
# 创建新项目
electron-forge init my-app
cd my-app
# 启动开发服务器
npm start
# 使用特定模板
electron-forge init my-react-app --template=webpack-template
---
b.Electron Builder
专注于应用打包和分发
支持多平台构建(Windows、macOS、Linux)
提供代码签名和自动更新功能
---
# 创建基础项目结构
mkdir my-electron-app && cd my-electron-app
npm init -y
# 安装Electron和相关依赖
npm install --save-dev electron
npm install --save-dev electron-builder
# 配置package.json构建脚本
"scripts": {
"start": "electron .",
"build": "electron-builder",
"dist": "electron-builder --publish=never"
}
---
c.Vite + Electron
现代化的快速构建工具
支持热重载和快速开发体验
与现代前端框架集成良好
---
# 使用Vite创建Electron项目
npm create vite@latest my-vite-electron-app -- --template vanilla-ts
cd my-vite-electron-app
npm install
# 添加Electron依赖
npm install --save-dev electron concurrently wait-on
# 配置开发脚本
"scripts": {
"dev": "concurrently \"npm run dev:vite\" \"wait-on http://localhost:5173 && npm run dev:electron\"",
"dev:vite": "vite",
"dev:electron": "electron .",
"build": "vite build && electron-builder"
}
---
02.项目结构设计
a.基础目录结构
src目录:源代码存放
public目录:静态资源文件
build目录:构建配置文件
dist目录:构建输出结果
---
my-electron-app/
├── src/
│ ├── main/ # 主进程代码
│ │ ├── index.ts # 主进程入口
│ │ ├── window.ts # 窗口管理
│ │ └── menu.ts # 菜单配置
│ ├── preload/ # 预加载脚本
│ │ └── index.ts # 预加载入口
│ └── renderer/ # 渲染进程代码
│ ├── index.html # HTML模板
│ ├── main.ts # 渲染进程入口
│ ├── style.css # 样式文件
│ └── components/ # UI组件
├── public/ # 静态资源
│ ├── icons/ # 应用图标
│ └── assets/ # 其他资源
├── build/ # 构建配置
│ ├── webpack.config.js # Webpack配置
│ └── electron-builder.yml# 打包配置
├── dist/ # 构建输出
├── package.json
├── tsconfig.json # TypeScript配置
└── README.md
---
b.配置文件组织
TypeScript配置:tsconfig.json
构建工具配置:webpack.config.js或vite.config.ts
ESLint配置:.eslintrc.js
Prettier配置:.prettierrc
---
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"allowJs": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
---
c.环境变量管理
开发环境:.env.development
生产环境:.env.production
测试环境:.env.test
---
# .env.development
NODE_ENV=development
ELECTRON_IS_DEV=true
API_BASE_URL=http://localhost:3000
# .env.production
NODE_ENV=production
ELECTRON_IS_DEV=false
API_BASE_URL=https://api.example.com
# 在代码中使用环境变量
const isDev = process.env.NODE_ENV === 'development';
const apiBaseUrl = process.env.API_BASE_URL;
---
03.核心文件配置
a.package.json配置
应用基本信息和依赖管理
构建脚本和开发命令
Electron Builder构建配置
---
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "A modern Electron application",
"main": "dist/main/index.js",
"homepage": "./",
"scripts": {
"dev": "concurrently \"npm run dev:vite\" \"npm run dev:electron\"",
"dev:vite": "vite",
"dev:electron": "wait-on http://localhost:5173 && electron .",
"build": "npm run build:vite && npm run build:electron",
"build:vite": "vite build",
"build:electron": "tsc -p tsconfig.main.json",
"dist": "npm run build && electron-builder",
"dist:win": "npm run build && electron-builder --win",
"dist:mac": "npm run build && electron-builder --mac",
"dist:linux": "npm run build && electron-builder --linux",
"lint": "eslint src --ext .ts,.js",
"lint:fix": "eslint src --ext .ts,.js --fix",
"format": "prettier --write \"src/**/*.{ts,js,html,css}\""
},
"keywords": ["electron", "typescript", "vite", "desktop"],
"author": "Your Name <[email protected]>",
"license": "MIT",
"devDependencies": {
"electron": "^27.0.0",
"electron-builder": "^24.6.4",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"concurrently": "^8.2.1",
"wait-on": "^7.0.1",
"@types/node": "^20.6.0",
"eslint": "^8.49.0",
"prettier": "^3.0.3"
},
"dependencies": {
"electron-log": "^4.4.8",
"electron-store": "^8.1.0"
},
"build": {
"appId": "com.example.my-electron-app",
"productName": "My Electron App",
"directories": {
"output": "release"
},
"files": [
"dist/**/*",
"package.json"
],
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
]
}
}
}
---
b.主进程入口文件
应用生命周期管理
窗口创建和管理
IPC通信设置
---
// src/main/index.ts
import { app, BrowserWindow, Menu } from 'electron';
import * as path from 'path';
const isDev = process.env.NODE_ENV === 'development';
let mainWindow: BrowserWindow | null = null;
function createWindow(): BrowserWindow {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
icon: path.join(__dirname, '../../public/icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, '../preload/index.js')
}
});
// 加载应用
if (isDev) {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
// 窗口准备好后显示
mainWindow.once('ready-to-show', () => {
mainWindow?.show();
});
// 窗口关闭时清理引用
mainWindow.on('closed', () => {
mainWindow = null;
});
return mainWindow;
}
// 应用就绪时创建窗口
app.whenReady().then(() => {
createWindow();
setupMenu();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// 所有窗口关闭时退出应用(macOS除外)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
function setupMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '新建',
accelerator: 'CmdOrCtrl+N',
click: () => {
mainWindow?.webContents.send('menu-new-file');
}
},
{
label: '打开',
accelerator: 'CmdOrCtrl+O',
click: () => {
mainWindow?.webContents.send('menu-open-file');
}
},
{ type: 'separator' },
{
label: '退出',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
}
]
},
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤销' },
{ role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' }
]
}
];
const menu = Menu.buildFromTemplate(template as any);
Menu.setApplicationMenu(menu);
}
---
c.预加载脚本配置
安全的API暴露
上下文隔离设置
IPC通信桥接
---
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
// 暴露安全的API到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath: string, content: string) =>
ipcRenderer.invoke('write-file', filePath, content),
// 系统对话框
showOpenDialog: (options: any) => ipcRenderer.invoke('show-open-dialog', options),
showSaveDialog: (options: any) => ipcRenderer.invoke('show-save-dialog', options),
// 应用信息
getVersion: () => process.env.npm_package_version,
getPlatform: () => process.platform,
// 事件监听
onMenuAction: (callback: (event: any, action: string) => void) => {
ipcRenderer.on('menu-action', callback);
},
removeMenuActionListener: (callback: (event: any, action: string) => void) => {
ipcRenderer.removeListener('menu-action', callback);
},
// 窗口控制
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
maximizeWindow: () => ipcRenderer.invoke('window-maximize'),
closeWindow: () => ipcRenderer.invoke('window-close')
});
// 类型定义
interface ElectronAPI {
readFile: (filePath: string) => Promise<any>;
writeFile: (filePath: string, content: string) => Promise<any>;
showOpenDialog: (options: any) => Promise<any>;
showSaveDialog: (options: any) => Promise<any>;
getVersion: () => string;
getPlatform: () => string;
onMenuAction: (callback: (event: any, action: string) => void) => void;
removeMenuActionListener: (callback: (event: any, action: string) => void) => void;
minimizeWindow: () => Promise<void>;
maximizeWindow: () => Promise<void>;
closeWindow: () => Promise<void>;
}
declare global {
interface Window {
electronAPI: ElectronAPI;
}
}
---
3.3 构建工具配置
01.Vite构建配置
a.基础配置
支持TypeScript和现代JavaScript特性
热重载和快速开发体验
优化的生产环境构建
---
// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src/renderer'),
'@shared': resolve(__dirname, 'src/shared')
}
},
base: './',
build: {
outDir: 'dist/renderer',
emptyOutDir: true,
rollupOptions: {
input: {
main: resolve(__dirname, 'src/renderer/index.html')
}
}
},
server: {
port: 5173,
strictPort: true
}
});
---
b.插件配置
@vitejs/plugin-vue:Vue 3支持
@vitejs/plugin-react:React支持
vite-plugin-electron:Electron集成
---
// Vue + TypeScript配置示例
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src/renderer'),
'@components': resolve(__dirname, 'src/renderer/components'),
'@utils': resolve(__dirname, 'src/renderer/utils')
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/variables.scss";'
}
}
}
});
---
c.环境变量配置
开发、生产、测试环境区分
敏感信息保护
构建时变量注入
---
// .env.development
VITE_APP_TITLE=My Electron App (Dev)
VITE_API_BASE_URL=http://localhost:3000
VITE_LOG_LEVEL=debug
// .env.production
VITE_APP_TITLE=My Electron App
VITE_API_BASE_URL=https://api.example.com
VITE_LOG_LEVEL=error
// 在代码中使用
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
const appTitle = import.meta.env.VITE_APP_TITLE;
---
02.Webpack构建配置
a.主进程配置
Node.js环境目标
TypeScript支持
外部依赖处理
---
// webpack.main.config.js
const path = require('path');
module.exports = {
mode: process.env.NODE_ENV || 'development',
entry: './src/main/index.ts',
target: 'electron-main',
output: {
path: path.resolve(__dirname, 'dist/main'),
filename: 'index.js'
},
node: {
__dirname: false,
__filename: false
},
resolve: {
extensions: ['.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
externals: {
electron: 'commonjs electron'
}
};
---
b.渲染进程配置
Web环境目标
HTML模板处理
静态资源加载
---
// webpack.renderer.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: process.env.NODE_ENV || 'development',
entry: './src/renderer/index.ts',
target: 'electron-renderer',
output: {
path: path.resolve(__dirname, 'dist/renderer'),
filename: 'bundle.js'
},
devtool: 'source-map',
plugins: [
new HtmlWebpackPlugin({
template: './src/renderer/index.html',
filename: 'index.html'
})
],
resolve: {
extensions: ['.ts', '.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src/renderer'),
'@components': path.resolve(__dirname, 'src/renderer/components'),
'@utils': path.resolve(__dirname, 'src/renderer/utils')
}
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource'
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
}
};
---
c.预加载脚本配置
独立的构建配置
安全沙箱环境
API暴露限制
---
// webpack.preload.config.js
const path = require('path');
module.exports = {
mode: process.env.NODE_ENV || 'development',
entry: './src/preload/index.ts',
target: 'electron-preload',
output: {
path: path.resolve(__dirname, 'dist/preload'),
filename: 'index.js'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
externals: {
electron: 'commonjs electron'
}
};
---
03.代码质量工具
a.ESLint配置
代码规范检查
TypeScript支持
自定义规则配置
---
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'prefer-const': 'error',
'no-var': 'error'
},
ignorePatterns: ['dist/', 'node_modules/', 'release/']
};
---
b.Prettier配置
代码格式化
统一的代码风格
与ESLint集成
---
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}
---
c.Husky和lint-staged
Git钩子配置
提交前代码检查
自动化代码修复
---
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{ts,js}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,html}": [
"prettier --write"
]
}
}
---
3.4 开发调试设置
01.调试工具配置
a.VS Code调试环境
主进程调试配置
渲染进程调试
多进程同时调试
---
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal"
},
{
"name": "Attach to Main Process",
"type": "node",
"request": "attach",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}"
},
{
"name": "Debug Renderer Process",
"type": "chrome",
"request": "attach",
"port": 9222,
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 30000
}
],
"compounds": [
{
"name": "Debug Electron App",
"configurations": ["Debug Main Process", "Debug Renderer Process"]
}
]
}
---
b.Chrome开发者工具
元素检查和样式调试
JavaScript断点调试
网络请求监控
性能分析工具
---
// 主进程中控制DevTools
const { app, BrowserWindow } = require('electron');
function createWindow() {
const window = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
devTools: true
}
});
// 开发环境配置
if (process.env.NODE_ENV === 'development') {
// 自动打开DevTools
window.webContents.openDevTools();
// 监听开发者工具打开/关闭
window.webContents.on('devtools-opened', () => {
console.log('DevTools opened');
});
window.webContents.on('devtools-closed', () => {
console.log('DevTools closed');
});
}
return window;
}
// 快捷键切换DevTools
app.on('browser-window-created', (event, window) => {
window.webContents.on('before-input-event', (event, input) => {
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
if (window.webContents.isDevToolsOpened()) {
window.webContents.closeDevTools();
} else {
window.webContents.openDevTools();
}
}
});
});
---
c.远程调试
支持远程设备调试
网络调试配置
安全考虑
---
// 启用远程调试
const { app } = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '9222');
app.commandLine.appendSwitch('remote-debugging-address', '0.0.0.0');
// 安全检查
if (process.env.NODE_ENV === 'development') {
console.log('Remote debugging enabled on port 9222');
}
---
02.日志系统配置
a.electron-log集成
多级别日志记录
文件和控制台输出
日志轮转和压缩
---
// src/shared/logger.ts
import log from 'electron-log';
import path from 'path';
// 配置日志
log.transports.file.level = 'info';
log.transports.console.level = process.env.NODE_ENV === 'development' ? 'debug' : 'warn';
// 设置日志文件路径
const userDataPath = app.getPath('userData');
log.transports.file.resolvePathFn = (variables) => {
return path.join(userDataPath, 'logs', variables.fileName);
};
// 开发环境额外配置
if (process.env.NODE_ENV === 'development') {
log.info('Logger initialized in development mode');
// 添加时间戳格式化
log.hooks.push((message, transport) => {
if (transport !== log.transports.console) {
return message;
}
const timestamp = new Date().toLocaleTimeString();
return `[${timestamp}] ${message}`;
});
}
// 导出便捷方法
export const logger = {
debug: (message: string, ...args: any[]) => log.debug(message, ...args),
info: (message: string, ...args: any[]) => log.info(message, ...args),
warn: (message: string, ...args: any[]) => log.warn(message, ...args),
error: (message: string, ...args: any[]) => log.error(message, ...args)
};
// 错误日志收集
export function setupErrorHandling() {
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', error);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
}
---
b.自定义日志服务
结构化日志格式
日志过滤和搜索
远程日志上传
---
// src/shared/logService.ts
export class LogService {
private logs: Array<{
timestamp: Date;
level: string;
message: string;
data?: any;
}> = [];
private maxLogs = 1000;
constructor() {
this.setupRotation();
}
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) {
const logEntry = {
timestamp: new Date(),
level,
message,
data
};
this.logs.push(logEntry);
// 保持日志数量限制
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
// 输出到控制台
console[level](message, data || '');
// 发送到主进程进行持久化
if (window.electronAPI) {
window.electronAPI.sendLog(logEntry);
}
}
debug(message: string, data?: any) {
this.log('debug', message, data);
}
info(message: string, data?: any) {
this.log('info', message, data);
}
warn(message: string, data?: any) {
this.log('warn', message, data);
}
error(message: string, data?: any) {
this.log('error', message, data);
}
getLogs(filter?: { level?: string; startDate?: Date; endDate?: Date }) {
let filteredLogs = this.logs;
if (filter?.level) {
filteredLogs = filteredLogs.filter(log => log.level === filter.level);
}
if (filter?.startDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp >= filter.startDate!);
}
if (filter?.endDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp <= filter.endDate!);
}
return filteredLogs;
}
private setupRotation() {
// 每天清理旧日志
setInterval(() => {
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
this.logs = this.logs.filter(log => log.timestamp > oneDayAgo);
}, 60 * 60 * 1000); // 每小时执行一次
}
}
export const logService = new LogService();
---
c.性能监控
内存使用监控
CPU使用率跟踪
渲染性能统计
---
// src/main/performanceMonitor.ts
import { app } from 'electron';
interface PerformanceMetrics {
timestamp: number;
memory: NodeJS.MemoryUsage;
cpu: NodeJS.CpuUsage;
eventLoopDelay: number;
}
export class PerformanceMonitor {
private metrics: PerformanceMetrics[] = [];
private isMonitoring = false;
private monitorInterval?: NodeJS.Timeout;
private lastCpuUsage = process.cpuUsage();
constructor(private maxMetrics = 100) {}
startMonitoring(intervalMs = 5000) {
if (this.isMonitoring) return;
this.isMonitoring = true;
console.log('Performance monitoring started');
this.monitorInterval = setInterval(() => {
this.collectMetrics();
}, intervalMs);
}
stopMonitoring() {
if (!this.isMonitoring) return;
this.isMonitoring = false;
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
this.monitorInterval = undefined;
}
console.log('Performance monitoring stopped');
}
private collectMetrics() {
const now = Date.now();
const memory = process.memoryUsage();
const cpu = process.cpuUsage(this.lastCpuUsage);
// 测量事件循环延迟
const start = process.hrtime.bigint();
setImmediate(() => {
const delay = Number(process.hrtime.bigint() - start) / 1000000;
const metrics: PerformanceMetrics = {
timestamp: now,
memory,
cpu,
eventLoopDelay: delay
};
this.metrics.push(metrics);
// 保持最近的数据点
if (this.metrics.length > this.maxMetrics) {
this.metrics.shift();
}
this.lastCpuUsage = process.cpuUsage();
});
}
getMetrics(): PerformanceMetrics[] {
return [...this.metrics];
}
getCurrentMetrics(): PerformanceMetrics | null {
return this.metrics.length > 0 ? this.metrics[this.metrics.length - 1] : null;
}
getAverageMetrics(durationMs = 60000): PerformanceMetrics | null {
const cutoff = Date.now() - durationMs;
const recentMetrics = this.metrics.filter(m => m.timestamp > cutoff);
if (recentMetrics.length === 0) return null;
return {
timestamp: Date.now(),
memory: this.averageMemory(recentMetrics),
cpu: this.averageCpu(recentMetrics),
eventLoopDelay: this.averageEventLoopDelay(recentMetrics)
};
}
private averageMemory(metrics: PerformanceMetrics[]): NodeJS.MemoryUsage {
const sum = metrics.reduce((acc, m) => ({
rss: acc.rss + m.memory.rss,
heapTotal: acc.heapTotal + m.memory.heapTotal,
heapUsed: acc.heapUsed + m.memory.heapUsed,
external: acc.external + m.memory.external,
arrayBuffers: acc.arrayBuffers + m.memory.arrayBuffers
}), { rss: 0, heapTotal: 0, heapUsed: 0, external: 0, arrayBuffers: 0 });
const count = metrics.length;
return {
rss: sum.rss / count,
heapTotal: sum.heapTotal / count,
heapUsed: sum.heapUsed / count,
external: sum.external / count,
arrayBuffers: sum.arrayBuffers / count
};
}
private averageCpu(metrics: PerformanceMetrics[]): NodeJS.CpuUsage {
const sum = metrics.reduce((acc, m) => ({
user: acc.user + m.cpu.user,
system: acc.system + m.cpu.system
}), { user: 0, system: 0 });
const count = metrics.length;
return {
user: sum.user / count,
system: sum.system / count
};
}
private averageEventLoopDelay(metrics: PerformanceMetrics[]): number {
const sum = metrics.reduce((acc, m) => acc + m.eventLoopDelay, 0);
return sum / metrics.length;
}
// 生成性能报告
generateReport(): string {
const current = this.getCurrentMetrics();
const average = this.getAverageMetrics();
if (!current || !average) {
return 'No performance data available';
}
return `
Performance Report (${new Date().toISOString()})
Current Metrics:
- Memory RSS: ${(current.memory.rss / 1024 / 1024).toFixed(2)} MB
- Memory Heap Used: ${(current.memory.heapUsed / 1024 / 1024).toFixed(2)} MB
- Event Loop Delay: ${current.eventLoopDelay.toFixed(2)} ms
Average Metrics (last 60s):
- Memory RSS: ${(average.memory.rss / 1024 / 1024).toFixed(2)} MB
- Memory Heap Used: ${(average.memory.heapUsed / 1024 / 1024).toFixed(2)} MB
- Event Loop Delay: ${average.eventLoopDelay.toFixed(2)} ms
`.trim();
}
}
export const performanceMonitor = new PerformanceMonitor();
---
03.热重载和开发体验
a.文件监控和自动重启
electron-reload配置
选择性文件监控
性能优化考虑
---
// src/main/index.ts 开发热重载配置
if (process.env.NODE_ENV === 'development') {
try {
require('electron-reload')(__dirname, {
electron: require('path').join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
hardResetMethod: 'exit',
options: {
watchRenderer: false // 只监控主进程文件
}
});
console.log('Hot reload enabled for main process');
} catch (error) {
console.log('Hot reload not available:', error.message);
}
}
// 优化版本:只监控特定文件
if (process.env.NODE_ENV === 'development') {
const chokidar = require('chokidar');
const { exec } = require('child_process');
// 只监控TypeScript和配置文件
const watcher = chokidar.watch([
path.join(__dirname, '**/*.ts'),
path.join(__dirname, '../preload/**/*.ts')
], {
ignored: /node_modules/,
persistent: true
});
let restartTimeout: NodeJS.Timeout;
watcher.on('change', (filePath) => {
console.log(`File changed: ${filePath}`);
// 防抖处理
if (restartTimeout) {
clearTimeout(restartTimeout);
}
restartTimeout = setTimeout(() => {
console.log('Restarting application...');
app.relaunch();
app.exit();
}, 1000);
});
}
---
b.Vite热重载集成
快速文件更新
组件状态保持
CSS热更新
---
// vite.config.ts 热重载优化
import { defineConfig } from 'vite';
export default defineConfig({
server: {
hmr: {
overlay: true, // 显示错误覆盖层
port: 24678 // 自定义HMR端口
}
},
optimizeDeps: {
// 预构建依赖以提升HMR性能
include: ['vue', 'react', 'electron-log']
},
build: {
// 开发环境下禁用一些优化以加快构建速度
minify: process.env.NODE_ENV === 'production' ? 'esbuild' : false,
sourcemap: process.env.NODE_ENV === 'development'
}
});
---
c.开发工具集成
React DevTools
Vue DevTools
Redux DevTools
---
// 主进程中安装DevTools扩展
const { app, session } = require('electron');
const path = require('path');
async function installDevTools() {
if (process.env.NODE_ENV !== 'development') return;
const { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } = require('electron-devtools-installer');
try {
await installExtension(REACT_DEVELOPER_TOOLS);
console.log('React DevTools installed');
await installExtension(REDUX_DEVTOOLS);
console.log('Redux DevTools installed');
} catch (error) {
console.error('Error installing dev tools:', error);
}
}
app.whenReady().then(async () => {
await installDevTools();
// 其他初始化代码...
});
// 或者手动加载扩展
function loadCustomExtension(extensionPath: string) {
const extensionId = path.basename(extensionPath);
session.defaultSession.loadExtension(extensionPath)
.then(() => console.log(`Extension ${extensionId} loaded`))
.catch(err => console.error('Failed to load extension:', err));
}
---
3.5 测试环境配置
01.单元测试配置
a.Jest配置
Electron环境适配
TypeScript支持
测试覆盖率报告
---
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.+(ts|tsx|js)',
'**/*.(test|spec).+(ts|tsx|js)'
],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest'
},
collectCoverageFrom: [
'src/**/*.{ts,tsx,js}',
'!src/**/*.d.ts',
'!src/**/index.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts'
],
coverageDirectory: 'coverage',
coverageReporters: [
'text',
'lcov',
'html',
'json-summary'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: [
'<rootDir>/tests/setup.ts'
],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@shared/(.*)$': '<rootDir>/src/shared/$1',
'^@main/(.*)$': '<rootDir>/src/main/$1',
'^@renderer/(.*)$': '<rootDir>/src/renderer/$1'
},
testTimeout: 10000,
verbose: true
};
---
b.测试工具配置
模拟对象和函数
异步测试处理
测试环境隔离
---
// tests/setup.ts
import { app, BrowserWindow } from 'electron';
import path from 'path';
let testWindow: BrowserWindow | null = null;
// 全局测试设置
beforeAll(async () => {
// 启动Electron应用(如果需要)
if (!app.isReady()) {
await app.whenReady();
}
// 创建测试窗口
testWindow = new BrowserWindow({
show: false,
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// 全局工具函数
global.createTestWindow = async (options = {}) => {
const window = new BrowserWindow({
show: false,
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
},
...options
});
return window;
};
global.waitForCondition = async (condition: () => Promise<boolean>, timeout = 5000) => {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
if (await condition()) {
return true;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`Condition not met within ${timeout}ms`);
};
// 模拟console方法以避免测试输出污染
global.console = {
...console,
log: jest.fn(),
warn: jest.fn(),
error: jest.fn()
};
});
afterAll(async () => {
// 清理测试窗口
if (testWindow && !testWindow.isDestroyed()) {
testWindow.close();
}
// 退出应用
if (app) {
await app.quit();
}
});
// 每个测试前的清理
beforeEach(() => {
jest.clearAllMocks();
});
---
c.主进程测试示例
应用生命周期测试
窗口管理测试
IPC通信测试
---
// tests/main/app.test.ts
import { app, BrowserWindow } from 'electron';
import { createMainWindow } from '../../src/main/window';
describe('Application Lifecycle', () => {
beforeAll(async () => {
await app.whenReady();
});
afterAll(async () => {
if (app) {
await app.quit();
}
});
test('should create main window successfully', () => {
const window = createMainWindow();
expect(window).toBeInstanceOf(BrowserWindow);
expect(window.isDestroyed()).toBe(false);
expect(window.webContents).toBeDefined();
window.close();
});
test('should configure window correctly', () => {
const window = createMainWindow();
const bounds = window.getBounds();
expect(bounds.width).toBe(1200);
expect(bounds.height).toBe(800);
const webPreferences = window.webContents.getWebPreferences();
expect(webPreferences.nodeIntegration).toBe(false);
expect(webPreferences.contextIsolation).toBe(true);
window.close();
});
test('should handle window close event', async () => {
const window = createMainWindow();
const closePromise = new Promise<void>((resolve) => {
window.once('closed', () => {
resolve();
});
});
window.close();
await closePromise;
expect(window.isDestroyed()).toBe(true);
});
});
// tests/main/ipc.test.ts
import { ipcMain } from 'electron';
import { setupIpcHandlers } from '../../src/main/ipc';
describe('IPC Communication', () => {
beforeAll(() => {
setupIpcHandlers();
});
afterEach(() => {
// 清理IPC监听器
ipcMain.removeAllListeners();
});
test('should handle read-file request', async () => {
const mockEvent = {
sender: {
getURL: () => 'file://test'
}
};
const result = await ipcMain.handle('read-file', mockEvent, 'test.txt');
expect(result).toBeDefined();
expect(typeof result).toBe('object');
});
test('should handle invalid file path', async () => {
const mockEvent = {
sender: {
getURL: () => 'file://test'
}
};
const result = await ipcMain.handle('read-file', mockEvent, '../invalid.txt');
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
---
02.集成测试配置
a.端到端测试框架
Spectron配置
Playwright集成
自定义测试工具
---
// tests/e2e/config.ts
import { Application } from 'spectron';
import path from 'path';
import electron from 'electron';
export class E2ETestHelper {
private app: Application;
constructor() {
this.app = new Application({
path: electron,
args: [path.join(__dirname, '../../src/main/index.ts')],
env: {
NODE_ENV: 'test',
ELECTRON_IS_DEV: 'false'
},
startTimeout: 30000,
waitTimeout: 10000
});
}
async start() {
await this.app.start();
await this.app.client.waitUntilWindowLoaded();
return this.app;
}
async stop() {
if (this.app && this.app.isRunning()) {
await this.app.stop();
}
}
async restart() {
await this.stop();
await this.start();
}
async waitForElement(selector: string, timeout = 5000) {
await this.app.client.waitForExist(selector, timeout);
return this.app.client.element(selector);
}
async clickElement(selector: string) {
await this.waitForElement(selector);
await this.app.client.click(selector);
}
async typeText(selector: string, text: string) {
await this.waitForElement(selector);
await this.app.client.setValue(selector, text);
}
async getText(selector: string) {
await this.waitForElement(selector);
return this.app.client.getText(selector);
}
async getScreenshot(): Promise<Buffer> {
return this.app.client.screenshot();
}
}
// 测试套件基类
export class E2ETestSuite {
protected helper: E2ETestHelper;
beforeAll(async () => {
this.helper = new E2ETestHelper();
await this.helper.start();
});
afterAll(async () => {
await this.helper.stop();
});
beforeEach(async () => {
// 确保应用在干净状态下
await this.helper.app.client.refresh();
});
}
---
b.端到端测试示例
用户流程测试
UI交互测试
数据持久化测试
---
// tests/e2e/user-workflow.test.ts
import { E2ETestSuite } from './config';
describe('User Workflow Tests', class extends E2ETestSuite {
test('should launch application and show main window', async () => {
const windowCount = await this.helper.app.client.getWindowCount();
expect(windowCount).toBe(1);
const title = await this.helper.app.client.getTitle();
expect(title).toBe('My Electron App');
});
test('should handle file menu operations', async () => {
// 点击文件菜单
await this.helper.clickElement('[label="File"]');
// 验证菜单项显示
const newMenuItem = await this.helper.waitForElement('[label="New"]');
expect(newMenuItem).toBeTruthy();
const openMenuItem = await this.helper.waitForElement('[label="Open"]');
expect(openMenuItem).toBeTruthy();
});
test('should create new file', async () => {
// 点击新建文件
await this.helper.clickElement('[label="File"]');
await this.helper.clickElement('[label="New"]');
// 等待编辑器加载
await this.helper.waitForElement('.editor');
// 验证编辑器状态
const editorText = await this.helper.getText('.editor');
expect(editorText).toBe('');
});
test('should save and load file content', async () => {
// 创建临时文件路径
const testContent = 'Hello, Electron Test!';
const testFilePath = path.join(__dirname, 'fixtures', 'test.txt');
// 输入测试内容
await this.helper.typeText('.editor', testContent);
// 模拟保存操作
await this.helper.app.client.electron.ipcRenderer.send('save-file', {
content: testContent,
path: testFilePath
});
// 等待保存完成
await new Promise(resolve => setTimeout(resolve, 1000));
// 验证文件是否保存
const fs = require('fs');
expect(fs.existsSync(testFilePath)).toBe(true);
const savedContent = fs.readFileSync(testFilePath, 'utf8');
expect(savedContent).toBe(testContent);
// 模拟加载操作
await this.helper.app.client.electron.ipcRenderer.send('load-file', testFilePath);
// 等待加载完成
await new Promise(resolve => setTimeout(resolve, 1000));
// 验证内容是否加载到编辑器
const editorContent = await this.helper.getText('.editor');
expect(editorContent).toBe(testContent);
// 清理测试文件
fs.unlinkSync(testFilePath);
});
test('should handle keyboard shortcuts', async () => {
// 测试Ctrl+N新建文件
await this.helper.app.client.keys(['Control', 'n']);
await new Promise(resolve => setTimeout(resolve, 500));
// 验证新建操作
const editorExists = await this.helper.app.client.isExisting('.editor');
expect(editorExists).toBe(true);
// 测试Ctrl+S保存文件
await this.helper.app.client.keys(['Control', 's']);
await new Promise(resolve => setTimeout(resolve, 500));
// 验证保存对话框显示(如果有)
const saveDialogExists = await this.helper.app.client.isExisting('.save-dialog');
// 根据应用具体实现调整验证逻辑
});
test('should handle window operations', async () => {
// 测试最小化
await this.helper.app.client.minimizeWindow();
let isMinimized = await this.helper.app.client.isMinimized();
expect(isMinimized).toBe(true);
// 恢复窗口
await this.helper.app.client.restoreWindow();
isMinimized = await this.helper.app.client.isMinimized();
expect(isMinimized).toBe(false);
// 测试最大化(如果支持)
await this.helper.app.client.maximizeWindow();
const isMaximized = await this.helper.app.client.isMaximized();
expect(isMaximized).toBe(true);
// 恢复正常大小
await this.helper.app.client.unmaximizeWindow();
const isMaximizedAfter = await this.helper.app.client.isMaximized();
expect(isMaximizedAfter).toBe(false);
});
});
---
c.性能测试
启动时间测试
内存使用测试
渲染性能测试
---
// tests/performance/app-performance.test.ts
import { Application } from 'spectron';
import { performance } from 'perf_hooks';
import path from 'path';
describe('Application Performance Tests', () => {
let app: Application;
beforeEach(async () => {
app = new Application({
path: require('electron'),
args: [path.join(__dirname, '../../src/main/index.ts')],
env: {
NODE_ENV: 'test'
}
});
});
afterEach(async () => {
if (app && app.isRunning()) {
await app.stop();
}
});
test('should start within acceptable time', async () => {
const startTime = performance.now();
await app.start();
await app.client.waitUntilWindowLoaded();
const startupTime = performance.now() - startTime;
// 启动时间应小于3秒
expect(startupTime).toBeLessThan(3000);
console.log(`Startup time: ${startupTime.toFixed(2)}ms`);
});
test('should maintain acceptable memory usage', async () => {
await app.start();
await app.client.waitUntilWindowLoaded();
// 等待应用稳定
await new Promise(resolve => setTimeout(resolve, 5000));
// 获取进程内存信息
const metrics = await app.client.execute(() => {
return process.getProcessMemoryInfo();
});
// 内存使用应小于200MB
expect(metrics.workingSetSize).toBeLessThan(200 * 1024 * 1024);
console.log(`Memory usage: ${(metrics.workingSetSize / 1024 / 1024).toFixed(2)}MB`);
});
test('should render frames smoothly', async () => {
await app.start();
await app.client.waitUntilWindowLoaded();
// 测量60帧的渲染时间
const frameMetrics = await app.client.execute(() => {
return new Promise((resolve) => {
let frameCount = 0;
let startTime = performance.now();
function measureFrame() {
frameCount++;
if (frameCount >= 60) {
const totalTime = performance.now() - startTime;
resolve(totalTime);
} else {
requestAnimationFrame(measureFrame);
}
}
requestAnimationFrame(measureFrame);
});
});
// 60帧应在1秒内完成(60fps)
expect(frameMetrics).toBeLessThan(1000);
const fps = (60 / frameMetrics) * 1000;
console.log(`Average FPS: ${fps.toFixed(2)}`);
});
test('should handle large file operations efficiently', async () => {
await app.start();
await app.client.waitUntilWindowLoaded();
// 生成大文件内容(1MB)
const largeContent = 'A'.repeat(1024 * 1024);
const startTime = performance.now();
// 模拟文件保存操作
await app.client.electron.ipcRenderer.send('save-file', {
content: largeContent,
path: path.join(__dirname, 'fixtures', 'large-test.txt')
});
// 等待操作完成
await new Promise(resolve => setTimeout(resolve, 1000));
const operationTime = performance.now() - startTime;
// 大文件操作应在合理时间内完成(5秒)
expect(operationTime).toBeLessThan(5000);
console.log(`Large file operation time: ${operationTime.toFixed(2)}ms`);
});
test('should not have memory leaks under repeated operations', async () => {
await app.start();
await app.client.waitUntilWindowLoaded();
const initialMemory = await app.client.execute(() => {
return process.getProcessMemoryInfo().workingSetSize;
});
// 执行多次操作
for (let i = 0; i < 100; i++) {
await app.client.electron.ipcRenderer.send('save-file', {
content: `Test content ${i}`,
path: path.join(__dirname, `fixtures/test-${i}.txt`)
});
await new Promise(resolve => setTimeout(resolve, 10));
}
// 强制垃圾回收(如果可用)
await app.client.execute(() => {
if (global.gc) {
global.gc();
}
});
const finalMemory = await app.client.execute(() => {
return process.getProcessMemoryInfo().workingSetSize;
});
const memoryIncrease = finalMemory - initialMemory;
// 内存增长应在合理范围内(50MB)
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024);
console.log(`Memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
});
});
---
03.CI/CD集成
a.GitHub Actions配置
多平台测试
自动化构建
发布流程
---
// .github/workflows/test.yml
name: Test Electron App
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
- name: Build application
run: npm run build
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
build:
needs: test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build for ${{ matrix.os }}
run: npm run build:${{ matrix.os }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: app-${{ matrix.os }}
path: release/
---
b.测试报告生成
覆盖率报告
性能基准报告
测试结果聚合
---
// scripts/generate-test-reports.js
const fs = require('fs');
const path = require('path');
function generateTestReports() {
const coverageDir = path.join(__dirname, '../coverage');
const testResultsDir = path.join(__dirname, '../test-results');
// 确保目录存在
[coverageDir, testResultsDir].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// 生成覆盖率报告
const coverageReport = {
timestamp: new Date().toISOString(),
coverage: JSON.parse(fs.readFileSync(path.join(coverageDir, 'coverage-final.json'), 'utf8')),
summary: JSON.parse(fs.readFileSync(path.join(coverageDir, 'coverage-summary.json'), 'utf8'))
};
fs.writeFileSync(
path.join(testResultsDir, 'coverage-report.json'),
JSON.stringify(coverageReport, null, 2)
);
console.log('Coverage report generated');
// 生成性能基准报告
const performanceReport = {
timestamp: new Date().toISOString(),
metrics: {
startupTime: '2000ms', // 从实际测试获取
memoryUsage: '150MB',
averageFPS: '60'
}
};
fs.writeFileSync(
path.join(testResultsDir, 'performance-report.json'),
JSON.stringify(performanceReport, null, 2)
);
console.log('Performance report generated');
}
if (require.main === module) {
generateTestReports();
}
module.exports = { generateTestReports };
---
c.部署脚本
自动化打包
代码签名
发布到各个平台
---
// scripts/deploy.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
async function deploy() {
const version = require('../package.json').version;
const platform = process.argv || 'all';
console.log(`Starting deployment for version ${version}, platform: ${platform}`);
try {
// 运行测试
console.log('Running tests...');
execSync('npm test', { stdio: 'inherit' });
// 构建应用
console.log('Building application...');
execSync('npm run build', { stdio: 'inherit' });
// 打包应用
console.log('Packaging application...');
if (platform === 'all') {
execSync('npm run dist', { stdio: 'inherit' });
} else {
execSync(`npm run dist:${platform}`, { stdio: 'inherit' });
}
// 创建发布信息
const releaseInfo = {
version,
timestamp: new Date().toISOString(),
platform,
artifacts: fs.readdirSync('release')
};
fs.writeFileSync(
path.join('release', 'release-info.json'),
JSON.stringify(releaseInfo, null, 2)
);
console.log('Deployment completed successfully!');
console.log(`Release artifacts available in: release/`);
} catch (error) {
console.error('Deployment failed:', error.message);
process.exit(1);
}
}
if (require.main === module) {
deploy();
}
module.exports = { deploy };
---
4 应用开发
4.1 应用周期
01.应用生命周期管理
a.核心事件处理
主进程是Electron应用的核心控制中心,负责创建和管理应用的生命周期、窗口、系统菜单等。主进程开发需要掌握Node.js编程和Electron提供的丰富API,是构建稳定桌面应用的基础。
应用生命周期管理是主进程的首要职责。通过监听app模块的各种事件,可以精确控制应用的启动、运行和关闭过程。合理处理这些事件可以确保应用在不同操作系统上都能正确启动和退出。
---
```javascript
const { app, BrowserWindow, Menu } = require('electron');
const path = require('path');
// 应用准备就绪事件
app.whenReady().then(() => {
console.log('应用准备就绪');
createMainWindow();
// macOS特定的应用激活处理
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow();
}
});
setupApplicationMenu();
});
// 所有窗口关闭事件
app.on('window-all-closed', () => {
// macOS在关闭所有窗口后通常保持应用运行
if (process.platform !== 'darwin') {
app.quit();
}
});
// 应用即将退出事件
app.on('before-quit', (event) => {
console.log('应用即将退出');
// 在这里可以保存应用状态、清理资源等
});
// 应用退出事件
app.on('will-quit', (event) => {
console.log('应用正在退出');
// 移除所有快捷键监听
globalShortcut.unregisterAll();
});
// 安全事件处理
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', (event, navigationUrl) => {
event.preventDefault();
require('electron').shell.openExternal(navigationUrl);
});
});
```
---
02.窗口管理
a.窗口创建与控制
窗口管理是主进程的核心功能之一。BrowserWindow类提供了丰富的窗口配置选项和控制方法,开发者可以创建不同类型和大小的窗口,实现复杂的窗口布局和交互逻辑。
---
```javascript
let mainWindow;
function createMainWindow() {
const windowConfig = {
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
icon: path.join(__dirname, 'assets/icon.png'),
show: false, // 窗口创建后不立即显示
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
webSecurity: true
}
};
// 根据操作系统调整窗口配置
if (process.platform === 'darwin') {
windowConfig.titleBarStyle = 'hiddenInset';
} else if (process.platform === 'win32') {
windowConfig.frame = false; // 无边框窗口
}
mainWindow = new BrowserWindow(windowConfig);
// 加载页面内容
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile('renderer/index.html');
}
// 窗口事件处理
mainWindow.once('ready-to-show', () => {
mainWindow.show();
mainWindow.focus();
});
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.on('maximize', () => {
mainWindow.webContents.send('window-maximized');
});
mainWindow.on('unmaximize', () => {
mainWindow.webContents.send('window-unmaximized');
});
return mainWindow;
}
// 创建子窗口
function createChildWindow(options = {}) {
const childWindow = new BrowserWindow({
parent: mainWindow,
modal: true,
width: 600,
height: 400,
show: false,
...options
});
childWindow.loadFile('renderer/dialog.html');
childWindow.once('ready-to-show', () => {
childWindow.show();
});
return childWindow;
}
```
---
03.系统集成
a.菜单、通知与托盘
系统集成功能使Electron应用能够更好地融入操作系统环境。通过主进程API,可以访问系统菜单、通知、托盘等原生功能,提供更接近原生应用的用户体验。
---
```javascript
function setupApplicationMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '新建文件',
accelerator: 'CmdOrCtrl+N',
click: () => {
mainWindow.webContents.send('menu-new-file');
}
},
{
label: '打开文件',
accelerator: 'CmdOrCtrl+O',
click: () => {
openFile();
}
},
{ type: 'separator' },
{
label: '退出',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
}
]
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '重做', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
{ label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' }
]
},
{
label: '视图',
submenu: [
{ label: '重新加载', accelerator: 'CmdOrCtrl+R', role: 'reload' },
{ label: '强制重新加载', accelerator: 'CmdOrCtrl+Shift+R', role: 'forceReload' },
{ label: '开发者工具', accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Ctrl+Shift+I', role: 'toggleDevTools' },
{ type: 'separator' },
{ label: '实际大小', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' },
{ label: '放大', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' },
{ label: '缩小', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' },
{ type: 'separator' },
{ label: '全屏', accelerator: process.platform === 'darwin' ? 'Ctrl+Command+F' : 'F11', role: 'togglefullscreen' }
]
}
];
// macOS菜单特殊处理
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{ label: '关于 ' + app.getName(), role: 'about' },
{ type: 'separator' },
{ label: '服务', role: 'services', submenu: [] },
{ type: 'separator' },
{ label: '隐藏 ' + app.getName(), accelerator: 'Command+H', role: 'hide' },
{ label: '隐藏其他', accelerator: 'Command+Shift+H', role: 'hideOthers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{ label: '退出', accelerator: 'Command+Q', click: () => app.quit() }
]
});
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// 系统通知
function showNotification(title, body) {
const { Notification } = require('electron');
if (Notification.isSupported()) {
const notification = new Notification({
title,
body,
icon: path.join(__dirname, 'assets/notification-icon.png'),
silent: false,
urgency: 'normal'
});
notification.on('click', () => {
console.log('通知被点击');
mainWindow.focus();
});
notification.show();
}
}
// 系统托盘
function setupTray() {
const { Tray } = require('electron');
const tray = new Tray(path.join(__dirname, 'assets/tray-icon.png'));
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
mainWindow.show();
mainWindow.focus();
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.quit();
}
}
]);
tray.setToolTip('My Electron App');
tray.setContextMenu(contextMenu);
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});
}
```
---
4.2 渲染进程开发
01.安全隔离
a.预加载脚本与安全实践
渲染进程负责用户界面的展示和交互,本质上是运行在Electron中的Chromium浏览器实例。渲染进程开发与传统Web开发类似,可以使用任何前端框架和工具,但需要特别注意安全性和性能优化。
安全隔离是渲染进程开发的重要考虑因素。Electron提供了多种安全机制,包括上下文隔离、预加载脚本和内容安全策略等,合理配置这些机制可以有效防止XSS攻击和代码注入。
---
```javascript
// preload.js - 渲染进程与主进程的安全桥梁
const { contextBridge, ipcRenderer } = require('electron');
// 暴露受限的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作API
openFileDialog: (options) => ipcRenderer.invoke('open-file-dialog', options),
saveFileDialog: (options) => ipcRenderer.invoke('save-file-dialog', options),
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, data) => ipcRenderer.invoke('write-file', filePath, data),
// 系统通知API
showNotification: (title, body) => ipcRenderer.invoke('show-notification', title, body),
// 窗口控制API
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window'),
// 应用信息API
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
getSystemInfo: () => ipcRenderer.invoke('get-system-info'),
// 事件监听
onWindowEvent: (callback) => ipcRenderer.on('window-event', callback),
removeWindowEventListener: (callback) => ipcRenderer.removeListener('window-event', callback)
});
// 安全性检查
if (process.env.NODE_ENV === 'development') {
console.log('Preload script loaded in development mode');
}
```
---
02.前端框架集成
a.React集成示例
前端框架集成使Electron应用能够享受现代Web开发的便利。React、Vue、Angular等框架可以无缝集成到Electron中,但需要注意处理框架的生命周期与Electron窗口生命周期的关系。
---
```javascript
// React应用入口文件 renderer/src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [fileContent, setFileContent] = useState('');
const [appVersion, setAppVersion] = useState('');
useEffect(() => {
// 获取应用版本
if (window.electronAPI) {
window.electronAPI.getAppVersion().then(version => {
setAppVersion(version);
});
// 监听窗口事件
const handleWindowEvent = (event, data) => {
console.log('Window event:', data);
};
window.electronAPI.onWindowEvent(handleWindowEvent);
// 清理事件监听
return () => {
window.electronAPI.removeWindowEventListener(handleWindowEvent);
};
}
}, []);
const handleOpenFile = async () => {
try {
const result = await window.electronAPI.openFileDialog({
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled) {
const content = await window.electronAPI.readFile(result.filePaths[0]);
setFileContent(content);
}
} catch (error) {
console.error('打开文件失败:', error);
alert('打开文件失败: ' + error.message);
}
};
const handleSaveFile = async () => {
try {
const result = await window.electronAPI.saveFileDialog({
defaultPath: 'document.txt',
filters: [
{ name: 'Text Files', extensions: ['txt'] }
]
});
if (!result.canceled) {
await window.electronAPI.writeFile(result.filePath, fileContent);
alert('文件保存成功');
}
} catch (error) {
console.error('保存文件失败:', error);
alert('保存文件失败: ' + error.message);
}
};
const showNotification = () => {
window.electronAPI.showNotification(
'测试通知',
'这是一个来自Electron应用的通知'
);
};
return (
<div className="App">
<header className="App-header">
<h1>My Electron App</h1>
<p>应用版本: {appVersion}</p>
</header>
<main className="App-main">
<div className="toolbar">
<button onClick={handleOpenFile}>打开文件</button>
<button onClick={handleSaveFile}>保存文件</button>
<button onClick={showNotification}>显示通知</button>
</div>
<div className="editor">
<textarea
value={fileContent}
onChange={(e) => setFileContent(e.target.value)}
placeholder="在这里编辑文件内容..."
rows={20}
cols={80}
/>
</div>
</main>
</div>
);
}
export default App;
```
---
03.性能优化
a.优化策略与代码示例
性能优化对渲染进程的响应性和资源消耗至关重要。Electron应用的渲染进程需要管理内存使用、CPU占用和网络请求,避免影响整体应用性能。
---
```javascript
// 虚拟滚动优化大列表渲染
class VirtualList {
constructor(container, itemHeight, renderItem) {
this.container = container;
this.itemHeight = itemHeight;
this.renderItem = renderItem;
this.data = [];
this.visibleStart = 0;
this.visibleEnd = 0;
this.setupScrollListener();
}
setData(data) {
this.data = data;
this.updateVisibleRange();
this.render();
}
setupScrollListener() {
let scrollTimeout;
this.container.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
this.updateVisibleRange();
this.render();
}, 16); // 约60fps
});
}
updateVisibleRange() {
const scrollTop = this.container.scrollTop;
const containerHeight = this.container.clientHeight;
this.visibleStart = Math.floor(scrollTop / this.itemHeight);
this.visibleEnd = Math.ceil((scrollTop + containerHeight) / this.itemHeight) + 1; // 缓冲
}
render() {
const fragment = document.createDocumentFragment();
const totalHeight = this.data.length * this.itemHeight;
// 设置容器高度
this.container.style.height = `${totalHeight}px`;
// 只渲染可见项
for (let i = this.visibleStart; i < this.visibleEnd && i < this.data.length; i++) {
const item = this.renderItem(this.data[i], i);
item.style.position = 'absolute';
item.style.top = `${i * this.itemHeight}px`;
item.style.height = `${this.itemHeight}px`;
fragment.appendChild(item);
}
// 清理旧内容
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
this.container.appendChild(fragment);
}
}
// 图片懒加载
class LazyImageLoader {
constructor() {
this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
this.loadedImages = new Set();
}
observe(image) {
if (!this.loadedImages.has(image.src)) {
this.observer.observe(image);
}
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer.unobserve(img);
}
});
}
loadImage(img) {
const src = img.dataset.src;
if (!src || this.loadedImages.has(src)) return;
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
this.loadedImages.add(src);
};
tempImg.onerror = () => {
img.src = 'placeholder.png'; // 加载失败时显示占位图
};
tempImg.src = src;
}
}
// 防抖函数优化输入处理
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 节流函数优化滚动处理
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
```
---
4.3 预加载脚本开发
01.上下文桥接
a.安全API暴露与实现
预加载脚本(Preload Script)是Electron安全模型的重要组成部分,它在渲染进程加载之前执行,可以访问Node.js API,并为渲染进程提供安全的接口。预加载脚本开发需要平衡功能性和安全性。
上下文桥接是预加载脚本的核心功能,通过contextBridge模块可以将Node.js功能安全地暴露给渲染进程。这种方式避免了直接的全局变量污染,同时保持了安全性。
---
```javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const fs = require('fs');
const path = require('path');
// 文件系统操作的安全封装
const fileSystemAPI = {
// 读取文件
readFile: async (filePath) => {
try {
// 验证文件路径安全性
if (!isPathSafe(filePath)) {
throw new Error('不安全的文件路径');
}
const content = await fs.promises.readFile(filePath, 'utf-8');
return { success: true, data: content };
} catch (error) {
return { success: false, error: error.message };
}
},
// 写入文件
writeFile: async (filePath, content) => {
try {
if (!isPathSafe(filePath)) {
throw new Error('不安全的文件路径');
}
await fs.promises.writeFile(filePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
},
// 获取文件信息
getFileStats: async (filePath) => {
try {
if (!isPathSafe(filePath)) {
throw new Error('不安全的文件路径');
}
const stats = await fs.promises.stat(filePath);
return {
success: true,
data: {
size: stats.size,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
created: stats.birthtime,
modified: stats.mtime
}
};
} catch (error) {
return { success: false, error: error.message };
}
}
};
// 系统操作API
const systemAPI = {
// 获取应用版本
getAppVersion: () => process.env.npm_package_version || '1.0.0',
// 获取平台信息
getPlatformInfo: () => ({
platform: process.platform,
arch: process.arch,
version: process.version
}),
// 显示消息框
showMessageBox: async (options) => {
return await ipcRenderer.invoke('show-message-box', options);
},
// 显示文件选择对话框
showOpenDialog: async (options) => {
return await ipcRenderer.invoke('show-open-dialog', options);
},
// 显示保存对话框
showSaveDialog: async (options) => {
return await ipcRenderer.invoke('show-save-dialog', options);
}
};
// 窗口控制API
const windowAPI = {
// 最小化窗口
minimize: () => ipcRenderer.invoke('window-minimize'),
// 最大化窗口
maximize: () => ipcRenderer.invoke('window-maximize'),
// 关闭窗口
close: () => ipcRenderer.invoke('window-close'),
// 设置窗口标题
setTitle: (title) => ipcRenderer.invoke('window-set-title', title),
// 监听窗口事件
onWindowEvent: (callback) => {
ipcRenderer.on('window-event', (event, data) => callback(data));
},
// 移除窗口事件监听
removeWindowEventListener: (callback) => {
ipcRenderer.removeListener('window-event', callback);
}
};
// 数据存储API
const storageAPI = {
// 保存设置
saveSettings: async (key, value) => {
return await ipcRenderer.invoke('storage-save', { key, value });
},
// 读取设置
loadSettings: async (key) => {
return await ipcRenderer.invoke('storage-load', { key });
},
// 删除设置
removeSettings: async (key) => {
return await ipcRenderer.invoke('storage-remove', { key });
}
};
// 事件总线API
const eventAPI = {
// 发送事件到主进程
emit: (channel, data) => {
// 只允许特定的事件通道
const allowedChannels = ['user-action', 'log-message', 'error-report'];
if (allowedChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
// 监听来自主进程的事件
on: (channel, callback) => {
const allowedChannels = ['app-update', 'notification', 'system-message'];
if (allowedChannels.includes(channel)) {
ipcRenderer.on(channel, (event, data) => callback(data));
}
},
// 移除事件监听
off: (channel, callback) => {
ipcRenderer.removeListener(channel, callback);
}
};
// 安全性检查函数
function isPathSafe(filePath) {
// 防止路径遍历攻击
const normalizedPath = path.normalize(filePath);
const userDocumentsPath = path.join(require('os').homedir(), 'Documents');
const userDesktopPath = path.join(require('os').homedir(), 'Desktop');
const appDataPath = require('electron').app.getPath('userData');
const safePaths = [userDocumentsPath, userDesktopPath, appDataPath];
return safePaths.some(safePath => normalizedPath.startsWith(safePath));
}
// 通过contextBridge暴露API
contextBridge.exposeInMainWorld('electron', {
fs: fileSystemAPI,
system: systemAPI,
window: windowAPI,
storage: storageAPI,
events: eventAPI
});
// 开发模式下的调试功能
if (process.env.NODE_ENV === 'development') {
contextBridge.exposeInMainWorld('electronDebug', {
log: (...args) => console.log('[Preload]', ...args),
error: (...args) => console.error('[Preload]', ...args),
warn: (...args) => console.warn('[Preload]', ...args)
});
}
```
---
02.高级功能
a.权限、缓存与API封装
预加载脚本的高级功能包括权限控制、错误处理和性能优化。合理设计API接口可以确保安全性同时提供必要的功能。
---
```javascript
// 高级预加载脚本功能
const { contextBridge, ipcRenderer } = require('electron');
// 权限管理系统
class PermissionManager {
constructor() {
this.permissions = new Map();
this.loadPermissions();
}
async loadPermissions() {
try {
const result = await ipcRenderer.invoke('get-permissions');
this.permissions = new Map(Object.entries(result));
} catch (error) {
console.error('加载权限失败:', error);
}
}
hasPermission(action) {
return this.permissions.get(action) === true;
}
async requestPermission(action) {
if (this.hasPermission(action)) {
return true;
}
const granted = await ipcRenderer.invoke('request-permission', action);
this.permissions.set(action, granted);
return granted;
}
}
// API调用包装器
class APIWrapper {
constructor() {
this.permissionManager = new PermissionManager();
this.callQueue = [];
this.isProcessing = false;
}
async call(api, method, ...args) {
// 检查权限
const action = `${api}.${method}`;
if (!await this.permissionManager.requestPermission(action)) {
throw new Error(`没有执行 ${action} 的权限`);
}
// 添加到队列
return new Promise((resolve, reject) => {
this.callQueue.push({
api, method, args,
resolve, reject
});
this.processQueue();
});
}
async processQueue() {
if (this.isProcessing || this.callQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.callQueue.length > 0) {
const { api, method, args, resolve, reject } = this.callQueue.shift();
try {
const result = await ipcRenderer.invoke(`${api}-${method}`, ...args);
resolve(result);
} catch (error) {
reject(error);
}
}
this.isProcessing = false;
}
}
// 缓存管理
class CacheManager {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
}
set(key, value, ttl = 300000) { // 默认5分钟过期
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
value,
expires: Date.now() + ttl
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.value;
}
clear() {
this.cache.clear();
}
}
// 创建API实例
const apiWrapper = new APIWrapper();
const cacheManager = new CacheManager();
// 缓存装饰器
function withCache(cacheKey, ttl) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args) {
const fullCacheKey = `${cacheKey}-${JSON.stringify(args)}`;
// 尝试从缓存获取
const cached = cacheManager.get(fullCacheKey);
if (cached !== null) {
return cached;
}
// 执行原方法
const result = await originalMethod.apply(this, args);
// 缓存结果
cacheManager.set(fullCacheKey, result, ttl);
return result;
};
return descriptor;
};
}
// 暴露增强的API
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作API
fs: {
readFile: (path) => apiWrapper.call('fs', 'readFile', path),
writeFile: (path, data) => apiWrapper.call('fs', 'writeFile', path, data),
listFiles: (dir) => apiWrapper.call('fs', 'listFiles', dir)
},
// 系统API
system: {
getVersion: () => apiWrapper.call('system', 'getVersion'),
getPlatform: () => apiWrapper.call('system', 'getPlatform'),
showNotification: (title, body) => apiWrapper.call('system', 'showNotification', title, body)
},
// 缓存API
cache: {
get: (key) => cacheManager.get(key),
set: (key, value, ttl) => cacheManager.set(key, value, ttl),
clear: () => cacheManager.clear()
},
// 权限API
permissions: {
request: (action) => apiWrapper.permissionManager.requestPermission(action),
has: (action) => apiWrapper.permissionManager.hasPermission(action)
}
});
```
---
4.4 数据持久化
01.本地文件存储
a.基于fs模块的文件操作
Electron应用的数据持久化需要考虑不同存储方式的特点和适用场景。从简单的JSON文件到复杂的数据库,选择合适的存储方案对应用性能和数据完整性至关重要。
本地文件存储是最简单的数据持久化方式,适合存储用户设置、配置文件和小量数据。使用Node.js的fs模块可以方便地进行文件读写操作,同时需要注意异步操作和错误处理。
---
```javascript
// src/main/storage/file-storage.js
const fs = require('fs').promises;
const path = require('path');
const { app } = require('electron');
class FileStorage {
constructor(dataDir = null) {
this.dataDir = dataDir || app.getPath('userData');
this.ensureDataDir();
}
async ensureDataDir() {
try {
await fs.mkdir(this.dataDir, { recursive: true });
} catch (error) {
console.error('创建数据目录失败:', error);
}
}
async saveJSON(filename, data) {
try {
const filePath = path.join(this.dataDir, `${filename}.json`);
const jsonContent = JSON.stringify(data, null, 2);
await fs.writeFile(filePath, jsonContent, 'utf-8');
return { success: true };
} catch (error) {
console.error('保存JSON文件失败:', error);
return { success: false, error: error.message };
}
}
async loadJSON(filename, defaultValue = null) {
try {
const filePath = path.join(this.dataDir, `${filename}.json`);
const jsonContent = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(jsonContent);
return { success: true, data };
} catch (error) {
if (error.code === 'ENOENT') {
// 文件不存在,返回默认值
return { success: true, data: defaultValue };
}
console.error('加载JSON文件失败:', error);
return { success: false, error: error.message, data: defaultValue };
}
}
async saveBinary(filename, buffer) {
try {
const filePath = path.join(this.dataDir, filename);
await fs.writeFile(filePath, buffer);
return { success: true };
} catch (error) {
console.error('保存二进制文件失败:', error);
return { success: false, error: error.message };
}
}
async loadBinary(filename) {
try {
const filePath = path.join(this.dataDir, filename);
const buffer = await fs.readFile(filePath);
return { success: true, data: buffer };
} catch (error) {
console.error('加载二进制文件失败:', error);
return { success: false, error: error.message };
}
}
async deleteFile(filename) {
try {
const filePath = path.join(this.dataDir, filename);
await fs.unlink(filePath);
return { success: true };
} catch (error) {
console.error('删除文件失败:', error);
return { success: false, error: error.message };
}
}
async fileExists(filename) {
try {
const filePath = path.join(this.dataDir, filename);
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async getList() {
try {
const files = await fs.readdir(this.dataDir);
return files.filter(file => !file.startsWith('.'));
} catch (error) {
console.error('获取文件列表失败:', error);
return [];
}
}
}
module.exports = FileStorage;
```
---
02.SQLite数据库
a.使用better-sqlite3进行结构化数据存储
SQLite数据库适合存储结构化数据和需要复杂查询的场景。通过better-sqlite3或sqlite3模块,可以在Electron应用中嵌入SQLite数据库,提供强大的数据存储和查询功能。
---
```javascript
// src/main/storage/sqlite-storage.js
const Database = require('better-sqlite3');
const path = require('path');
const { app } = require('electron');
class SQLiteStorage {
constructor(dbName = 'app.db') {
this.dbPath = path.join(app.getPath('userData'), dbName);
this.db = null;
this.connect();
}
connect() {
try {
this.db = new Database(this.dbPath);
this.db.pragma('journal_mode = WAL');
this.db.pragma('foreign_keys = ON');
console.log('SQLite数据库连接成功');
} catch (error) {
console.error('SQLite数据库连接失败:', error);
throw error;
}
}
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
createTable(tableName, schema) {
try {
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${schema})`;
this.db.exec(sql);
return { success: true };
} catch (error) {
console.error('创建表失败:', error);
return { success: false, error: error.message };
}
}
insert(tableName, data) {
try {
const columns = Object.keys(data).join(', ');
const placeholders = Object.keys(data).map(() => '?').join(', ');
const values = Object.values(data);
const sql = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`;
const stmt = this.db.prepare(sql);
const result = stmt.run(values);
return { success: true, lastID: result.lastInsertRowid, changes: result.changes };
} catch (error) {
console.error('插入数据失败:', error);
return { success: false, error: error.message };
}
}
update(tableName, data, whereClause, whereParams = []) {
try {
const setClause = Object.keys(data).map(key => `${key} = ?`).join(', ');
const values = [...Object.values(data), ...whereParams];
const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`;
const stmt = this.db.prepare(sql);
const result = stmt.run(values);
return { success: true, changes: result.changes };
} catch (error) {
console.error('更新数据失败:', error);
return { success: false, error: error.message };
}
}
delete(tableName, whereClause, whereParams = []) {
try {
const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`;
const stmt = this.db.prepare(sql);
const result = stmt.run(whereParams);
return { success: true, changes: result.changes };
} catch (error) {
console.error('删除数据失败:', error);
return { success: false, error: error.message };
}
}
select(tableName, columns = '*', whereClause = '', whereParams = []) {
try {
let sql = `SELECT ${columns} FROM ${tableName}`;
if (whereClause) {
sql += ` WHERE ${whereClause}`;
}
const stmt = this.db.prepare(sql);
const rows = stmt.all(whereParams);
return { success: true, data: rows };
} catch (error) {
console.error('查询数据失败:', error);
return { success: false, error: error.message, data: [] };
}
}
selectOne(tableName, columns = '*', whereClause, whereParams = []) {
try {
let sql = `SELECT ${columns} FROM ${tableName}`;
if (whereClause) {
sql += ` WHERE ${whereClause}`;
}
sql += ' LIMIT 1';
const stmt = this.db.prepare(sql);
const row = stmt.get(whereParams);
return { success: true, data: row };
} catch (error) {
console.error('查询单条数据失败:', error);
return { success: false, error: error.message, data: null };
}
}
beginTransaction() {
try {
this.db.exec('BEGIN TRANSACTION');
return { success: true };
} catch (error) {
console.error('开始事务失败:', error);
return { success: false, error: error.message };
}
}
commit() {
try {
this.db.exec('COMMIT');
return { success: true };
} catch (error) {
console.error('提交事务失败:', error);
return { success: false, error: error.message };
}
}
rollback() {
try {
this.db.exec('ROLLBACK');
return { success: true };
} catch (error) {
console.error('回滚事务失败:', error);
return { success: false, error: error.message };
}
}
async backup(backupPath) {
try {
await this.db.backup(backupPath);
return { success: true };
} catch (error) {
console.error('备份数据库失败:', error);
return { success: false, error: error.message };
}
}
}
module.exports = SQLiteStorage;
```
---
03.IndexedDB
a.在渲染进程中使用Dexie.js操作IndexedDB
IndexedDB是浏览器端的数据库,适合在渲染进程中存储大量结构化数据。通过Dexie.js等库可以简化IndexedDB的使用,提供类似ORM的API。
---
```javascript
// src/renderer/storage/indexed-storage.js
import Dexie from 'dexie';
class IndexedStorage {
constructor(dbName = 'ElectronAppDB') {
this.db = new Dexie(dbName);
this.setupSchema();
}
setupSchema() {
// 定义数据库版本和表结构
this.db.version(1).stores({
users: '++id, name, email, createdAt, updatedAt',
settings: '++id, key, value, category',
documents: '++id, title, content, createdAt, updatedAt, tags'
});
this.db.version(2).stores({
users: '++id, name, email, avatar, createdAt, updatedAt',
settings: '++id, key, value, category',
documents: '++id, title, content, createdAt, updatedAt, tags, isDeleted'
});
// 添加数据钩子
this.db.users.hook('creating', (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
});
this.db.users.hook('updating', (modifications, primKey, obj, trans) => {
modifications.updatedAt = new Date();
});
this.db.documents.hook('creating', (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
obj.isDeleted = false;
});
}
async addUser(userData) {
try {
const id = await this.db.users.add(userData);
return { success: true, id };
} catch (error) {
console.error('添加用户失败:', error);
return { success: false, error: error.message };
}
}
async updateUser(id, userData) {
try {
await this.db.users.update(id, userData);
return { success: true };
} catch (error) {
console.error('更新用户失败:', error);
return { success: false, error: error.message };
}
}
async deleteUser(id) {
try {
await this.db.users.delete(id);
return { success: true };
} catch (error) {
console.error('删除用户失败:', error);
return { success: false, error: error.message };
}
}
async getUser(id) {
try {
const user = await this.db.users.get(id);
return { success: true, data: user };
} catch (error) {
console.error('获取用户失败:', error);
return { success: false, error: error.message, data: null };
}
}
async getAllUsers() {
try {
const users = await this.db.users.toArray();
return { success: true, data: users };
} catch (error) {
console.error('获取所有用户失败:', error);
return { success: false, error: error.message, data: [] };
}
}
async searchUsers(query) {
try {
const users = await this.db.users
.where('name')
.startsWithIgnoreCase(query)
.or('email')
.startsWithIgnoreCase(query)
.toArray();
return { success: true, data: users };
} catch (error) {
console.error('搜索用户失败:', error);
return { success: false, error: error.message, data: [] };
}
}
async saveSetting(key, value, category = 'general') {
try {
const existingSetting = await this.db.settings.where('key').equals(key).first();
if (existingSetting) {
await this.db.settings.update(existingSetting.id, { value, category });
} else {
await this.db.settings.add({ key, value, category });
}
return { success: true };
} catch (error) {
console.error('保存设置失败:', error);
return { success: false, error: error.message };
}
}
async getSetting(key) {
try {
const setting = await this.db.settings.where('key').equals(key).first();
return { success: true, data: setting ? setting.value : null };
} catch (error) {
console.error('获取设置失败:', error);
return { success: false, error: error.message, data: null };
}
}
async getAllSettings(category = null) {
try {
let query = this.db.settings;
if (category) {
query = query.where('category').equals(category);
}
const settings = await query.toArray();
const settingsObject = settings.reduce((acc, setting) => {
acc[setting.key] = setting.value;
return acc;
}, {});
return { success: true, data: settingsObject };
} catch (error) {
console.error('获取所有设置失败:', error);
return { success: false, error: error.message, data: {} };
}
}
async clearAllData() {
try {
await this.db.transaction('rw', this.db.users, this.db.settings, this.db.documents, async () => {
await this.db.users.clear();
await this.db.settings.clear();
await this.db.documents.clear();
});
return { success: true };
} catch (error) {
console.error('清空数据失败:', error);
return { success: false, error: error.message };
}
}
async exportData() {
try {
const users = await this.db.users.toArray();
const settings = await this.db.settings.toArray();
const documents = await this.db.documents.toArray();
const exportData = {
version: 1,
timestamp: new Date().toISOString(),
data: {
users,
settings,
documents
}
};
return { success: true, data: exportData };
} catch (error) {
console.error('导出数据失败:', error);
return { success: false, error: error.message };
}
}
async importData(importData) {
try {
await this.db.transaction('rw', this.db.users, this.db.settings, this.db.documents, async () => {
// 清空现有数据
await this.db.users.clear();
await this.db.settings.clear();
await this.db.documents.clear();
// 导入新数据
if (importData.data.users) {
await this.db.users.bulkAdd(importData.data.users);
}
if (importData.data.settings) {
await this.db.settings.bulkAdd(importData.data.settings);
}
if (importData.data.documents) {
await this.db.documents.bulkAdd(importData.data.documents);
}
});
return { success: true };
} catch (error) {
console.error('导入数据失败:', error);
return { success: false, error: error.message };
}
}
}
export default IndexedStorage;
```
---
04.数据加密和安全存储
a.结合electron-store和safeStorage实现安全存储
数据加密和安全存储对敏感信息至关重要。使用electron-store或自定义加密方案可以保护用户数据安全,防止未授权访问。
---
```javascript
// src/main/storage/secure-storage.js
const crypto = require('crypto');
const Storage = require('electron-store');
const { safeStorage } = require('electron');
class SecureStorage {
constructor() {
this.storage = new Storage({
name: 'secure-config',
defaults: {
encrypted: {}
}
});
this.algorithm = 'aes-256-gcm';
this.keyLength = 32;
this.ivLength = 16;
this.tagLength = 16;
this.initEncryption();
}
initEncryption() {
// 尝试从系统密钥链获取加密密钥
try {
const storedKey = safeStorage.decryptString(this.storage.get('master_key'));
this.masterKey = Buffer.from(storedKey, 'hex');
} catch (error) {
// 生成新的加密密钥
this.masterKey = crypto.randomBytes(this.keyLength);
const encryptedKey = safeStorage.encryptString(this.masterKey.toString('hex'));
this.storage.set('master_key', encryptedKey);
}
}
encrypt(text) {
try {
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipher(this.algorithm, this.masterKey);
cipher.setAAD(Buffer.from('additional-data'), 'utf8');
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
} catch (error) {
console.error('加密失败:', error);
throw error;
}
}
decrypt(encryptedData) {
try {
const { encrypted, iv, tag } = encryptedData;
const decipher = crypto.createDecipher(this.algorithm, this.masterKey);
decipher.setAAD(Buffer.from('additional-data'), 'utf8');
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.error('解密失败:', error);
throw error;
}
}
setSecure(key, value) {
try {
const encryptedValue = this.encrypt(JSON.stringify(value));
const encryptedData = this.storage.get('encrypted', {});
encryptedData[key] = encryptedValue;
this.storage.set('encrypted', encryptedData);
return true;
} catch (error) {
console.error('安全存储失败:', error);
return false;
}
}
getSecure(key, defaultValue = null) {
try {
const encryptedData = this.storage.get('encrypted', {});
const encryptedValue = encryptedData[key];
if (!encryptedValue) {
return defaultValue;
}
const decryptedValue = this.decrypt(encryptedValue);
return JSON.parse(decryptedValue);
} catch (error) {
console.error('安全读取失败:', error);
return defaultValue;
}
}
deleteSecure(key) {
try {
const encryptedData = this.storage.get('encrypted', {});
delete encryptedData[key];
this.storage.set('encrypted', encryptedData);
return true;
} catch (error) {
console.error('删除安全数据失败:', error);
return false;
}
}
hasSecure(key) {
const encryptedData = this.storage.get('encrypted', {});
return key in encryptedData;
}
clearSecure() {
try {
this.storage.set('encrypted', {});
return true;
} catch (error) {
console.error('清空安全数据失败:', error);
return false;
}
}
// 密码哈希和验证
hashPassword(password, salt = null) {
if (!salt) {
salt = crypto.randomBytes(16).toString('hex');
}
const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512');
return {
hash: hash.toString('hex'),
salt
};
}
verifyPassword(password, hash, salt) {
const hashVerify = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512');
return hashVerify.toString('hex') === hash;
}
}
module.exports = SecureStorage;
```
---
4.5 模块化架构设计
01.主进程模块化
a.按功能职责拆分模块
模块化架构设计是构建大型Electron应用的关键。通过合理的模块划分和依赖管理,可以提高代码的可维护性、可扩展性和团队协作效率。
主进程模块化需要将不同的功能职责分离到独立的模块中,通过统一的API接口进行通信。每个模块负责特定的功能领域,如窗口管理、文件操作、网络请求等。
---
```javascript
// src/main/modules/window-manager.js
const { BrowserWindow, Menu } = require('electron');
const path = require('path');
class WindowManager {
constructor() {
this.windows = new Map();
this.defaultWindowConfig = {
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload.js')
}
};
}
createWindow(name, options = {}) {
if (this.windows.has(name)) {
throw new Error(`窗口 ${name} 已存在`);
}
const config = { ...this.defaultWindowConfig, ...options };
const window = new BrowserWindow(config);
this.windows.set(name, window);
// 窗口事件处理
window.on('closed', () => {
this.windows.delete(name);
});
window.once('ready-to-show', () => {
window.show();
window.focus();
});
return window;
}
getWindow(name) {
return this.windows.get(name) || null;
}
closeWindow(name) {
const window = this.getWindow(name);
if (window) {
window.close();
return true;
}
return false;
}
hideWindow(name) {
const window = this.getWindow(name);
if (window) {
window.hide();
return true;
}
return false;
}
showWindow(name) {
const window = this.getWindow(name);
if (window) {
window.show();
window.focus();
return true;
}
return false;
}
getAllWindows() {
return Array.from(this.windows.keys());
}
closeAllWindows() {
this.windows.forEach((window, name) => {
window.close();
});
this.windows.clear();
}
// 广播消息到所有窗口
broadcast(channel, ...args) {
this.windows.forEach(window => {
window.webContents.send(channel, ...args);
});
}
// 发送消息到指定窗口
sendToWindow(windowName, channel, ...args) {
const window = this.getWindow(windowName);
if (window) {
window.webContents.send(channel, ...args);
return true;
}
return false;
}
}
module.exports = WindowManager;
// src/main/modules/file-manager.js
const fs = require('fs').promises;
const path = require('path');
const { dialog, app } = require('electron');
class FileManager {
constructor() {
this.defaultPath = app.getPath('documents');
}
async openFile(options = {}) {
const defaultOptions = {
title: '选择文件',
defaultPath: this.defaultPath,
properties: ['openFile'],
filters: [
{ name: '所有文件', extensions: ['*'] },
{ name: '文本文件', extensions: ['txt', 'md', 'json'] }
]
};
const mergedOptions = { ...defaultOptions, ...options };
try {
const result = await dialog.showOpenDialog(mergedOptions);
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
const content = await fs.readFile(filePath, 'utf-8');
const stats = await fs.stat(filePath);
return {
success: true,
filePath,
content,
stats: {
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
isFile: stats.isFile()
}
};
}
return { success: false, canceled: true };
} catch (error) {
return { success: false, error: error.message };
}
}
async saveFile(content, options = {}) {
const defaultOptions = {
title: '保存文件',
defaultPath: path.join(this.defaultPath, 'untitled.txt'),
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: 'JSON文件', extensions: ['json'] },
{ name: 'Markdown文件', extensions: ['md'] }
]
};
const mergedOptions = { ...defaultOptions, ...options };
try {
const result = await dialog.showSaveDialog(mergedOptions);
if (!result.canceled) {
await fs.writeFile(result.filePath, content, 'utf-8');
return { success: true, filePath: result.filePath };
}
return { success: false, canceled: true };
} catch (error) {
return { success: false, error: error.message };
}
}
async selectDirectory(options = {}) {
const defaultOptions = {
title: '选择目录',
defaultPath: this.defaultPath,
properties: ['openDirectory']
};
const mergedOptions = { ...defaultOptions, ...options };
try {
const result = await dialog.showOpenDialog(mergedOptions);
if (!result.canceled && result.filePaths.length > 0) {
return { success: true, directoryPath: result.filePaths[0] };
}
return { success: false, canceled: true };
} catch (error) {
return { success: false, error: error.message };
}
}
async listFiles(directoryPath, options = {}) {
const defaultOptions = {
includeHidden: false,
includeDirectories: true,
recursive: false
};
const mergedOptions = { ...defaultOptions, ...options };
try {
const entries = await fs.readdir(directoryPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (!mergedOptions.includeHidden && entry.name.startsWith('.')) {
continue;
}
if (entry.isFile() || (entry.isDirectory() && mergedOptions.includeDirectories)) {
const fullPath = path.join(directoryPath, entry.name);
const stats = await fs.stat(fullPath);
files.push({
name: entry.name,
path: fullPath,
isDirectory: entry.isDirectory(),
size: stats.size,
created: stats.birthtime,
modified: stats.mtime
});
// 递归处理子目录
if (entry.isDirectory() && mergedOptions.recursive) {
const subFiles = await this.listFiles(fullPath, mergedOptions);
files.push(...subFiles.data);
}
}
}
return { success: true, data: files };
} catch (error) {
return { success: false, error: error.message, data: [] };
}
}
}
module.exports = FileManager;
```
---
02.事件总线模式
a.实现解耦的模块间通信
事件总线模式提供了解耦的模块间通信机制。通过中央事件管理器,不同模块可以发布和订阅事件,实现松耦合的系统架构。
---
```javascript
// src/main/modules/event-bus.js
const EventEmitter = require('events');
class EventBus extends EventEmitter {
constructor() {
super();
this.setMaxListeners(100); // 防止内存泄漏警告
}
// 发布事件
publish(event, data, source = 'unknown') {
const eventData = {
data,
source,
timestamp: new Date().toISOString()
};
this.emit(event, eventData);
return true;
}
// 订阅事件
subscribe(event, callback, options = {}) {
const defaultOptions = {
once: false,
priority: 0
};
const mergedOptions = { ...defaultOptions, ...options };
const wrapperCallback = (eventData) => {
try {
callback(eventData);
} catch (error) {
console.error(`事件处理器错误 (${event}):`, error);
this.emit('error', { event, error, eventData });
}
};
if (mergedOptions.once) {
this.once(event, wrapperCallback);
} else {
this.on(event, wrapperCallback);
}
// 返回取消订阅函数
return () => {
this.off(event, wrapperCallback);
};
}
// 取消订阅
unsubscribe(event, callback) {
this.off(event, callback);
return true;
}
// 获取事件监听器数量
getListenerCount(event) {
return this.listenerCount(event);
}
// 清除所有事件监听器
clear() {
this.removeAllListeners();
return true;
}
// 批量订阅
subscribeBatch(eventHandlers, options = {}) {
const unsubscribers = [];
for (const [event, callback] of Object.entries(eventHandlers)) {
const unsubscribe = this.subscribe(event, callback, options);
unsubscribers.push({ event, unsubscribe });
}
return {
unsubscribeAll: () => {
unsubscribers.forEach(({ unsubscribe }) => unsubscribe());
},
unsubscribe: (event) => {
const handler = unsubscribers.find(h => h.event === event);
if (handler) {
handler.unsubscribe();
return true;
}
return false;
}
};
}
}
// 创建全局事件总线实例
const globalEventBus = new EventBus();
// 模块导出
module.exports = {
EventBus,
global: globalEventBus
};
// src/main/modules/app-service.js
const { global } = require('./event-bus');
class AppService {
constructor(name, eventBus = global) {
this.name = name;
this.eventBus = eventBus;
this.initialized = false;
this.setupEventHandlers();
}
setupEventHandlers() {
// 订阅应用级别事件
this.eventBus.subscribe('app.init', (eventData) => {
if (eventData.source !== this.name) {
this.onAppInit(eventData);
}
});
this.eventBus.subscribe('app.shutdown', (eventData) => {
if (eventData.source !== this.name) {
this.onAppShutdown(eventData);
}
});
this.eventBus.subscribe('app.error', (eventData) => {
this.onAppError(eventData);
});
}
async initialize() {
if (this.initialized) {
return true;
}
try {
await this.onInitialize();
this.initialized = true;
this.eventBus.publish('service.initialized', {
serviceName: this.name
}, this.name);
return true;
} catch (error) {
this.eventBus.publish('app.error', {
source: this.name,
error: error.message,
stack: error.stack
}, this.name);
return false;
}
}
async shutdown() {
if (!this.initialized) {
return true;
}
try {
await this.onShutdown();
this.initialized = false;
this.eventBus.publish('service.shutdown', {
serviceName: this.name
}, this.name);
return true;
} catch (error) {
this.eventBus.publish('app.error', {
source: this.name,
error: error.message,
stack: error.stack
}, this.name);
return false;
}
}
// 子类可重写的生命周期方法
async onInitialize() {
// 子类实现
}
async onShutdown() {
// 子类实现
}
onAppInit(eventData) {
// 子类可重写
}
onAppShutdown(eventData) {
// 子类可重写
}
onAppError(eventData) {
console.error(`[${this.name}] 应用错误:`, eventData);
}
// 发布事件
publish(event, data) {
return this.eventBus.publish(event, data, this.name);
}
// 订阅事件
subscribe(event, callback, options = {}) {
return this.eventBus.subscribe(event, callback, options);
}
}
module.exports = AppService;
```
---
03.渲染进程模块化
a.基于组件化的UI架构
渲染进程模块化通过组件化架构实现,使用现代前端框架的模块系统和构建工具。每个组件负责特定的UI功能,通过props和事件进行通信。
---
```javascript
// src/renderer/components/FileSystemExplorer.js
import React, { useState, useEffect } from 'react';
import './FileSystemExplorer.css';
const FileSystemExplorer = ({ onFileSelect, onDirectoryChange, initialPath = null }) => {
const [currentPath, setCurrentPath] = useState(initialPath || '');
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
useEffect(() => {
if (currentPath) {
loadDirectory(currentPath);
}
}, [currentPath]);
const loadDirectory = async (path) => {
setLoading(true);
setError(null);
try {
if (window.electronAPI && window.electronAPI.fs) {
const result = await window.electronAPI.fs.listFiles(path);
if (result.success) {
setFiles(result.data);
if (onDirectoryChange) {
onDirectoryChange(path);
}
} else {
setError(result.error);
}
}
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
const handleFileClick = async (file) => {
if (file.isDirectory) {
setCurrentPath(file.path);
} else {
setSelectedFile(file);
if (onFileSelect) {
onFileSelect(file);
}
}
};
const handleBackClick = () => {
const parentPath = currentPath.split('/').slice(0, -1).join('/');
setCurrentPath(parentPath || '');
};
const handleRefresh = () => {
loadDirectory(currentPath);
};
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString();
};
return (
<div className="file-system-explorer">
<div className="explorer-toolbar">
<div className="current-path">{currentPath || '根目录'}</div>
<div className="toolbar-actions">
<button onClick={handleBackClick} disabled={!currentPath}>
← 返回
</button>
<button onClick={handleRefresh}>
刷新
</button>
</div>
</div>
{loading && <div className="loading">加载中...</div>}
{error && <div className="error">错误: {error}</div>}
<div className="file-list">
{files.map((file) => (
<div
key={file.path}
className={`file-item ${selectedFile === file ? 'selected' : ''}`}
onClick={() => handleFileClick(file)}
>
<div className="file-icon">
{file.isDirectory ? '📁' : '📄'}
</div>
<div className="file-info">
<div className="file-name">{file.name}</div>
<div className="file-details">
{!file.isDirectory && <span>{formatFileSize(file.size)}</span>}
<span>{formatDate(file.modified)}</span>
</div>
</div>
</div>
))}
</div>
{files.length === 0 && !loading && !error && (
<div className="empty-state">
此目录为空
</div>
)}
</div>
);
};
export default FileSystemExplorer;
// src/renderer/components/StatusBar.js
import React, { useState, useEffect } from 'react';
import './StatusBar.css';
const StatusBar = ({ onStatusClick }) => {
const [status, setStatus] = useState('就绪');
const [progress, setProgress] = useState(0);
const [visible, setVisible] = useState(false);
const showStatus = (message, showProgress = false, progressValue = 0) => {
setStatus(message);
setVisible(true);
setProgress(progressValue);
if (!showProgress) {
setTimeout(() => {
setVisible(false);
}, 3000);
}
};
const updateProgress = (value) => {
setProgress(value);
};
const hideStatus = () => {
setVisible(false);
setProgress(0);
};
useEffect(() => {
// 监听全局状态事件
const handleStatusEvent = (event, data) => {
showStatus(data.message, data.showProgress, data.progress);
};
const handleProgressEvent = (event, data) => {
updateProgress(data.progress);
};
const handleHideStatusEvent = (event, data) => {
hideStatus();
};
if (window.electronAPI && window.electronAPI.events) {
window.electronAPI.events.on('status.show', handleStatusEvent);
window.electronAPI.events.on('status.progress', handleProgressEvent);
window.electronAPI.events.on('status.hide', handleHideStatusEvent);
return () => {
window.electronAPI.events.off('status.show', handleStatusEvent);
window.electronAPI.events.off('status.progress', handleProgressEvent);
window.electronAPI.events.off('status.hide', handleHideStatusEvent);
};
}
}, []);
const handleClick = () => {
if (onStatusClick && visible) {
onStatusClick({ status, progress });
}
};
return (
<div className={`status-bar ${visible ? 'visible' : 'hidden'}`} onClick={handleClick}>
<div className="status-message">{status}</div>
{progress > 0 && (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${progress}%` }}
/>
<span className="progress-text">{progress}%</span>
</div>
)}
</div>
);
};
export default StatusBar;
```
---
5 进程通信
5.1 IPC基础概念
01.主进程IPC处理
a.核心实现与安全验证
进程间通信(Inter-Process Communication, IPC)是Electron架构的核心机制,它允许主进程和渲染进程之间安全地传递数据和执行命令。理解IPC的工作原理对于开发功能完善的Electron应用至关重要。
Electron的IPC机制基于Chromium的IPC系统,提供了同步和异步两种通信方式。异步通信是推荐的方式,因为它不会阻塞主进程,保证了应用的响应性。同步通信虽然简单,但可能导致应用冻结,应该谨慎使用。
IPC通信的安全性是开发者需要重点关注的问题。由于渲染进程运行在不受信任的环境中,主进程必须对所有来自渲染进程的请求进行严格验证,防止恶意代码执行或系统资源滥用。
---
```javascript
// main.js - 主进程IPC处理
const { ipcMain } = require('electron');
// 异步IPC处理器
ipcMain.handle('read-file', async (event, filePath) => {
try {
// 验证文件路径安全性
if (!isSecurePath(filePath)) {
throw new Error('不安全的文件路径');
}
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, data: content };
} catch (error) {
return { success: false, error: error.message };
}
});
// 同步IPC处理器(不推荐)
ipcMain.on('sync-operation', (event, data) => {
console.log('收到同步请求:', data);
event.returnValue = '同步响应';
});
// 一次性通信
ipcMain.on('one-way-message', (event, data) => {
console.log('收到一次性消息:', data);
// 不需要返回值
});
// 广播消息到所有渲染进程
function broadcastMessage(channel, message) {
const { BrowserWindow } = require('electron');
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send(channel, message);
});
}
// 安全性验证函数
function isSecurePath(filePath) {
const path = require('path');
const os = require('os');
// 只允许访问用户文档目录和桌面
const allowedDirs = [
path.join(os.homedir(), 'Documents'),
path.join(os.homedir(), 'Desktop'),
path.join(os.homedir(), 'Downloads')
];
const normalizedPath = path.normalize(filePath);
return allowedDirs.some(dir => normalizedPath.startsWith(dir));
}
```
---
02.渲染进程IPC封装
a.通过预加载脚本暴露API
渲染进程通过ipcRenderer模块与主进程通信。为了安全性,推荐通过预加载脚本将ipcRenderer的功能暴露给渲染进程,而不是直接在渲染进程中使用nodeIntegration。
---
```javascript
// preload.js - 预加载脚本中的IPC封装
const { contextBridge, ipcRenderer } = require('electron');
// 安全的IPC API封装
const electronAPI = {
// 文件操作API
file: {
read: (filePath) => ipcRenderer.invoke('read-file', filePath),
write: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
list: (directory) => ipcRenderer.invoke('list-directory', directory)
},
// 系统操作API
system: {
showMessageBox: (options) => ipcRenderer.invoke('show-message-box', options),
showOpenDialog: (options) => ipcRenderer.invoke('show-open-dialog', options),
showSaveDialog: (options) => ipcRenderer.invoke('show-save-dialog', options),
getSystemInfo: () => ipcRenderer.invoke('get-system-info')
},
// 应用控制API
app: {
getVersion: () => ipcRenderer.invoke('get-app-version'),
quit: () => ipcRenderer.send('quit-app'),
minimize: () => ipcRenderer.send('minimize-window'),
maximize: () => ipcRenderer.send('maximize-window')
},
// 事件监听API
events: {
on: (channel, callback) => {
// 只允许特定的事件通道
const allowedChannels = ['app-update', 'notification', 'system-message'];
if (allowedChannels.includes(channel)) {
ipcRenderer.on(channel, callback);
}
},
off: (channel, callback) => {
ipcRenderer.removeListener(channel, callback);
},
once: (channel, callback) => {
ipcRenderer.once(channel, callback);
}
},
// 一次性操作
send: (channel, data) => {
const allowedChannels = ['log-message', 'user-action'];
if (allowedChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
}
};
// 通过contextBridge暴露API
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
```
---
03.IPC通道命名规范
a.结构化通道名称
IPC通道的命名和组织对于维护大型Electron应用非常重要。建议使用命名空间和清晰的命名规范,避免通道名称冲突和混乱。
---
```javascript
// IPC通道命名规范
const IPC_CHANNELS = {
// 文件操作通道
FILE: {
READ: 'fs:read',
WRITE: 'fs:write',
LIST: 'fs:list',
DELETE: 'fs:delete'
},
// 系统操作通道
SYSTEM: {
GET_INFO: 'system:get-info',
SHOW_NOTIFICATION: 'system:show-notification',
OPEN_EXTERNAL: 'system:open-external'
},
// 应用控制通道
APP: {
GET_VERSION: 'app:get-version',
QUIT: 'app:quit',
RESTART: 'app:restart'
},
// 窗口控制通道
WINDOW: {
MINIMIZE: 'window:minimize',
MAXIMIZE: 'window:maximize',
CLOSE: 'window:close',
SET_TITLE: 'window:set-title'
},
// 事件通知通道
EVENTS: {
APP_UPDATE: 'events:app-update',
USER_LOGIN: 'events:user-login',
DATA_CHANGED: 'events:data-changed'
}
};
module.exports = { IPC_CHANNELS };
```
---
5.2 异步通信模式
01.主进程异步处理器
a.使用ipcMain.handle处理请求
异步IPC通信是Electron应用的主要通信方式,它使用Promise和async/await语法,提供了非阻塞的消息传递机制。异步通信能够保持应用的响应性,避免用户界面冻结。
异步通信的基本模式是渲染进程通过ipcRenderer.invoke()发送请求,主进程通过ipcMain.handle()处理请求并返回结果。这种方式支持复杂的数据交换和错误处理。
---
```javascript
// main.js - 异步IPC处理器示例
const { ipcMain, dialog, shell } = require('electron');
const fs = require('fs').promises;
const path = require('path');
// 文件操作处理器
ipcMain.handle('file:read', async (event, filePath) => {
try {
// 验证参数
if (!filePath || typeof filePath !== 'string') {
throw new Error('无效的文件路径');
}
// 安全性检查
if (!isSecurePath(filePath)) {
throw new Error('不安全的文件路径');
}
// 检查文件是否存在
await fs.access(filePath);
// 读取文件内容
const content = await fs.readFile(filePath, 'utf-8');
const stats = await fs.stat(filePath);
return {
success: true,
data: {
content,
size: stats.size,
modified: stats.mtime,
created: stats.birthtime
}
};
} catch (error) {
console.error('读取文件失败:', error);
return {
success: false,
error: {
message: error.message,
code: error.code
}
};
}
});
// 文件写入处理器
ipcMain.handle('file:write', async (event, filePath, content, options = {}) => {
try {
// 验证参数
if (!filePath || typeof filePath !== 'string') {
throw new Error('无效的文件路径');
}
if (content === undefined) {
throw new Error('文件内容不能为空');
}
// 安全性检查
if (!isSecurePath(filePath)) {
throw new Error('不安全的文件路径');
}
// 确保目录存在
const dir = path.dirname(filePath);
await fs.mkdir(dir, { recursive: true });
// 写入文件
await fs.writeFile(filePath, content, {
encoding: options.encoding || 'utf-8',
flag: options.flag || 'w'
});
const stats = await fs.stat(filePath);
return {
success: true,
data: {
size: stats.size,
modified: stats.mtime
}
};
} catch (error) {
console.error('写入文件失败:', error);
return {
success: false,
error: {
message: error.message,
code: error.code
}
};
}
});
// 对话框处理器
ipcMain.handle('dialog:showOpen', async (event, options) => {
try {
const result = await dialog.showOpenDialog({
title: options.title || '选择文件',
defaultPath: options.defaultPath,
buttonLabel: options.buttonLabel || '打开',
filters: options.filters || [
{ name: '所有文件', extensions: ['*'] }
],
properties: options.properties || ['openFile']
});
return {
success: true,
data: result
};
} catch (error) {
console.error('显示打开对话框失败:', error);
return {
success: false,
error: {
message: error.message
}
};
}
});
// 复杂业务逻辑处理器
ipcMain.handle('data:process', async (event, data, options = {}) => {
try {
console.log('开始处理数据:', data.length, '项');
// 模拟耗时操作
const result = [];
for (let i = 0; i < data.length; i++) {
// 发送进度更新
event.sender.send('progress:update', {
current: i + 1,
total: data.length,
percentage: Math.round(((i + 1) / data.length) * 100)
});
// 处理单个数据项
const processed = await processSingleItem(data[i], options);
result.push(processed);
// 防止阻塞主进程
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
console.log('数据处理完成');
return {
success: true,
data: {
result,
processed: result.length,
total: data.length
}
};
} catch (error) {
console.error('数据处理失败:', error);
return {
success: false,
error: {
message: error.message,
stack: error.stack
}
};
}
});
async function processSingleItem(item, options) {
// 模拟数据处理
return new Promise((resolve) => {
setTimeout(() => {
resolve({
original: item,
processed: item.toUpperCase(),
timestamp: new Date().toISOString()
});
}, 10);
});
}
```
---
02.渲染进程异步通信
a.使用ipcRenderer.invoke发送请求
渲染进程中的异步通信使用起来非常直观,可以使用async/await语法或者Promise链式调用。错误处理需要特别注意,建议使用try-catch或Promise.catch()来捕获和处理通信错误。
---
```javascript
// renderer.js - 渲染进程异步通信示例
class FileManager {
constructor() {
this.setupEventListeners();
}
async readFile(filePath) {
try {
const response = await window.electronAPI.file.read(filePath);
if (!response.success) {
throw new Error(response.error.message);
}
return response.data;
} catch (error) {
console.error('读取文件失败:', error);
throw error;
}
}
async writeFile(filePath, content, options = {}) {
try {
const response = await window.electronAPI.file.write(filePath, content, options);
if (!response.success) {
throw new Error(response.error.message);
}
return response.data;
} catch (error) {
console.error('写入文件失败:', error);
throw error;
}
}
async selectFile(options = {}) {
try {
const response = await window.electronAPI.dialog.showOpen({
title: options.title || '选择文件',
filters: options.filters || [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile']
});
if (!response.success) {
throw new Error(response.error.message);
}
if (response.data.canceled) {
return null;
}
return response.data.filePaths[0];
} catch (error) {
console.error('选择文件失败:', error);
throw error;
}
}
setupEventListeners() {
// 监听进度更新
window.electronAPI.events.on('progress:update', (event, progress) => {
this.updateProgress(progress);
});
}
updateProgress(progress) {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
if (progressBar) {
progressBar.style.width = `${progress.percentage}%`;
}
if (progressText) {
progressText.textContent = `${progress.current} / ${progress.total} (${progress.percentage}%)`;
}
}
async processLargeData(data, options = {}) {
try {
// 显示进度条
this.showProgressBar();
const response = await window.electronAPI.data.process(data, options);
if (!response.success) {
throw new Error(response.error.message);
}
return response.data;
} catch (error) {
console.error('数据处理失败:', error);
throw error;
} finally {
// 隐藏进度条
this.hideProgressBar();
}
}
showProgressBar() {
const progressBar = document.getElementById('progress-container');
if (progressBar) {
progressBar.style.display = 'block';
}
}
hideProgressBar() {
const progressBar = document.getElementById('progress-container');
if (progressBar) {
progressBar.style.display = 'none';
}
}
}
// 使用示例
const fileManager = new FileManager();
// 读取文件按钮点击事件
document.getElementById('read-file-btn').addEventListener('click', async () => {
try {
const filePath = await fileManager.selectFile();
if (!filePath) return;
const fileData = await fileManager.readFile(filePath);
// 显示文件内容
document.getElementById('file-content').value = fileData.content;
document.getElementById('file-info').textContent = `
大小: ${fileData.size} 字节,
修改时间: ${new Date(fileData.modified).toLocaleString()}
`;
} catch (error) {
alert('读取文件失败: ' + error.message);
}
});
// 批量处理数据按钮点击事件
document.getElementById('process-data-btn').addEventListener('click', async () => {
try {
const data = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);
const result = await fileManager.processLargeData(data);
alert(`处理完成: ${result.processed} / ${result.total} 项成功`);
} catch (error) {
alert('数据处理失败: ' + error.message);
}
});
```
---
5.3 事件驱动通信
01.主进程事件广播
a.状态与系统事件通知
事件驱动的IPC通信模式适用于主进程向渲染进程发送通知或广播消息的场景。这种模式使用ipcRenderer和webContents的send/on机制,实现了松耦合的消息传递。
事件驱动通信的主要用途包括状态通知、系统事件广播和实时数据更新。与请求-响应模式不同,事件驱动通信不需要等待响应,适合发布-订阅场景。
---
```javascript
// main.js - 事件发送示例
const { ipcMain, app, BrowserWindow } = require('electron');
const os = require('os');
// 系统事件监听和广播
function setupSystemEventListeners() {
// 监听系统主题变化
if (process.platform === 'darwin') {
const { nativeTheme } = require('electron');
nativeTheme.on('updated', () => {
broadcastMessage('system:theme-changed', {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
shouldUseHighContrastColors: nativeTheme.shouldUseHighContrastColors
});
});
}
// 监听网络状态变化
const checkNetworkStatus = () => {
const isOnline = require('dns').resolve('www.google.com', () => {});
broadcastMessage('system:network-status-changed', {
online: navigator.onLine
});
};
setInterval(checkNetworkStatus, 5000);
// 监听系统空闲状态
const { powerMonitor } = require('electron');
powerMonitor.on('lock-screen', () => {
broadcastMessage('system:screen-locked');
});
powerMonitor.on('unlock-screen', () => {
broadcastMessage('system:screen-unlocked');
});
powerMonitor.on('suspend', () => {
broadcastMessage('system:suspend');
});
powerMonitor.on('resume', () => {
broadcastMessage('system:resume');
});
}
// 应用状态事件广播
function setupAppEventBroadcasters() {
// 应用更新检查
app.on('before-quit', () => {
broadcastMessage('app:will-quit');
});
app.on('window-all-closed', () => {
broadcastMessage('app:all-windows-closed');
});
// 窗口事件广播
BrowserWindow.getAllWindows().forEach(window => {
setupWindowEvents(window);
});
// 监听新窗口创建
app.on('web-contents-created', (event, contents) => {
if (contents.getType() === 'window') {
const window = BrowserWindow.fromWebContents(contents);
setupWindowEvents(window);
}
});
}
function setupWindowEvents(window) {
window.on('maximize', () => {
window.webContents.send('window:maximized');
});
window.on('unmaximize', () => {
window.webContents.send('window:unmaximized');
});
window.on('minimize', () => {
window.webContents.send('window:minimized');
});
window.on('restore', () => {
window.webContents.send('window:restored');
});
window.on('focus', () => {
window.webContents.send('window:focused');
});
window.on('blur', () => {
window.webContents.send('window:blurred');
});
// 文件拖放事件
window.webContents.on('will-navigate', (event, url) => {
window.webContents.send('window:will-navigate', { url });
});
}
// 数据变化事件广播
function setupDataChangeEvents() {
// 模拟数据变化监控
let dataVersion = 0;
setInterval(() => {
dataVersion++;
const changeEvent = {
version: dataVersion,
timestamp: new Date().toISOString(),
changes: [
{ type: 'add', item: `Item ${dataVersion}` },
{ type: 'update', item: `Item ${dataVersion - 1}` }
]
};
broadcastMessage('data:changed', changeEvent);
}, 10000); // 每10秒发送一次数据变化事件
}
// 广播消息到所有窗口
function broadcastMessage(channel, data) {
console.log(`广播消息: ${channel}`, data);
BrowserWindow.getAllWindows().forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send(channel, data);
}
});
}
// 发送消息到特定窗口
function sendMessageToWindow(window, channel, data) {
if (!window.isDestroyed()) {
window.webContents.send(channel, data);
}
}
// 初始化事件系统
function initializeEventSystem() {
setupSystemEventListeners();
setupAppEventBroadcasters();
setupDataChangeEvents();
console.log('事件系统初始化完成');
}
// 应用启动时初始化事件系统
app.whenReady().then(() => {
initializeEventSystem();
});
module.exports = {
broadcastMessage,
sendMessageToWindow
};
```
---
02.渲染进程事件管理
a.监听器生命周期管理
渲染进程中的事件监听需要合理管理,避免内存泄漏。建议在组件卸载时移除事件监听器,使用WeakMap或WeakRef来管理监听器引用。
---
```javascript
// renderer.js - 事件监听管理
class EventManager {
constructor() {
this.listeners = new Map();
this.setupEventListeners();
}
setupEventListeners() {
// 系统事件监听
this.addListener('system:theme-changed', this.handleThemeChange.bind(this));
this.addListener('system:network-status-changed', this.handleNetworkStatusChange.bind(this));
this.addListener('system:screen-locked', this.handleScreenLock.bind(this));
this.addListener('system:screen-unlocked', this.handleScreenUnlock.bind(this));
// 应用事件监听
this.addListener('app:will-quit', this.handleAppQuit.bind(this));
this.addListener('app:all-windows-closed', this.handleAllWindowsClosed.bind(this));
// 窗口事件监听
this.addListener('window:maximized', this.handleWindowMaximized.bind(this));
this.addListener('window:unmaximized', this.handleWindowUnmaximized.bind(this));
this.addListener('window:minimized', this.handleWindowMinimized.bind(this));
this.addListener('window:restored', this.handleWindowRestored.bind(this));
// 数据变化事件监听
this.addListener('data:changed', this.handleDataChange.bind(this));
}
addListener(channel, callback) {
if (!this.listeners.has(channel)) {
this.listeners.set(channel, new Set());
}
const listenerSet = this.listeners.get(channel);
listenerSet.add(callback);
// 使用electronAPI添加监听器
window.electronAPI.events.on(channel, callback);
}
removeListener(channel, callback) {
const listenerSet = this.listeners.get(channel);
if (listenerSet) {
listenerSet.delete(callback);
if (listenerSet.size === 0) {
this.listeners.delete(channel);
}
}
window.electronAPI.events.off(channel, callback);
}
removeAllListeners() {
for (const [channel, listenerSet] of this.listeners) {
for (const callback of listenerSet) {
window.electronAPI.events.off(channel, callback);
}
}
this.listeners.clear();
}
// 事件处理器
handleThemeChange(event, themeData) {
console.log('主题变化:', themeData);
document.body.classList.toggle('dark-theme', themeData.shouldUseDarkColors);
// 通知其他组件
document.dispatchEvent(new CustomEvent('theme-changed', {
detail: themeData
}));
}
handleNetworkStatusChange(event, networkData) {
console.log('网络状态变化:', networkData);
const statusIndicator = document.getElementById('network-status');
if (statusIndicator) {
statusIndicator.className = networkData.online ? 'online' : 'offline';
statusIndicator.textContent = networkData.online ? '在线' : '离线';
}
}
handleScreenLock() {
console.log('屏幕已锁定');
// 清理敏感数据
this.clearSensitiveData();
}
handleScreenUnlock() {
console.log('屏幕已解锁');
// 重新验证用户身份
this.revalidateUser();
}
handleAppQuit() {
console.log('应用即将退出');
// 保存应用状态
this.saveAppState();
}
handleAllWindowsClosed() {
console.log('所有窗口已关闭');
// 清理资源
this.cleanup();
}
handleWindowMaximized() {
console.log('窗口已最大化');
document.body.classList.add('maximized');
}
handleWindowUnmaximized() {
console.log('窗口已取消最大化');
document.body.classList.remove('maximized');
}
handleWindowMinimized() {
console.log('窗口已最小化');
// 暂停动画和定时器
this.pauseBackgroundTasks();
}
handleWindowRestored() {
console.log('窗口已恢复');
// 恢复动画和定时器
this.resumeBackgroundTasks();
}
handleDataChange(event, changeData) {
console.log('数据发生变化:', changeData);
// 更新UI
this.updateUIWithDataChange(changeData);
// 显示通知
if (changeData.changes.length > 0) {
this.showDataChangeNotification(changeData);
}
}
// 辅助方法
clearSensitiveData() {
// 清理敏感数据
localStorage.removeItem('user-token');
sessionStorage.clear();
}
revalidateUser() {
// 重新验证用户身份
// 这里可以显示密码输入对话框
}
saveAppState() {
const appState = {
windowState: {
maximized: document.body.classList.contains('maximized'),
scrollPosition: window.scrollY
},
lastActivity: new Date().toISOString()
};
localStorage.setItem('app-state', JSON.stringify(appState));
}
cleanup() {
this.removeAllListeners();
this.clearSensitiveData();
}
pauseBackgroundTasks() {
// 暂停后台任务
if (this.dataRefreshInterval) {
clearInterval(this.dataRefreshInterval);
}
}
resumeBackgroundTasks() {
// 恢复后台任务
this.startDataRefresh();
}
updateUIWithDataChange(changeData) {
const dataContainer = document.getElementById('data-container');
if (dataContainer) {
changeData.changes.forEach(change => {
const item = document.createElement('div');
item.className = `data-item ${change.type}`;
item.textContent = `${change.type}: ${change.item}`;
dataContainer.appendChild(item);
});
}
}
showDataChangeNotification(changeData) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = `数据已更新 (${changeData.version})`;
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
}, 3000);
}
startDataRefresh() {
this.dataRefreshInterval = setInterval(() => {
// 定期刷新数据
}, 30000);
}
}
// 创建全局事件管理器实例
const eventManager = new EventManager();
// 页面卸载时清理事件监听器
window.addEventListener('beforeunload', () => {
eventManager.cleanup();
});
// 导出事件管理器供其他模块使用
window.eventManager = eventManager;
```
---
6 打包发布
6.1 打包工具选择
01.electron-builder
a.配置与使用
Electron应用的打包发布是将开发完成的应用转换为可分发的安装包的过程。选择合适的打包工具对于应用的性能、安全性和用户体验至关重要。目前主流的Electron打包工具包括electron-builder、electron-forge和electron-packager。
electron-builder是目前最流行的打包解决方案,它提供了完整的打包、代码签名和自动更新功能。electron-builder支持多平台打包,可以生成Windows的.exe和.msi安装包、macOS的.dmg和.app包,以及Linux的.deb、.rpm、.AppImage等多种格式。
---
```json
// package.json中的electron-builder配置
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "A cross-platform Electron application",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "react-scripts build",
"dist": "npm run build && electron-builder",
"dist:win": "npm run build && electron-builder --win",
"dist:mac": "npm run build && electron-builder --mac",
"dist:linux": "npm run build && electron-builder --linux",
"publish": "electron-builder --publish onTagOrDraft"
},
"build": {
"appId": "com.mycompany.myapp",
"productName": "My Electron App",
"directories": {
"output": "dist-electron",
"buildResources": "build"
},
"files": [
"build/**/*",
"main.js",
"preload.js",
"package.json"
],
"extraResources": [
{
"from": "assets",
"to": "assets"
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "build/icon.ico",
"requestedExecutionLevel": "asInvoker"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "build/icon.icns",
"category": "public.app-category.productivity",
"hardenedRuntime": true
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
}
],
"icon": "build/icon.png",
"category": "Office"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/installer-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico",
"installerHeaderIcon": "build/installer-header-icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "My Electron App"
},
"dmg": {
"title": "${productName} ${version}",
"icon": "build/volume-icon.icns",
"background": "build/dmg-background.png",
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
}
},
"devDependencies": {
"electron": "^25.0.0",
"electron-builder": "^24.0.0"
}
}
```
---
02.electron-forge
a.配置与使用
electron-forge是官方推荐的开发工具链,它集成了项目初始化、开发、测试和打包的完整流程。electron-forge支持多种模板和插件,可以根据项目需求定制打包配置。
---
```javascript
// forge.config.js
module.exports = {
packagerConfig: {
name: 'My Electron App',
executableName: 'my-electron-app',
icon: 'build/icon',
extraResource: [
'assets'
],
appBundleId: 'com.mycompany.myapp',
appCategoryType: 'public.app-category.productivity',
osxSign: {
identity: 'Developer ID Application: Your Name (TEAM_ID)',
'hardened-runtime': true,
'gatekeeper-assess': false,
entitlements: 'build/entitlements.mac.plist',
'entitlements-inherit': 'build/entitlements.mac.plist'
}
},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
name: 'my_electron_app'
}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin']
},
{
name: '@electron-forge/maker-deb',
config: {
options: {
maintainer: 'Your Name',
homepage: 'https://yourwebsite.com'
}
}
},
{
name: '@electron-forge/maker-rpm',
config: {
options: {
maintainer: 'Your Name',
homepage: 'https://yourwebsite.com'
}
}
}
],
publishers: [
{
name: '@electron-forge/publisher-github',
config: {
repository: {
owner: 'your-username',
name: 'your-repository'
}
}
}
]
};
```
---
03.electron-packager
a.配置与使用
electron-packager是一个轻量级的打包工具,专注于将Electron应用打包为可执行文件。它不包含安装程序生成功能,但适合需要快速打包和简单分发的场景。
---
```javascript
// packager.js
const packager = require('electron-packager');
async function packageApp() {
const options = {
dir: '.', // 项目目录
out: 'packaged-apps', // 输出目录
name: 'MyElectronApp', // 应用名称
platform: ['win32', 'darwin', 'linux'], // 目标平台
arch: ['x64', 'ia32'], // 目标架构
electronVersion: '25.0.0', // Electron版本
icon: 'build/icon', // 应用图标
overwrite: true, // 覆盖现有输出
prune: true, // 删除devDependencies
ignore: [
'/build($|/)',
'/dist($|/)',
'/node_modules/electron($|/)',
'/.git($|/)',
'/src($|/)'
]
};
try {
const appPaths = await packager(options);
console.log('应用打包完成:', appPaths);
} catch (error) {
console.error('打包失败:', error);
}
}
packageApp();
```
---
6.2 多平台构建
01.Windows平台构建
a.NSIS与自定义脚本
多平台构建是Electron应用的重要优势,一次开发可以在Windows、macOS和Linux上运行。然而,不同平台的构建环境配置和打包过程存在差异,需要针对每个平台进行特定的配置。
Windows平台构建需要在Windows操作系统上进行,或者使用跨平台构建服务如GitHub Actions。Windows打包可以生成NSIS安装程序、便携版应用和MSI安装包。
---
```json
// Windows特定配置
{
"build": {
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
},
{
"target": "msi",
"arch": ["x64"]
}
],
"icon": "build/icon.ico",
"requestedExecutionLevel": "asInvoker",
"publisherName": "My Company",
"verifyUpdateCodeSignature": false
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"allowElevation": true,
"installerIcon": "build/installer-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico",
"installerHeaderIcon": "build/installer-header-icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "My Electron App",
"include": "build/installer.nsh",
"script": "build/installer.nsi"
}
}
}
```
```nsis
; installer.nsh - NSIS自定义安装脚本
!macro customInstall
; 创建注册表项
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_UNINST_KEY}" "Publisher" "My Company"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_UNINST_KEY}" "HelpLink" "https://support.mycompany.com"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_UNINST_KEY}" "URLInfoAbout" "https://mycompany.com"
; 安装Visual C++ Redistributable
ExecWait '"$INSTDIR\vcredist_x64.exe" /quiet'
!macroend
!macro customUnInstall
; 删除注册表项
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_UNINST_KEY}"
!macroend
```
---
02.macOS平台构建
a.DMG与权限配置
macOS平台构建需要macOS系统和Xcode Command Line Tools。macOS应用需要代码签名才能正常分发,同时可以选择进行公证以获得更好的用户体验。
---
```json
// macOS特定配置
{
"build": {
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "build/icon.icns",
"category": "public.app-category.productivity",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"provisioningProfile": "build/embedded.provisionprofile",
"extendInfo": {
"NSCameraUsageDescription": "This app needs access to camera for video calls",
"NSMicrophoneUsageDescription": "This app needs access to microphone for voice calls",
"NSDocumentsFolderUsageDescription": "This app needs access to documents folder",
"LSMinimumSystemVersion": "10.13.0"
}
},
"dmg": {
"title": "${productName} ${version}",
"icon": "build/volume-icon.icns",
"background": "build/dmg-background.png",
"iconSize": 100,
"contents": [
{
"x": 380,
"y": 280,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"type": "file"
}
],
"window": {
"width": 540,
"height": 380
}
}
}
}
```
```xml
<!-- entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
</dict>
</plist>
```
---
03.Linux平台构建
a.AppImage、DEB与RPM配置
Linux平台构建相对简单,不需要代码签名,但需要针对不同的Linux发行版生成合适的包格式。Linux支持AppImage、DEB、RPM、FreeBSD等多种格式。
---
```json
// Linux特定配置
{
"build": {
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
},
{
"target": "snap",
"arch": ["x64"]
}
],
"icon": "build/icon.png",
"category": "Office",
"desktop": {
"Name": "My Electron App",
"Comment": "A cross-platform application",
"Keywords": "electron;desktop;productivity;"
}
},
"deb": {
"depends": [
"libgtk-3-0",
"libnotify4",
"libnss3",
"libxss1",
"libxtst6",
"xdg-utils",
"libatspi2.0-0",
"libdrm2",
"libxcomposite1",
"libxdamage1",
"libxrandr2",
"libgbm1",
"libxkbcommon0",
"libasound2"
]
},
"rpm": {
"depends": [
"gtk3",
"libnotify",
"nss",
"libXScrnSaver",
"libXtst",
"xdg-utils",
"at-spi2-core",
"libdrm",
"libXcomposite",
"libXcursor",
"libXdamage",
"libXrandr",
"gbm",
"libxkbcommon",
"alsa-lib"
]
},
"snap": {
"plugs": [
"default",
"desktop",
"desktop-legacy",
"home",
"x11",
"unity7",
"browser-support",
"network",
"gsettings",
"audio-playback",
"pulseaudio",
"opengl"
]
}
}
}
```
---
04.CI/CD自动化构建
a.使用GitHub Actions
CI/CD自动化构建可以大大简化多平台构建的复杂性。使用GitHub Actions、GitLab CI或Jenkins等工具,可以配置自动化的构建流水线,支持跨平台并行构建。
---
```yaml
# .github/workflows/build.yml
name: Build and Release
on:
push:
tags:
- 'v*'
pull_request:
branches: [ main ]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Build Electron app
run: npm run dist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}-build
path: dist-electron/*
release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Download all artifacts
uses: actions/download-artifact@v3
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
**/*.exe
**/*.msi
**/*.dmg
**/*.zip
**/*.deb
**/*.rpm
**/*.AppImage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
---
6.3 代码签名和公证
01.Windows代码签名
a.证书与signtool配置
代码签名是Electron应用分发的重要安全措施,它可以验证应用的来源和完整性,防止恶意篡改。不同平台的代码签名流程和要求各不相同,需要专门的证书和工具。
Windows代码签名需要购买代码签名证书,主要提供商包括DigiCert、Sectigo等。签名后的应用可以在Windows上正常安装运行,避免安全警告。
---
```javascript
// Windows代码签名配置
const { exec } = require('child_process');
const path = require('path');
async function signWindows(filePath, certificatePath, certificatePassword) {
return new Promise((resolve, reject) => {
const signtool = path.join(
'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.19041.0\\x64\\signtool.exe'
);
const command = `"${signtool}" sign /f "${certificatePath}" /p "${certificatePassword}" /tr http://timestamp.digicert.com /td sha256 /fd sha256 "${filePath}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('签名失败:', error);
reject(error);
} else {
console.log('签名成功:', stdout);
resolve(stdout);
}
});
});
}
// 使用electron-builder的自动签名功能
// package.json中的签名配置
{
"build": {
"win": {
"certificateFile": "build/certificate.p12",
"certificatePassword": "your-password",
"certificateSha1": "your-certificate-sha1",
"publisherName": "Your Publisher Name"
}
}
}
```
---
02.macOS代码签名
a.证书与codesign配置
macOS代码签名需要Apple Developer账户和相应的证书和配置文件。macOS支持开发证书、分发证书等多个类型的证书,每种证书有不同的用途和限制。
---
```bash
# macOS代码签名脚本
#!/bin/bash
APP_PATH="$1"
IDENTITY="$2"
ENTITLEMENTS="$3"
# 检查参数
if [ -z "$APP_PATH" ] || [ -z "$IDENTITY" ] || [ -z "$ENTITLEMENTS" ]; then
echo "使用方法: $0 <app-path> <identity> <entitlements>"
exit 1
fi
# 签名应用
codesign --force --deep --sign "$IDENTITY" --entitlements "$ENTITLEMENTS" "$APP_PATH"
# 验证签名
codesign --verify --verbose "$APP_PATH"
# 检查签名
spctl --assess --verbose --type execute "$APP_PATH"
echo "代码签名完成"
```
```json
// electron-builder中的macOS签名配置
{
"build": {
"mac": {
"identity": "Developer ID Application: Your Name (TEAM_ID)",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"provisioningProfile": "build/embedded.provisionprofile"
}
}
}
```
---
03.macOS公证
a.altool与stapler配置
macOS公证是Apple要求的安全措施,用于验证应用不包含恶意代码。公证后的应用在macOS上不会显示"无法验证开发者"的警告。
---
```javascript
// macOS公证脚本
const { exec } = require('child_process');
const path = require('path');
async function notarizeMac(appPath, appleId, applePassword, teamId) {
return new Promise((resolve, reject) => {
// 上传应用进行公证
const command = `xcrun altool --notarize-app --primary-bundle-id "com.mycompany.myapp" --username "${appleId}" --password "${applePassword}" --asc-provider "${teamId}" --file "${appPath}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('公证上传失败:', error);
reject(error);
} else {
console.log('公证上传成功:', stdout);
// 提取请求UUID
const uuidMatch = stdout.match(/RequestUUID = (.+)/);
if (uuidMatch) {
const requestUuid = uuidMatch;
checkNotarizationStatus(requestUuid, appleId, applePassword)
.then(resolve)
.catch(reject);
} else {
reject(new Error('无法获取公证请求UUID'));
}
}
});
});
}
async function checkNotarizationStatus(requestUuid, appleId, applePassword) {
return new Promise((resolve, reject) => {
const checkCommand = `xcrun altool --notarization-info "${requestUuid}" --username "${appleId}" --password "${applePassword}"`;
const checkStatus = () => {
exec(checkCommand, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
if (stdout.includes('Status: success')) {
resolve('公证成功');
} else if (stdout.includes('Status: invalid')) {
reject(new Error('公证失败'));
} else {
console.log('公证进行中,等待检查...');
setTimeout(checkStatus, 30000); // 30秒后再次检查
}
});
};
checkStatus();
});
}
// stapling公证到应用
async function stapleNotarization(appPath) {
return new Promise((resolve, reject) => {
const command = `xcrun stapler staple "${appPath}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('stapling失败:', error);
reject(error);
} else {
console.log('stapling成功:', stdout);
resolve(stdout);
}
});
});
}
```
---
04.自动化签名与公证
a.CI/CD集成
自动化签名和公证流程可以通过CI/CD工具实现,大大简化了发布流程。GitHub Actions提供了专门的市场place actions来处理代码签名和公证。
---
```yaml
# GitHub Actions中的macOS签名和公证
- name: Import Code-Signing Certificates
uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.DEVELOPER_CERTIFICATE }}
p12-password: ${{ secrets.DEVELOPER_CERTIFICATE_PASSWORD }}
- name: Build and Sign macOS App
run: |
npm run build
npm run dist
- name: Notarize macOS App
uses: Apple-Actions/notarize-release@v1
with:
app-path: dist-electron/My Electron App.app
apple-id: ${{ secrets.APPLE_ID }}
apple-id-password: ${{ secrets.APPLE_ID_PASSWORD }}
team-id: ${{ secrets.APPLE_TEAM_ID }}
```
---
6.4 自动更新机制
01.主进程更新逻辑
a.使用electron-updater
自动更新机制是Electron应用的重要功能,它允许应用自动检查和安装更新,无需用户手动下载和安装。electron-updater是最流行的自动更新解决方案,与electron-builder完美集成。
配置自动更新需要在服务器上部署更新信息文件和应用安装包。electron-updater会定期检查更新信息,当发现新版本时自动下载并提示用户安装。
---
```javascript
// main.js - 自动更新配置
const { app, dialog, BrowserWindow } = require('electron');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
// 配置日志
log.transports.file.level = 'info';
autoUpdater.logger = log;
// 配置更新服务器
autoUpdater.setFeedURL({
provider: 'github',
owner: 'your-username',
repo: 'your-repo',
private: false
});
// 自动更新事件处理
autoUpdater.on('checking-for-update', () => {
log.info('检查更新中...');
sendUpdateMessageToWindow('checking-for-update');
});
autoUpdater.on('update-available', (info) => {
log.info('发现新版本:', info.version);
sendUpdateMessageToWindow('update-available', info);
});
autoUpdater.on('update-not-available', (info) => {
log.info('当前已是最新版本');
sendUpdateMessageToWindow('update-not-available', info);
});
autoUpdater.on('error', (error) => {
log.error('自动更新错误:', error);
sendUpdateMessageToWindow('update-error', error.message);
});
autoUpdater.on('download-progress', (progressObj) => {
let log_message = "下载速度: " + progressObj.bytesPerSecond;
log_message = log_message + ' - 已下载: ' + progressObj.percent + '%';
log_message = log_message + ' (' + progressObj.transferred + "/" + progressObj.total + ')';
log.info(log_message);
sendUpdateMessageToWindow('download-progress', {
percent: progressObj.percent,
transferred: progressObj.transferred,
total: progressObj.total
});
});
autoUpdater.on('update-downloaded', (info) => {
log.info('更新下载完成');
sendUpdateMessageToWindow('update-downloaded', info);
// 询问用户是否立即安装
dialog.showMessageBox({
type: 'info',
title: '应用更新',
message: '新版本已下载完成',
detail: '应用将重启以安装更新',
buttons: ['立即重启', '稍后重启']
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});
// 发送更新消息到渲染进程
function sendUpdateMessageToWindow(type, data) {
const windows = BrowserWindow.getAllWindows();
windows.forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send('update-message', { type, data });
}
});
}
// 检查更新
function checkForUpdates() {
autoUpdater.checkForUpdatesAndNotify();
}
// 应用启动时检查更新
app.whenReady().then(() => {
setTimeout(checkForUpdates, 5000); // 延迟5秒检查更新
});
```
---
02.渲染进程更新UI
a.更新状态与进度展示
渲染进程中的更新UI需要显示更新状态、下载进度和安装提示。良好的用户体验是提供清晰的更新信息和适当的控制选项。
---
```javascript
// renderer.js - 更新UI管理
class UpdateManager {
constructor() {
this.updateInfo = null;
this.setupEventListeners();
}
setupEventListeners() {
// 监听更新消息
if (window.electronAPI && window.electronAPI.events) {
window.electronAPI.events.on('update-message', (event, data) => {
this.handleUpdateMessage(data);
});
}
// 手动检查更新按钮
const checkUpdateBtn = document.getElementById('check-update-btn');
if (checkUpdateBtn) {
checkUpdateBtn.addEventListener('click', () => {
this.checkForUpdates();
});
}
}
handleUpdateMessage({ type, data }) {
switch (type) {
case 'checking-for-update':
this.showCheckingForUpdate();
break;
case 'update-available':
this.showUpdateAvailable(data);
break;
case 'update-not-available':
this.showUpToDate();
break;
case 'update-error':
this.showUpdateError(data);
break;
case 'download-progress':
this.showDownloadProgress(data);
break;
case 'update-downloaded':
this.showUpdateDownloaded(data);
break;
}
}
showCheckingForUpdate() {
this.updateStatus('正在检查更新...');
this.hideUpdateButton();
}
showUpdateAvailable(updateInfo) {
this.updateInfo = updateInfo;
this.updateStatus('发现新版本 ' + updateInfo.version);
this.showUpdateButton(`下载更新 (${this.formatSize(updateInfo.size)})`, () => {
this.downloadUpdate();
});
}
showUpToDate() {
this.updateStatus('当前已是最新版本');
this.hideUpdateButton();
}
showUpdateError(errorMessage) {
this.updateStatus('更新检查失败: ' + errorMessage);
this.showUpdateButton('重试', () => {
this.checkForUpdates();
});
}
showDownloadProgress(progress) {
this.updateStatus(`下载中... ${Math.round(progress.percent)}%`);
this.updateProgressBar(progress.percent);
}
showUpdateDownloaded(updateInfo) {
this.updateStatus('更新下载完成,准备安装');
this.showInstallButton();
}
showUpdateButton(text, onClick) {
let button = document.getElementById('update-button');
if (!button) {
button = document.createElement('button');
button.id = 'update-button';
button.className = 'update-button';
const statusContainer = document.getElementById('update-status');
if (statusContainer) {
statusContainer.appendChild(button);
}
}
button.textContent = text;
button.onclick = onClick;
button.style.display = 'block';
}
hideUpdateButton() {
const button = document.getElementById('update-button');
if (button) {
button.style.display = 'none';
}
}
showInstallButton() {
this.showUpdateButton('立即安装', () => {
this.installUpdate();
});
}
updateProgressBar(percent) {
let progressBar = document.getElementById('update-progress-bar');
if (!progressBar) {
progressBar = document.createElement('div');
progressBar.id = 'update-progress-bar';
progressBar.className = 'progress-bar';
const statusContainer = document.getElementById('update-status');
if (statusContainer) {
statusContainer.appendChild(progressBar);
}
}
progressBar.style.width = `${percent}%`;
progressBar.style.display = 'block';
}
updateStatus(message) {
const statusElement = document.getElementById('update-status-text');
if (statusElement) {
statusElement.textContent = message;
}
}
checkForUpdates() {
if (window.electronAPI && window.electronAPI.updater) {
window.electronAPI.updater.checkForUpdates();
}
}
downloadUpdate() {
if (window.electronAPI && window.electronAPI.updater) {
window.electronAPI.updater.downloadUpdate();
}
}
installUpdate() {
if (window.electronAPI && window.electronAPI.updater) {
window.electronAPI.updater.installUpdate();
}
}
formatSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// 创建更新管理器实例
const updateManager = new UpdateManager();
// 导出供其他模块使用
window.updateManager = updateManager;
```
---
03.更新服务器配置
a.使用GitHub Releases
自动更新的服务器配置相对简单,只需在GitHub Releases中发布带有特定标签的版本,electron-updater会自动检查更新。
---
```json
// package.json中的发布配置
{
"scripts": {
"release": "electron-builder --publish onTagOrDraft"
},
"build": {
"publish": {
"provider": "github",
"owner": "your-username",
"repo": "your-repo"
}
}
}
```
---
7 最佳实践
7.1 安全最佳实践
01.上下文隔离与窗口配置
a.启用安全特性
Electron应用的安全性是开发过程中必须重点关注的问题。由于Electron结合了Web技术和原生应用能力,安全风险既包括传统Web应用的安全问题,也包括桌面应用的特有安全问题。
上下文隔离(Context Isolation)是Electron推荐的安全配置,它将预加载脚本和渲染进程的JavaScript上下文分离,防止恶意代码访问Node.js API。启用上下文隔离可以有效防止XSS攻击和代码注入。
---
```javascript
// main.js - 安全的窗口配置
const { app, BrowserWindow } = require('electron');
function createSecureWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// 启用上下文隔离
contextIsolation: true,
// 禁用Node.js集成
nodeIntegration: false,
// 禁用remote模块
enableRemoteModule: false,
// 启用预加载脚本
preload: path.join(__dirname, 'preload.js'),
// 启用Web安全
webSecurity: true,
// 禁用Blink特性API
enableBlinkFeatures: '',
// 限制事件能力
experimentalFeatures: false,
// 启用沙箱模式
sandbox: true,
// 内容安全策略
additionalArguments: ['--disable-site-isolation-trials']
}
});
return mainWindow;
}
// 内容安全策略配置
const csp = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; font-src 'self';";
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
if (parsedUrl.origin !== 'http://localhost:3000' && parsedUrl.origin !== 'https://yourapp.com') {
event.preventDefault();
}
});
contents.on('new-window', (event, navigationUrl) => {
event.preventDefault();
require('electron').shell.openExternal(navigationUrl);
});
});
```
---
02.预加载脚本安全设计
a.输入验证与API限制
预加载脚本的安全设计至关重要。预加载脚本应该只暴露必要的API给渲染进程,并且对所有输入进行严格的验证和清理。
---
```javascript
// preload.js - 安全的预加载脚本
const { contextBridge, ipcRenderer } = require('electron');
const fs = require('fs');
const path = require('path');
// 输入验证函数
function validateFilePath(filePath) {
if (typeof filePath !== 'string') {
throw new Error('文件路径必须是字符串');
}
// 防止路径遍历攻击
const normalizedPath = path.normalize(filePath);
if (normalizedPath.includes('..')) {
throw new Error('不允许路径遍历');
}
// 只允许特定目录
const allowedDirectories = [
path.join(require('os').homedir(), 'Documents'),
path.join(require('os').homedir(), 'Desktop'),
path.join(require('os').homedir(), 'Downloads')
];
const isAllowed = allowedDirectories.some(dir => normalizedPath.startsWith(dir));
if (!isAllowed) {
throw new Error('不允许访问该目录');
}
return normalizedPath;
}
// 安全的文件操作API
const fileSystemAPI = {
readFile: async (filePath) => {
try {
const safePath = validateFilePath(filePath);
const content = await fs.promises.readFile(safePath, 'utf-8');
return { success: true, data: content };
} catch (error) {
return { success: false, error: error.message };
}
},
writeFile: async (filePath, content) => {
try {
const safePath = validateFilePath(filePath);
await fs.promises.writeFile(safePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
},
// 限制文件大小读取
readSmallFile: async (filePath, maxSize = 1024 * 1024) => { // 1MB默认限制
try {
const safePath = validateFilePath(filePath);
const stats = await fs.promises.stat(safePath);
if (stats.size > maxSize) {
throw new Error(`文件过大,最大允许 ${maxSize} 字节`);
}
const content = await fs.promises.readFile(safePath, 'utf-8');
return { success: true, data: content };
} catch (error) {
return { success: false, error: error.message };
}
}
};
// 安全的系统操作API
const systemAPI = {
showNotification: async (title, body) => {
// 验证输入参数
if (typeof title !== 'string' || typeof body !== 'string') {
throw new Error('标题和内容必须是字符串');
}
if (title.length > 100 || body.length > 500) {
throw new Error('标题或内容过长');
}
return await ipcRenderer.invoke('show-notification', { title, body });
},
openExternal: async (url) => {
// 验证URL
try {
const parsedUrl = new URL(url);
const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
if (!allowedProtocols.includes(parsedUrl.protocol)) {
throw new Error('不允许的协议');
}
return await ipcRenderer.invoke('open-external', url);
} catch (error) {
throw new Error('无效的URL格式');
}
}
};
// 事件管理API - 只允许特定事件通道
const eventAPI = {
on: (channel, callback) => {
const allowedChannels = ['app-update', 'notification', 'system-message'];
if (allowedChannels.includes(channel) && typeof callback === 'function') {
ipcRenderer.on(channel, callback);
}
},
off: (channel, callback) => {
if (typeof callback === 'function') {
ipcRenderer.removeListener(channel, callback);
}
},
send: (channel, data) => {
const allowedChannels = ['user-action', 'log-message', 'error-report'];
if (allowedChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
}
};
// 通过contextBridge暴露安全的API
contextBridge.exposeInMainWorld('electronAPI', {
fs: fileSystemAPI,
system: systemAPI,
events: eventAPI
});
```
---
03.主进程安全配置
a.权限管理与IPC验证
主进程安全配置同样重要,需要验证所有来自渲染进程的请求,并实施适当的权限控制。
---
```javascript
// main.js - 主进程安全配置
const { ipcMain, dialog, app, BrowserWindow } = require('electron');
const fs = require('fs');
const path = require('path');
// 权限管理
const permissions = {
fileRead: false,
fileWrite: false,
networkAccess: false,
cameraAccess: false,
microphoneAccess: false
};
// IPC处理器安全包装器
function secureIpcHandler(channel, handler) {
return async (event, ...args) => {
try {
// 验证来源窗口
const webContents = event.sender;
const window = BrowserWindow.fromWebContents(webContents);
if (!window) {
throw new Error('无效的来源窗口');
}
// 验证参数
if (!validateIpcArgs(args)) {
throw new Error('无效的IPC参数');
}
// 执行处理器
return await handler(event, ...args);
} catch (error) {
console.error(`IPC错误 [${channel}]:`, error);
return {
success: false,
error: error.message
};
}
};
}
// 参数验证函数
function validateIpcArgs(args) {
// 防止过大的数据传输
const totalSize = JSON.stringify(args).length;
if (totalSize > 1024 * 1024) { // 1MB限制
return false;
}
// 防止原型污染
for (const arg of args) {
if (arg && typeof arg === 'object') {
if (arg.__proto__ || arg.constructor || arg.prototype) {
return false;
}
}
}
return true;
}
// 安全的文件读取处理器
ipcMain.handle('secure-read-file', secureIpcHandler('secure-read-file', async (event, filePath) => {
// 验证权限
if (!permissions.fileRead) {
throw new Error('没有文件读取权限');
}
// 安全路径验证
const safePath = validateFilePath(filePath);
// 检查文件大小
const stats = await fs.promises.stat(safePath);
if (stats.size > 10 * 1024 * 1024) { // 10MB限制
throw new Error('文件过大');
}
// 读取文件
const content = await fs.promises.readFile(safePath, 'utf-8');
return { success: true, data: content };
}));
// 网络请求控制
const allowedDomains = [
'api.example.com',
'cdn.example.com',
'auth.example.com'
];
app.on('web-contents-created', (event, contents) => {
contents.session.webRequest.onBeforeRequest((details, callback) => {
const url = new URL(details.url);
if (!allowedDomains.includes(url.hostname)) {
callback({ cancel: true });
} else {
callback({});
}
});
});
// 权限请求处理
async function requestPermission(permission, window) {
const result = await dialog.showMessageBox(window, {
type: 'question',
title: '权限请求',
message: `应用请求 ${permission} 权限`,
detail: '是否允许此权限?',
buttons: ['允许', '拒绝'],
defaultId: 1,
cancelId: 1
});
return result.response === 0;
}
// 动态权限管理
ipcMain.handle('request-permission', secureIpcHandler('request-permission', async (event, permission) => {
const window = BrowserWindow.fromWebContents(event.sender);
const granted = await requestPermission(permission, window);
permissions[permission] = granted;
return { success: true, granted };
}));
```
---
7.2 性能优化实践
01.内存管理
a.内存泄漏监控与避免
Electron应用的性能优化需要从内存管理、CPU使用、启动速度和响应时间等多个维度进行考虑。良好的性能优化可以显著提升用户体验,降低系统资源消耗。
内存泄漏是Electron应用常见的性能问题。需要定期清理不再使用的对象、移除事件监听器、避免循环引用,并监控内存使用情况。
---
```javascript
// 内存监控和管理
const { app } = require('electron');
class MemoryManager {
constructor() {
this.objects = new WeakMap();
this.timers = new Set();
this.listeners = new Map();
}
// 注册对象进行监控
registerObject(object, metadata) {
this.objects.set(object, metadata);
}
// 管理定时器
addTimer(timerId) {
this.timers.add(timerId);
}
removeTimer(timerId) {
this.timers.delete(timerId);
}
// 管理事件监听器
addListener(target, event, listener) {
if (!this.listeners.has(target)) {
this.listeners.set(target, new Set());
}
this.listeners.get(target).add({ event, listener });
target.addEventListener(event, listener);
}
removeListener(target, event, listener) {
if (this.listeners.has(target)) {
const listenerSet = this.listeners.get(target);
listenerSet.forEach(item => {
if (item.event === event && item.listener === listener) {
listenerSet.delete(item);
}
});
if (listenerSet.size === 0) {
this.listeners.delete(target);
}
}
target.removeEventListener(event, listener);
}
// 清理所有资源
cleanup() {
// 清理定时器
this.timers.forEach(timerId => {
clearTimeout(timerId);
clearInterval(timerId);
});
this.timers.clear();
// 清理事件监听器
this.listeners.forEach((listenerSet, target) => {
listenerSet.
好的,这是剩余部分的格式化结果。
---
forEach(({ event, listener }) => {
target.removeEventListener(event, listener);
});
});
this.listeners.clear();
}
// 内存使用报告
getMemoryReport() {
const process = require('process');
const memoryUsage = process.memoryUsage();
return {
heapUsed: memoryUsage.heapUsed,
heapTotal: memoryUsage.heapTotal,
external: memoryUsage.external,
rss: memoryUsage.rss,
timersCount: this.timers.size,
listenersCount: Array.from(this.listeners.values())
.reduce((total, set) => total + set.size, 0)
};
}
// 定期内存检查
startMemoryCheck(interval = 30000) {
this.memoryCheckInterval = setInterval(() => {
const report = this.getMemoryReport();
console.log('内存使用情况:', report);
// 如果内存使用过高,触发清理
if (report.heapUsed > 500 * 1024 * 1024) { // 500MB
console.warn('内存使用过高,触发垃圾回收');
if (global.gc) {
global.gc();
}
}
}, interval);
}
stopMemoryCheck() {
if (this.memoryCheckInterval) {
clearInterval(this.memoryCheckInterval);
this.memoryCheckInterval = null;
}
}
}
const memoryManager = new MemoryManager();
// 应用关闭时清理内存
app.on('before-quit', () => {
memoryManager.cleanup();
});
```
---
02.渲染进程性能优化
a.虚拟滚动与懒加载
渲染进程性能优化包括虚拟滚动、图片懒加载、代码分割和缓存策略。这些优化措施可以显著提升大列表、图片展示和复杂页面的性能。
---
```javascript
// renderer.js - 渲染进程性能优化
class PerformanceOptimizer {
constructor() {
this.imageCache = new Map();
this.loadedImages = new Set();
this.observer = null;
this.setupLazyLoading();
this.setupVirtualScrolling();
}
// 图片懒加载
setupLazyLoading() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px',
threshold: 0.1
});
}
}
loadImage(img) {
const src = img.dataset.src;
if (!src || this.loadedImages.has(src)) return;
// 检查缓存
if (this.imageCache.has(src)) {
img.src = this.imageCache.get(src);
this.loadedImages.add(src);
return;
}
// 加载图片
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
this.imageCache.set(src, src);
this.loadedImages.add(src);
// 限制缓存大小
if (this.imageCache.size > 100) {
const firstKey = this.imageCache.keys().next().value;
this.imageCache.delete(firstKey);
}
};
tempImg.onerror = () => {
img.src = 'placeholder.png';
img.classList.add('error');
};
tempImg.src = src;
}
observeImage(img) {
if (this.observer && img.dataset.src) {
this.observer.observe(img);
}
}
// 虚拟滚动
setupVirtualScrolling() {
this.virtualContainers = new Map();
}
createVirtualList(container, items, itemHeight, renderItem) {
const virtualList = {
container,
items,
itemHeight,
renderItem,
visibleStart: 0,
visibleEnd: 0,
scrollTop: 0
};
this.virtualContainers.set(container, virtualList);
// 设置容器高度
container.style.height = `${items.length * itemHeight}px`;
// 添加滚动监听
container.addEventListener('scroll', this.handleVirtualScroll.bind(this, container));
// 初始渲染
this.renderVirtualList(container);
return virtualList;
}
handleVirtualScroll(container) {
const virtualList = this.virtualContainers.get(container);
if (!virtualList) return;
const { scrollTop } = container;
const containerHeight = container.clientHeight;
const { itemHeight, items } = virtualList;
const visibleStart = Math.floor(scrollTop / itemHeight);
const visibleEnd = Math.ceil((scrollTop + containerHeight) / itemHeight);
if (visibleStart !== virtualList.visibleStart || visibleEnd !== virtualList.visibleEnd) {
virtualList.visibleStart = Math.max(0, visibleStart - 2); // 缓冲区
virtualList.visibleEnd = Math.min(items.length, visibleEnd + 2);
virtualList.scrollTop = scrollTop;
this.renderVirtualList(container);
}
}
renderVirtualList(container) {
const virtualList = this.virtualContainers.get(container);
if (!virtualList) return;
const { items, itemHeight, visibleStart, visibleEnd, renderItem } = virtualList;
// 清理现有元素
const existingItems = container.querySelectorAll('.virtual-item');
existingItems.forEach(item => item.remove());
// 渲染可见项
const fragment = document.createDocumentFragment();
for (let i = visibleStart; i < visibleEnd; i++) {
const itemElement = renderItem(items[i], i);
itemElement.className = 'virtual-item';
itemElement.style.position = 'absolute';
itemElement.style.top = `${i * itemHeight}px`;
itemElement.style.height = `${itemHeight}px`;
fragment.appendChild(itemElement);
}
container.appendChild(fragment);
}
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 节流函数
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 批量DOM更新
batchDOMUpdates(updates) {
return new Promise(resolve => {
requestAnimationFrame(() => {
updates.forEach(update => update());
requestAnimationFrame(resolve);
});
});
}
// 清理资源
cleanup() {
if (this.observer) {
this.observer.disconnect();
}
this.imageCache.clear();
this.loadedImages.clear();
this.virtualContainers.clear();
}
}
// 创建性能优化器实例
const performanceOptimizer = new PerformanceOptimizer();
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
performanceOptimizer.cleanup();
});
```
---
03.启动性能优化
a.延迟加载与预加载
启动性能优化包括延迟加载、代码分割和预加载策略。合理的启动优化可以显著减少应用的冷启动时间。
---
```javascript
// main.js - 启动性能优化
const { app, BrowserWindow } = require('electron');
class StartupOptimizer {
constructor() {
this.startupTime = Date.now();
this.optimizations = [];
}
// 异步创建窗口,避免阻塞启动
async createMainWindow() {
// 预创建窗口但不显示
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
show: false, // 关键:不立即显示
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
// 监听内容加载完成
mainWindow.webContents.once('did-finish-load', () => {
console.log(`页面加载完成,耗时: ${Date.now() - this.startupTime}ms`);
});
// 延迟显示窗口,避免白屏
mainWindow.once('ready-to-show', () => {
setTimeout(() => {
mainWindow.show();
mainWindow.focus();
console.log(`窗口显示完成,总启动时间: ${Date.now() - this.startupTime}ms`);
}, 100);
});
return mainWindow;
}
// 预加载关键资源
async preloadResources() {
const promises = [
this.preloadConfiguration(),
this.preloadThemes(),
this.preloadUserData()
];
await Promise.all(promises);
console.log('资源预加载完成');
}
async preloadConfiguration() {
// 异步加载配置文件
return new Promise((resolve) => {
setTimeout(() => {
// 模拟配置加载
console.log('配置加载完成');
resolve();
}, 50);
});
}
async preloadThemes() {
// 预加载主题资源
return new Promise((resolve) => {
setTimeout(() => {
console.log('主题资源加载完成');
resolve();
}, 30);
});
}
async preloadUserData() {
// 预加载用户数据
return new Promise((resolve) => {
setTimeout(() => {
console.log('用户数据加载完成');
resolve();
}, 20);
});
}
// 延迟加载非关键模块
loadNonCriticalModules() {
setTimeout(() => {
// 延迟加载非关键功能模块
require('./modules/analytics');
require('./modules/crash-reporter');
console.log('非关键模块加载完成');
}, 2000);
}
// 内存预热
warmupMemory() {
// 预分配常用对象,避免运行时分配延迟
const buffer = new ArrayBuffer(1024 * 1024); // 1MB预热
const typedArray = new Uint8Array(buffer);
console.log('内存预热完成');
}
// 启动性能监控
monitorStartupPerformance() {
app.whenReady().then(() => {
console.log(`应用就绪时间: ${Date.now() - this.startupTime}ms`);
});
app.on('window-all-closed', () => {
console.log(`总运行时间: ${Date.now() - this.startupTime}ms`);
});
}
}
const startupOptimizer = new StartupOptimizer();
// 优化后的应用启动流程
app.whenReady().then(async () => {
try {
// 并行执行启动优化
const [mainWindow] = await Promise.all([
startupOptimizer.createMainWindow(),
startupOptimizer.preloadResources()
]);
// 内存预热
startupOptimizer.warmupMemory();
// 延迟加载非关键模块
startupOptimizer.loadNonCriticalModules();
// 启动性能监控
startupOptimizer.monitorStartupPerformance();
// 加载页面
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile('build/index.html');
}
} catch (error) {
console.error('启动优化失败:', error);
}
});
```
---
7.3 架构设计实践
01.模块化架构
a.模块加载器与依赖注入
良好的架构设计是Electron应用成功的基础。合理的架构设计能够提高代码的可维护性、可扩展性和团队协作效率。
模块化架构要求将应用划分为独立的模块,每个模块负责特定的功能域。模块间通过定义良好的接口进行通信,降低耦合度,提高内聚性。
---
```javascript
// main.js - 模块化架构示例
const path = require('path');
const { app, BrowserWindow } = require('electron');
// 模块加载器
class ModuleLoader {
constructor() {
this.modules = new Map();
this.dependencies = new Map();
}
// 注册模块
register(name, moduleClass, dependencies = []) {
this.dependencies.set(name, dependencies);
this.modules.set(name, null); // 延迟实例化
}
// 加载模块
async load(name) {
if (this.modules.get(name)) {
return this.modules.get(name);
}
const dependencies = this.dependencies.get(name) || [];
const dependencyInstances = await Promise.all(
dependencies.map(dep => this.load(dep))
);
const ModuleClass = require(`./modules/${name}`);
const instance = new ModuleClass(...dependencyInstances);
this.modules.set(name, instance);
if (instance.initialize) {
await instance.initialize();
}
return instance;
}
// 获取模块实例
get(name) {
return this.modules.get(name);
}
}
// 应用主控制器
class ApplicationController {
constructor() {
this.moduleLoader = new ModuleLoader();
this.windows = new Map();
this.setupModules();
}
setupModules() {
// 注册模块及其依赖关系
this.moduleLoader.register('config', ConfigModule);
this.moduleLoader.register('logger', LoggerModule);
this.moduleLoader.register('database', DatabaseModule, ['config', 'logger']);
this.moduleLoader.register('auth', AuthModule, ['database', 'logger']);
this.moduleLoader.register('windowManager', WindowManagerModule, ['config', 'logger']);
}
async initialize() {
// 加载核心模块
await this.moduleLoader.load('config');
await this.moduleLoader.load('logger');
await this.moduleLoader.load('windowManager');
// 初始化应用
await this.createMainWindow();
this.setupEventHandlers();
}
async createMainWindow() {
const windowManager = this.moduleLoader.get('windowManager');
const mainWindow = await windowManager.createWindow({
name: 'main',
options: {
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
}
});
this.windows.set('main', mainWindow);
// 加载页面
if (process.env.NODE_ENV === 'development') {
await mainWindow.loadURL('http://localhost:3000');
} else {
await mainWindow.loadFile('build/index.html');
}
return mainWindow;
}
setupEventHandlers() {
const logger = this.moduleLoader.get('logger');
app.on('window-all-closed', () => {
logger.info('所有窗口已关闭');
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('before-quit', async () => {
logger.info('应用即将退出');
await this.cleanup();
});
}
async cleanup() {
// 清理所有模块
for (const [name, module] of this.moduleLoader.modules) {
if (module && module.cleanup) {
try {
await module.cleanup();
} catch (error) {
console.error(`清理模块 ${name} 失败:`, error);
}
}
}
// 清理窗口
this.windows.clear();
}
}
// 模块基类
class BaseModule {
constructor(...dependencies) {
this.dependencies = dependencies;
this.initialized = false;
}
async initialize() {
if (this.initialized) return;
await this.onInitialize();
this.initialized = true;
}
async onInitialize() {
// 子类实现具体初始化逻辑
}
async cleanup() {
await this.onCleanup();
}
async onCleanup() {
// 子类实现具体清理逻辑
}
}
// 配置模块
class ConfigModule extends BaseModule {
constructor() {
super();
this.config = {};
}
async onInitialize() {
this.loadConfig();
}
loadConfig() {
this.config = {
development: process.env.NODE_ENV === 'development',
databasePath: path.join(app.getPath('userData'), 'database.sqlite'),
logLevel: 'info',
autoUpdate: true
};
}
get(key, defaultValue = null) {
return this.config[key] || defaultValue;
}
set(key, value) {
this.config[key] = value;
}
}
// 日志模块
class LoggerModule extends BaseModule {
constructor() {
super();
this.loggers = new Map();
}
createLogger(name) {
if (!this.loggers.has(name)) {
const logger = {
info: (message, ...args) => console.log(`[${name}] ${message}`, ...args),
warn: (message, ...args) => console.warn(`[${name}] ${message}`, ...args),
error: (message, ...args) => console.error(`[${name}] ${message}`, ...args)
};
this.loggers.set(name, logger);
}
return this.loggers.get(name);
}
}
// 窗口管理模块
class WindowManagerModule extends BaseModule {
constructor(config, logger) {
super();
this.config = config;
this.logger = logger;
this.windows = new Map();
}
async createWindow(options) {
const { name, options: windowOptions } = options;
const logger = this.logger.createLogger('WindowManager');
logger.info(`创建窗口: ${name}`);
const window = new BrowserWindow({
...windowOptions,
show: false
});
this.windows.set(name, window);
window.once('ready-to-show', () => {
window.show();
logger.info(`窗口显示: ${name}`);
});
window.on('closed', () => {
this.windows.delete(name);
logger.info(`窗口关闭: ${name}`);
});
return window;
}
getWindow(name) {
return this.windows.get(name);
}
closeWindow(name) {
const window = this.windows.get(name);
if (window && !window.isDestroyed()) {
window.close();
}
}
async onCleanup() {
for (const [name, window] of this.windows) {
if (!window.isDestroyed()) {
window.close();
}
}
this.windows.clear();
}
}
// 初始化应用
const appController = new ApplicationController();
app.whenReady().then(async () => {
try {
await appController.initialize();
} catch (error) {
console.error('应用初始化失败:', error);
app.quit();
}
});
```
---
02.状态管理
a.实现轻量级状态管理器
状态管理模式对于复杂应用的数据一致性至关重要。可以使用Redux、MobX等状态管理库,或者实现轻量级的状态管理方案。
---
```javascript
// renderer.js - 状态管理实现
class StateManager {
constructor() {
this.state = {};
this.subscribers = new Set();
this.middleware = [];
this.history = [];
this.maxHistorySize = 50;
}
// 初始化状态
initialize(initialState) {
this.state = { ...initialState };
this.saveToHistory('INIT', initialState);
}
// 获取状态
getState(path = null) {
if (path) {
return this.getNestedValue(this.state, path);
}
return this.state;
}
// 更新状态
setState(path, value) {
const oldValue = this.getNestedValue(this.state, path);
const action = { type: 'SET_STATE', path, value, oldValue };
// 应用中间件
let shouldUpdate = true;
for (const middleware of this.middleware) {
const result = middleware(action, this.state);
if (result === false) {
shouldUpdate = false;
break;
}
}
if (shouldUpdate) {
this.setNestedValue(this.state, path, value);
this.saveToHistory(action.type, this.state);
this.notifySubscribers();
}
}
// 批量更新状态
batchUpdate(updates) {
const oldState = { ...this.state };
const actions = [];
for (const { path, value } of updates) {
const oldValue = this.getNestedValue(this.state, path);
actions.push({ type: 'SET_STATE', path, value, oldValue });
this.setNestedValue(this.state, path, value);
}
this.saveToHistory('BATCH_UPDATE', this.state);
this.notifySubscribers();
return actions;
}
// 订阅状态变化
subscribe(callback) {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}
// 添加中间件
addMiddleware(middleware) {
this.middleware.push(middleware);
}
// 通知订阅者
notifySubscribers() {
const state = { ...this.state };
this.subscribers.forEach(callback => {
try {
callback(state);
} catch (error) {
console.error('状态订阅者回调错误:', error);
}
});
}
// 获取嵌套值
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current && current[key], obj);
}
// 设置嵌套值
setNestedValue(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((current, key) => {
if (!current[key] || typeof current[key] !== 'object') {
current[key] = {};
}
return current[key];
}, obj);
target[lastKey] = value;
}
// 保存历史记录
saveToHistory(action, state) {
this.history.push({
action,
state: JSON.parse(JSON.stringify(state)),
timestamp: Date.now()
});
if (this.history.length > this.maxHistorySize) {
this.history.shift();
}
}
// 获取历史记录
getHistory() {
return [...this.history];
}
// 撤销操作
undo() {
if (this.history.length > 1) {
this.history.pop(); // 移除当前状态
const previousState = this.history[this.history.length - 1];
this.state = JSON.parse(JSON.stringify(previousState.state));
this.notifySubscribers();
return true;
}
return false;
}
}
// 日志中间件
const loggerMiddleware = (action, state) => {
console.log('State Update:', action);
console.log('New State:', state);
};
// 验证中间件
const validationMiddleware = (action, state) => {
if (action.path === 'user.age' && (action.value < 0 || action.value > 150)) {
console.error('Invalid age value:', action.value);
return false;
}
};
// 创建状态管理器
const stateManager = new StateManager();
stateManager.addMiddleware(loggerMiddleware);
stateManager.addMiddleware(validationMiddleware);
// 初始化状态
stateManager.initialize({
user: {
name: 'John Doe',
age: 30,
preferences: {
theme: 'light',
language: 'en'
}
},
app: {
isLoading: false,
notifications: []
}
});
// 订阅状态变化
stateManager.subscribe((state) => {
console.log('状态已更新:', state);
});
// 使用示例
stateManager.setState('user.name', 'Jane Doe');
stateManager.setState('user.preferences.theme', 'dark');
// 批量更新
stateManager.batchUpdate([
{ path: 'user.age', value: 25 },
{ path: 'app.isLoading', value: true }
]);
```
---