🏆 生产级 Electron + Vue 3 桌面应用开发实战教程 🚀

🏆 生产级 Electron + Vue 3 桌面应用开发实战教程 🚀

从架构设计到部署发布,全面解析桌面应用开发最佳实践

您好!作为一名资深全栈工程师和桌面应用开发专家,我将为您呈现一份**完整、系统且具有生产实践指导意义的 Electron + Vue 3 桌面应用开发教程**。这份教程不仅覆盖您提及的所有核心点,更会融入我在实际项目中沉淀下来的最佳实践、架构思考、性能优化深度解析、版本管理以及部署策略,助您构建出高质量、高性能的跨平台桌面应用。

本教程将引导您从零开始,理解 Electron 的设计哲学,学习如何结合现代前端框架 Vue 3 高效开发,并掌握交付可维护、可扩展桌面应用的关键技能。

一、Electron 核心架构与设计哲学

Electron 并非简单地将网页打包成桌面应用,其背后是精妙的多进程架构,这是理解一切的基础。

1.1 主进程 (Main Process) - 应用的“大脑”

1.2 渲染进程 (Renderer Process) - 应用的“面孔”

1.3 进程间通信 (IPC) - 桥接“大脑”与“面孔”

主进程和渲染进程之间无法直接共享内存或调用函数,必须通过 IPC 机制进行通信。

graph TD Main[主进程
安全层级高] -->|ipcMain.handle()
双向通信| IPC_Gateway[IPC 网关
preload.js] IPC_Gateway -->|contextBridge.exposeInMainWorld()
暴露API| Renderer[渲染进程
沙盒化环境] Main -->|mainWindow.webContents.send()
主 -> 渲染| Renderer Renderer -->|ipcRenderer.invoke()
渲染 -> 主, 带回调| Main Renderer -->|ipcRenderer.send()
渲染 -> 主, 无回调| Main subgraph 核心通信流 Main IPC_Gateway Renderer end style Main fill:#E3F2FD,stroke:#2196F3,stroke-width:2px; style Renderer fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px; style IPC_Gateway fill:#FFECB3,stroke:#FFC107,stroke-width:2px;

二、项目骨架搭建:Electron + Vite + Vue 3

我们将采用现代、高效的构建工具 Vite,结合 electron-builder 进行项目初始化。

2.1 项目初始化与依赖安装

终端命令

# 1. 创建 Vue 3 项目 (使用 Vite)
npm create vue@latest my-electron-app -- --typescript # 可选 TypeScript
cd my-electron-app
npm install

# 2. 安装 Electron 和构建工具
npm install --save-dev electron electron-builder concurrently wait-on

# 3. 推荐安装一些辅助开发工具
# electron-devtools-installer: 在 Electron 应用中安装 Chrome 开发者工具扩展(如Vue Devtools)
npm install --save-dev electron-devtools-installer
            

2.2 目录结构规划

一个清晰的目录结构对于大型项目至关重要。

项目结构

my-electron-app/
├── public/                 # 静态资源,直接拷贝到生产环境
├── src/                    # Vue 3 前端代码
│   ├── assets/
│   ├── components/
│   ├── router/
│   ├── stores/             # Vuex 或 Pinia 状态管理
│   ├── App.vue
│   ├── main.ts             # Vue 应用入口
│   └── shims-vue.d.ts      # TypeScript 类型声明
├── electron/               # Electron 主进程及相关配置
│   ├── main.ts             # 主进程入口
│   ├── preload.ts          # 预加载脚本
│   └── utils/              # 主进程工具函数 (如文件操作、数据库连接)
│       └── db.ts           # 示例:SQLite 数据库操作
│       └── update.ts       # 自动更新相关逻辑
├── build/                  # 存放应用图标等构建资源
│   ├── icon.ico
│   └── icon.icns
├── package.json            # 项目配置
├── tsconfig.json           # TypeScript 配置
├── vite.config.ts          # Vite 构建配置
└── .env                    # 环境变量
            

2.3 核心文件配置

package.json

package.json (重点部分)

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "electron/main.js", // 注意:这里通常指向编译后的JS文件
  "scripts": {
    "dev": "vite", // 启动 Vue 开发服务器
    "build:vue": "vue-tsc --noEmit && vite build", // 构建 Vue 生产包
    "build:electron": "tsc --project electron", // 编译 Electron 主进程和预加载脚本
    "electron:start": "npm run build:vue && npm run build:electron && electron .", // 运行 Electron (生产模式)
    "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && npm run build:electron && electron .\"", // 开发模式
    "electron:build": "npm run build:vue && npm run build:electron && electron-builder", // 打包应用
    "postinstall": "electron-builder install-app-deps", // 安装Electron相关依赖
    "test": "vitest", // 单元测试,可选
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0",
    "concurrently": "^8.0.0",
    "wait-on": "^7.0.0",
    "electron-devtools-installer": "^3.2.0",
    "@vitejs/plugin-vue": "^4.0.0",
    "vite": "^5.0.0",
    "vue": "^3.0.0",
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0",
    # ...其他如 ESLint, Prettier, Vitest 等
  },
  "build": {
    "appId": "com.yourcompany.yourappname",
    "productName": "My Awesome App",
    "copyright": "Copyright © 2023 ${author}",
    "directories": {
      "output": "dist-electron" // 打包输出目录
    },
    "files": [
      "dist/**/*", // Vue 构建后的前端文件
      "electron/**/*.js", // 编译后的主进程/预加载JS文件
      "electron/**/*.json" // 如果有配置文件
    ],
    "asar": true, // 推荐:打包成asar格式,保护源码并加速文件读取
    "win": {
      "target": [
        { "target": "nsis", "arch": ["x64"] } // Windows 64位安装包
      ],
      "icon": "build/icon.ico"
    },
    "mac": {
      "target": [
        "dmg"
      ],
      "icon": "build/icon.icns"
    },
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ],
      "icon": "build/icon.png"
    },
    "publish": [ // 自动更新配置,此处以 GitHub Releases 为例
      {
        "provider": "github",
        "owner": "your-github-username",
        "repo": "your-github-repo"
      }
    ]
  }
}
            

vite.config.ts

vite.config.ts

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: './', // 确保生产构建的相对路径正确,Electron加载本地文件需要
  build: {
    outDir: 'dist', // Vue 构建输出目录
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html')
      }
    }
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'), // 配置路径别名
    },
  },
  server: {
    port: 5173, // Vite 开发服务器端口
  }
});
            

electron/main.ts (主进程)

electron/main.ts

import { app, BrowserWindow, ipcMain, screen, Menu, dialog } from 'electron';
import path from 'path';
import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer';
import { handleIPC } from './utils/ipcHandlers'; // 集中管理IPC处理
import { initAutoUpdater } from './utils/update'; // 自动更新逻辑

let mainWindow: BrowserWindow | null;

const isDev = process.env.NODE_ENV === 'development';
const VITE_DEV_SERVER_URL = 'http://localhost:5173'; // Vite 开发服务器地址

function createMainWindow() {
    const primaryDisplay = screen.getPrimaryDisplay();
    const { width, height } = primaryDisplay.workAreaSize; // 获取屏幕工作区域大小

    mainWindow = new BrowserWindow({
        width: Math.min(1200, width * 0.8), // 初始窗口大小
        height: Math.min(800, height * 0.8),
        minWidth: 800,
        minHeight: 600,
        show: false, // 先隐藏,待内容加载完毕再显示
        frame: true, // 是否显示系统默认边框,可设置为false自定义
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'), // 预加载脚本
            nodeIntegration: false, // 禁用 Node.js 集成,必须!
            contextIsolation: true, // 启用上下文隔离,必须!
            webSecurity: !isDev, // 开发时可关闭,生产务必开启
            // allowRunningInsecureContent: isDev // 不推荐,仅极端开发情况
        }
    });

    if (isDev) {
        mainWindow.loadURL(VITE_DEV_SERVER_URL);
        // 打开开发者工具并安装Vue Devtools
        mainWindow.webContents.once('dom-ready', async () => {
            try {
                await installExtension(VUEJS3_DEVTOOLS);
                mainWindow?.webContents.openDevTools();
            } catch (e) {
                console.error('Failed to install Vue Devtools:', e);
            }
        });
    } else {
        mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
    }

    // 内容加载完毕后显示窗口,优化启动体验
    mainWindow.webContents.on('did-finish-load', () => {
        mainWindow?.show();
    });

    // 窗口关闭事件
    mainWindow.on('closed', () => {
        mainWindow = null;
    });

    // 创建应用菜单 (可选)
    const template: Electron.MenuItemConstructorOptions[] = [
        {
            label: '文件',
            submenu: [
                { label: '新建', accelerator: 'CmdOrCtrl+N', click: () => console.log('New File') },
                { type: 'separator' },
                { label: '退出', role: 'quit' }
            ]
        },
        // ...其他菜单项
    ];
    const menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);
}

// Electron 应用准备就绪
app.whenReady().then(() => {
    createMainWindow();
    handleIPC(ipcMain, mainWindow); // 注册所有IPC处理函数
    if (process.env.NODE_ENV === 'production') {
        initAutoUpdater(mainWindow); // 生产环境检查更新
    }

    app.on('activate', () => {
        // macOS 上当点击 dock 图标时,如果没有其他窗口打开则重新创建一个
        if (BrowserWindow.getAllWindows().length === 0) {
            createMainWindow();
        }
    });
});

// 所有窗口关闭时退出应用 (macOS 除外)
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

// 在 Electron 进程中捕获未处理的异常
process.on('uncaughtException', (error) => {
    console.error('主进程未捕获的异常:', error);
    dialog.showErrorBox('应用错误', '发生了一个未预期的错误,应用将退出。' + error.message);
    app.quit();
});
            

electron/preload.ts (预加载脚本)

安全地暴露主进程功能给渲染进程。

electron/preload.ts

import { contextBridge, ipcRenderer } from 'electron';

// 定义需要暴露给渲染进程的 API 接口
export interface ElectronAPI {
    // 双向通信 (渲染 -> 主,带返回值)
    selectFile: () => Promise<string | undefined>;
    saveFile: (content: string) => Promise<boolean>;
    // 单向通信 (渲染 -> 主,无返回值)
    showNotification: (message: string) => void;
    // 单向通信 (主 -> 渲染,监听器)
    onUpdateProgress: (callback: (progress: number) => void) => () => void; // 返回一个取消监听的函数
    // 自动更新相关
    onUpdateStatus: (callback: (status: string) => void) => () => void;
    restartApp: () => void;
}

const electronAPI: ElectronAPI = {
    selectFile: () => ipcRenderer.invoke('dialog:openFile'),
    saveFile: (content: string) => ipcRenderer.invoke('dialog:saveFile', content),
    showNotification: (message: string) => ipcRenderer.send('app:showNotification', message),
    onUpdateProgress: (callback: (progress: number) => void) => {
        const handler = (event: Electron.IpcRendererEvent, progress: number) => callback(progress);
        ipcRenderer.on('main:updateProgress', handler);
        return () => ipcRenderer.off('main:updateProgress', handler); // 返回取消函数
    },
    onUpdateStatus: (callback: (status: string) => void) => {
        const handler = (event: Electron.IpcRendererEvent, status: string) => callback(status);
        ipcRenderer.on('update-status', handler);
        return () => ipcRenderer.off('update-status', handler);
    },
    restartApp: () => ipcRenderer.send('app:restart')
};

// 将 API 暴露到全局 window 对象,名称为 electronAPI
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
            

electron/utils/ipcHandlers.ts (IPC 集中管理)

将所有的 IPC 监听逻辑集中在一个文件,方便管理和维护。

electron/utils/ipcHandlers.ts

import { ipcMain, dialog, Notification, app, BrowserWindow } from 'electron';
import path from 'path';
import fs from 'fs';

export function handleIPC(ipcMainInstance: typeof ipcMain, mainWindow: BrowserWindow | null) {
    // 监听打开文件对话框
    ipcMainInstance.handle('dialog:openFile', async (event) => {
        const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow!, {
            properties: ['openFile'],
            filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }]
        });
        if (!canceled && filePaths.length > 0) {
            const content = fs.readFileSync(filePaths[0], 'utf8');
            return content;
        }
        return undefined;
    });

    // 监听保存文件对话框
    ipcMainInstance.handle('dialog:saveFile', async (event, content: string) => {
        const { canceled, filePath } = await dialog.showSaveDialog(mainWindow!, {
            filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }]
        });
        if (!canceled && filePath) {
            fs.writeFileSync(filePath, content, 'utf8');
            return true;
        }
        return false;
    });

    // 监听显示通知
    ipcMainInstance.on('app:showNotification', (event, message: string) => {
        if (Notification.isSupported()) {
            new Notification({
                title: 'Electron App Notification',
                body: message
            }).show();
        } else {
            console.log('系统不支持通知:', message);
        }
    });

    // 监听重启应用
    ipcMainInstance.on('app:restart', () => {
        app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) });
        app.exit(0);
    });

    // 示例:主进程主动发送消息给渲染进程 (模拟进度更新)
    let progressInterval: NodeJS.Timeout | null = null;
    ipcMainInstance.on('main:startProgressUpdate', (event, start: boolean) => {
        if (start) {
            let progress = 0;
            progressInterval = setInterval(() => {
                progress += 10;
                if (mainWindow) {
                    mainWindow.webContents.send('main:updateProgress', progress);
                }
                if (progress >= 100) {
                    clearInterval(progressInterval!);
                    progressInterval = null;
                    if (mainWindow) {
                        mainWindow.webContents.send('main:updateProgress', 100); // 确保发送100%
                    }
                }
            }, 500);
        } else if (progressInterval) {
            clearInterval(progressInterval);
            progressInterval = null;
        }
    });
}
            

三、Vue 3 前端开发与数据交互

在 Vue 3 应用中,通过预加载脚本暴露的 window.electronAPI 来与主进程交互。

3.1 Vue 3 组件中使用 Electron API

src/App.vue (示例)

<template>
  <h1>Electron + Vue 3 应用</h1>
  <button @click="handleSelectFile">选择文件</button>
  <p v-if="selectedFileContent">文件内容:{{ selectedFileContent.substring(0, 100) }}...</p>

  <button @click="handleSaveFile">保存文件</button>

  <button @click="handleShowNotification">显示通知</button>

  <h3>主进程进度更新:</h3>
  <p>当前进度: {{ progress }}%</p>
  <button @click="toggleProgressUpdate">{{ isUpdatingProgress ? '停止更新' : '开始更新' }}</button>

  <h3>应用更新状态:</h3>
  <p>{{ updateStatus }}</p>
  <button v-if="updateStatus.includes('下载完毕')" @click="handleRestartApp">立即重启安装</button>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import type { ElectronAPI } from '../electron/preload'; // 导入类型声明

// 确保在 Electron 环境下才能访问 electronAPI
declare global {
  interface Window {
    electronAPI?: ElectronAPI;
  }
}

const selectedFileContent = ref<string | undefined>(undefined);
const progress = ref(0);
const isUpdatingProgress = ref(false);
const updateStatus = ref('未检查更新');

// 选择文件
const handleSelectFile = async () => {
  if (window.electronAPI) {
    const content = await window.electronAPI.selectFile();
    if (content) {
      selectedFileContent.value = content;
      window.electronAPI.showNotification('文件已成功读取!');
    } else {
      selectedFileContent.value = '未选择文件或读取失败。';
    }
  } else {
    console.warn('Electron API 不可用');
  }
};

// 保存文件
const handleSaveFile = async () => {
  if (window.electronAPI) {
    const success = await window.electronAPI.saveFile('这是一段要保存到文件的内容。');
    if (success) {
      window.electronAPI.showNotification('文件已成功保存!');
    } else {
      window.electronAPI.showNotification('文件保存失败。');
    }
  }
};

// 显示通知
const handleShowNotification = () => {
  if (window.electronAPI) {
    window.electronAPI.showNotification('这是一条来自Vue的通知!');
  }
};

// 主进程进度更新控制
const toggleProgressUpdate = () => {
  if (window.electronAPI) {
    isUpdatingProgress.value = !isUpdatingProgress.value;
    // 通过 IPC 向主进程发送控制信号
    if (isUpdatingProgress.value) {
        window.electronAPI.showNotification('开始模拟进度更新...');
        window.electronAPI.onUpdateProgress((p: number) => {
            progress.value = p;
            if (p === 100) {
                isUpdatingProgress.value = false;
                window.electronAPI?.showNotification('进度更新完成!');
            }
        });
        // 告诉主进程开始发送更新消息
        window.electronAPI.onUpdateProgress(() => {}); // 占位,实际调用主进程方法来启动
        ipcRenderer.send('main:startProgressUpdate', true); // 主进程需要监听这个事件
    } else {
        window.electronAPI.showNotification('停止模拟进度更新。');
        ipcRenderer.send('main:startProgressUpdate', false);
    }
  }
};

// 应用更新相关
const handleRestartApp = () => {
    if (window.electronAPI) {
        window.electronAPI.restartApp();
    }
};

onMounted(() => {
    // 监听自动更新状态
    if (window.electronAPI) {
        window.electronAPI.onUpdateStatus((status: string) => {
            updateStatus.value = status;
        });
    }
});

onUnmounted(() => {
    // 清理监听器 (如果 onUpdateStatus 返回了取消函数)
    // 例如:const unsubscribe = window.electronAPI.onUpdateStatus(...)
    //       unsubscribe();
});
</script>
            

3.2 状态管理与路由

四、性能优化深度解析

Electron 应用的性能优化是生产级应用的关键。它涉及多个层面,从代码编写到构建配置。

4.1 启动性能优化

4.2 运行时性能优化

4.3 打包体积优化

五、数据存储与持久化

根据数据类型和需求,选择合适的存储方案:

经验建议: 对于大多数应用,主进程 + SQLite 是管理本地结构化数据的最佳实践。配置、日志等少量数据可直接写入文件。渲染进程只通过 IPC 访问这些数据。

六、应用升级与版本管理

提供顺畅的应用升级体验是桌面应用长期维护的关键。

6.1 自动更新 (electron-updater)

electron-updaterautoUpdater 模块的高级封装,与 electron-builder 完美结合。

electron/utils/update.ts (示例)

import { autoUpdater } from 'electron-updater';
import { dialog, BrowserWindow, app } from 'electron';
import log from 'electron-log'; // 推荐使用 electron-log 进行日志管理

autoUpdater.logger = log; // 设置日志输出
autoUpdater.logger.transports.file.level = 'info';
autoUpdater.autoDownload = true; // 默认自动下载新版本

export function initAutoUpdater(mainWindow: BrowserWindow | null) {
    if (!mainWindow) return;

    // 设置更新服务器地址 (如果不是GitHub Releases,需要配置)
    // autoUpdater.setFeedURL({
    //     provider: 'generic', // 例如通用HTTP服务器
    //     url: 'http://your-update-server.com/updates/' + process.platform + '/' + app.getVersion()
    // });

    // 检查更新
    const checkForUpdates = () => {
        console.log('检查更新...');
        mainWindow?.webContents.send('update-status', '正在检查更新...');
        autoUpdater.checkForUpdates(); // 不需要 .then,监听事件即可
    };

    // 监听事件
    autoUpdater.on('checking-for-update', () => {
        mainWindow?.webContents.send('update-status', '正在检查更新...');
    });

    autoUpdater.on('update-available', (info) => {
        log.info('发现新版本:', info.version);
        mainWindow?.webContents.send('update-status', `发现新版本: ${info.version},正在下载...`);
    });

    autoUpdater.on('update-not-available', () => {
        mainWindow?.webContents.send('update-status', '当前已是最新版本。');
    });

    autoUpdater.on('download-progress', (progressObj) => {
        const message = `下载进度: ${progressObj.percent ? progressObj.percent.toFixed(2) : 0}% (${progressObj.transferred}/${progressObj.total})`;
        log.info(message);
        mainWindow?.webContents.send('update-status', message);
    });

    autoUpdater.on('update-downloaded', (info) => {
        log.info('更新下载完毕,准备安装...', info);
        mainWindow?.webContents.send('update-status', `新版本 ${info.version} 已下载,重启应用以安装。`);

        dialog.showMessageBox(mainWindow!, {
            type: 'info',
            title: '应用更新',
            message: `新版本 ${info.version} 已下载,是否立即安装并重启?`,
            buttons: ['立即安装', '稍后']
        }).then(result => {
            if (result.response === 0) { // 用户选择立即安装
                autoUpdater.quitAndInstall(); // 退出并安装
            }
        });
    });

    autoUpdater.on('error', (err) => {
        log.error('更新出错:', err);
        mainWindow?.webContents.send('update-status', `更新出错: ${err.message}`);
        dialog.showErrorBox('更新失败', `检查更新时发生错误: ${err.message}`);
    });

    // 启动时延迟检查更新,避免阻塞应用启动
    setTimeout(checkForUpdates, 5000);
}
            

版本发布与服务器:

6.2 版本回滚与兼容性

七、生产级部署与发布

将开发完成的 Electron 应用交付给用户。

7.1 使用 electron-builder 打包

electron-builder 是功能强大的打包工具,支持跨平台打包(Windows, macOS, Linux)并生成各种安装格式。

终端命令

# 打包所有平台
npm run electron:build

# 只打包 Windows
npm run electron:build --win

# 只打包 macOS
npm run electron:build --mac

# 只打包 Linux
npm run electron:build --linux
            

7.2 持续集成/持续部署 (CI/CD)

自动化构建、测试、打包和发布流程,是生产环境的最佳实践。

graph TD Dev[开发者提交代码] --> Git[Git 仓库] Git --> CI[CI 服务
(GitHub Actions, GitLab CI, Jenkins)] CI --> BuildVue[构建 Vue 前端] CI --> BuildElectron[编译 Electron 主进程] CI --> PackApp[使用 electron-builder 打包应用] PackApp --> SignApp[应用签名 (可选,但推荐)] SignApp --> Release[发布到更新服务器
(GitHub Releases, S3, CDN)] Release --> User[用户下载/自动更新] subgraph CI/CD Pipeline BuildVue BuildElectron PackApp SignApp Release end

八、开发调试与测试

高效的调试和严谨的测试是保证应用质量的关键。

九、未来展望与生态

十、结语

这份教程涵盖了从 Electron 基础、项目搭建、核心功能实现、进程间通信、深度性能优化、版本升级到部署发布的全链路知识。我希望它能为您在 Electron + Vue 3 的桌面应用开发之路上提供坚实的指导和宝贵的实践经验。

记住,桌面应用开发是一个系统工程,需要对前端、后端、操作系统特性甚至部署运维都有所了解。持续学习、勤于实践、保持对新技术的探索,您必将成为一名优秀的 Electron 开发者!祝您编码顺利,项目成功!

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流