1. 为什么选择ElectronViteVue3组合在开始动手之前我们先聊聊为什么这个技术组合如此受欢迎。我最初接触Electron开发时用的是WebpackVue2的组合每次保存代码都要等上好几秒才能看到效果调试起来特别痛苦。后来尝试了ViteVue3开发体验直接提升了好几个档次。速度优势是Vite最明显的特性。传统的打包工具需要先打包整个应用才能启动开发服务器而Vite直接利用浏览器原生ES模块支持启动时间几乎可以忽略不计。在我的MacBook Pro上一个中等规模的Electron项目用Webpack启动要15秒左右而Vite只需要不到1秒。Vue3的组合式API让代码组织更加灵活。特别是在Electron开发中我们经常需要处理各种原生事件和状态管理组合式API能让我们把相关逻辑集中在一起而不是分散在多个选项块中。比如窗口大小变化的响应逻辑可以封装成一个独立的useWindowSize组合函数。Electron本身则解决了跨平台桌面应用的核心需求。我做过一个数据可视化工具需要读取本地文件系统并展示复杂图表。用纯Web技术无法直接访问文件系统而用传统原生开发又太复杂。Electron完美地结合了两者的优势让我能用熟悉的Web技术实现桌面应用功能。2. 环境准备与项目初始化2.1 基础环境配置首先确保你的开发环境满足以下要求Node.js 18.x或更高版本推荐使用nvm管理Node版本npm 8.x或更高版本或使用yarn、pnpm一个顺手的代码编辑器VSCode是我的首选建议先配置好国内镜像源可以大幅提升依赖安装速度npm config set registry https://registry.npmmirror.com/2.2 使用electron-vite创建项目electron-vite是一个专门为Electron优化的构建工具它简化了配置过程。我们用它来初始化项目npm create quick-start/electron my-electron-app创建过程中会有几个选项需要选择选择框架时选VueTypeScript按需选择本文示例用JavaScript建议启用Electron更新插件国内用户一定要开启镜像代理初始化完成后目录结构是这样的my-electron-app ├── src │ ├── main # 主进程代码 │ ├── preload # 预加载脚本 │ └── renderer # 渲染进程Vue项目 ├── electron.vite.config.mjs # 构建配置 └── package.json2.3 启动开发模式进入项目目录并安装依赖cd my-electron-app npm install然后修改package.json在dev脚本后添加--watch参数以启用主进程热更新scripts: { dev: electron-vite dev --watch }现在运行开发服务器npm run dev你会看到一个Electron窗口弹出里面是Vue的欢迎页面。试着修改src/renderer/App.vue文件保存后能看到即时更新的效果。3. 核心概念与进程通信3.1 Electron的进程模型Electron应用由三种进程组成主进程每个应用只有一个负责创建窗口和管理应用生命周期渲染进程每个窗口对应一个负责展示Web内容预加载脚本在渲染进程加载前执行桥接主进程和渲染进程理解这个模型很重要。我刚开始时经常混淆各个进程的能力边界比如试图在渲染进程直接调用Node.js API结果遇到各种问题。3.2 进程间通信实践让我们实现一个实用功能在Vue组件中点击按钮让主进程弹出一个原生对话框。首先在预加载脚本(src/preload/index.js)中暴露APIconst { contextBridge, ipcRenderer } require(electron) contextBridge.exposeInMainWorld(electronAPI, { openDialog: () ipcRenderer.invoke(dialog:open) })然后在主进程(src/main/index.js)中添加处理程序const { ipcMain, dialog } require(electron) ipcMain.handle(dialog:open, async () { const { filePaths } await dialog.showOpenDialog({ properties: [openFile] }) return filePaths[0] || null })最后在Vue组件中使用template button clickhandleOpen选择文件/button /template script setup const handleOpen async () { const filePath await window.electronAPI.openDialog() if (filePath) { console.log(选择的文件:, filePath) } } /script这种模式是Electron开发中最常用的通信方式。记住几个要点预加载脚本使用contextBridge安全地暴露API主进程用ipcMain.handle处理调用渲染进程通过window对象访问暴露的API4. 完善Vue开发环境4.1 添加Vue全家桶虽然electron-vite已经配置了Vue3但我们还需要一些常用工具安装路由和状态管理npm install vue-router pinia配置路由(src/renderer/router/index.js):import { createRouter, createWebHashHistory } from vue-router const routes [ { path: /, component: () import(../views/Home.vue) }, { path: /about, component: () import(../views/About.vue) } ] export const router createRouter({ history: createWebHashHistory(), routes })配置Pinia(src/renderer/stores/index.js):import { defineStore } from pinia export const useAppStore defineStore(app, { state: () ({ count: 0 }), actions: { increment() { this.count } } })在main.js中安装它们import { createApp } from vue import App from ./App.vue import { router } from ./router import { createPinia } from pinia const app createApp(App) app.use(router) app.use(createPinia()) app.mount(#app)4.2 样式与工具类配置我习惯使用SCSS和TailwindCSS。先安装必要的依赖npm install -D sass tailwindcss postcss autoprefixer npx tailwindcss init配置tailwind.config.js:module.exports { content: [ ./src/renderer/**/*.{vue,js,ts,jsx,tsx} ], theme: { extend: {}, }, plugins: [], }然后在src/renderer/assets目录下创建main.scss:tailwind base; tailwind components; tailwind utilities; /* 自定义样式 */最后在main.js中引入import ./assets/main.scss5. 窗口管理与原生功能5.1 自定义窗口行为Electron允许我们完全控制窗口的外观和行为。修改主进程代码创建一个自定义窗口const createWindow () { const win new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, frame: false, // 无边框窗口 titleBarStyle: hidden, titleBarOverlay: { color: #2f3241, symbolColor: #fff }, webPreferences: { preload: path.join(__dirname, ../preload/index.js), webSecurity: false // 开发时允许跨域 } }) if (process.env.VITE_DEV_SERVER_URL) { win.loadURL(process.env.VITE_DEV_SERVER_URL) win.webContents.openDevTools() } else { win.loadFile(path.join(__dirname, ../renderer/index.html)) } }5.2 实现自定义标题栏由于我们使用了无边框窗口需要自己实现标题栏。在Vue组件中template div classtitlebar div classtitle我的应用/div div classcontrols button clickminimize-/button button clickmaximize□/button button clickclose×/button /div /div /template script setup const minimize () window.electronAPI.minimize() const maximize () window.electronAPI.maximize() const close () window.electronAPI.close() /script style scoped .titlebar { -webkit-app-region: drag; display: flex; justify-content: space-between; background: #2f3241; color: white; padding: 0 10px; } .controls { -webkit-app-region: no-drag; } .controls button { background: none; border: none; color: white; padding: 0 10px; cursor: pointer; } /style对应的预加载脚本APIcontextBridge.exposeInMainWorld(electronAPI, { minimize: () ipcRenderer.send(window:minimize), maximize: () ipcRenderer.send(window:maximize), close: () ipcRenderer.send(window:close) })主进程处理ipcMain.on(window:minimize, () win.minimize()) ipcMain.on(window:maximize, () { if (win.isMaximized()) { win.unmaximize() } else { win.maximize() } }) ipcMain.on(window:close, () win.close())6. 打包与发布6.1 配置打包选项electron-vite使用electron-builder进行打包。修改electron-builder.ymlappId: com.example.myapp productName: 我的应用 copyright: Copyright © 2023 directories: output: dist buildResources: build files: - from: . filter: - **/* to: . win: target: - target: nsis arch: - x64 icon: build/icon.ico nsis: oneClick: false perMachine: false allowToChangeInstallationDirectory: true6.2 处理打包常见问题国内网络环境下打包可能会遇到下载工具失败的问题。解决方法手动下载所需二进制文件winCodeSign: https://cdn.npm.taobao.org/dist/electron-builder-binaries/winCodeSign-2.6.0/winCodeSign-2.6.0.7znsis: https://cdn.npm.taobao.org/dist/electron-builder-binaries/nsis-3.0.4.1/nsis-3.0.4.1.7znsis-resources: https://cdn.npm.taobao.org/dist/electron-builder-binaries/nsis-resources-3.4.1/nsis-resources-3.4.1.7z解压到指定目录Windows:C:\Users\你的用户名\AppData\Local\electron-builder\Cache6.3 生成安装包运行打包命令npm run build打包完成后dist目录下会有安装包和可执行文件。如果要生成免安装的绿色版可以配置win: target: - target: portable arch: - x647. 进阶技巧与优化7.1 性能优化建议Electron应用容易变得臃肿这里有几个优化技巧按需加载Node模块很多Node模块体积很大只在主进程需要时再require启用上下文隔离防止渲染进程直接访问Node API提高安全性使用Vite的代码分割Vue路由的懒加载可以显著减少初始加载时间优化渲染进程像Web应用一样避免不必要的DOM操作和大型状态7.2 调试技巧开发时可以利用这些调试工具主进程VSCode的JavaScript调试终端渲染进程Chrome开发者工具默认会自动打开进程通信在控制台打印ipc通信日志一个有用的调试配置(.vscode/launch.json){ version: 0.2.0, configurations: [ { name: Debug Main Process, type: node, request: launch, cwd: ${workspaceFolder}, runtimeExecutable: ${workspaceFolder}/node_modules/.bin/electron-vite, runtimeArgs: [dev], outputCapture: std, skipFiles: [node_internals/**] } ] }7.3 原生功能扩展Electron的强大之处在于可以轻松集成原生功能。比如实现一个全局快捷键主进程中const { globalShortcut } require(electron) app.whenReady().then(() { globalShortcut.register(CommandOrControlShiftI, () { win.webContents.openDevTools() }) }) app.on(will-quit, () { globalShortcut.unregisterAll() })或者使用系统托盘const { Tray, Menu } require(electron) const path require(path) let tray null app.whenReady().then(() { tray new Tray(path.join(__dirname, ../resources/tray.png)) const contextMenu Menu.buildFromTemplate([ { label: 显示, click: () win.show() }, { label: 退出, click: () app.quit() } ]) tray.setToolTip(我的应用) tray.setContextMenu(contextMenu) })8. 项目结构与代码组织8.1 推荐的项目结构经过多个Electron项目实践我总结出这样的目录结构src/ ├── main/ │ ├── lib/ # 主进程工具库 │ ├── services/ # 后台服务 │ ├── windows/ # 多窗口管理 │ └── index.js # 主入口 ├── preload/ │ ├── lib/ # 预加载脚本工具 │ └── index.js # 主入口 └── renderer/ ├── src/ │ ├── assets/ │ ├── components/ │ ├── composables/ # Vue组合式函数 │ ├── router/ │ ├── stores/ │ ├── views/ │ ├── App.vue │ └── main.js └── index.html8.2 状态共享模式主进程和渲染进程之间共享状态是个常见需求。我通常采用这种模式主进程维护真实状态通过ipc暴露状态读写接口渲染进程通过Pinia封装这些接口例如实现一个主题切换功能主进程let currentTheme light ipcMain.handle(theme:get, () currentTheme) ipcMain.handle(theme:set, (_, theme) { currentTheme theme win.webContents.send(theme:changed, theme) })渲染进程的Pinia storeexport const useThemeStore defineStore(theme, () { const theme ref(light) const getTheme async () { theme.value await window.electronAPI.invoke(theme:get) } const setTheme async (newTheme) { await window.electronAPI.invoke(theme:set, newTheme) theme.value newTheme } // 监听主题变化 window.electronAPI.on(theme:changed, (_, newTheme) { theme.value newTheme }) return { theme, getTheme, setTheme } })这种模式保持了单一数据源同时提供了响应式的使用体验。