- 发布于
Electron 桌面应用开发:使用 Web 技术构建跨平台桌面应用
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Electron 桌面应用开发:使用 Web 技术构建跨平台桌面应用
Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用的框架。本文将深入探讨 Electron 的架构原理、开发实践和性能优化。
Electron 架构基础
主进程与渲染进程
// main.js - 主进程
const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron')
const path = require('path')
const isDev = process.env.NODE_ENV === 'development'
class ElectronApp {
constructor() {
this.mainWindow = null
this.init()
}
init() {
// 应用准备就绪
app.whenReady().then(() => {
this.createWindow()
this.createMenu()
this.setupIPC()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
this.createWindow()
}
})
})
// 所有窗口关闭
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// 应用即将退出
app.on('before-quit', (event) => {
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
event.preventDefault()
this.mainWindow.webContents.send('app-closing')
}
})
}
createWindow() {
this.mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
icon: path.join(__dirname, 'assets/icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
},
})
// 加载应用
if (isDev) {
this.mainWindow.loadURL('http://localhost:3000')
this.mainWindow.webContents.openDevTools()
} else {
this.mainWindow.loadFile('dist/index.html')
}
// 窗口事件
this.mainWindow.once('ready-to-show', () => {
this.mainWindow.show()
if (isDev) {
this.mainWindow.webContents.openDevTools()
}
})
this.mainWindow.on('closed', () => {
this.mainWindow = null
})
}
createMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '新建',
accelerator: 'CmdOrCtrl+N',
click: () => {
this.mainWindow.webContents.send('menu-new-file')
},
},
{
label: '打开',
accelerator: 'CmdOrCtrl+O',
click: async () => {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'All Files', extensions: ['*'] },
],
})
if (!result.canceled) {
this.mainWindow.webContents.send('menu-open-file', result.filePaths[0])
}
},
},
{ 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)
}
setupIPC() {
// 文件操作
ipcMain.handle('read-file', async (event, filePath) => {
try {
const fs = require('fs').promises
const content = await fs.readFile(filePath, 'utf8')
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, 'utf8')
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 系统信息
ipcMain.handle('get-system-info', () => {
const os = require('os')
return {
platform: process.platform,
arch: process.arch,
version: process.getSystemVersion(),
memory: os.totalmem(),
cpus: os.cpus().length,
}
})
// 应用信息
ipcMain.handle('get-app-info', () => {
return {
name: app.getName(),
version: app.getVersion(),
path: app.getAppPath(),
}
})
}
}
new ElectronApp()
预加载脚本
// preload.js - 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
// 系统信息
getSystemInfo: () => ipcRenderer.invoke('get-system-info'),
getAppInfo: () => ipcRenderer.invoke('get-app-info'),
// 菜单事件监听
onMenuNewFile: (callback) => ipcRenderer.on('menu-new-file', callback),
onMenuOpenFile: (callback) => ipcRenderer.on('menu-open-file', callback),
onAppClosing: (callback) => ipcRenderer.on('app-closing', callback),
// 移除监听器
removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel),
// 窗口控制
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window'),
// 通知
showNotification: (title, body) => {
new Notification(title, { body })
},
// 剪贴板
writeToClipboard: (text) => {
navigator.clipboard.writeText(text)
},
readFromClipboard: () => {
return navigator.clipboard.readText()
},
})
// 渲染进程中使用
// renderer.js
class App {
constructor() {
this.init()
}
async init() {
// 获取系统信息
const systemInfo = await window.electronAPI.getSystemInfo()
console.log('系统信息:', systemInfo)
// 监听菜单事件
window.electronAPI.onMenuNewFile(() => {
this.createNewFile()
})
window.electronAPI.onMenuOpenFile((event, filePath) => {
this.openFile(filePath)
})
window.electronAPI.onAppClosing(() => {
this.saveBeforeClose()
})
// 设置 UI 事件
this.setupUI()
}
async createNewFile() {
// 创建新文件逻辑
document.getElementById('editor').value = ''
document.title = '新文件 - 文本编辑器'
}
async openFile(filePath) {
try {
const result = await window.electronAPI.readFile(filePath)
if (result.success) {
document.getElementById('editor').value = result.content
document.title = `${filePath} - 文本编辑器`
} else {
alert(`打开文件失败: ${result.error}`)
}
} catch (error) {
alert(`打开文件失败: ${error.message}`)
}
}
async saveFile(filePath, content) {
try {
const result = await window.electronAPI.writeFile(filePath, content)
if (result.success) {
window.electronAPI.showNotification('保存成功', '文件已保存')
} else {
alert(`保存文件失败: ${result.error}`)
}
} catch (error) {
alert(`保存文件失败: ${error.message}`)
}
}
saveBeforeClose() {
const content = document.getElementById('editor').value
if (content.trim()) {
// 提示用户保存
const save = confirm('是否保存当前文件?')
if (save) {
// 这里应该实现保存逻辑
}
}
}
setupUI() {
// 快捷键
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
case 's':
e.preventDefault()
this.saveCurrentFile()
break
case 'o':
e.preventDefault()
// 触发打开文件对话框
break
}
}
})
// 拖拽文件
document.addEventListener('dragover', (e) => {
e.preventDefault()
})
document.addEventListener('drop', async (e) => {
e.preventDefault()
const files = Array.from(e.dataTransfer.files)
if (files.length > 0) {
await this.openFile(files[0].path)
}
})
}
}
new App()
原生功能集成
系统集成
// main.js - 系统集成功能
const { app, BrowserWindow, Tray, nativeImage, powerMonitor, screen } = require('electron')
class SystemIntegration {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.tray = null
this.setupSystemIntegration()
}
setupSystemIntegration() {
// 系统托盘
this.createTray()
// 电源管理
this.setupPowerMonitor()
// 屏幕管理
this.setupScreenMonitor()
// 系统通知
this.setupNotifications()
}
createTray() {
const icon = nativeImage.createFromPath(path.join(__dirname, 'assets/tray-icon.png'))
this.tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
this.mainWindow.show()
},
},
{
label: '隐藏窗口',
click: () => {
this.mainWindow.hide()
},
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.quit()
},
},
])
this.tray.setContextMenu(contextMenu)
this.tray.setToolTip('我的应用')
// 双击托盘图标显示窗口
this.tray.on('double-click', () => {
this.mainWindow.isVisible() ? this.mainWindow.hide() : this.mainWindow.show()
})
}
setupPowerMonitor() {
// 系统挂起
powerMonitor.on('suspend', () => {
console.log('系统即将挂起')
this.mainWindow.webContents.send('system-suspend')
})
// 系统恢复
powerMonitor.on('resume', () => {
console.log('系统已恢复')
this.mainWindow.webContents.send('system-resume')
})
// 电源状态变化
powerMonitor.on('on-ac', () => {
this.mainWindow.webContents.send('power-ac-connected')
})
powerMonitor.on('on-battery', () => {
this.mainWindow.webContents.send('power-on-battery')
})
}
setupScreenMonitor() {
// 屏幕配置变化
screen.on('display-added', (event, newDisplay) => {
console.log('新显示器已连接:', newDisplay)
this.mainWindow.webContents.send('display-added', newDisplay)
})
screen.on('display-removed', (event, oldDisplay) => {
console.log('显示器已断开:', oldDisplay)
this.mainWindow.webContents.send('display-removed', oldDisplay)
})
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
console.log('显示器配置已更改:', display, changedMetrics)
this.mainWindow.webContents.send('display-metrics-changed', display, changedMetrics)
})
}
setupNotifications() {
// 处理通知点击
ipcMain.on('show-notification', (event, { title, body, actions }) => {
const notification = new Notification({
title,
body,
actions: actions || [],
})
notification.on('click', () => {
this.mainWindow.show()
this.mainWindow.focus()
})
notification.on('action', (event, index) => {
this.mainWindow.webContents.send('notification-action', index)
})
notification.show()
})
}
}
文件系统操作
// fileSystem.js - 文件系统操作
const { ipcMain, dialog } = require('electron')
const fs = require('fs').promises
const path = require('path')
class FileSystemManager {
constructor() {
this.setupFileOperations()
}
setupFileOperations() {
// 选择文件
ipcMain.handle('select-file', async (event, options = {}) => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: options.filters || [{ name: 'All Files', extensions: ['*'] }],
})
return result
})
// 选择文件夹
ipcMain.handle('select-directory', async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
})
return result
})
// 保存文件对话框
ipcMain.handle('save-file-dialog', async (event, options = {}) => {
const result = await dialog.showSaveDialog({
filters: options.filters || [{ name: 'All Files', extensions: ['*'] }],
defaultPath: options.defaultPath,
})
return result
})
// 读取文件
ipcMain.handle('read-file', async (event, filePath) => {
try {
const content = await fs.readFile(filePath, 'utf8')
const stats = await fs.stat(filePath)
return {
success: true,
content,
stats: {
size: stats.size,
modified: stats.mtime,
created: stats.birthtime,
},
}
} catch (error) {
return {
success: false,
error: error.message,
}
}
})
// 写入文件
ipcMain.handle('write-file', async (event, filePath, content) => {
try {
await fs.writeFile(filePath, content, 'utf8')
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 读取目录
ipcMain.handle('read-directory', async (event, dirPath) => {
try {
const items = await fs.readdir(dirPath, { withFileTypes: true })
const result = []
for (const item of items) {
const fullPath = path.join(dirPath, item.name)
const stats = await fs.stat(fullPath)
result.push({
name: item.name,
path: fullPath,
isDirectory: item.isDirectory(),
isFile: item.isFile(),
size: stats.size,
modified: stats.mtime,
})
}
return { success: true, items: result }
} catch (error) {
return { success: false, error: error.message }
}
})
// 创建目录
ipcMain.handle('create-directory', async (event, dirPath) => {
try {
await fs.mkdir(dirPath, { recursive: true })
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 删除文件/目录
ipcMain.handle('delete-path', async (event, targetPath) => {
try {
const stats = await fs.stat(targetPath)
if (stats.isDirectory()) {
await fs.rmdir(targetPath, { recursive: true })
} else {
await fs.unlink(targetPath)
}
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 监听文件变化
ipcMain.handle('watch-file', (event, filePath) => {
try {
const watcher = fs.watch(filePath, (eventType, filename) => {
event.sender.send('file-changed', {
eventType,
filename,
filePath,
})
})
// 存储 watcher 以便后续清理
event.sender.on('destroyed', () => {
watcher.close()
})
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
}
}
new FileSystemManager()
性能优化
内存管理
// memoryManager.js - 内存管理
class MemoryManager {
constructor() {
this.setupMemoryMonitoring()
}
setupMemoryMonitoring() {
// 定期检查内存使用
setInterval(() => {
const memoryUsage = process.memoryUsage()
// 如果内存使用过高,触发垃圾回收
if (memoryUsage.heapUsed > 100 * 1024 * 1024) {
// 100MB
if (global.gc) {
global.gc()
console.log('触发垃圾回收')
}
}
// 发送内存使用情况到渲染进程
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
this.mainWindow.webContents.send('memory-usage', {
heapUsed: memoryUsage.heapUsed,
heapTotal: memoryUsage.heapTotal,
external: memoryUsage.external,
rss: memoryUsage.rss,
})
}
}, 30000) // 每30秒检查一次
}
// 清理未使用的资源
cleanup() {
// 清理事件监听器
ipcMain.removeAllListeners()
// 清理定时器
// clearInterval(this.memoryCheckInterval)
// 强制垃圾回收
if (global.gc) {
global.gc()
}
}
}
渲染进程优化
// renderer-optimization.js - 渲染进程优化
class RendererOptimization {
constructor() {
this.setupOptimizations()
}
setupOptimizations() {
// 虚拟滚动
this.setupVirtualScrolling()
// 图片懒加载
this.setupLazyLoading()
// 防抖和节流
this.setupDebounceThrottle()
}
setupVirtualScrolling() {
// 虚拟滚动实现
class VirtualList {
constructor(container, itemHeight, items) {
this.container = container
this.itemHeight = itemHeight
this.items = items
this.visibleStart = 0
this.visibleEnd = 0
this.init()
}
init() {
this.container.style.height = `${this.items.length * this.itemHeight}px`
this.container.addEventListener('scroll', this.onScroll.bind(this))
this.render()
}
onScroll() {
const scrollTop = this.container.scrollTop
const containerHeight = this.container.clientHeight
this.visibleStart = Math.floor(scrollTop / this.itemHeight)
this.visibleEnd = Math.min(
this.visibleStart + Math.ceil(containerHeight / this.itemHeight) + 1,
this.items.length
)
this.render()
}
render() {
const fragment = document.createDocumentFragment()
for (let i = this.visibleStart; i < this.visibleEnd; i++) {
const item = document.createElement('div')
item.style.height = `${this.itemHeight}px`
item.style.position = 'absolute'
item.style.top = `${i * this.itemHeight}px`
item.textContent = this.items[i]
fragment.appendChild(item)
}
this.container.innerHTML = ''
this.container.appendChild(fragment)
}
}
}
setupLazyLoading() {
// 图片懒加载
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
img.classList.remove('lazy')
imageObserver.unobserve(img)
}
})
})
document.querySelectorAll('img[data-src]').forEach((img) => {
imageObserver.observe(img)
})
}
setupDebounceThrottle() {
// 防抖函数
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)
}
}
}
// 使用示例
const searchInput = document.getElementById('search')
if (searchInput) {
searchInput.addEventListener(
'input',
debounce((e) => {
// 搜索逻辑
console.log('搜索:', e.target.value)
}, 300)
)
}
const scrollContainer = document.getElementById('scroll-container')
if (scrollContainer) {
scrollContainer.addEventListener(
'scroll',
throttle(() => {
// 滚动处理逻辑
console.log('滚动位置:', scrollContainer.scrollTop)
}, 100)
)
}
}
}
new RendererOptimization()
应用打包与分发
构建配置
// package.json - 构建配置
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "我的 Electron 应用",
"main": "main.js",
"scripts": {
"start": "electron .",
"dev": "NODE_ENV=development electron .",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux",
"dist": "npm run build -- --publish=never",
"publish": "npm run build -- --publish=always"
},
"build": {
"appId": "com.example.myapp",
"productName": "我的应用",
"directories": {
"output": "dist"
},
"files": ["main.js", "preload.js", "renderer/", "assets/", "node_modules/", "package.json"],
"extraResources": [
{
"from": "resources/",
"to": "resources/",
"filter": ["**/*"]
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "assets/icon.ico"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "assets/icon.icns",
"category": "public.app-category.productivity"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
],
"icon": "assets/icon.png",
"category": "Office"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
},
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "your-repo"
}
]
}
}
自动更新
// updater.js - 自动更新
const { autoUpdater } = require('electron-updater')
const { dialog } = require('electron')
class AutoUpdater {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.setupAutoUpdater()
}
setupAutoUpdater() {
// 检查更新
autoUpdater.checkForUpdatesAndNotify()
// 发现更新
autoUpdater.on('update-available', (info) => {
console.log('发现新版本:', info.version)
this.mainWindow.webContents.send('update-available', info)
})
// 没有更新
autoUpdater.on('update-not-available', (info) => {
console.log('当前已是最新版本')
})
// 下载进度
autoUpdater.on('download-progress', (progressObj) => {
this.mainWindow.webContents.send('download-progress', {
percent: progressObj.percent,
transferred: progressObj.transferred,
total: progressObj.total,
})
})
// 更新下载完成
autoUpdater.on('update-downloaded', (info) => {
console.log('更新下载完成')
dialog
.showMessageBox(this.mainWindow, {
type: 'info',
title: '更新已下载',
message: '新版本已下载完成,是否立即重启应用以完成更新?',
buttons: ['立即重启', '稍后重启'],
})
.then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
// 更新错误
autoUpdater.on('error', (error) => {
console.error('更新错误:', error)
this.mainWindow.webContents.send('update-error', error.message)
})
}
checkForUpdates() {
autoUpdater.checkForUpdatesAndNotify()
}
}
module.exports = AutoUpdater
总结
Electron 为 Web 开发者提供了构建桌面应用的强大能力:
- 跨平台支持:一套代码运行在多个平台
- 丰富的 API:访问系统原生功能
- 灵活的架构:主进程与渲染进程分离
- 强大的生态:丰富的插件和工具链
- 便捷的分发:自动更新和多平台打包
掌握 Electron,你就能用熟悉的 Web 技术构建出功能强大的桌面应用!
Electron 是 Web 开发者进入桌面应用开发的最佳选择,值得深入学习和实践。