Electron + Vue3 桌面应用开发实战指南
作为一名拥有 8 年前端开发经验的工程师,我深知从 Web 到桌面应用开发的转型痛点。Electron 框架虽然降低了跨平台开发门槛,但实际项目中仍会遇到各种棘手问题。本文基于最新环境(Node.js v20.12.2、npm v9.8.1、Electron v29.1.1),结合实战经验,从基础搭建到生产部署,全方位讲解 Electron+Vue3 开发要点。
一、Electron 原生开发核心流程
1. 项目初始化最佳实践
csharp
npm init -y
关键注意点:
- package.json中的name字段必须是小写字母 + 连字符组合(如my-electron-app),避免中文或特殊字符,否则后续打包可能出现莫名其妙的错误
- 初始化后立即设置"private": true,防止意外发布到 npm 仓库
2. Electron 安装与镜像配置
国内网络环境下,直接安装 Electron 极易失败,推荐以下配置方案:
ini
# 创建.npmrc文件(优先级高于全局配置)
cat > .npmrc << EOF
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
registry=https://registry.npmmirror.com/
EOF
# 安装指定版本(锁定版本号是项目稳定的关键)
npm install electron@29.1.1 --save-dev
验证安装:
bash
npx electron --version # 成功输出版本号即安装正常
3. 核心文件编写规范
index.html(渲染进程)
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- 内容安全策略(开发环境配置) -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'">
<title>Electron基础示例</title>
</head>
<body>
<h1>Hello Electron 👋</h1>
<p>当前版本: <span id="version"></span></p>
<script>
// 通过预加载脚本暴露的API获取版本信息
document.getElementById('version').textContent = window.electronAPI.getVersion()
</script>
</body>
</html>
preload.js(进程通信桥梁)
javascript
// 严格的进程间通信边界是安全的关键
const { contextBridge } = require('electron/renderer')
// 只暴露必要的API,禁止直接暴露整个process对象
contextBridge.exposeInMainWorld('electronAPI', {
getVersion: () => process.versions.electron,
// 后续可添加更多通信方法
// showDialog: (message) => ipcRenderer.invoke('show-dialog', message)
})
main.js(主进程)
less
const { app, BrowserWindow } = require('electron/main')
const { join } = require('path')
let mainWindow = null
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 预加载脚本路径
preload: join(__dirname, 'preload.js'),
// 安全配置
contextIsolation: true, // 必须开启上下文隔离
nodeIntegration: false, // 禁止渲染进程直接访问Node.js
enableRemoteModule: false, // 已废弃的API,确保关闭
sandbox: false // 生产环境可考虑开启沙箱
}
})
// 加载页面
mainWindow.loadFile(join(__dirname, 'index.html'))
// 开发环境自动打开调试工具
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({ mode: 'detach' })
}
// 窗口关闭时清理资源
mainWindow.on('closed', () => {
mainWindow = null
})
}
// 应用生命周期管理
app.whenReady().then(() => {
createWindow()
// macOS特殊处理
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
// 所有窗口关闭时退出应用(macOS除外)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
4. 开发命令配置
json
{
"scripts": {
"dev": "cross-env NODE_ENV=development electron .",
"start": "npm run dev"
},
"devDependencies": {
"cross-env": "^7.0.3", // 跨平台设置环境变量
"electron": "^29.1.1"
}
}
安装 cross-env:npm install cross-env --save-dev
二、Vue3 + Electron 高效开发方案
对于中大型项目,推荐使用 electron-vite 框架,它完美整合了 Vite 的热更新能力和 Electron 的桌面特性。
1. 项目创建与配置
sql
# 推荐使用pnpm(速度更快,依赖管理更严格)
npm install -g pnpm
pnpm create @quick-start/electron@latest
交互选项建议:
- Project name: 用小写英文 + 连字符(如vue-electron-app)
- Select a framework: vue
- Add TypeScript? Yes(长期项目强烈建议使用 TS)
- Add Electron updater plugin? Yes(提前集成自动更新)
- Enable Electron download mirror proxy? Yes(国内加速)
2. 项目结构深度解析
bash
vue-electron-app/
├── electron/ # 主进程代码(Node环境)
│ ├── main/ # 主进程核心逻辑
│ │ └── index.js # 主进程入口
│ └── preload/ # 预加载脚本
│ └── index.js # 预加载入口
├── src/ # 渲染进程代码(Vue环境)
│ ├── components/ # Vue组件
│ ├── App.vue # 根组件
│ └── main.js # Vue入口
├── electron.vite.config.js # 构建配置(核心文件)
└── package.json # 项目配置
3. 热更新优化配置
electron-vite 已内置热更新,通过electron.vite.config.js可进一步优化:
php
import { defineConfig } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import { join } from 'path'
export default defineConfig({
main: {
build: {
outDir: 'dist-electron/main'
}
},
preload: {
build: {
outDir: 'dist-electron/preload'
}
},
renderer: {
// 集成Vue插件
plugins: [vue()],
// 开发服务器配置
server: {
port: 5173,
strictPort: true, // 端口被占用时不自动切换
open: false // 不自动打开浏览器
},
build: {
outDir: 'dist-renderer',
// 静态资源处理
assetsDir: 'assets',
rollupOptions: {
input: join(__dirname, 'src/index.html')
}
}
}
})
4. 图标配置全平台方案
不同操作系统对图标有不同要求,推荐方案:
csharp
// electron/main/index.js
import { BrowserWindow, nativeImage } from 'electron'
import { join } from 'path'
const getIconPath = () => {
// 根据系统返回不同图标
switch (process.platform) {
case 'win32':
return join(__dirname, '../../resources/icons/win/icon.ico')
case 'darwin':
return join(__dirname, '../../resources/icons/mac/icon.icns')
default:
return join(__dirname, '../../resources/icons/linux/icon.png')
}
}
const createWindow = () => {
const mainWindow = new BrowserWindow({
icon: nativeImage.createFromPath(getIconPath()),
// 其他配置...
})
}
图标规范:
- Windows: .ico格式,至少包含 256x256 像素
- macOS: .icns格式,建议包含 1024x1024 像素
- Linux: .png格式,512x512 像素
5. 系统托盘高级实现
dart
// electron/main/index.js
import { Tray, Menu, nativeImage, app, BrowserWindow } from 'electron'
import { join } from 'path'
let tray = null
let mainWindow = null
const createTray = () => {
// 创建托盘图标
const icon = nativeImage.createFromPath(join(__dirname, '../../resources/icons/tray.ico'))
.resize({ width: 16, height: 16 }) // 托盘图标通常较小
tray = new Tray(icon)
// 托盘菜单模板
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => {
mainWindow.show()
mainWindow.setSkipTaskbar(false) // 从任务栏显示
}
},
{
label: '隐藏窗口',
click: () => {
mainWindow.hide()
mainWindow.setSkipTaskbar(true) // 从任务栏隐藏
}
},
{ type: 'separator' }, // 分隔线
{
label: '退出',
click: () => {
// 确保完全退出应用
mainWindow.destroy()
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
tray.setToolTip('Electron Vue应用')
// 双击托盘切换窗口显示状态
tray.on('double-click', () => {
if (mainWindow.isVisible()) {
mainWindow.hide()
} else {
mainWindow.show()
}
})
}
// 在窗口创建后初始化托盘
app.whenReady().then(() => {
mainWindow = new BrowserWindow(/* 配置 */)
createTray()
})
三、打包与自动更新全流程
1. 打包环境准备
csharp
# 安装打包工具
pnpm add electron-builder --save-dev
# 若使用原生模块(如C++扩展)
pnpm add electron-rebuild --save-dev
2. 打包配置详解
在package.json中添加:
kotlin
{
"build": {
"appId": "com.yourcompany.yourapp", // 反向域名格式,唯一标识
"productName": "你的应用名称",
"copyright": "Copyright © 2024 ${author}",
"asar": true, // 启用asar打包(保护源码)
"directories": {
"output": "release/${version}", // 输出到版本化目录
"buildResources": "resources" // 构建资源目录
},
"files": [
"dist-electron/**/*",
"dist-renderer/**/*"
],
"win": {
"icon": "resources/icons/win/icon.ico",
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"] // 支持32位和64位
}
],
"artifactName": "${productName}-v${version}-${arch}-setup.${ext}"
},
"nsis": {
"oneClick": false, // 不启用一键安装
"allowToChangeInstallationDirectory": true,
"installerIcon": "resources/icons/win/installer.ico",
"uninstallerIcon": "resources/icons/win/uninstaller.ico",
"installerHeaderIcon": "resources/icons/win/installer.ico",
"createDesktopShortcut": true,
"shortcutName": "${productName}"
},
"mac": {
"icon": "resources/icons/mac/icon.icns",
"target": ["dmg", "zip"],
"artifactName": "${productName}-v${version}-${arch}.${ext}"
},
"linux": {
"icon": "resources/icons/linux",
"target": ["AppImage", "deb"],
"artifactName": "${productName}-v${version}-${arch}.${ext}"
}
},
"scripts": {
"build": "electron-vite build && electron-builder",
"build:win": "electron-vite build && electron-builder --win",
"build:mac": "electron-vite build && electron-builder --mac",
"build:linux": "electron-vite build && electron-builder --linux"
}
}
3. 原生模块处理(C++ 库)
如果项目依赖 C++ 扩展(如 serialport、sqlite3 等),必须重新编译:
bash
# 编译命令(Windows使用cmd执行)
./node_modules/.bin/electron-rebuild
# 或在package.json中添加脚本
"scripts": {
"rebuild": "electron-rebuild"
}
# 执行:npm run rebuild
常见问题:
- 编译失败通常是因为缺少 C++ 编译工具链,Windows 需安装 Visual Studio Build Tools
- macOS 需要安装 Xcode Command Line Tools: xcode-select --install
- Linux 需要安装 build-essential: sudo apt-get install build-essential
4. 自动更新完整实现
1)安装依赖
csharp
pnpm add electron-updater
2)主进程更新逻辑
javascript
// electron/main/index.js
import { app, dialog, BrowserWindow } from 'electron'
import { autoUpdater } from 'electron-updater'
import log from 'electron-log'
// 配置日志
log.transports.file.level = 'info'
autoUpdater.logger = log
let mainWindow = null
// 初始化更新检查
const initUpdater = () => {
// 生产环境才启用自动更新
if (!app.isPackaged) {
// 开发环境可指定测试更新服务器
autoUpdater.setFeedURL({
provider: 'generic',
url: 'http://localhost:3000/updates/'
})
}
// 检查更新
autoUpdater.checkForUpdates().catch(err => {
log.error('更新检查失败:', err)
})
// 发现新版本
autoUpdater.on('update-available', (info) => {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '发现新版本',
message: `新版本 ${info.version} 已发布,是否下载更新?`,
buttons: ['立即下载', '稍后再说']
}).then(res => {
if (res.response === 0) {
// 用户选择下载
mainWindow.webContents.send('update-start')
}
})
})
// 下载进度
autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('update-progress', {
percent: Math.round(progress.percent),
speed: Math.round(progress.bytesPerSecond / 1024) // KB/s
})
})
// 下载完成
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '更新就绪',
message: '更新已下载完成,是否立即重启应用?',
buttons: ['立即重启', '稍后重启']
}).then(res => {
if (res.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
}
// 在应用就绪后初始化
app.whenReady().then(() => {
mainWindow = new BrowserWindow(/* 配置 */)
// 延迟检查更新,避免影响启动速度
setTimeout(initUpdater, 5000)
})
3)更新服务器配置
- 在package.json中配置更新服务器地址:
json
"build": {
"publish": [
{
"provider": "generic",
"url": "https://your-server.com/updates/"
}
]
}
- 本地测试更新服务器:
bash
# 安装静态服务器
pnpm add -g serve
# 在打包输出目录启动服务器
serve -s release/</doubaocanvas>