第 3 章:Vue3 + Electron 工程化实践
本章将带你使用现代前端工程化方案搭建 Vue3 + Electron 项目。告别手动配置,拥抱 Vite 的极速开发体验。
3.1 为什么选 Vue3
在 Electron 中集成前端框架,Vue3 是一个极佳的选择。以下是核心优势:
| 特性 | Vue3 优势 | 对 Electron 开发的意义 |
|---|---|---|
| Composition API | 更灵活的代码组织方式,逻辑复用更简单 | IPC 通信、窗口管理等逻辑可以封装为可复用的 composables |
| 性能 | Proxy 响应式,初始化速度提升 2x+ | 桌面应用启动更快,内存占用更低 |
| TypeScript | 原生支持,类型推导完善 | 与 Electron 的 API 类型完美配合 |
| 体积 | 运行时约 10KB(gzip) | 减少打包体积(虽然 Electron 本身较大) |
| 生态 | Element Plus、Ant Design Vue 等成熟 UI 库 | 快速构建漂亮的桌面界面 |
给前端开发者的建议
如果你已经熟悉 Vue3 的 Composition API,那么在 Electron 中使用它几乎没有任何学习成本。你可以把 Electron 的 API 当作一个新的 "composable" 来使用——useWindowState()、useFileDialog()、useSystemNotification(),这些都可以封装成可复用的组合式函数。
3.2 项目搭建:electron-vite
electron-vite 是目前最推荐的 Vue3 + Electron 工程化方案。它基于 Vite,提供了:
- ⚡ 极速的 HMR(热更新)
- 📦 开箱即用的多进程构建(主进程 + 预加载 + 渲染进程)
- 🔧 自动化的开发模式(无需手动重启 Electron)
- 🎯 生产环境优化打包
创建项目
1
使用脚手架创建项目
npm create electron-vite@latest my-electron-vue-app
# 交互式选项:
# ? Project template: Vue
# ? Add TypeScript? Yes
# ? Add Electron updater? No (后续可手动添加)
# ? Enable Electron download mirror? Yes (国内推荐)
cd my-electron-vue-app
npm install
2
安装 Vue 生态依赖
# 安装 Vue Router 和 Pinia(状态管理)
npm install vue-router@4 pinia
# 安装 UI 库(以 Element Plus 为例)
npm install element-plus
# 安装 Electron 工具库
npm install electron-store # 配置持久化
3.3 项目目录结构
electron-vite 生成的项目采用多入口结构,每个进程有独立的代码目录:
my-electron-vue-app/
├── electron.vite.config.ts # Vite 配置(多进程构建)
├── package.json
├── tsconfig.json
│
├── src/
│ ├── main/ # 主进程代码
│ │ ├── index.ts # 主进程入口
│ │ └── utils/ # 主进程工具函数
│ │
│ ├── preload/ # 预加载脚本
│ │ ├── index.ts # 暴露给渲染进程的 API
│ │ └── types/ # 类型定义
│ │
│ └── renderer/ # 渲染进程(Vue 应用)
│ ├── index.html # HTML 模板
│ ├── main.ts # Vue 应用入口
│ ├── App.vue # 根组件
│ ├── router/ # Vue Router
│ ├── stores/ # Pinia 状态管理
│ ├── components/ # 公共组件
│ ├── views/ # 页面视图
│ ├── composables/ # 组合式函数(Electron API 封装)
│ └── styles/ # 全局样式
│
├── build/ # 构建输出
└── out/ # 打包输出
目录设计哲学
这种多入口结构与传统的 Vue 项目不同,但逻辑清晰:main 是 "后端",preload 是 "API 网关",renderer 是 "前端"。三者职责分离,符合 Electron 的安全架构。
3.4 核心配置文件
electron.vite.config.ts
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
// 主进程构建配置
plugins: [externalizeDepsPlugin()],
build: {
lib: {
entry: resolve(__dirname, 'src/main/index.ts'),
formats: ['cjs'],
fileName: () => '[name].js'
},
rollupOptions: {
external: ['electron']
}
}
},
preload: {
// 预加载脚本构建配置
plugins: [externalizeDepsPlugin()],
build: {
lib: {
entry: resolve(__dirname, 'src/preload/index.ts'),
formats: ['cjs'],
fileName: () => '[name].js'
}
}
},
renderer: {
// 渲染进程构建配置(Vue 应用)
resolve: {
alias: {
'@': resolve(__dirname, 'src/renderer'),
'@main': resolve(__dirname, 'src/main'),
'@preload': resolve(__dirname, 'src/preload')
}
},
plugins: [vue()],
server: {
// 开发服务器配置
port: 3000
}
}
})
主进程入口(src/main/index.ts)
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow(): void {
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
show: false, // 先不显示,等加载完成再显示
autoHideMenuBar: true, // 自动隐藏菜单栏
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
contextIsolation: true,
nodeIntegration: false
}
})
// 加载完成后再显示窗口(避免白屏)
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
// 打开外部链接时使用系统浏览器
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// 根据环境加载不同内容
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
// 开发模式:加载 Vite 开发服务器 URL
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
// 生产模式:加载打包后的文件
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// 应用初始化
app.whenReady().then(() => {
// 设置 Windows 应用用户模型 ID(任务栏图标)
electronApp.setAppUserModelId('com.electron')
// 默认打开或关闭 DevTools 的快捷键(F12 / Cmd+Option+I)
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC 示例:处理来自渲染进程的请求
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
预加载脚本(src/preload/index.ts)
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// 暴露 API 给渲染进程
const api = {
// 获取应用版本
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
// 文件操作(示例)
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content: string) => ipcRenderer.invoke('dialog:saveFile', content),
// 窗口控制
minimizeWindow: () => ipcRenderer.send('window:minimize'),
maximizeWindow: () => ipcRenderer.send('window:maximize'),
closeWindow: () => ipcRenderer.send('window:close')
}
// 使用 contextBridge 安全暴露
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
// 降级方案(不推荐生产环境使用)
// @ts-ignore
window.electron = electronAPI
// @ts-ignore
window.api = api
}
Vue 应用入口(src/renderer/main.ts)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
// 引入 Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')
3.5 开发模式与生产模式
开发模式(Development)
npm run dev
# 内部流程:
# 1. Vite 启动开发服务器(端口 3000)
# 2. Electron 主进程启动
# 3. 主进程加载 http://localhost:3000 到 BrowserWindow
# 4. 文件修改 → HMR 热更新 → 界面自动刷新
开发体验
electron-vite 在开发模式下会同时启动 Vite 开发服务器和 Electron。修改 Vue 组件时,界面会热更新而不丢失状态;修改主进程代码时,Electron 会自动重启。
生产模式(Production)
# 构建应用(编译主进程、预加载、渲染进程)
npm run build
# 打包为可执行文件(使用 electron-builder)
npm run build:win # Windows
npm run build:mac # macOS
npm run build:linux # Linux
# 构建 + 打包一步到位
npm run dist
环境判断
// 在主进程中判断环境
import { is } from '@electron-toolkit/utils'
if (is.dev) {
console.log('开发模式')
// 打开开发者工具
mainWindow.webContents.openDevTools()
} else {
console.log('生产模式')
}
// 判断平台
if (is.windows) { /* Windows 特有逻辑 */ }
if (is.macos) { /* macOS 特有逻辑 */ }
if (is.linux) { /* Linux 特有逻辑 */ }
3.6 热更新配置
electron-vite 已经内置了热更新,但你可以进一步优化体验:
Vue 组件热更新(已内置)
Vite 的 HMR 对 Vue 单文件组件(SFC)支持完美,修改模板、样式或逻辑都会即时更新。
主进程热重启配置
{
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"preview": "electron-vite preview",
"postinstall": "electron-builder install-app-deps",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux"
}
}
3.7 工程化最佳实践
1. 封装 Composables(组合式函数)
import { ref, onMounted } from 'vue'
// 封装 Electron API 为 Vue composable
export function useAppVersion() {
const version = ref('')
onMounted(async () => {
if (window.api?.getAppVersion) {
version.value = await window.api.getAppVersion()
}
})
return { version }
}
// 窗口控制
export function useWindowControl() {
const minimize = () => window.api?.minimizeWindow?.()
const maximize = () => window.api?.maximizeWindow?.()
const close = () => window.api?.closeWindow?.()
return { minimize, maximize, close }
}
// 文件对话框
export function useFileDialog() {
const openFile = async () => {
return await window.api?.openFile?.()
}
const saveFile = async (content: string) => {
return await window.api?.saveFile?.(content)
}
return { openFile, saveFile }
}
2. 类型声明(TypeScript 支持)
// 为 window.api 添加类型声明
export interface ElectronAPI {
getAppVersion: () => Promise
openFile: () => Promise<{ canceled: boolean; filePaths: string[] }>
saveFile: (content: string) => Promise<{ canceled: boolean; filePath?: string }>
minimizeWindow: () => void
maximizeWindow: () => void
closeWindow: () => void
}
declare global {
interface Window {
api: ElectronAPI
electron: typeof import('@electron-toolkit/preload').electronAPI
}
}
3. 状态管理(Pinia)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAppStore = defineStore('app', () => {
// State
const windowState = ref<'normal' | 'maximized' | 'minimized'>('normal')
const recentFiles = ref([])
// Getters
const hasRecentFiles = computed(() => recentFiles.value.length > 0)
// Actions
const addRecentFile = (path: string) => {
recentFiles.value.unshift(path)
if (recentFiles.value.length > 10) {
recentFiles.value.pop()
}
}
const setWindowState = (state: typeof windowState.value) => {
windowState.value = state
}
return {
windowState,
recentFiles,
hasRecentFiles,
addRecentFile,
setWindowState
}
})
最佳实践清单
- ✅ 使用 TypeScript 获得完整的类型提示
- ✅ 通过 Composables 封装 Electron API,保持组件纯净
- ✅ 使用 Pinia 管理跨组件状态
- ✅ 主进程和渲染进程代码严格分离
- ✅ 预加载脚本只暴露必要的 API
- ✅ 使用 Vue Router 管理多页面导航
- ✅ 开发时打开 DevTools,生产时关闭
本章小结
本章核心要点:
- Vue3 + Vite + Electron 是现代桌面开发的最佳组合
- electron-vite 提供开箱即用的多进程工程化方案
- 项目分为 main、preload、renderer 三个独立入口
- 开发模式享受 HMR 热更新,生产模式一键打包
- 通过 Composables 封装 Electron API,代码更优雅
下一章我们将学习窗口管理和原生菜单,让你的应用拥有专业的桌面体验。