第 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,提供了:

创建项目

1

使用脚手架创建项目

bash
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 生态依赖

bash
# 安装 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

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)

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)

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)

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)支持完美,修改模板、样式或逻辑都会即时更新。

主进程热重启配置

package.json scripts
{
  "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(组合式函数)

src/renderer/composables/useElectron.ts
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 支持)

src/preload/types/electron.d.ts
// 为 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)

src/renderer/stores/app.ts
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,代码更优雅

下一章我们将学习窗口管理原生菜单,让你的应用拥有专业的桌面体验。