Electron 开发指南
📋 目录
🎯 简介
Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它结合了 Chromium 和 Node.js,让开发者可以使用 Web 技术构建桌面应用。
1.1 核心优势
- 跨平台兼容性:一套代码可运行在 Windows、macOS 和 Linux 上
- Web 技术栈:使用熟悉的 HTML、CSS 和 JavaScript 开发
- 原生功能访问:通过 Electron API 访问系统原生功能
- 活跃的社区:丰富的插件和生态系统
- 强大的性能:基于 Chromium,提供现代化的 Web 体验
1.2 应用场景
- 桌面工具:开发跨平台的实用工具
- 企业应用:构建内部管理系统
- 媒体播放器:开发视频和音频播放器
- 代码编辑器:如 VS Code、Atom 等
- 聊天应用:构建实时通信工具
1.3 最新版本特性
Electron 团队持续更新框架,带来新特性和性能改进:
- Electron 20+:支持最新的 Chromium 版本
- 安全增强:默认启用上下文隔离和沙箱
- 性能优化:改进启动速度和内存使用
- API 改进:简化原生功能访问
- TypeScript 支持:内置 TypeScript 类型定义
🔧 环境搭建
2.1 开发环境配置
2.1.1 安装 Node.js
确保安装了最新版本的 Node.js(建议 v16+):
# 检查 Node.js 版本
node -v
# 检查 npm 版本
npm -v
# 推荐使用 nvm 管理 Node.js 版本
# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 使用 nvm 安装 Node.js
nvm install 20
nvm use 202.1.2 创建项目
使用官方推荐的方式创建 Electron 项目:
# 方法 1: 使用 npm create vite@latest
npm create vite@latest my-electron-app -- --template vanilla
cd my-electron-app
# 安装 Electron
npm install electron --save-dev
# 安装 electron-builder(用于打包)
npm install electron-builder --save-dev
# 方法 2: 使用官方示例
git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install2.1.3 配置 package.json
// package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Electron 应用",
"main": "src/main/index.js",
"scripts": {
"start": "electron .",
"dev": "electron .",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux",
"build:all": "electron-builder --win --mac --linux",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"electron",
"desktop",
"app"
],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.0.0"
}
}2.2 项目结构
推荐的项目结构:
my-electron-app/
├── src/ # 源代码目录
│ ├── main/ # 主进程代码
│ │ ├── index.js # 主进程入口
│ │ ├── ipc.js # IPC 通信
│ │ ├── dialogs.js # 系统对话框
│ │ ├── tray.js # 系统托盘
│ │ └── updater.js # 自动更新
│ ├── renderer/ # 渲染进程代码
│ │ ├── index.html # 主页面
│ │ ├── renderer.js # 渲染进程逻辑
│ │ └── styles.css # 样式文件
│ ├── preload/ # 预加载脚本
│ │ └── index.js # 预加载脚本
│ └── assets/ # 静态资源
│ ├── icon.ico # Windows 图标
│ ├── icon.icns # macOS 图标
│ └── icon.png # Linux 图标
├── package.json # 项目配置
├── README.md # 项目说明
└── .gitignore # Git 忽略文件2.3 开发工具配置
2.3.1 VS Code 配置
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"html"
]
}2.3.2 ESLint 配置
// .eslintrc.js
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2021: true
},
extends: [
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
};2.3.3 Prettier 配置
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}🖥️ 主进程开发
3.1 主进程配置
主进程是 Electron 应用的入口点,负责创建窗口、处理系统事件和管理应用生命周期。
3.1.1 基本配置
// src/main/index.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { createTray } = require('./tray');
const { setupAutoUpdater } = require('./updater');
// 保持窗口引用,防止被垃圾回收
let mainWindow;
let tray;
function createWindow() {
// 创建浏览器窗口
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
title: 'Electron App',
icon: path.join(__dirname, '../assets/icon.png'),
frame: true, // 使用自定义框架时设为 false
transparent: false, // 透明窗口
resizable: true,
webPreferences: {
// 安全配置
nodeIntegration: false, // 禁用节点集成
contextIsolation: true, // 启用上下文隔离
sandbox: false, // 沙箱模式
// 预加载脚本
preload: path.join(__dirname, '../preload/index.js'),
// 性能配置
backgroundThrottling: false, // 禁用后台节流
disableHtmlFullscreenWindowResize: true, // 禁用 HTML 全屏窗口调整
// 开发配置
devTools: process.env.NODE_ENV === 'development'
}
});
// 加载页面
if (process.env.NODE_ENV === 'development') {
// 开发环境加载本地服务器
mainWindow.loadURL('http://localhost:3000');
} else {
// 生产环境加载本地文件
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
// 打开开发者工具
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({ mode: 'detach' });
}
// 窗口事件
mainWindow.on('closed', () => {
// 释放窗口引用
mainWindow = null;
});
mainWindow.on('resize', () => {
// 处理窗口 resize 事件
});
mainWindow.on('focus', () => {
// 处理窗口聚焦事件
});
// 创建系统托盘
tray = createTray(mainWindow);
// 设置自动更新
setupAutoUpdater(mainWindow);
}
// 应用生命周期事件
app.whenReady().then(() => {
createWindow();
// macOS 特定: 点击 Dock 图标时重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// 关闭所有窗口时退出应用(Windows & Linux)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// 应用启动完成
app.on('ready', () => {
console.log('应用启动完成');
});
// 应用将退出
app.on('will-quit', (event) => {
console.log('应用将退出');
// 可以在这里阻止退出
// event.preventDefault();
});
// 应用已退出
app.on('quit', (event, exitCode) => {
console.log(`应用已退出,退出码: ${exitCode}`);
});
// 导出窗口引用,供其他模块使用
module.exports = { mainWindow };3.1.2 多窗口管理
// src/main/windows.js
const { BrowserWindow } = require('electron');
const path = require('path');
class WindowManager {
constructor() {
this.windows = new Map();
}
// 创建新窗口
createWindow(id, options = {}) {
const window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload/index.js')
},
...options
});
this.windows.set(id, window);
// 监听窗口关闭事件
window.on('closed', () => {
this.windows.delete(id);
});
return window;
}
// 获取窗口
getWindow(id) {
return this.windows.get(id);
}
// 关闭窗口
closeWindow(id) {
const window = this.windows.get(id);
if (window) {
window.close();
this.windows.delete(id);
}
}
// 关闭所有窗口
closeAllWindows() {
this.windows.forEach(window => {
if (!window.isDestroyed()) {
window.close();
}
});
this.windows.clear();
}
// 广播消息到所有窗口
broadcast(message, data) {
this.windows.forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send(message, data);
}
});
}
}
module.exports = new WindowManager();3.2 进程间通信
进程间通信(IPC)是 Electron 应用中主进程和渲染进程之间交换数据的重要方式。
3.2.1 基本 IPC 通信
// src/main/ipc.js
const { ipcMain, dialog } = require('electron');
const { mainWindow } = require('./index');
// 处理异步消息
ipcMain.handle('get-app-info', async () => {
return {
name: app.name,
version: app.getVersion(),
electron: process.versions.electron,
platform: process.platform
};
});
// 处理同步消息
ipcMain.on('get-sync-data', (event) => {
event.returnValue = '同步数据响应';
});
// 处理带参数的消息
ipcMain.handle('calculate', async (event, { a, b, operation }) => {
switch (operation) {
case 'add':
return a + b;
case 'subtract':
return a - b;
case 'multiply':
return a * b;
case 'divide':
return a / b;
default:
throw new Error('不支持的操作');
}
});
// 处理文件对话框
ipcMain.handle('open-file-dialog', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'],
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
]
});
if (!result.canceled) {
return result.filePaths;
}
return [];
});
// 处理保存文件对话框
ipcMain.handle('save-file-dialog', async (event, defaultPath) => {
const result = await dialog.showSaveDialog({
defaultPath,
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
});
if (!result.canceled) {
return result.filePath;
}
return null;
});
// 窗口操作
ipcMain.handle('window-minimize', () => {
if (mainWindow) {
mainWindow.minimize();
}
});
ipcMain.handle('window-maximize', () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
}
});
ipcMain.handle('window-close', () => {
if (mainWindow) {
mainWindow.close();
}
});
// 消息广播
function broadcastMessage(channel, data) {
const windows = BrowserWindow.getAllWindows();
windows.forEach(window => {
if (!window.isDestroyed()) {
window.webContents.send(channel, data);
}
});
}
module.exports = { broadcastMessage };3.2.2 安全的 IPC 通信
// src/main/ipc-security.js
const { ipcMain } = require('electron');
// 定义允许的通道
const ALLOWED_CHANNELS = {
// 应用操作
'app:get-info': true,
'app:restart': true,
'app:quit': true,
// 文件操作
'file:open': true,
'file:save': true,
'file:delete': true,
// 窗口操作
'window:minimize': true,
'window:maximize': true,
'window:close': true,
// 系统操作
'system:tray': true,
'system:dialog': true
};
// 验证通道是否允许
function validateChannel(channel) {
return ALLOWED_CHANNELS[channel] === true;
}
// 验证数据格式
function validateData(data, schema) {
// 实现数据验证逻辑
return true;
}
// 安全的 IPC 处理
class SecureIPC {
constructor() {
this.setupHandlers();
}
setupHandlers() {
// 通用消息处理
ipcMain.handle('secure:message', (event, { channel, data, schema }) => {
// 验证通道
if (!validateChannel(channel)) {
throw new Error(`禁止的通道: ${channel}`);
}
// 验证数据
if (schema && !validateData(data, schema)) {
throw new Error('无效的数据格式');
}
// 处理消息
return this.handleMessage(channel, data);
});
}
async handleMessage(channel, data) {
switch (channel) {
case 'app:get-info':
return this.getAppInfo();
case 'file:open':
return this.openFile(data);
// 其他通道处理
default:
throw new Error(`未处理的通道: ${channel}`);
}
}
async getAppInfo() {
return {
name: app.name,
version: app.getVersion()
};
}
async openFile(data) {
// 实现文件打开逻辑
return [];
}
}
module.exports = new SecureIPC();🖥️ 渲染进程开发
渲染进程是 Electron 应用中负责显示用户界面的部分,基于 Chromium 引擎运行,可以使用现代 Web 技术构建。
4.1 页面开发
4.1.1 基本页面结构
<!-- src/renderer/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron App</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none';">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<header>
<h1>欢迎使用 Electron</h1>
<div class="window-controls">
<button id="minimize">−</button>
<button id="maximize">□</button>
<button id="close">×</button>
</div>
</header>
<main>
<button id="openFile">打开文件</button>
<button id="saveFile">保存文件</button>
<div id="content"></div>
</main>
<footer>
<p>Electron App © 2026</p>
</footer>
</div>
<script src="renderer.js"></script>
</body>
</html>/* src/renderer/styles.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f5f5f5;
color: #333;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
background-color: #2c3e50;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.window-controls {
display: flex;
gap: 0.5rem;
}
.window-controls button {
width: 30px;
height: 30px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
}
#minimize {
background-color: #f39c12;
color: white;
}
#maximize {
background-color: #27ae60;
color: white;
}
#close {
background-color: #e74c3c;
color: white;
}
main {
flex: 1;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
button {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
background-color: #3498db;
color: white;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #2980b9;
}
#content {
margin-top: 1rem;
padding: 1rem;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height: 200px;
}
footer {
background-color: #ecf0f1;
padding: 1rem;
text-align: center;
font-size: 14px;
color: #7f8c8d;
}// src/renderer/renderer.js
// 使用暴露的 API
if (window.electronAPI) {
// 窗口控制
document.getElementById('minimize').addEventListener('click', () => {
window.electronAPI.minimize();
});
document.getElementById('maximize').addEventListener('click', () => {
window.electronAPI.maximize();
});
document.getElementById('close').addEventListener('click', () => {
window.electronAPI.close();
});
// 文件操作
document.getElementById('openFile').addEventListener('click', async () => {
try {
const files = await window.electronAPI.openFile();
if (files && files.length > 0) {
document.getElementById('content').textContent = `打开的文件: ${files.join(', ')}`;
}
} catch (error) {
console.error('打开文件失败:', error);
}
});
document.getElementById('saveFile').addEventListener('click', async () => {
try {
const content = 'Hello Electron!';
const filePath = await window.electronAPI.saveFile(content);
if (filePath) {
document.getElementById('content').textContent = `文件已保存到: ${filePath}`;
}
} catch (error) {
console.error('保存文件失败:', error);
}
});
// 获取应用信息
async function loadAppInfo() {
try {
const appInfo = await window.electronAPI.getAppInfo();
console.log('应用信息:', appInfo);
} catch (error) {
console.error('获取应用信息失败:', error);
}
}
// 加载应用信息
loadAppInfo();
// 监听更新事件
window.electronAPI.onUpdateAvailable((version) => {
console.log('发现新版本:', version);
alert(`发现新版本 ${version},请更新应用`);
});
window.electronAPI.onUpdateProgress((progress) => {
console.log('更新进度:', progress);
});
}
// 页面加载完成
window.addEventListener('DOMContentLoaded', () => {
console.log('页面加载完成');
});
// 窗口关闭前
window.addEventListener('beforeunload', (event) => {
// 可以在这里提示用户保存更改
// event.preventDefault();
// event.returnValue = '';
});4.2 预加载脚本
预加载脚本在渲染进程加载之前运行,用于安全地暴露 Electron API 到渲染进程。
4.2.1 基本配置
// src/preload/index.js
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
openFile: () => ipcRenderer.invoke('file:open'),
saveFile: (content) => ipcRenderer.invoke('file:save', content),
// 窗口操作
minimize: () => ipcRenderer.invoke('window:minimize'),
maximize: () => ipcRenderer.invoke('window:maximize'),
close: () => ipcRenderer.invoke('window:close'),
// 应用信息
getAppInfo: () => ipcRenderer.invoke('app:get-info'),
getAppPath: () => ipcRenderer.invoke('app:get-path'),
// 系统操作
showMessageBox: (options) => ipcRenderer.invoke('system:message-box', options),
// 消息通信(仅允许特定通道)
onUpdateAvailable: (callback) => {
ipcRenderer.on('update:available', (event, version) => callback(version));
},
onUpdateProgress: (callback) => {
ipcRenderer.on('update:progress', (event, progress) => callback(progress));
},
onNotification: (callback) => {
ipcRenderer.on('notification', (event, data) => callback(data));
}
});
// 导出类型定义(用于 TypeScript)
if (typeof module !== 'undefined' && module.exports) {
module.exports = {};
}4.2.2 高级预加载脚本
// src/preload/advanced.js
const { contextBridge, ipcRenderer, webFrame } = require('electron');
// 限制 webFrame 权限
webFrame.setZoomLevelLimits(1, 1);
webFrame.setLayoutZoomLevelLimits(0, 0);
// 安全的 API 暴露
const api = {
// 应用相关
app: {
getVersion: () => ipcRenderer.invoke('app:get-version'),
getName: () => ipcRenderer.invoke('app:get-name'),
restart: () => ipcRenderer.invoke('app:restart'),
quit: () => ipcRenderer.invoke('app:quit')
},
// 文件系统
fs: {
readFile: (path) => ipcRenderer.invoke('fs:read-file', path),
writeFile: (path, content) => ipcRenderer.invoke('fs:write-file', path, content),
exists: (path) => ipcRenderer.invoke('fs:exists', path),
listDir: (path) => ipcRenderer.invoke('fs:list-dir', path)
},
// 系统相关
system: {
getOS: () => ipcRenderer.invoke('system:get-os'),
getCPUUsage: () => ipcRenderer.invoke('system:get-cpu-usage'),
getMemoryUsage: () => ipcRenderer.invoke('system:get-memory-usage')
},
// 通信
ipc: {
on: (channel, callback) => {
// 验证通道
const validChannels = ['update', 'notification', 'progress'];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => callback(...args));
}
},
once: (channel, callback) => {
const validChannels = ['update', 'notification', 'progress'];
if (validChannels.includes(channel)) {
ipcRenderer.once(channel, (event, ...args) => callback(...args));
}
}
}
};
// 暴露 API
contextBridge.exposeInMainWorld('electron', api);
// 清理
process.once('loaded', () => {
console.log('预加载脚本执行完成');
});4.3 现代前端框架集成
4.3.1 React 集成
# 创建 React + Electron 项目
npm create vite@latest my-electron-app -- --template react
cd my-electron-app
npm install electron --save-dev// src/main/index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
nodeIntegration: false,
contextIsolation: true
}
});
// 开发环境加载 Vite 服务器
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173');
} else {
// 生产环境加载构建文件
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
}
// 其他配置...// src/renderer/src/App.jsx
import { useState, useEffect } from 'react';
function App() {
const [files, setFiles] = useState([]);
const [appInfo, setAppInfo] = useState(null);
// 打开文件
const handleOpenFile = async () => {
if (window.electronAPI) {
try {
const result = await window.electronAPI.openFile();
setFiles(result);
} catch (error) {
console.error('打开文件失败:', error);
}
}
};
// 获取应用信息
useEffect(() => {
const loadAppInfo = async () => {
if (window.electronAPI) {
try {
const info = await window.electronAPI.getAppInfo();
setAppInfo(info);
} catch (error) {
console.error('获取应用信息失败:', error);
}
}
};
loadAppInfo();
}, []);
return (
<div className="App">
<header className="App-header">
<h1>Electron + React App</h1>
{appInfo && (
<p>版本: {appInfo.version}</p>
)}
</header>
<main>
<button onClick={handleOpenFile}>打开文件</button>
<div>
<h2>打开的文件:</h2>
<ul>
{files.map((file, index) => (
<li key={index}>{file}</li>
))}
</ul>
</div>
</main>
</div>
);
}
export default App;4.3.2 Vue 集成
# 创建 Vue + Electron 项目
npm create vite@latest my-electron-app -- --template vue
cd my-electron-app
npm install electron --save-dev// src/renderer/src/App.vue
<template>
<div class="app">
<header>
<h1>Electron + Vue App</h1>
<p v-if="appInfo">版本: {{ appInfo.version }}</p>
</header>
<main>
<button @click="handleOpenFile">打开文件</button>
<button @click="handleSaveFile">保存文件</button>
<div class="file-list">
<h2>打开的文件:</h2>
<ul>
<li v-for="(file, index) in files" :key="index">{{ file }}</li>
</ul>
</div>
</main>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const files = ref([]);
const appInfo = ref(null);
// 打开文件
const handleOpenFile = async () => {
if (window.electronAPI) {
try {
const result = await window.electronAPI.openFile();
files.value = result;
} catch (error) {
console.error('打开文件失败:', error);
}
}
};
// 保存文件
const handleSaveFile = async () => {
if (window.electronAPI) {
try {
const content = 'Hello Electron + Vue!';
const filePath = await window.electronAPI.saveFile(content);
if (filePath) {
alert(`文件已保存到: ${filePath}`);
}
} catch (error) {
console.error('保存文件失败:', error);
}
}
};
// 获取应用信息
onMounted(async () => {
if (window.electronAPI) {
try {
const info = await window.electronAPI.getAppInfo();
appInfo.value = info;
} catch (error) {
console.error('获取应用信息失败:', error);
}
}
});
</script>
<style scoped>
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
background-color: #42b883;
color: white;
padding: 1rem;
text-align: center;
}
main {
flex: 1;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
button {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
background-color: #35495e;
color: white;
cursor: pointer;
}
button:hover {
background-color: #2c3e50;
}
.file-list {
margin-top: 1rem;
padding: 1rem;
background-color: #f5f5f5;
border-radius: 4px;
}
</style>4.4 渲染进程生命周期
4.4.1 生命周期事件
// src/renderer/lifecycle.js
// 页面加载完成
window.addEventListener('DOMContentLoaded', () => {
console.log('DOM 内容加载完成');
// 初始化应用
initializeApp();
});
// 页面完全加载
window.addEventListener('load', () => {
console.log('页面完全加载');
// 执行需要完整 DOM 的操作
setupEventListeners();
});
// 窗口失去焦点
window.addEventListener('blur', () => {
console.log('窗口失去焦点');
// 暂停动画、视频等
});
// 窗口获得焦点
window.addEventListener('focus', () => {
console.log('窗口获得焦点');
// 恢复动画、视频等
});
// 窗口大小变化
window.addEventListener('resize', () => {
console.log('窗口大小变化');
// 调整布局
resizeLayout();
});
// 窗口关闭前
window.addEventListener('beforeunload', (event) => {
console.log('窗口即将关闭');
// 检查是否有未保存的更改
if (hasUnsavedChanges()) {
event.preventDefault();
event.returnValue = '您有未保存的更改,确定要离开吗?';
}
});
// 页面卸载
window.addEventListener('unload', () => {
console.log('页面正在卸载');
// 清理资源
cleanupResources();
});
// 初始化应用
function initializeApp() {
console.log('初始化应用');
// 初始化状态、加载配置等
}
// 设置事件监听器
function setupEventListeners() {
console.log('设置事件监听器');
// 绑定用户交互事件
}
// 调整布局
function resizeLayout() {
// 响应式布局调整
}
// 检查未保存的更改
function hasUnsavedChanges() {
// 检查逻辑
return false;
}
// 清理资源
function cleanupResources() {
console.log('清理资源');
// 清理定时器、取消网络请求等
}4.5 渲染进程性能优化
4.5.1 渲染优化
// src/renderer/performance.js
// 使用 requestAnimationFrame 进行动画
function animate() {
requestAnimationFrame(animate);
// 动画逻辑
}
// 防抖
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 executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 优化滚动事件
const optimizedScroll = debounce(() => {
console.log('滚动事件触发');
// 处理滚动逻辑
}, 100);
window.addEventListener('scroll', optimizedScroll);
// 优化 resize 事件
const optimizedResize = throttle(() => {
console.log(' resize 事件触发');
// 处理 resize 逻辑
}, 200);
window.addEventListener('resize', optimizedResize);
// 虚拟滚动(处理大量数据)
function setupVirtualScroll(container, items, itemHeight) {
const containerHeight = container.clientHeight;
const totalItems = items.length;
const totalHeight = totalItems * itemHeight;
container.style.height = `${containerHeight}px`;
container.style.overflow = 'auto';
const content = document.createElement('div');
content.style.height = `${totalHeight}px`;
content.style.position = 'relative';
container.appendChild(content);
const renderWindow = document.createElement('div');
renderWindow.style.position = 'absolute';
renderWindow.style.top = '0';
renderWindow.style.left = '0';
renderWindow.style.right = '0';
content.appendChild(renderWindow);
function renderItems() {
const scrollTop = container.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
totalItems
);
renderWindow.innerHTML = '';
renderWindow.style.transform = `translateY(${startIndex * itemHeight}px)`;
for (let i = startIndex; i < endIndex; i++) {
const item = document.createElement('div');
item.style.height = `${itemHeight}px`;
item.style.borderBottom = '1px solid #eee';
item.style.padding = '10px';
item.textContent = items[i];
renderWindow.appendChild(item);
}
}
container.addEventListener('scroll', renderItems);
renderItems();
}
// 使用示例
const container = document.getElementById('virtual-list');
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
setupVirtualScroll(container, items, 50);4.5.2 内存优化
// src/renderer/memory.js
// 避免内存泄漏
function avoidMemoryLeaks() {
// 1. 正确清理事件监听器
const element = document.getElementById('my-element');
const handler = () => console.log('点击事件');
element.addEventListener('click', handler);
// 清理事件监听器
function cleanup() {
element.removeEventListener('click', handler);
}
// 2. 避免闭包陷阱
function createHandler() {
const data = Array(1000000).fill('test');
return function() {
console.log('处理事件');
// 避免引用大量数据
};
}
// 3. 使用 WeakMap 和 WeakSet
const weakMap = new WeakMap();
function storeData(obj, data) {
weakMap.set(obj, data);
}
// 4. 及时清理定时器
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
function clearTimer() {
clearInterval(timer);
}
// 5. 清理 WebSocket 连接
let socket;
function setupWebSocket() {
socket = new WebSocket('wss://example.com');
socket.onclose = () => {
console.log('WebSocket 关闭');
};
}
function closeWebSocket() {
if (socket) {
socket.close();
socket = null;
}
}
}
// 监控内存使用
function monitorMemory() {
if (performance && performance.memory) {
setInterval(() => {
const memory = performance.memory;
console.log('内存使用情况:', {
usedJSHeapSize: (memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
totalJSHeapSize: (memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
jsHeapSizeLimit: (memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2) + ' MB'
});
}, 5000);
}
}
// 初始化内存监控
monitorMemory();4.6 错误处理
4.6.1 全局错误处理
// src/renderer/error-handling.js
// 捕获未处理的错误
window.addEventListener('error', (event) => {
console.error('未捕获的错误:', event.error);
console.error('错误文件:', event.filename);
console.error('错误行号:', event.lineno);
console.error('错误列号:', event.colno);
// 显示错误提示
showErrorNotification('应用发生错误', event.error.message);
// 阻止默认行为
event.preventDefault();
});
// 捕获未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的 Promise 拒绝:', event.reason);
// 显示错误提示
showErrorNotification('操作失败', event.reason.message || String(event.reason));
// 阻止默认行为
event.preventDefault();
});
// 显示错误通知
function showErrorNotification(title, message) {
if (window.electronAPI) {
window.electronAPI.showMessageBox({
type: 'error',
title: title,
message: message,
buttons: ['确定']
});
} else {
alert(`${title}: ${message}`);
}
}
// 安全的异步操作
async function safeAsyncOperation(operation, errorMessage) {
try {
return await operation();
} catch (error) {
console.error(errorMessage, error);
showErrorNotification(errorMessage, error.message);
return null;
}
}
// 使用示例
async function loadData() {
return await safeAsyncOperation(async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('网络请求失败');
}
return await response.json();
}, '加载数据失败');
}4.7 最佳实践
4.7.1 渲染进程最佳实践
安全优先
- 始终使用预加载脚本暴露 API
- 避免使用
nodeIntegration: true - 启用
contextIsolation: true - 使用 Content Security Policy
性能优化
- 使用
requestAnimationFrame进行动画 - 优化事件监听器(防抖、节流)
- 避免频繁 DOM 操作
- 使用虚拟滚动处理大量数据
- 延迟加载非关键资源
- 使用
用户体验
- 响应式设计适配不同窗口大小
- 提供加载状态和错误提示
- 优化启动时间
- 合理使用动画和过渡效果
代码组织
- 模块化代码结构
- 使用现代前端框架(React、Vue、Svelte)
- 遵循前端代码规范
- 合理使用状态管理
调试技巧
- 使用 Chrome DevTools 调试渲染进程
- 合理使用 console 日志
- 监控性能和内存使用
- 使用 React DevTools 或 Vue DevTools 等扩展
4.7.2 常见问题及解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 白屏 | 页面加载失败、预加载脚本错误 | 检查控制台错误、确保预加载脚本正确 |
| 渲染卡顿 | 频繁 DOM 操作、复杂动画 | 使用 requestAnimationFrame、虚拟滚动 |
| 内存泄漏 | 未清理事件监听器、闭包 | 正确清理资源、使用 WeakMap/WeakSet |
| API 不可用 | 预加载脚本配置错误 | 检查 contextBridge 配置、确保 API 正确暴露 |
| 样式问题 | 平台差异、CSS 兼容性 | 使用跨平台兼容的 CSS、考虑使用 CSS 框架 |
🔌 原生功能集成
Electron 提供了丰富的 API 来访问系统原生功能,让桌面应用拥有更强大的能力。
5.1 系统对话框
系统对话框是与用户交互的重要方式,用于文件选择、消息提示等操作。
5.1.1 文件对话框
// src/main/dialogs.js
const { dialog } = require('electron');
// 打开文件对话框
async function openFileDialog(options = {}) {
const defaultOptions = {
title: '打开文件',
defaultPath: '',
buttonLabel: '打开',
filters: [
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile'],
message: '选择要打开的文件'
};
const result = await dialog.showOpenDialog({
...defaultOptions,
...options
});
if (!result.canceled) {
return result.filePaths;
}
return [];
}
// 打开多个文件
async function openMultipleFiles() {
return await openFileDialog({
properties: ['openFile', 'multiSelections']
});
}
// 选择文件夹
async function selectFolder() {
const result = await dialog.showOpenDialog({
title: '选择文件夹',
properties: ['openDirectory']
});
if (!result.canceled) {
return result.filePaths[0];
}
return null;
}
// 保存文件对话框
async function saveFileDialog(options = {}) {
const defaultOptions = {
title: '保存文件',
defaultPath: '',
buttonLabel: '保存',
filters: [
{ name: '所有文件', extensions: ['*'] }
],
message: '选择保存位置'
};
const result = await dialog.showSaveDialog({
...defaultOptions,
...options
});
if (!result.canceled) {
return result.filePath;
}
return null;
}
// 示例:保存文本文件
async function saveTextFile(content, defaultName = 'untitled.txt') {
const filePath = await saveFileDialog({
defaultPath: defaultName,
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
});
if (filePath) {
// 保存文件内容
const fs = require('fs').promises;
await fs.writeFile(filePath, content, 'utf8');
return filePath;
}
return null;
}
module.exports = {
openFileDialog,
openMultipleFiles,
selectFolder,
saveFileDialog,
saveTextFile
};5.1.2 消息对话框
// src/main/message-dialogs.js
const { dialog } = require('electron');
// 显示信息对话框
function showInfoMessage(message, options = {}) {
return dialog.showMessageBox({
type: 'info',
title: '信息',
message,
buttons: ['确定'],
defaultId: 0,
cancelId: -1,
...options
});
}
// 显示警告对话框
function showWarningMessage(message, options = {}) {
return dialog.showMessageBox({
type: 'warning',
title: '警告',
message,
buttons: ['确定'],
defaultId: 0,
cancelId: -1,
...options
});
}
// 显示错误对话框
function showErrorMessage(message, options = {}) {
return dialog.showMessageBox({
type: 'error',
title: '错误',
message,
buttons: ['确定'],
defaultId: 0,
cancelId: -1,
...options
});
}
// 显示确认对话框
function showConfirmMessage(message, options = {}) {
return dialog.showMessageBox({
type: 'question',
title: '确认',
message,
buttons: ['取消', '确定'],
defaultId: 1,
cancelId: 0,
...options
});
}
// 显示选择对话框
function showSelectMessage(message, choices, options = {}) {
return dialog.showMessageBox({
type: 'question',
title: '选择',
message,
buttons: choices,
defaultId: 0,
cancelId: -1,
...options
});
}
// 示例:确认退出
async function confirmQuit(window) {
const result = await showConfirmMessage(
'确定要退出应用吗?未保存的更改将会丢失。',
{
parent: window,
icon: null, // 可以指定图标
detail: '请确认您的操作',
checkboxLabel: '不再提示',
checkboxChecked: false
}
);
return {
confirmed: result.response === 1,
dontAskAgain: result.checkboxChecked
};
}
module.exports = {
showInfoMessage,
showWarningMessage,
showErrorMessage,
showConfirmMessage,
showSelectMessage,
confirmQuit
};5.2 系统托盘
系统托盘是应用在系统任务栏中的图标,提供快速访问和状态显示。
5.2.1 基本托盘
// src/main/tray.js
const { app, Tray, Menu, nativeImage } = require('electron');
const path = require('path');
class AppTray {
constructor() {
this.tray = null;
this.mainWindow = null;
}
// 创建托盘
create(mainWindow) {
this.mainWindow = mainWindow;
// 创建托盘图标
const iconPath = path.join(__dirname, '../assets/tray-icon.png');
const icon = nativeImage.createFromPath(iconPath);
// 设置图标大小
icon.setTemplateImage(true); // 在 macOS 上使用模板模式
// 创建托盘
this.tray = new Tray(icon);
// 设置托盘提示
this.tray.setToolTip('Electron App');
// 设置上下文菜单
this.updateMenu();
// 绑定事件
this.bindEvents();
return this.tray;
}
// 更新菜单
updateMenu() {
if (!this.tray) return;
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => this.showWindow(),
enabled: !this.mainWindow.isVisible()
},
{
label: '隐藏主窗口',
click: () => this.hideWindow(),
enabled: this.mainWindow.isVisible()
},
{
type: 'separator'
},
{
label: '设置',
click: () => this.openSettings()
},
{
label: '关于',
click: () => this.openAbout()
},
{
type: 'separator'
},
{
label: '退出',
click: () => this.quitApp()
}
]);
this.tray.setContextMenu(contextMenu);
}
// 绑定事件
bindEvents() {
if (!this.tray) return;
// 点击托盘图标
this.tray.on('click', () => {
this.toggleWindow();
});
// 双击托盘图标
this.tray.on('double-click', () => {
this.showWindow();
});
// 右键点击托盘图标
this.tray.on('right-click', () => {
this.updateMenu();
});
}
// 显示窗口
showWindow() {
if (this.mainWindow) {
this.mainWindow.show();
this.mainWindow.focus();
}
}
// 隐藏窗口
hideWindow() {
if (this.mainWindow) {
this.mainWindow.hide();
}
}
// 切换窗口显示/隐藏
toggleWindow() {
if (this.mainWindow) {
if (this.mainWindow.isVisible()) {
this.mainWindow.hide();
} else {
this.mainWindow.show();
this.mainWindow.focus();
}
}
}
// 打开设置
openSettings() {
// 实现打开设置窗口的逻辑
console.log('打开设置');
}
// 打开关于
openAbout() {
// 实现打开关于窗口的逻辑
console.log('打开关于');
}
// 退出应用
quitApp() {
app.quit();
}
// 更新托盘图标
updateIcon(iconPath) {
if (this.tray) {
const icon = nativeImage.createFromPath(iconPath);
icon.setTemplateImage(true);
this.tray.setImage(icon);
}
}
// 更新托盘提示
updateToolTip(tooltip) {
if (this.tray) {
this.tray.setToolTip(tooltip);
}
}
// 销毁托盘
destroy() {
if (this.tray) {
this.tray.destroy();
this.tray = null;
}
}
}
// 导出单例
module.exports = new AppTray();5.2.2 高级托盘
// src/main/advanced-tray.js
const { app, Tray, Menu, nativeImage, Notification } = require('electron');
const path = require('path');
class AdvancedTray extends AppTray {
constructor() {
super();
this.unreadCount = 0;
this.isConnected = false;
}
// 创建托盘
create(mainWindow) {
super.create(mainWindow);
this.updateIconWithBadge();
return this.tray;
}
// 更新菜单
updateMenu() {
if (!this.tray) return;
const contextMenu = Menu.buildFromTemplate([
{
label: `连接状态: ${this.isConnected ? '已连接' : '未连接'}`,
enabled: false,
icon: this.getStatusIcon()
},
{
label: `未读消息: ${this.unreadCount}`,
enabled: false
},
{
type: 'separator'
},
{
label: '显示主窗口',
click: () => this.showWindow(),
enabled: !this.mainWindow.isVisible()
},
{
label: '隐藏主窗口',
click: () => this.hideWindow(),
enabled: this.mainWindow.isVisible()
},
{
type: 'separator'
},
{
label: '刷新',
click: () => this.refresh()
},
{
label: '检查更新',
click: () => this.checkUpdates()
},
{
type: 'separator'
},
{
label: '设置',
click: () => this.openSettings()
},
{
label: '关于',
click: () => this.openAbout()
},
{
type: 'separator'
},
{
label: '退出',
click: () => this.quitApp()
}
]);
this.tray.setContextMenu(contextMenu);
}
// 获取状态图标
getStatusIcon() {
const iconPath = this.isConnected
? path.join(__dirname, '../assets/connected.png')
: path.join(__dirname, '../assets/disconnected.png');
return nativeImage.createFromPath(iconPath);
}
// 更新图标和徽章
updateIconWithBadge() {
if (!this.tray) return;
// 基础图标
const baseIconPath = path.join(__dirname, '../assets/tray-icon.png');
let icon = nativeImage.createFromPath(baseIconPath);
// 如果有未读消息,添加徽章
if (this.unreadCount > 0) {
icon = this.addBadgeToIcon(icon, this.unreadCount);
}
// 设置图标
icon.setTemplateImage(true);
this.tray.setImage(icon);
}
// 添加徽章到图标
addBadgeToIcon(baseIcon, count) {
// 创建徽章图标
const badgeCanvas = document.createElement('canvas');
const badgeCtx = badgeCanvas.getContext('2d');
// 设置徽章大小
const badgeSize = 20;
badgeCanvas.width = badgeSize;
badgeCanvas.height = badgeSize;
// 绘制红色圆形背景
badgeCtx.beginPath();
badgeCtx.arc(badgeSize / 2, badgeSize / 2, badgeSize / 2, 0, Math.PI * 2);
badgeCtx.fillStyle = '#ff3b30';
badgeCtx.fill();
// 绘制白色文字
badgeCtx.fillStyle = '#ffffff';
badgeCtx.font = 'bold 12px sans-serif';
badgeCtx.textAlign = 'center';
badgeCtx.textBaseline = 'middle';
badgeCtx.fillText(count > 99 ? '99+' : count.toString(), badgeSize / 2, badgeSize / 2);
// 创建徽章图像
const badgeImage = nativeImage.createFromDataURL(badgeCanvas.toDataURL());
// 合并图标和徽章
const finalCanvas = document.createElement('canvas');
const finalCtx = finalCanvas.getContext('2d');
const baseSize = baseIcon.getSize();
finalCanvas.width = baseSize.width;
finalCanvas.height = baseSize.height;
// 绘制基础图标
const baseData = baseIcon.toDataURL();
const baseImg = new Image();
baseImg.src = baseData;
return new Promise((resolve) => {
baseImg.onload = () => {
finalCtx.drawImage(baseImg, 0, 0);
// 绘制徽章(右上角)
finalCtx.drawImage(
badgeImage.toDataURL(),
baseSize.width - badgeSize,
0,
badgeSize,
badgeSize
);
resolve(nativeImage.createFromDataURL(finalCanvas.toDataURL()));
};
});
}
// 更新未读消息数
setUnreadCount(count) {
this.unreadCount = Math.max(0, count);
this.updateIconWithBadge();
this.updateMenu();
// 显示通知
if (count > 0) {
new Notification({
title: '新消息',
body: `您有 ${count} 条未读消息`,
icon: path.join(__dirname, '../assets/icon.png')
}).show();
}
}
// 更新连接状态
setConnected(connected) {
this.isConnected = connected;
this.updateMenu();
// 显示通知
new Notification({
title: '连接状态',
body: connected ? '已连接到服务器' : '与服务器断开连接',
icon: path.join(__dirname, '../assets/icon.png')
}).show();
}
// 刷新
async refresh() {
try {
// 实现刷新逻辑
console.log('刷新中...');
// 模拟刷新
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('刷新完成');
// 显示通知
new Notification({
title: '刷新完成',
body: '应用已成功刷新',
icon: path.join(__dirname, '../assets/icon.png')
}).show();
} catch (error) {
console.error('刷新失败:', error);
// 显示错误通知
new Notification({
title: '刷新失败',
body: '刷新过程中发生错误',
icon: path.join(__dirname, '../assets/icon.png')
}).show();
}
}
// 检查更新
async checkUpdates() {
try {
// 实现检查更新逻辑
console.log('检查更新中...');
// 模拟检查更新
await new Promise(resolve => setTimeout(resolve, 1500));
console.log('检查更新完成');
// 显示通知
new Notification({
title: '更新检查',
body: '当前已是最新版本',
icon: path.join(__dirname, '../assets/icon.png')
}).show();
} catch (error) {
console.error('检查更新失败:', error);
}
}
}
// 导出单例
module.exports = new AdvancedTray();5.3 原生菜单
原生菜单是应用顶部的菜单栏,提供应用的主要功能入口。
5.3.1 基本菜单
// src/main/menu.js
const { app, Menu, dialog } = require('electron');
const isMac = process.platform === 'darwin';
// 创建应用菜单
function createMenu(mainWindow) {
const template = [
// macOS 应用菜单
...(isMac ? [{
label: app.name,
submenu: [
{
label: '关于',
click: () => showAboutDialog()
},
{
type: 'separator'
},
{
label: '服务',
submenu: []
},
{
type: 'separator'
},
{
label: '偏好设置',
accelerator: 'Cmd+,',
click: () => openSettings()
},
{
type: 'separator'
},
{
label: '隐藏',
accelerator: 'Cmd+H',
click: () => app.hide()
},
{
label: '隐藏其他',
accelerator: 'Cmd+Alt+H',
click: () => app.hideOtherApplications()
},
{
label: '显示全部',
click: () => app.showAllApplications()
},
{
type: 'separator'
},
{
label: '退出',
accelerator: 'Cmd+Q',
click: () => app.quit()
}
]
}] : []),
// 文件菜单
{
label: '文件',
submenu: [
{
label: '新建',
accelerator: isMac ? 'Cmd+N' : 'Ctrl+N',
click: () => createNewFile(mainWindow)
},
{
label: '打开',
accelerator: isMac ? 'Cmd+O' : 'Ctrl+O',
click: () => openFile(mainWindow)
},
{
label: '打开最近',
submenu: [
{
label: '清除最近文件',
click: () => clearRecentFiles()
}
]
},
{
type: 'separator'
},
{
label: '保存',
accelerator: isMac ? 'Cmd+S' : 'Ctrl+S',
click: () => saveFile(mainWindow)
},
{
label: '另存为',
accelerator: isMac ? 'Cmd+Shift+S' : 'Ctrl+Shift+S',
click: () => saveAsFile(mainWindow)
},
{
type: 'separator'
},
{
label: '退出',
accelerator: isMac ? 'Cmd+Q' : 'Alt+F4',
click: () => app.quit()
}
]
},
// 编辑菜单
{
label: '编辑',
submenu: [
{
label: '撤销',
accelerator: isMac ? 'Cmd+Z' : 'Ctrl+Z',
role: 'undo'
},
{
label: '重做',
accelerator: isMac ? 'Cmd+Shift+Z' : 'Ctrl+Y',
role: 'redo'
},
{
type: 'separator'
},
{
label: '剪切',
accelerator: isMac ? 'Cmd+X' : 'Ctrl+X',
role: 'cut'
},
{
label: '复制',
accelerator: isMac ? 'Cmd+C' : 'Ctrl+C',
role: 'copy'
},
{
label: '粘贴',
accelerator: isMac ? 'Cmd+V' : 'Ctrl+V',
role: 'paste'
},
{
label: '删除',
role: 'delete'
},
{
type: 'separator'
},
{
label: '全选',
accelerator: isMac ? 'Cmd+A' : 'Ctrl+A',
role: 'selectAll'
}
]
},
// 视图菜单
{
label: '视图',
submenu: [
{
label: '刷新',
accelerator: isMac ? 'Cmd+R' : 'F5',
click: () => mainWindow.reload()
},
{
label: '切换开发者工具',
accelerator: isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I',
click: () => mainWindow.webContents.toggleDevTools()
},
{
type: 'separator'
},
{
label: '切换全屏',
accelerator: isMac ? 'Ctrl+Cmd+F' : 'F11',
click: () => mainWindow.setFullScreen(!mainWindow.isFullScreen())
},
{
label: '缩放',
submenu: [
{
label: '放大',
accelerator: 'Cmd+Plus',
click: () => mainWindow.webContents zoomIn()
},
{
label: '缩小',
accelerator: 'Cmd+-',
click: () => mainWindow.webContents.zoomOut()
},
{
label: '重置',
accelerator: 'Cmd+0',
click: () => mainWindow.webContents.zoomLevel = 0
}
]
}
]
},
// 窗口菜单
{
label: '窗口',
submenu: [
{
label: '最小化',
accelerator: isMac ? 'Cmd+M' : 'Ctrl+M',
click: () => mainWindow.minimize()
},
{
label: '最大化',
click: () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
}
},
{
label: '关闭',
accelerator: isMac ? 'Cmd+W' : 'Ctrl+W',
click: () => mainWindow.close()
},
{
type: 'separator'
},
{
label: '前置所有窗口',
click: () => mainWindow.focus()
}
]
},
// 帮助菜单
{
label: '帮助',
submenu: [
{
label: '文档',
click: () => openDocumentation()
},
{
label: '教程',
click: () => openTutorial()
},
{
label: '常见问题',
click: () => openFAQ()
},
{
type: 'separator'
},
{
label: '检查更新',
click: () => checkUpdates()
},
{
label: '关于',
click: () => showAboutDialog()
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
return menu;
}
// 显示关于对话框
function showAboutDialog() {
dialog.showMessageBox({
title: '关于 Electron App',
message: 'Electron App',
detail: `版本: ${app.getVersion()}\nElectron: ${process.versions.electron}\nNode.js: ${process.version}\nV8: ${process.versions.v8}`,
buttons: ['确定'],
icon: null
});
}
// 新建文件
function createNewFile(mainWindow) {
mainWindow.webContents.send('file:new');
}
// 打开文件
function openFile(mainWindow) {
mainWindow.webContents.send('file:open');
}
// 保存文件
function saveFile(mainWindow) {
mainWindow.webContents.send('file:save');
}
// 另存为
function saveAsFile(mainWindow) {
mainWindow.webContents.send('file:save-as');
}
// 清除最近文件
function clearRecentFiles() {
app.clearRecentDocuments();
}
// 打开设置
function openSettings() {
console.log('打开设置');
}
// 打开文档
function openDocumentation() {
console.log('打开文档');
}
// 打开教程
function openTutorial() {
console.log('打开教程');
}
// 打开常见问题
function openFAQ() {
console.log('打开常见问题');
}
// 检查更新
function checkUpdates() {
console.log('检查更新');
}
// 创建上下文菜单
function createContextMenu() {
const contextMenu = Menu.buildFromTemplate([
{
label: '复制',
role: 'copy'
},
{
label: '粘贴',
role: 'paste'
},
{
label: '剪切',
role: 'cut'
},
{
type: 'separator'
},
{
label: '全选',
role: 'selectAll'
}
]);
return contextMenu;
}
module.exports = {
createMenu,
createContextMenu,
showAboutDialog
};5.4 原生通知
原生通知可以向用户发送系统级别的通知,提高用户体验。
5.4.1 基本通知
// src/main/notifications.js
const { app, Notification } = require('electron');
const path = require('path');
// 检查通知权限
function checkNotificationPermission() {
if (Notification.isSupported()) {
console.log('通知功能可用');
return true;
} else {
console.log('通知功能不可用');
return false;
}
}
// 发送通知
function sendNotification(options) {
if (!checkNotificationPermission()) {
return null;
}
const defaultOptions = {
title: 'Electron App',
body: '这是一条通知',
icon: path.join(__dirname, '../assets/icon.png'),
silent: false,
timeoutType: 'default',
urgency: 'normal' // low, normal, critical
};
const notification = new Notification({
...defaultOptions,
...options
});
notification.show();
return notification;
}
// 发送成功通知
function sendSuccessNotification(title, body) {
return sendNotification({
title,
body,
icon: path.join(__dirname, '../assets/success.png')
});
}
// 发送错误通知
function sendErrorNotification(title, body) {
return sendNotification({
title,
body,
icon: path.join(__dirname, '../assets/error.png'),
urgency: 'critical'
});
}
// 发送警告通知
function sendWarningNotification(title, body) {
return sendNotification({
title,
body,
icon: path.join(__dirname, '../assets/warning.png'),
urgency: 'normal'
});
}
// 发送信息通知
function sendInfoNotification(title, body) {
return sendNotification({
title,
body,
icon: path.join(__dirname, '../assets/info.png'),
urgency: 'low'
});
}
// 发送带按钮的通知(仅支持某些平台)
function sendNotificationWithButtons(title, body, buttons) {
return sendNotification({
title,
body,
actions: buttons.map((button, index) => ({
type: 'button',
text: button.text,
id: `button-${index}`
})),
hasReply: false
});
}
// 发送带回复的通知(仅支持某些平台)
function sendNotificationWithReply(title, body) {
return sendNotification({
title,
body,
hasReply: true,
replyPlaceholder: '输入回复...'
});
}
// 示例:发送下载完成通知
function sendDownloadCompleteNotification(fileName, filePath) {
const notification = sendSuccessNotification(
'下载完成',
`文件 "${fileName}" 已下载完成`
);
if (notification) {
notification.on('click', () => {
// 打开文件所在目录
const { shell } = require('electron');
shell.showItemInFolder(filePath);
});
}
return notification;
}
// 示例:发送更新通知
function sendUpdateNotification(version) {
const notification = sendInfoNotification(
'应用更新',
`发现新版本 ${version},是否立即更新?`
);
if (notification) {
notification.on('click', () => {
// 打开更新页面
console.log('打开更新页面');
});
}
return notification;
}
module.exports = {
checkNotificationPermission,
sendNotification,
sendSuccessNotification,
sendErrorNotification,
sendWarningNotification,
sendInfoNotification,
sendNotificationWithButtons,
sendNotificationWithReply,
sendDownloadCompleteNotification,
sendUpdateNotification
};5.5 系统剪贴板
系统剪贴板可以在应用和系统之间共享数据,支持文本、图片等多种格式。
5.5.1 基本操作
// src/main/clipboard.js
const { clipboard, nativeImage } = require('electron');
// 写入文本
function writeText(text) {
clipboard.writeText(text);
console.log('文本已复制到剪贴板');
}
// 读取文本
function readText() {
const text = clipboard.readText();
console.log('剪贴板中的文本:', text);
return text;
}
// 写入HTML
function writeHTML(html) {
clipboard.writeHTML(html);
console.log('HTML已复制到剪贴板');
}
// 读取HTML
function readHTML() {
const html = clipboard.readHTML();
console.log('剪贴板中的HTML:', html);
return html;
}
// 写入图片
function writeImage(imagePath) {
const image = nativeImage.createFromPath(imagePath);
clipboard.writeImage(image);
console.log('图片已复制到剪贴板');
}
// 读取图片
function readImage() {
const image = clipboard.readImage();
if (!image.isEmpty()) {
console.log('剪贴板中有图片');
return image;
}
console.log('剪贴板中没有图片');
return null;
}
// 写入RTF
function writeRTF(rtf) {
clipboard.writeRTF(rtf);
console.log('RTF已复制到剪贴板');
}
// 读取RTF
function readRTF() {
const rtf = clipboard.readRTF();
console.log('剪贴板中的RTF:', rtf);
return rtf;
}
// 写入多种格式
function writeMultipleFormats(data) {
clipboard.write(data);
console.log('多种格式数据已复制到剪贴板');
}
// 读取多种格式
function readMultipleFormats() {
const formats = clipboard.availableFormats();
console.log('剪贴板中可用的格式:', formats);
const data = {};
formats.forEach(format => {
try {
data[format] = clipboard.read(format);
} catch (error) {
console.error(`读取格式 ${format} 时出错:`, error);
}
});
return data;
}
// 清除剪贴板
function clearClipboard() {
clipboard.clear();
console.log('剪贴板已清除');
}
// 检查剪贴板是否为空
function isClipboardEmpty() {
const isEmpty = clipboard.isEmpty();
console.log('剪贴板是否为空:', isEmpty);
return isEmpty;
}
// 示例:复制文件路径
function copyFilePath(filePath) {
writeText(filePath);
console.log(`文件路径已复制: ${filePath}`);
}
// 示例:复制图片
function copyImageToClipboard(imagePath) {
writeImage(imagePath);
console.log(`图片已复制: ${imagePath}`);
}
module.exports = {
writeText,
readText,
writeHTML,
readHTML,
writeImage,
readImage,
writeRTF,
readRTF,
writeMultipleFormats,
readMultipleFormats,
clearClipboard,
isClipboardEmpty,
copyFilePath,
copyImageToClipboard
};5.5.2 高级操作
// src/main/advanced-clipboard.js
const { clipboard, nativeImage } = require('electron');
const fs = require('fs').promises;
const path = require('path');
// 监控剪贴板变化
function watchClipboard(callback, interval = 1000) {
let previousText = clipboard.readText();
let previousImage = clipboard.readImage().toDataURL();
const watcher = setInterval(() => {
const currentText = clipboard.readText();
const currentImage = clipboard.readImage().toDataURL();
if (currentText !== previousText) {
previousText = currentText;
callback('text', currentText);
}
if (currentImage !== previousImage) {
previousImage = currentImage;
callback('image', currentImage);
}
}, interval);
return {
stop: () => clearInterval(watcher)
};
}
// 从剪贴板保存图片
async function saveImageFromClipboard(savePath) {
const image = clipboard.readImage();
if (image.isEmpty()) {
throw new Error('剪贴板中没有图片');
}
// 确保目录存在
const dir = path.dirname(savePath);
await fs.mkdir(dir, { recursive: true });
// 保存图片
const imageBuffer = image.toPNG();
await fs.writeFile(savePath, imageBuffer);
console.log(`图片已保存到: ${savePath}`);
return savePath;
}
// 复制带有元数据的文本
function copyTextWithMetadata(text, metadata) {
const data = {
'text/plain': text,
'application/json': JSON.stringify(metadata)
};
clipboard.write(data);
console.log('文本和元数据已复制到剪贴板');
}
// 读取带有元数据的文本
function readTextWithMetadata() {
const text = clipboard.readText();
let metadata = null;
try {
const metadataStr = clipboard.read('application/json');
metadata = JSON.parse(metadataStr);
} catch (error) {
console.error('读取元数据时出错:', error);
}
return {
text,
metadata
};
}
module.exports = {
watchClipboard,
saveImageFromClipboard,
copyTextWithMetadata,
readTextWithMetadata
};5.6 Shell 操作
Shell 模块提供了与桌面集成的功能,如打开文件、文件夹、URL 等。
5.6.1 基本操作
// src/main/shell.js
const { shell } = require('electron');
// 打开文件
function openFile(filePath) {
return shell.openPath(filePath);
}
// 打开文件夹
function openFolder(folderPath) {
return shell.showItemInFolder(folderPath);
}
// 打开URL
function openURL(url) {
return shell.openExternal(url);
}
// 移动文件到回收站
function moveToTrash(filePath) {
return shell.trashItem(filePath);
}
// 显示文件属性
function showFileProperties(filePath) {
return shell.showItemInFolder(filePath);
}
// 执行默认操作
function executeDefaultAction(filePath) {
return shell.openPath(filePath);
}
// 示例:打开文档
function openDocument(docPath) {
shell.openPath(docPath)
.then(success => {
if (success) {
console.log(`文档已打开: ${docPath}`);
} else {
console.error(`无法打开文档: ${docPath}`);
}
});
}
// 示例:打开GitHub
function openGitHub() {
shell.openExternal('https://github.com')
.then(success => {
if (success) {
console.log('GitHub已打开');
} else {
console.error('无法打开GitHub');
}
});
}
// 示例:删除文件到回收站
function deleteToTrash(filePath) {
shell.trashItem(filePath)
.then(success => {
if (success) {
console.log(`文件已移动到回收站: ${filePath}`);
} else {
console.error(`无法删除文件: ${filePath}`);
}
});
}
module.exports = {
openFile,
openFolder,
openURL,
moveToTrash,
showFileProperties,
executeDefaultAction,
openDocument,
openGitHub,
deleteToTrash
};📦 打包发布
打包和发布是Electron应用开发的重要环节,涉及多平台构建、代码签名、自动更新等多个方面。
6.1 electron-builder 配置
6.1.1 基本配置
// package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Electron 应用",
"main": "src/main/index.js",
"scripts": {
"start": "electron .",
"dev": "electron .",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux",
"build:all": "electron-builder --win --mac --linux",
"build:win32": "electron-builder --win --ia32",
"build:win64": "electron-builder --win --x64",
"build:mac-arm": "electron-builder --mac --arm64",
"build:mac-x64": "electron-builder --mac --x64",
"build:linux-arm": "electron-builder --linux --arm64",
"build:linux-x64": "electron-builder --linux --x64"
},
"build": {
"appId": "com.example.app",
"productName": "My Electron App",
"copyright": "Copyright © 2026 ${author}",
"directories": {
"output": "dist",
"buildResources": "src/assets"
},
"files": [
"src/**/*",
"package.json"
],
"extraResources": [
{
"from": "src/resources",
"to": "resources"
}
],
"mac": {
"target": [
"dmg",
"pkg",
"zip"
],
"icon": "src/assets/icon.icns",
"category": "public.app-category.utilities",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"notarize": {
"teamId": "YOUR_TEAM_ID"
}
},
"win": {
"target": [
"nsis",
"portable",
"zip"
],
"icon": "src/assets/icon.ico",
"publisherName": "Your Company",
"signingHashAlgorithms": ["sha256"],
"rfc3161TimeStampServer": "http://timestamp.digicert.com"
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm",
"snap",
"tar.gz"
],
"icon": "src/assets/icon.png",
"category": "Utility",
"desktop": {
"Name": "My Electron App",
"Comment": "An Electron application",
"Categories": "Utility;Application"
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "src/assets/installer.ico",
"uninstallerIcon": "src/assets/uninstaller.ico",
"installerHeaderIcon": "src/assets/installer.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "My App"
},
"appImage": {
"category": "Utility"
},
"deb": {
"maintainer": "Your Name <your.email@example.com>",
"depends": ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]
},
"rpm": {
"license": "MIT",
"vendor": "Your Company",
"category": "Utility"
},
"snap": {
"grade": "stable",
"confinement": "strict",
"summary": "My Electron App",
"description": "An Electron application built with modern web technologies"
},
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "your-repo",
"token": "${GH_TOKEN}",
"releaseType": "release",
"prerelease": false
}
]
},
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.0.0",
"electron-notarize": "^1.2.0"
}
}6.1.2 签名配置
<!-- build/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.app-sandbox</key>
<true/>
<!-- 网络访问 -->
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- 文件系统访问 -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<!-- 进程间通信 -->
<key>com.apple.security.automation.apple-events</key>
<true/>
<!-- 硬件访问 -->
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<!-- 系统功能 -->
<key>com.apple.security.screenCapture</key>
<true/>
<key>com.apple.security.print</key>
<true/>
</dict>
</plist>6.2 自动更新
6.2.1 electron-updater 配置
// src/main/updater.js
const { app, dialog, autoUpdater } = require('electron');
const path = require('path');
const log = require('electron-log');
// 配置日志
log.transports.file.level = 'info';
log.transports.console.level = 'info';
autoUpdater.logger = log;
// 配置自动更新
function setupAutoUpdater(mainWindow) {
// 设置更新源
const updateFeed = 'https://github.com/your-username/your-repo/releases/download/v{version}/';
if (process.env.NODE_ENV === 'production') {
autoUpdater.setFeedURL({
provider: 'github',
owner: 'your-username',
repo: 'your-repo',
releaseType: 'release'
});
// 检查更新频率(每小时)
setInterval(() => {
autoUpdater.checkForUpdates();
}, 60 * 60 * 1000);
// 初始检查
autoUpdater.checkForUpdates();
}
// 监听更新事件
autoUpdater.on('checking-for-update', () => {
log.info('检查更新中...');
});
autoUpdater.on('update-available', (info) => {
log.info('发现新版本:', info.version);
dialog.showMessageBox({
type: 'info',
title: '更新可用',
message: `发现新版本 ${info.version}`,
detail: '是否立即下载并安装更新?',
buttons: ['稍后', '立即更新'],
defaultId: 1
}).then((result) => {
if (result.response === 1) {
autoUpdater.downloadUpdate();
}
});
});
autoUpdater.on('update-not-available', () => {
log.info('当前已是最新版本');
});
autoUpdater.on('error', (error) => {
log.error('更新出错:', error);
dialog.showMessageBox({
type: 'error',
title: '更新错误',
message: '检查更新时出错',
detail: error.message,
buttons: ['确定']
});
});
autoUpdater.on('download-progress', (progressObj) => {
const percentage = Math.round(progressObj.percent);
log.info(`下载进度: ${percentage}%`);
// 发送进度到渲染进程
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-progress', {
percentage,
transferred: progressObj.transferred,
total: progressObj.total
});
}
});
autoUpdater.on('update-downloaded', (info) => {
log.info('更新已下载完成');
dialog.showMessageBox({
type: 'info',
title: '更新已下载',
message: `新版本 ${info.version} 已下载完成`,
detail: '应用将重启并安装更新',
buttons: ['立即重启', '稍后重启'],
defaultId: 0
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});
}
// 手动检查更新
function checkForUpdates() {
if (process.env.NODE_ENV === 'production') {
autoUpdater.checkForUpdates();
}
}
module.exports = {
setupAutoUpdater,
checkForUpdates
};6.3 CI/CD 配置
6.3.1 GitHub Actions
# .github/workflows/build.yml
name: Build and Release
on:
push:
tags: [ 'v*' ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build for Windows
if: matrix.os == 'windows-latest'
run: npm run build:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
- name: Build for macOS
if: matrix.os == 'macos-latest'
run: npm run build:mac
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Build for Linux
if: matrix.os == 'ubuntu-latest'
run: npm run build:linux
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: builds-${{ matrix.os }}
path: dist/
release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
artifacts: 'dist/**/*'
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
prerelease: false
generateReleaseNotes: true🔒 安全最佳实践
安全是Electron应用开发的重要考虑因素,以下是一些关键的安全最佳实践。
7.1 基本安全配置
// src/main/index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// 安全配置
nodeIntegration: false, // 禁用节点集成
contextIsolation: true, // 启用上下文隔离
sandbox: true, // 启用沙箱
webSecurity: true, // 启用Web安全
allowRunningInsecureContent: false, // 禁用不安全内容
enableRemoteModule: false, // 禁用远程模块
preload: path.join(__dirname, '../preload/index.js')
}
});
// 其他配置...
}7.2 Content Security Policy
<!-- src/renderer/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron App</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none';">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- 内容 -->
</body>
</html>7.3 安全的 IPC 通信
// src/preload/index.js
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
openFile: () => ipcRenderer.invoke('file:open'),
saveFile: (content) => ipcRenderer.invoke('file:save', content),
// 窗口操作
minimize: () => ipcRenderer.invoke('window:minimize'),
maximize: () => ipcRenderer.invoke('window:maximize'),
close: () => ipcRenderer.invoke('window:close'),
// 应用信息
getAppInfo: () => ipcRenderer.invoke('app:get-info'),
// 消息通信(仅允许特定通道)
onUpdateAvailable: (callback) => ipcRenderer.on('update:available', (event, ...args) => callback(...args)),
onUpdateProgress: (callback) => ipcRenderer.on('update:progress', (event, ...args) => callback(...args))
});7.4 防注入措施
// src/main/ipc-security.js
const { ipcMain } = require('electron');
// 定义允许的通道
const ALLOWED_CHANNELS = {
'file:open': true,
'file:save': true,
'window:minimize': true,
'window:maximize': true,
'window:close': true,
'app:get-info': true
};
// 验证通道
function validateChannel(channel) {
return ALLOWED_CHANNELS[channel] === true;
}
// 验证数据
function validateData(data, expectedType) {
if (expectedType === 'string') {
return typeof data === 'string';
} else if (expectedType === 'object') {
return typeof data === 'object' && data !== null;
}
return true;
}
// 安全的 IPC 处理
ipcMain.handle('file:save', (event, content) => {
if (!validateData(content, 'string')) {
throw new Error('无效的数据格式');
}
// 处理保存文件逻辑
// ...
});7.5 依赖安全
7.5.1 依赖检查
# 安装依赖检查工具
npm install -g npm-audit
# 检查依赖漏洞
npm audit
# 自动修复漏洞
npm audit fix
# 深度检查
npm audit --production7.5.2 安全的依赖管理
// package.json
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"security": "npm audit && npm outdated"
}
}⚡ 性能优化
性能优化是Electron应用开发的重要环节,以下是一些关键的优化策略。
8.1 启动优化
8.1.1 减少启动时间
// src/main/index.js
const { app, BrowserWindow } = require('electron');
// 禁用硬件加速(如果遇到启动问题)
// app.disableHardwareAcceleration();
// 延迟加载非关键模块
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// 性能配置
backgroundThrottling: false,
disableHtmlFullscreenWindowResize: true,
// 其他配置...
}
});
// 加载页面
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
// 延迟加载其他功能
setTimeout(() => {
require('./tray');
require('./updater');
}, 1000);
}8.2 运行时优化
8.2.1 内存优化
// 监控内存使用
function monitorMemory() {
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log('内存使用情况:', {
rss: (memoryUsage.rss / 1024 / 1024).toFixed(2) + ' MB',
heapTotal: (memoryUsage.heapTotal / 1024 / 1024).toFixed(2) + ' MB',
heapUsed: (memoryUsage.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
external: (memoryUsage.external / 1024 / 1024).toFixed(2) + ' MB'
});
}, 5000);
}
// 清理内存
function cleanupMemory() {
if (global.gc) {
global.gc();
console.log('内存已清理');
}
}8.2.2 渲染性能
// src/renderer/performance.js
// 使用 requestAnimationFrame 进行动画
function animate() {
requestAnimationFrame(animate);
// 动画逻辑...
}
// 防抖和节流
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 executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}8.3 资源优化
8.3.1 打包优化
// package.json
{
"build": {
"compression": "maximum",
"asar": true,
"asarUnpack": [
"**/*.node",
"**/*.dll",
"**/*.so",
"**/*.dylib"
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
}
}
}🔍 调试工具
9.1 开发工具
9.1.1 Chrome DevTools
// src/main/index.js
const { app, BrowserWindow } = require('electron');
function createWindow() {
const mainWindow = new BrowserWindow({/*...*/});
// 打开开发者工具
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({
mode: 'detach',
activate: true
});
}
}9.1.2 主进程调试
# 启动主进程调试
npm run start:debug
# 或直接使用命令
electron --inspect=5858 .
# 然后在 Chrome 中访问
# chrome://inspect/#devices9.2 日志管理
// src/main/logger.js
const log = require('electron-log');
const path = require('path');
// 配置日志
log.transports.file.resolvePath = () => path.join(__dirname, '../../logs/main.log');
log.transports.file.level = 'info';
log.transports.console.level = 'info';
// 自定义日志格式
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';
// 导出日志实例
module.exports = {
info: (message) => log.info(message),
warn: (message) => log.warn(message),
error: (message) => log.error(message),
debug: (message) => log.debug(message)
};🎯 最佳实践
10.1 代码组织
10.1.1 模块化设计
- 主进程模块:按功能拆分(窗口管理、IPC、托盘等)
- 渲染进程模块:按页面和功能组织
- 预加载脚本:仅暴露必要的API
- 共享模块:提取通用功能到共享目录
10.1.2 命名规范
- 文件命名:使用kebab-case(如
window-manager.js) - 类命名:使用PascalCase(如
WindowManager) - 函数命名:使用camelCase(如
createWindow) - 常量命名:使用UPPER_SNAKE_CASE(如
MAX_WINDOW_WIDTH)
10.2 开发流程
10.2.1 开发环境
- 热重载:使用
electron-reload或 Vite - 代码格式化:使用 Prettier
- 代码检查:使用 ESLint
- 类型检查:使用 TypeScript(可选)
10.2.2 版本管理
- 语义化版本:遵循 MAJOR.MINOR.PATCH
- Git 规范:使用 Conventional Commits
- 发布流程:自动化构建和发布
10.3 跨平台兼容性
10.3.1 平台差异处理
// src/main/platform.js
const os = require('os');
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
function getPlatformSpecificPath() {
if (isMac) {
return path.join(os.homedir(), 'Library', 'Application Support', 'My App');
} else if (isWindows) {
return path.join(process.env.APPDATA, 'My App');
} else {
return path.join(os.homedir(), '.my-app');
}
}
module.exports = {
isMac,
isWindows,
isLinux,
getPlatformSpecificPath
};❓ 常见问题
11.1 开发问题
11.1.1 白屏问题
- 原因:页面加载失败、渲染进程崩溃、依赖错误
- 解决方案:
- 检查控制台错误
- 确保预加载脚本正确
- 检查 CSP 配置
- 验证依赖安装
11.1.2 HMR 不工作
- 原因:配置错误、端口被占用
- 解决方案:
- 检查开发服务器配置
- 确保端口未被占用
- 验证文件监听配置
11.2 打包问题
11.2.1 打包失败
- 原因:依赖错误、配置错误、签名问题
- 解决方案:
- 检查依赖安装
- 验证 electron-builder 配置
- 检查签名证书
- 查看详细错误日志
11.2.2 应用启动失败
- 原因:缺少依赖、权限问题、路径错误
- 解决方案:
- 检查 asar 打包配置
- 确保依赖正确打包
- 验证文件权限
- 检查日志文件
11.3 安全问题
11.3.1 安全警告
- 原因:不安全的配置、过时的依赖
- 解决方案:
- 启用所有安全配置
- 更新依赖到最新版本
- 定期运行安全扫描
📝 总结
Electron 是一个强大的框架,允许开发者使用 Web 技术构建跨平台桌面应用。通过本文的学习,您应该掌握了:
- 核心概念:主进程/渲染进程架构、IPC 通信、原生功能集成
- 开发流程:环境搭建、项目结构、调试技巧
- 高级特性:系统集成、打包发布、自动更新
- 最佳实践:安全配置、性能优化、跨平台兼容性
12.1 关键要点
- 安全第一:始终启用安全配置,避免使用不安全的 API
- 性能优化:关注启动时间、内存使用和渲染性能
- 用户体验:利用原生功能提升应用体验
- 跨平台兼容:处理平台差异,确保多平台正常运行
- 持续更新:保持依赖和 Electron 版本更新
12.2 未来发展
Electron 生态系统持续发展,未来的趋势包括:
- 更好的性能:持续优化启动时间和内存使用
- 更安全:加强默认安全配置
- 更集成:与原生系统更深度集成
- 更便捷:简化开发和打包流程
📚 参考资源
13.1 官方文档
13.2 学习资源
13.3 工具和库
开发工具:
UI 框架:
13.4 社区资源
13.5 相关文章
// 复制文件路径到剪贴板 function copyFiles(filePaths) { clipboard.writeBuffer('public.file-urls', Buffer.from(filePaths.join('\n'))); return true; }
// 从剪贴板读取文件路径 function getFiles() { const buffer = clipboard.readBuffer('public.file-urls'); if (buffer) { return buffer.toString().split('\n').filter(Boolean); } return []; }
// 检查剪贴板内容类型 function getClipboardType() { if (clipboard.hasImage()) { return 'image'; } else if (clipboard.readHTML() !== '') { return 'html'; } else if (clipboard.readText() !== '') { return 'text'; } else if (getFiles().length > 0) { return 'files'; } else { return 'empty'; } }
// 清空剪贴板 function clearClipboard() { clipboard.clear(); return getClipboardType() === 'empty'; }
// 示例:复制代码 function copyCode(code, language = 'javascript') { // 复制纯文本 const success = copyText(code);
// 同时复制 HTML 格式(带语法高亮) const html = <pre><code class="language-${language}">${code}</code></pre>; copyHTML(html, code);
return success; }
// 示例:复制文件路径 function copyFilePath(filePath) { return copyText(filePath); }
module.exports = { copyText, getText, copyHTML, getHTML, copyImage, getImage, copyFiles, getFiles, getClipboardType, clearClipboard, copyCode, copyFilePath };
### 5.6 系统 Shell
系统 Shell 用于执行系统级别的操作,如打开文件、文件夹和 URL。
#### 5.6.1 基本 Shell 操作
```javascript
// src/main/shell.js
const { shell } = require('electron');
const fs = require('fs').promises;
const path = require('path');
// 打开文件
async function openFile(filePath) {
try {
// 检查文件是否存在
await fs.access(filePath);
await shell.openPath(filePath);
return true;
} catch (error) {
console.error('打开文件失败:', error);
return false;
}
}
// 打开文件夹
async function openFolder(folderPath) {
try {
// 检查文件夹是否存在
const stats = await fs.stat(folderPath);
if (!stats.isDirectory()) {
throw new Error('路径不是文件夹');
}
await shell.openPath(folderPath);
return true;
} catch (error) {
console.error('打开文件夹失败:', error);
return false;
}
}
// 显示文件在文件夹中
async function showFileInFolder(filePath) {
try {
// 检查文件是否存在
await fs.access(filePath);
shell.showItemInFolder(filePath);
return true;
} catch (error) {
console.error('显示文件失败:', error);
return false;
}
}
// 打开 URL
async function openURL(url) {
try {
await shell.openExternal(url);
return true;
} catch (error) {
console.error('打开 URL 失败:', error);
return false;
}
}
// 打开邮箱
async function openEmail(email, subject = '', body = '') {
const mailtoUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
return await openURL(mailtoUrl);
}
// 打开搜索
async function openSearch(query, engine = 'google') {
const searchUrls = {
google: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
baidu: `https://www.baidu.com/s?wd=${encodeURIComponent(query)}`,
bing: `https://www.bing.com/search?q=${encodeURIComponent(query)}`,
duckduckgo: `https://duckduckgo.com/?q=${encodeURIComponent(query)}`
};
const url = searchUrls[engine] || searchUrls.google;
return await openURL(url);
}
// 移动文件到回收站
async function moveToTrash(filePath) {
try {
await shell.trashItem(filePath);
return true;
} catch (error) {
console.error('移动到回收站失败:', error);
return false;
}
}
// 检查是否可以处理协议
function canHandleProtocol(protocol) {
return shell.hasDefaultProtocolClient(protocol);
}
// 设置默认协议客户端
function setDefaultProtocolClient(protocol, path, args) {
return shell.setAsDefaultProtocolClient(protocol, path, args);
}
// 取消默认协议客户端
function unsetDefaultProtocolClient(protocol) {
return shell.unsetAsDefaultProtocolClient(protocol);
}
// 示例:打开项目文档
async function openDocumentation() {
const docPath = path.join(__dirname, '../../docs/index.html');
return await openFile(docPath);
}
// 示例:打开 GitHub 仓库
async function openGitHub() {
return await openURL('https://github.com/yourusername/yourapp');
}
// 示例:联系支持
async function contactSupport() {
return await openEmail('support@example.com', '应用支持', '请描述您遇到的问题:\n\n');
}
module.exports = {
openFile,
openFolder,
showFileInFolder,
openURL,
openEmail,
openSearch,
moveToTrash,
canHandleProtocol,
setDefaultProtocolClient,
unsetDefaultProtocolClient,
openDocumentation,
openGitHub,
contactSupport
};5.7 原生拖放
原生拖放功能允许用户在应用和系统之间拖放文件和数据。
5.7.1 基本拖放实现
// src/main/drag-and-drop.js
const { ipcMain } = require('electron');
// 注册拖放事件
function setupDragAndDrop(mainWindow) {
// 监听渲染进程的拖放事件
ipcMain.on('file-dropped', (event, files) => {
console.log('收到拖放的文件:', files);
// 处理拖放的文件
handleDroppedFiles(files);
});
// 监听渲染进程的拖动开始事件
ipcMain.on('drag-start', (event, data) => {
console.log('开始拖动:', data);
// 处理拖动开始
});
}
// 处理拖放的文件
async function handleDroppedFiles(files) {
for (const file of files) {
console.log('处理文件:', file);
// 根据文件类型进行不同处理
if (file.endsWith('.txt')) {
await handleTextFile(file);
} else if (file.endsWith('.json')) {
await handleJsonFile(file);
} else if (file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg')) {
await handleImageFile(file);
} else {
await handleUnknownFile(file);
}
}
}
// 处理文本文件
async function handleTextFile(filePath) {
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf8');
console.log('文本文件内容:', content);
// 可以发送到渲染进程显示
} catch (error) {
console.error('读取文本文件失败:', error);
}
}
// 处理 JSON 文件
async function handleJsonFile(filePath) {
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf8');
const data = JSON.parse(content);
console.log('JSON 文件内容:', data);
// 可以发送到渲染进程显示
} catch (error) {
console.error('读取 JSON 文件失败:', error);
}
}
// 处理图像文件
async function handleImageFile(filePath) {
try {
console.log('图像文件:', filePath);
// 可以发送到渲染进程显示
} catch (error) {
console.error('处理图像文件失败:', error);
}
}
// 处理未知类型文件
async function handleUnknownFile(filePath) {
console.log('未知类型文件:', filePath);
// 可以显示文件信息
}
module.exports = {
setupDragAndDrop,
handleDroppedFiles
};// src/preload/drag-and-drop.js
const { contextBridge, ipcRenderer } = require('electron');
// 暴露拖放 API
contextBridge.exposeInMainWorld('dragAndDrop', {
// 发送拖放文件事件
sendDroppedFiles: (files) => {
ipcRenderer.send('file-dropped', files);
},
// 发送拖动开始事件
sendDragStart: (data) => {
ipcRenderer.send('drag-start', data);
}
});// src/renderer/drag-and-drop.js
// 初始化拖放
function initializeDragAndDrop() {
setupDragAndDropListeners();
setupDragStartListeners();
}
// 设置拖放监听器
function setupDragAndDropListeners() {
const dropZone = document.getElementById('drop-zone');
if (!dropZone) return;
// 拖动进入
dropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('drag-over');
});
// 拖动离开
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('drag-over');
});
// 拖动悬停
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
});
// 放置
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('drag-over');
// 获取拖放的文件
const files = Array.from(e.dataTransfer.files).map(file => file.path);
if (files.length > 0) {
// 发送到主进程
window.dragAndDrop.sendDroppedFiles(files);
// 显示拖放信息
showDropInfo(files);
}
});
}
// 设置拖动开始监听器
function setupDragStartListeners() {
const draggableItems = document.querySelectorAll('.draggable');
draggableItems.forEach(item => {
item.setAttribute('draggable', 'true');
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', item.textContent);
e.dataTransfer.effectAllowed = 'copy';
// 发送到主进程
window.dragAndDrop.sendDragStart({
text: item.textContent,
id: item.id
});
});
});
}
// 显示拖放信息
function showDropInfo(files) {
const dropInfo = document.getElementById('drop-info');
if (!dropInfo) return;
dropInfo.innerHTML = `
<h3>拖放的文件:</h3>
<ul>
${files.map(file => `<li>${file}</li>`).join('')}
</ul>
`;
// 3秒后隐藏
setTimeout(() => {
dropInfo.innerHTML = '';
}, 3000);
}
// 初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeDragAndDrop);
} else {
initializeDragAndDrop();
}5.8 原生功能最佳实践
5.8.1 性能优化
- 延迟加载:只在需要时加载原生模块
- 缓存结果:缓存频繁使用的系统信息
- 批量操作:批量处理文件和系统操作
- 异步处理:使用异步 API 避免阻塞主线程
- 错误处理:妥善处理原生 API 可能的错误
5.8.2 跨平台兼容性
- 平台检测:使用
process.platform检测平台 - 条件代码:为不同平台提供不同实现
- 测试:在所有目标平台上测试应用
- 文档:记录平台特定的行为
5.8.3 用户体验
- 响应速度:保持 UI 响应,避免长时间阻塞
- 反馈:为用户操作提供明确的反馈
- 一致性:遵循平台的设计规范
- 可访问性:确保应用对所有用户可用
- 隐私:尊重用户隐私,合理使用系统权限
📦 打包发布
打包发布是 Electron 应用开发的重要环节,涉及到应用的构建、签名、分发和更新。
6.1 打包配置
6.1.1 基本配置
// package.json
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Electron 应用",
"main": "src/main/index.js",
"scripts": {
"start": "electron .",
"dev": "electron .",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux",
"build:all": "electron-builder --win --mac --linux",
"build:win32": "electron-builder --win --ia32",
"build:win64": "electron-builder --win --x64",
"build:mac-arm": "electron-builder --mac --arm64",
"build:mac-intel": "electron-builder --mac --x64",
"release": "electron-builder --publish always"
},
"build": {
"appId": "com.example.myapp",
"productName": "My Electron App",
"copyright": "Copyright © 2024 ${author}",
"version": "${version}",
"directories": {
"output": "dist",
"buildResources": "src/assets",
"app": "."
},
"files": [
"src/**/*",
"package.json",
"!node_modules/**/*",
"!*.md",
"!.gitignore",
"!.eslintrc.js",
"!.prettierrc"
],
"extraResources": [
{
"from": "resources/",
"to": "resources/"
}
],
"extraFiles": [
{
"from": "config/",
"to": "config/"
}
],
"asar": true,
"asarUnpack": [
"**/*.node",
"**/*.dll",
"**/*.so",
"**/*.dylib"
],
"protocols": [
{
"name": "My App Protocol",
"schemes": ["myapp"]
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "src/assets/icon.ico",
"publisherName": "Your Company Name",
"signingHashAlgorithms": ["sha256"],
"verifyUpdateCodeSignature": true,
"rfc3161TimeStampServer": "http://timestamp.digicert.com",
"legalTrademarks": "Your Trademark",
"fileAssociations": [
{
"ext": "txt",
"name": "Text File",
"icon": "src/assets/file-icons/txt.ico"
}
]
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "pkg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "src/assets/icon.icns",
"category": "public.app-category.utilities",
"bundleVersion": "1.0.0",
"minimumSystemVersion": "10.15.0",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.inherit.plist",
"provisioningProfile": "build/profile.provisionprofile"
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm",
"tar.gz"
],
"icon": "src/assets",
"category": "Utility",
"executableName": "my-electron-app",
"desktop": {
"Name": "My Electron App",
"Comment": "An Electron application",
"Categories": "Utility;Application;"
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "My Electron App",
"installerIcon": "src/assets/installer.ico",
"uninstallerIcon": "src/assets/uninstaller.ico",
"installerHeaderIcon": "src/assets/installer-header.ico",
"include": "build/nsis/include.nsh",
"script": "build/nsis/installer.nsh",
"deleteAppDataOnUninstall": false,
"runAfterFinish": true
},
"dmg": {
"background": "build/dmg/background.png",
"icon": "src/assets/dmg-icon.icns",
"iconSize": 80,
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
}
},
"appImage": {
"license": "LICENSE",
"category": "Utility"
},
"deb": {
"depends": ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"],
"maintainer": "Your Name <your@email.com>",
"section": "utils",
"priority": "optional"
},
"rpm": {
"depends": ["gconf2", "gconf-service", "libnotify", "libappindicator", "libXtst", "nss"],
"license": "MIT",
"group": "Applications/Utilities"
},
"publish": [
{
"provider": "github",
"owner": "yourusername",
"repo": "yourapp",
"releaseType": "release",
"draft": false,
"prerelease": false,
"token": "${GH_TOKEN}"
},
{
"provider": "s3",
"bucket": "your-bucket",
"region": "us-east-1",
"path": "releases"
}
]
},
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.0.0",
"electron-updater": "^6.0.0"
}
}6.1.2 高级配置
// electron-builder.config.js
module.exports = {
appId: 'com.example.myapp',
productName: 'My Electron App',
copyright: 'Copyright © 2024 ${author}',
version: '${version}',
directories: {
output: 'dist',
buildResources: 'src/assets',
app: '.'
},
files: [
'src/**/*',
'package.json',
'!node_modules/**/*',
'!*.md',
'!.gitignore',
'!.eslintrc.js',
'!.prettierrc'
],
extraResources: [
{
from: 'resources/',
to: 'resources/'
}
],
asar: true,
asarUnpack: [
'**/*.node',
'**/*.dll',
'**/*.so',
'**/*.dylib'
],
win: {
target: ['nsis', 'portable'],
icon: 'src/assets/icon.ico',
publisherName: 'Your Company Name'
},
mac: {
target: ['dmg', 'pkg', 'zip'],
icon: 'src/assets/icon.icns',
category: 'public.app-category.utilities'
},
linux: {
target: ['AppImage', 'deb', 'rpm', 'tar.gz'],
icon: 'src/assets',
category: 'Utility'
},
publish: [
{
provider: 'github',
owner: 'yourusername',
repo: 'yourapp'
}
]
};6.2 多平台构建
6.2.1 构建命令
# 构建所有平台
npm run build:all
# 构建 Windows 32 位
npm run build:win32
# 构建 Windows 64 位
npm run build:win64
# 构建 macOS ARM
npm run build:mac-arm
# 构建 macOS Intel
npm run build:mac-intel
# 构建 Linux
npm run build:linux
# 发布应用
npm run release6.2.2 CI/CD 配置
# .github/workflows/build.yml
name: Build and Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build application
run: npm run build
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}-build
path: dist/
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: dist/**/*
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}6.3 自动更新
6.3.1 基本更新
// src/main/updater.js
const { app, autoUpdater, dialog } = require('electron-updater');
const { ipcMain } = require('electron');
class AppUpdater {
constructor() {
this.mainWindow = null;
this.isChecking = false;
}
// 初始化更新器
initialize(mainWindow) {
this.mainWindow = mainWindow;
this.setupAutoUpdater();
this.setupIPC();
}
// 设置自动更新器
setupAutoUpdater() {
// 配置更新源
autoUpdater.setFeedURL({
provider: 'github',
owner: 'yourusername',
repo: 'yourapp',
private: false
});
// 配置更新选项
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.allowPrerelease = false;
// 监听更新事件
this.setupEventListeners();
}
// 设置事件监听器
setupEventListeners() {
// 检查更新开始
autoUpdater.on('checking-for-update', () => {
console.log('检查更新中...');
this.sendStatus('checking');
});
// 发现新版本
autoUpdater.on('update-available', (info) => {
console.log('发现新版本:', info.version);
this.sendStatus('available', info);
});
// 无新版本
autoUpdater.on('update-not-available', () => {
console.log('当前已是最新版本');
this.sendStatus('not-available');
});
// 更新错误
autoUpdater.on('error', (error) => {
console.error('更新出错:', error);
this.sendStatus('error', error);
});
// 下载进度
autoUpdater.on('download-progress', (progress) => {
console.log('下载进度:', Math.round(progress.percent));
this.sendStatus('downloading', progress);
});
// 下载完成
autoUpdater.on('update-downloaded', (info) => {
console.log('更新下载完成:', info.version);
this.sendStatus('downloaded', info);
this.showUpdateDialog();
});
}
// 设置 IPC 通信
setupIPC() {
// 检查更新
ipcMain.handle('check-for-updates', async () => {
if (this.isChecking) {
return { status: 'checking' };
}
this.isChecking = true;
try {
await autoUpdater.checkForUpdates();
return { status: 'success' };
} catch (error) {
console.error('检查更新失败:', error);
return { status: 'error', error: error.message };
} finally {
this.isChecking = false;
}
});
// 下载更新
ipcMain.handle('download-update', async () => {
try {
await autoUpdater.downloadUpdate();
return { status: 'success' };
} catch (error) {
console.error('下载更新失败:', error);
return { status: 'error', error: error.message };
}
});
// 安装更新
ipcMain.handle('install-update', async () => {
try {
autoUpdater.quitAndInstall();
return { status: 'success' };
} catch (error) {
console.error('安装更新失败:', error);
return { status: 'error', error: error.message };
}
});
// 取消更新
ipcMain.handle('cancel-update', async () => {
try {
autoUpdater.cancel();
return { status: 'success' };
} catch (error) {
console.error('取消更新失败:', error);
return { status: 'error', error: error.message };
}
});
}
// 发送状态到渲染进程
sendStatus(status, data = null) {
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
this.mainWindow.webContents.send('update-status', { status, data });
}
}
// 显示更新对话框
showUpdateDialog() {
dialog.showMessageBox({
title: '应用更新',
message: '发现新版本',
detail: '更新已下载完成,是否立即安装并重启应用?',
buttons: ['稍后', '立即安装'],
defaultId: 1,
cancelId: 0,
type: 'info'
}).then((result) => {
if (result.response === 1) {
autoUpdater.quitAndInstall();
}
});
}
// 手动检查更新
checkForUpdates() {
return autoUpdater.checkForUpdates();
}
}
// 导出单例
module.exports = new AppUpdater();6.3.2 高级更新策略
// src/main/advanced-updater.js
const { app, autoUpdater, dialog, Notification } = require('electron');
const { ipcMain } = require('electron');
const path = require('path');
class AdvancedUpdater extends AppUpdater {
constructor() {
super();
this.updateInterval = null;
}
// 初始化
initialize(mainWindow) {
super.initialize(mainWindow);
this.setupAutoCheck();
}
// 设置自动检查
setupAutoCheck() {
// 应用启动时检查更新
this.checkForUpdates();
// 每小时自动检查一次
this.updateInterval = setInterval(() => {
this.checkForUpdates();
}, 3600000);
}
// 检查更新
async checkForUpdates() {
try {
console.log('自动检查更新');
await super.checkForUpdates();
} catch (error) {
console.error('自动检查更新失败:', error);
}
}
// 显示更新通知
showUpdateNotification(info) {
const notification = new Notification({
title: '应用更新',
body: `发现新版本 ${info.version},是否立即更新?`,
icon: path.join(__dirname, '../assets/icon.png')
});
notification.on('click', () => {
this.showUpdateDialog();
});
notification.show();
}
// 处理更新可用
handleUpdateAvailable(info) {
console.log('处理更新可用:', info);
this.sendStatus('available', info);
// 显示通知
this.showUpdateNotification(info);
}
// 清理资源
cleanup() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
}
// 导出单例
module.exports = new AdvancedUpdater();6.4 应用签名
6.4.1 Windows 签名
# 安装签名工具
npm install --save-dev electron-windows-sign
# 配置签名环境变量
# 设置证书密码
set CSC_LINK=path/to/certificate.pfx
set CSC_KEY_PASSWORD=your-certificate-password
# 构建并签名
npm run build:win6.4.2 macOS 签名
# 配置签名环境变量
# 设置开发者 ID
export CSC_NAME="Developer ID Application: Your Company (TEAMID)"
# 构建并签名
npm run build:mac
# 验证签名
codesign --verify --deep --strict --verbose=2 dist/mac/My\ Electron\ App.app
# 验证 Notarization
spctl --assess --type execute --verbose=2 dist/mac/My\ Electron\ App.app6.4.3 Notarization (macOS)
// electron-builder.config.js
module.exports = {
// ... 其他配置
mac: {
// ... 其他配置
hardenedRuntime: true,
gatekeeperAssess: false,
entitlements: 'build/entitlements.mac.plist',
entitlementsInherit: 'build/entitlements.mac.inherit.plist',
notarize: {
teamId: 'YOUR_TEAM_ID'
}
}
};<!-- build/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.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>6.5 发布到应用商店
6.5.1 Microsoft Store
// package.json
{
"build": {
// ... 其他配置
"win": {
// ... 其他配置
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "msstore",
"arch": ["x64"]
}
]
},
"msstore": {
"applicationId": "YourAppId",
"publisher": "CN=YourPublisherId"
}
}
}6.5.2 Mac App Store
// package.json
{
"build": {
// ... 其他配置
"mac": {
// ... 其他配置
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "mas",
"arch": ["x64", "arm64"]
}
]
},
"mas": {
"identity": "3rd Party Mac Developer Application: Your Company (TEAMID)",
"provisioningProfile": "build/mas.provisionprofile"
}
}
}6.6 部署策略
6.6.1 手动部署
- 构建应用:使用
npm run build构建应用 - 测试应用:在目标平台上测试构建产物
- 上传构建产物:上传到 GitHub Releases、S3 等
- 发布更新:通知用户更新
6.6.2 自动部署
- 配置 CI/CD:使用 GitHub Actions、Jenkins 等
- 自动构建:代码推送时自动构建
- 自动测试:构建后自动运行测试
- 自动发布:测试通过后自动发布
6.6.3 企业部署
- 内部应用商店:搭建内部应用分发系统
- 静默安装:配置静默安装脚本
- 组策略部署:使用 Windows 组策略部署
- MDM 集成:集成移动设备管理系统
6.7 打包最佳实践
6.7.1 性能优化
减少应用体积:
- 移除未使用的依赖
- 使用 tree-shaking
- 压缩资源文件
- 合理配置
files字段
优化启动速度:
- 延迟加载非关键模块
- 预加载关键资源
- 优化主进程代码
减少内存使用:
- 合理管理资源
- 及时释放不需要的资源
- 使用
asar压缩
6.7.2 安全性
代码签名:
- 对应用进行签名
- 验证更新包签名
安全配置:
- 使用 HTTPS 分发更新
- 配置合理的内容安全策略
- 保护敏感信息
漏洞修复:
- 及时更新依赖
- 定期安全扫描
- 修复已知漏洞
6.7.3 用户体验
安装体验:
- 提供简洁的安装向导
- 支持自定义安装路径
- 提供卸载选项
更新体验:
- 提供明确的更新提示
- 支持自动更新
- 提供手动更新选项
错误处理:
- 妥善处理安装错误
- 提供清晰的错误信息
- 支持回滚机制
6.8 常见打包问题
6.8.1 构建失败
- 问题:构建过程中出现错误
- 解决方案:
- 检查依赖是否正确安装
- 检查构建配置是否正确
- 检查证书配置是否正确
- 查看详细的错误日志
6.8.2 应用无法启动
- 问题:构建后的应用无法启动
- 解决方案:
- 检查主进程入口文件是否正确
- 检查依赖是否完整
- 检查权限是否正确
- 查看应用日志
6.8.3 更新失败
- 问题:自动更新失败
- 解决方案:
- 检查更新源配置是否正确
- 检查网络连接是否正常
- 检查签名是否正确
- 查看更新日志
6.8.4 签名问题
- 问题:应用签名失败
- 解决方案:
- 检查证书是否有效
- 检查证书配置是否正确
- 检查网络连接是否正常
- 查看签名工具日志
6.8.5 平台兼容性
- 问题:应用在某些平台上不兼容
- 解决方案:
- 为不同平台提供不同的构建配置
- 测试所有目标平台
- 使用平台特定的代码
- 提供平台特定的资源
安全最佳实践
1. 上下文隔离
// src/main/index.js
function createWindow() {
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, '../preload/index.js')
}
});
}2. CSP 配置
<!-- src/renderer/index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';">3. 安全通信
// src/preload/index.js
contextBridge.exposeInMainWorld('electronAPI', {
// 只暴露必要的 API
sendMessage: (channel, data) => {
// 白名单通道
const validChannels = ['toMain', 'toRenderer'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
// 验证数据
validateData: (data) => {
// 实现数据验证逻辑
return typeof data === 'object' && data !== null;
}
});性能优化
1. 内存管理
// src/main/index.js
function createWindow() {
const mainWindow = new BrowserWindow({
webPreferences: {
// 启用内存优化
backgroundThrottling: false,
// 禁用远程模块
enableRemoteModule: false
}
});
// 监听内存警告
app.on('render-process-gone', (event, webContents, details) => {
console.log('渲染进程崩溃:', details);
});
// 定期清理内存
setInterval(() => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.session.clearCache();
}
}, 3600000); // 每小时清理一次
}2. 启动优化
// src/main/index.js
app.whenReady().then(() => {
// 延迟加载非关键模块
setTimeout(() => {
require('./background-tasks');
}, 1000);
// 使用 ready-to-show 事件
const mainWindow = new BrowserWindow({
show: false
});
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
});调试工具
1. 开发工具
// src/main/index.js
function createWindow() {
const mainWindow = new BrowserWindow({
webPreferences: {
devTools: process.env.NODE_ENV === 'development'
}
});
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
}2. 日志系统
// src/main/logger.js
const { app } = require('electron');
const path = require('path');
const fs = require('fs');
class Logger {
constructor() {
this.logPath = path.join(app.getPath('userData'), 'logs');
this.ensureLogDirectory();
}
ensureLogDirectory() {
if (!fs.existsSync(this.logPath)) {
fs.mkdirSync(this.logPath);
}
}
log(message, level = 'info') {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
const logFile = path.join(this.logPath, `${level}.log`);
fs.appendFileSync(logFile, logMessage);
if (level === 'error') {
console.error(logMessage);
} else {
console.log(logMessage);
}
}
error(message) {
this.log(message, 'error');
}
warn(message) {
this.log(message, 'warn');
}
info(message) {
this.log(message, 'info');
}
}
module.exports = new Logger();最佳实践
1. 项目结构
src/
├── main/ # 主进程代码
│ ├── index.js # 主进程入口
│ ├── ipc.js # IPC 通信
│ ├── dialogs.js # 系统对话框
│ ├── tray.js # 系统托盘
│ └── updater.js # 自动更新
├── renderer/ # 渲染进程代码
│ ├── index.html # 主页面
│ ├── renderer.js # 渲染进程逻辑
│ └── styles.css # 样式文件
├── preload/ # 预加载脚本
│ └── index.js # 预加载脚本
└── assets/ # 静态资源
├── icon.ico # Windows 图标
├── icon.icns # macOS 图标
└── icon.png # Linux 图标2. 代码规范
// .eslintrc.js
module.exports = {
root: true,
env: {
node: true,
browser: true
},
extends: [
'eslint:recommended'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
};
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}总结
Electron 提供了强大的跨平台桌面应用开发能力,通过合理使用框架特性和遵循最佳实践,可以构建出安全、高性能的桌面应用。