技术栈简介#
- Vue(建议 Vue 3 + Vite)
- 负责界面;组件生态全上,热重载飞起。
- Electron
- 主进程跑 Node 能力(文件、系统、窗口),渲染进程负责界面。
- 跨平台就是它的强项:macOS、Windows、Linux 一锅端。
- 脚手架/打包方案(挑一个)
electron-vite
:开箱即用,主/预加载/渲染三端都走 Vite,开发体验顺滑。vite-plugin-electron
:在现有 Vite 项目里加 Electron 能力,改动小。electron-builder
:产物打包/签名/自动更新一条龙(也是最常用)。
一句话:Vue 管“好看好用”,Electron 管“能干能装”,打包交给 electron-builder
,这仨配起来就差不多打通任督二脉了。
环境准备:别在门口摔跤#
- Node.js LTS(建议用
nvm
管理) - 包管理器:pnpm/yarn/npm 都行(我偏爱 pnpm)
- macOS 开发
- 打包和签名需要 Xcode Command Line Tools(
xcode-select --install
) - 真要上架或自动更新,后面还得准备 Apple 签名证书
- 打包和签名需要 Xcode Command Line Tools(
- Windows 开发
- Code Signing 证书可后配;打包 NSIS/portable 均可
- Linux 开发
- 常见目标是
AppImage
/deb
/rpm
- 常见目标是
初始化(任选其一):
# 方案 A:electron-vite(推荐上手)npm create electron-vite@latest my-app# 选 Vue 或 Vue + TypeScript
# 方案 B:已有 Vite + Vue 项目,叠加 Electronpnpm add -D electron vite-plugin-electron electron-builder
桌面应用开发实战:把“壳”和“页面”跑起来#
目录心智(常见三件套):
/electron/ main.ts # 主进程入口(创建窗口) preload.ts # 预加载脚本(桥接 API)/src/ # Vue 渲染进程 main.ts App.vue
主进程(electron/main.ts
)
import { app, BrowserWindow } from 'electron'import path from 'node:path'
const isDev = !app.isPackagedlet win: BrowserWindow | null = null
async function createWindow() { win = new BrowserWindow({ width: 1100, height: 760, minWidth: 960, title: 'Vue + Electron', webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, // 安全第一 }, })
if (isDev) { await win.loadURL('http://localhost:5173') win.webContents.openDevTools({ mode: 'detach' }) } else { win.removeMenu() await win.loadFile(path.join(__dirname, '../renderer/index.html')) }}
app.whenReady().then(createWindow)app.on('window-all-closed', () => process.platform !== 'darwin' && app.quit())app.on('activate', () => BrowserWindow.getAllWindows().length === 0 && createWindow())
预加载(electron/preload.ts
)
import { contextBridge, ipcRenderer } from 'electron'
// 只暴露你愿意给到渲染进程的“白名单”APIcontextBridge.exposeInMainWorld('api', { ping: () => ipcRenderer.invoke('ping'), openFile: (filters?: Electron.FileFilter[]) => ipcRenderer.invoke('dialog:openFile', filters),})
Vue 里用(src/main.ts
)
import { createApp } from 'vue'import App from './App.vue'createApp(App).mount('#app')
页面里调用(src/App.vue
)
<script setup lang="ts">const hi = async () => { const res = await window.api.ping() console.log(res)}</script>
<template> <div class="p-6"> <h1>Vue + Electron</h1> <button @click="hi">Ping 主进程</button> </div></template>
开发命令(以 electron-vite
为例,原样可跑):
pnpm dev# 会同时起 Electron 主进程 + Vite 渲染进程
IPC 通信:说话要走正道#
主进程开门(ipcMain.handle
):
import { ipcMain, dialog } from 'electron'
ipcMain.handle('ping', () => 'pong from main')
ipcMain.handle('dialog:openFile', async (_evt, filters) => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], filters, }) return canceled ? null : filePaths[0]})
渲染进程通过预加载桥接(上文 preload.ts
已暴露):
// window.api.ping() / window.api.openFile()
要点(别嫌啰嗦,都是血泪):
- 不要在渲染进程开
nodeIntegration
;该用什么都走preload + contextBridge
。 ipcRenderer.invoke
/ipcMain.handle
(请求-响应)比send/on
好控。- 统一定义频道名和参数类型,避免“口误”式 bug。
自动更新:吃过一次香一整年#
通用方案:electron-builder
+ electron-updater
- 托管渠道:GitHub Releases、私有 HTTP 服务器、S3、OSS 都行
- 流程:应用启动检查 → 有更新就静默下载 → 提示重启 → 下次起飞就是新版本
安装:
pnpm add -D electron-builder electron-updater
主进程里加(autoUpdater
):
import { autoUpdater } from 'electron-updater'import { dialog } from 'electron'
function setupAutoUpdate() { autoUpdater.autoDownload = true
autoUpdater.on('update-available', () => { console.log('发现新版本,开始下载…') })
autoUpdater.on('update-downloaded', () => { const res = dialog.showMessageBoxSync({ type: 'info', buttons: ['重启更新', '稍后'], title: '更新完成', message: '新版本已准备就绪,是否立即重启应用?', defaultId: 0, cancelId: 1, }) if (res === 0) autoUpdater.quitAndInstall() })
autoUpdater.on('error', (e) => console.error('更新失败:', e))
// 触发检查 autoUpdater.checkForUpdatesAndNotify()}
app.whenReady().then(() => { setupAutoUpdate() createWindow()})
发布端(最省心是 GitHub Releases):
electron-builder
会把产物 +latest.yml
(win)/appcast.xml
(mac)一并推上去- 客户端按
appId
和渠道去找更新清单
打包:三平台一锅端#
electron-builder
配置(放 package.json
或 electron-builder.yml
)
最小可用(package.json
片段):
{ "name": "vue-electron-demo", "version": "1.0.0", "main": "dist/electron/main.js", "build": { "appId": "com.example.vue_electron_demo", "productName": "VueElectronDemo", "directories": { "output": "release" }, "files": [ "dist/**", "dist-electron/**", "!**/*.map" ], "mac": { "target": ["dmg", "zip"], "category": "public.app-category.productivity", "hardenedRuntime": true, "entitlements": "entitlements.mac.plist", "entitlementsInherit": "entitlements.mac.plist" }, "win": { "target": ["nsis", "zip"] }, "linux": { "target": ["AppImage", "deb"], "category": "Utility" }, "publish": [ { "provider": "github" } ] }, "scripts": { "dev": "electron-vite dev", "build": "electron-vite build", "build:app": "electron-builder", "release": "electron-builder --publish always" }}
签名与发布小抄:
- macOS
- 开发者 ID 证书 + Hardened Runtime + 公证(notarize)
- CI 上记得注入
APPLE_ID
、APPLE_APP_SPECIFIC_PASSWORD
- Windows
- 购买/申请代码签名证书(
certificateSubjectName
或certificateFile
) - NSIS 安装器是常用选择
- 购买/申请代码签名证书(
- Linux
AppImage
覆盖最广,deb
/rpm
看发行版
体积与性能:
- 体积靠“瘦身”:把大依赖(如 Python/ffmpeg 这类原生家伙)移到外部;UI 静态资源做分包与压缩
- 性能靠“拆活儿”:CPU 密集型工作放 Node 侧或子进程,渲染进程主线程别硬抗
加餐:常见坑与处理#
- 刷新白屏/路径错误
- 生产环境加载本地文件要用
loadFile
,资源路径走file://
;Vite 的基础路径要检查
- 生产环境加载本地文件要用
- 多窗口共享数据
- 建议统一走主进程(或一个共享服务)中转,不要窗口间互相裸连
- 权限与沙箱
contextIsolation: true
+preload
白名单;尽量别开启nodeIntegration
- 原生模块
- 涉及
node-gyp
的依赖,CI 上注意平台差异与编译工具链
- 涉及
- 自动更新失败
- 80% 是证书/发布配置问题;本地先用 “private server” 跑通一遍,确认流程稳定再上公有渠道
小结#
用 Vue + Electron 开桌面端,其实就是把“前端开发体验”搬进桌面世界。开发期手感顺滑,能力边界也足够宽;真正上路,记得在 IPC 安全、自动更新、打包签名这些“看起来琐碎”的地方多花一分钟,后面能省一堆小时。要是遇到必须“忍痛割爱”的环节(比如某些巨无霸依赖),先想想有没有更轻的替代;能省的体积就别硬撑,真撑也是压榨自己。