Electron + Vue 项目结构搭建:主进程、渲染进程与IPC通信实践 我最早是在做一个后台管理系统的时候碰上了 Electron。当时需求很明确系统本身是 Vue 写好的网页但客户希望在 Windows 桌面上能直接双击打开、离线可用、还能调用一些浏览器里做不了的系统能力比如文件拖拽和本地目录读写。如果单独做一套桌面客户端等于推翻重来用 Electron 给 Vue 套一层壳是最顺的路径。最近又帮一个新手团队把这个结构重新搭了一遍踩了不少坑顺手把整个搭建流程和背后的考虑写清楚。这篇文章不搞长篇大论就聚焦在“Electron 构建 Vue 项目基本结构”这个主题上把这几个问题讲透为什么要这样搭、脚手架怎么选、主进程和渲染进程怎么分工、Vue 在桌面环境里有哪些必须处理的细节、打包发布前要注意什么。不管你是刚接触 Electron 的 Vue 开发者还是打算把现有网页改造成桌面软件的老手这份内容都能直接对照着操作。我尽量把每一步背后的为什么也说清楚不只是丢一条命令给你。1. 为什么是 Electron Vue方案选型怎么想1.1 Electron 到底解决什么问题很多人一上来就纠结 Electron 和原生客户端的区别。其实 Electron 的本质就是一个“浏览器壳”它内嵌了完整的 Chromium 内核和 Node.js 运行时前端代码跑在渲染进程里系统底层能力由主进程通过 Node.js 的 API 暴露出来。你用 Vue 写的页面本质上还是在写网页只是这个网页被 Electron 装进了桌面窗口里。这个方案最大的价值在于技术栈复用一个前端团队不需要重新学 C 或 Rust 就能交付桌面应用业务逻辑、UI 组件、状态管理全部沿用 Web 端的既有资产。我见过很多团队就是因为这个原因选 Electron 而不是其他方案。代价是打包体积偏大、内存占用偏高但在大部分企业管理类和工具类软件场景里这些缺点完全可接受。在你动手之前心里要有数Electron 适合做“交互重、界面复杂、更新频繁”的应用比如编辑器、聊天工具、数据看板不适合做“极简且性能敏感”的工具比如文本编辑器插件、后台驻留服务。搞清楚这一点你才知道后面的一些取舍该怎么定。1.2 渲染层为什么用 Vue 而不是别家Electron 本身不限制前端框架原生 JavaScript、React、Vue、Svelte 都能跑。选 Vue 的核心理由是它的渐进式设计你可以只在某个页面里引入 Vue也可以做一个完整的单页应用它的响应式系统和单文件组件结构跟 Electron 的窗口 页面模型匹配得非常好。具体到工程上Vue 生态里的路由、状态管理、组件库都是现成的。做中后台管理系统的话市面上有大量免费的开源管理模板可以改造成 Electron 渲染层几天就能出一个能看的桌面端原型。再加上 Vue 的开发者工具在 Electron 渲染进程里是可以直接调试的这一点对排查问题非常有用。如果你问我 React 或原生能不能用当然能。但这个主题定的就是 Vue所以后续内容我全部以 Vue 3 Vite 技术栈为基线展开这也是目前创建新项目时最合理的选择。1.3 脚手架方案对比快速搭建还是手动集成搭建 Electron Vue 项目主要有三条路我整理了一张对比表格方案优势劣势适合场景electron-vite 官方脚手架主进程、预加载、渲染层三端统一构建热更新完善需要适应它的目录约定新项目、常规桌面应用已有 Vue 项目 手动引入 Electron不改动现有业务代码逻辑需要自己处理通信、打包、路径等大量细节把现有网页改造成桌面端Vue CLI electron-builder 插件老一批教程常用生态较成熟技术栈偏旧Vite 生态已经明显更优维护老项目时有参考价值我个人的建议是新项目一律用 electron-vite。它内置了主进程、预加载脚本、渲染进程三套独立的构建配置开发时主进程和 renderer 同时启动改主进程代码能自动重启 Electron改 Vue 页面走 Vite 的热更新整个开发体验非常顺滑。这篇博文后面所有操作也基于这个方案。2. 从零初始化 Electron Vue 项目2.1 环境检查与准备动手之前先确认本机的环境。Node.js 版本建议 18 以上我用的是 20 LTS整个过程没有遇到兼容性问题。检查命令node -v npm -v如果你还在用 npm 6 或者更老的 Node建议先用 nvm 切换到新版本不要在这上面节省时间。Electron 相关的依赖包对 Node 版本有要求老版本经常会出现模块编译失败的情况。还有一点要提前说因为 Electron 本身要下载二进制文件国内网络环境下安装依赖时经常卡住。解决办法是设置 npm 镜像和 Electron 镜像在项目根目录下的.npmrc文件里写入registryhttps://registry.npmmirror.com electron_mirrorhttps://npmmirror.com/mirrors/electron/这两行配置能避免大半的下载超时问题。如果不设置你可能会反复看到安装进程卡在download electron这个地方非常折磨人。2.2 用 electron-vite 创建一个可直接运行的工程electron-vite 官方提供了一条脚手架命令直接执行npm create quick-start/electronlatest命令运行后会询问项目名称我建议不要用带大写字母的名字包名规范一点后面打包时省不少麻烦。接下来会让你选择项目模板这里直接选vue模板TypeScript 选或不选都可以新手的话我建议直接选上ESLint 也可以默认装上能做最基本的代码规范检查。生成完毕后的目录结构大致长这样my-electron-app/ ├── src/ │ ├── main/ # 主进程代码 │ │ └── index.ts │ ├── preload/ # 预加载脚本 │ │ └── index.ts │ └── renderer/ # Vue 渲染进程 │ ├── src/ │ │ ├── App.vue │ │ ├── main.ts │ │ └── components/ │ └── index.html ├── electron.vite.config.ts # 三端构建配置 ├── electron-builder.yml # 打包配置 └── package.json这个目录结构就是 Electron Vue 项目的基本骨架后面所有的业务代码都往对应目录里放千万不要乱放后面讲每个目录的职责你会更清楚为什么这么分。然后安装依赖npm install依赖装完后启动开发模式npm run dev如果一切顺利你会看到一个默认窗口弹出来这就是最基础的 Electron Vue 可运行工程。2.3 安装依赖踩过的坑第一次执行npm install时最常见的两个坑一个是 Electron 二进制下载失败另一个是 electron-vite 或某些原生模块编译失败。二进制下载失败症状就是安装进度一直停在 Electron 的下载阶段或者提示超时。解决办法就是前文提到的.npmrc配置。第二个坑通常出现在 Node 版本不对时比如某些模块需要 Node 18但你用的是 16编译时会报gyp ERR之类的错误这种问题升级 Node 版本基本就解决了。还有一个小提示如果你在 Windows 上安装时看到类似 “electron 安装失败” 的错误可以先手动删除项目里的node_modules清理 npm 缓存后再安装。命令rm -rf node_modules package-lock.json npm cache clean --force npm install遇到过好多次了这种“先彻底清一遍”的笨办法比各种花式排查靠谱得多。3. 主进程、预加载、渲染层如何分工3.1 三个目录三种角色Electron 应用里最核心的概念是“进程”这个不搞懂后面一写功能就乱。简单说主进程应用的大脑负责创建窗口、管理生命周期、操作系统 API、菜单、托盘等。它运行在 Node.js 环境里可以随意使用文件系统、网络、子进程等能力。渲染进程浏览器环境跑 Vue 页面。它基本等同于一个 Chrome 标签页无法直接使用 Node.js API出于安全考虑。预加载脚本主进程和渲染进程之间的“信使”运行在一个特殊的上下文里通过contextBridge把安全封装好的 API 暴露给渲染进程。对应的就是src/main、src/renderer、src/preload三个目录。这个结构不光是 electron-vite 的约定也是 Electron 官方推荐的最佳实践。3.2 预加载脚本与 contextBridge 安全桥接早期 Electron 教程里有种做法是直接在渲染进程里开启nodeIntegration: true让 Vue 组件里直接require(fs)读文件。这在原型开发时很快但非常危险——万一页面里有用户的不可信输入攻击者就能通过你的页面直接操作使用者电脑属于典型的严重漏洞。现在新项目里建议全部禁用 nodeIntegration统一走contextBridge。预加载脚本的作用就是安全地暴露主进程的能力。在src/preload/index.ts里这样写import { contextBridge, ipcRenderer } from electron const api { getAppVersion: () ipcRenderer.invoke(app:get-version), openExternal: (url: string) ipcRenderer.invoke(shell:open-external, url) } if (process.contextIsolated) { contextBridge.exposeInMainWorld(api, api) }然后在 Vue 组件里通过window.api来调用const version await window.api.getAppVersion()这个模式既能保证渲染进程拿不到完整的 Node 能力又能把主进程的能力以白名单方式安全传递下去。把预加载脚本当成“统一出口”来用后面维护 IPC 通信会变得特别清晰。3.3 IPC 通信渲染进程和主进程怎么互相喊话Electron 的 IPCInter-Process Communication是双向的。渲染进程往主进程发消息常用ipcRenderer.invokeipcMain.handle适合一问一答式调用如果只是单向通知可用sendon。主进程侧注册接口import { ipcMain, shell } from electron ipcMain.handle(app:get-version, (event) process.env.npm_package_version) ipcMain.handle(shell:open-external, async (event, url: string) { await shell.openExternal(url) })这里有个经验所有 IPC 通道都用域:动作的格式命名。比如app:get-version、user:login、file:save。项目大了之后会有几十个 IPC 通道统一的命名规则能让你一眼看出这个接口是哪个模块在用什么功能否则后面找接口就是你最痛苦的事情。主进程向渲染进程主动推送消息的场景比如主进程监听某个文件变化后通知页面更新用webContents.sendmainWindow.webContents.send(file:changed, filePath)渲染进程里对应监听window.api.onFileChanged((path) { console.log(文件变更:, path) })如果不需要持续监听记得在组件卸载时取消监听否则会造成内存泄漏。4. Vue 页面在桌面环境里的关键适配4.1 资源路径问题为什么打包后页面白屏很多人的 Electron Vue 项目开发时一切正常一打包就白屏。十有八九是 Vite 的 base 路径没配好。网页项目部署在服务器上资源路径可以用绝对路径/assets/xxx.js但 Electron 打包后是通过file://协议加载本地文件的根路径/对应的不是你的项目目录这就导致资源加载失败。解决办法是在electron.vite.config.ts里给渲染进程设置base: ./用相对路径方式引用所有资源export default defineConfig({ main: { ... }, preload: { ... }, renderer: { base: ./ } })这样打包出来的 HTML 里引用资源用的就是相对的./assets/xxx.js在file://协议下能正确加载。只要是 Electron 项目这个配置几乎是必须的。4.2 路由模式为什么强烈建议用 hash 路由Vue Router 默认的 history 路由适合网站因为服务器可以配置 fallback 把所有未知路径重定向到入口 HTML。但 Electron 加载本地文件时没有服务器做这个重定向你点一下页面里的a或者手动改路由一旦刷新页面很容易变成白屏或者直接提示找不到文件。所以 Electron Vue 项目里路由模式统一用import { createWebHashHistory, createRouter } from vue-router const router createRouter({ history: createWebHashHistory(), routes: [...] })hash 模式下路由变化靠#后面的内容记录刷新页面时不会发起新的文件请求在桌面环境下稳得很。网上很多人在 Electron 里遇到“刷新页面白屏”“直接打开某个路由路径打不开”的问题根源基本就是这里。4.3 跨域与接口请求别再直接在渲染进程发请求Node.js 渲染进程其实是一个正常的浏览器环境页面里的fetch/axios依然受同源策略限制。你在网页开发时可以通过后端配置 CORS 解决在 Electron 开发环境里也能通过webSecurity: false这种配置临时处理但千万不要在正式环境里这么干——关闭这个就等于给用户留了一个安全后门。比较推荐的做法分两种如果只是请求某个 HTTP 接口而接口服务允许跨域那么正常请求即可。如果接口不允许跨域或者需要带本地文件路径访问就通过 IPC 把请求交给主进程完成。主进程里没有跨域限制请求完再把结果返回给渲染进程。比如下载一个远程文件// 主进程 ipcMain.handle(net:download, async (event, url, savePath) { const res await fetch(url) const buffer Buffer.from(await res.arrayBuffer()) await fs.promises.writeFile(savePath, buffer) return { ok: true } })这样所有和系统强相关的操作都收口到主进程Vue 里只用调一个封装好的方法就行。5. 窗口配置与调试技巧5.1 定制窗口的基础配置Electron 的窗口体验和普通浏览器差别很大你可以在src/main/index.ts里通过BrowserWindow配置很多参数。常见几个const mainWindow new BrowserWindow({ width: 1200, height: 800, minWidth: 900, minHeight: 600, autoHideMenuBar: true, webPreferences: { preload: join(__dirname, ../preload/index.js), contextIsolation: true, nodeIntegration: false } })autoHideMenuBar: true是我用得非常顺手的一个配置它会自动隐藏默认菜单栏视觉上更像原生应用。如果你做的是全屏看板类应用可能还需要设置fullscreen: true或者kiosk: true。窗口打开时的加载逻辑也有讲究开发环境和生产环境加载方式不同需要这样处理if (process.env.ELECTRON_RENDERER_URL) { mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL) } else { mainWindow.loadFile(join(__dirname, ../renderer/index.html)) }ELECTRON_RENDERER_URL是 electron-vite 开发模式下自动注入的环境变量指向本地开发服务器生产环境则直接加载打包后的 HTML 文件。5.2 调试方法renderer 和 main 分开看Electron 开发时的调试体验其实比很多原生桌面框架好。渲染进程就是普通的 Chromium DevTools在窗口里按CtrlShiftI就能调出来你能像调试网页一样检查 DOM、看网络请求、打断点。主进程的调试要麻烦一点。electron-vite 在开发模式下会以 Node.js 调试端口运行主进程你可以打开 Chrome 地址栏输入chrome://inspect来调试主进程代码。但说实话日常开发中主进程用console.log输出日志基本够用因为主进程逻辑通常不复杂大多数问题都出在 IPC 和路径上。还有一个我常用的技巧在窗口创建完成时自动打开 DevTools省去手动按快捷键mainWindow.webContents.openDevTools()平时建议留着需要发布时才删掉。6. 打包与发布前的常见问题实录6.1 electron-builder 配置要点electron-vite 脚手架默认集成了 electron-builder打包配置写在electron-builder.yml里。一份最基础的 Windows 打包配置appId: com.example.myapp productName: MyApp directories: buildResources: build files: - !**/.vscode/* - !src/* - !electron.vite.config.{js,ts,mjs,cjs} - !{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md} - !{.env,.env.*,.npmrc,pnpm-lock.yaml} win: target: - nsis nsis: oneClick: false allowToChangeInstallationDirectory: true createDesktopShortcut: true执行npm run build npm run build:win然后dist目录下就会生成安装包和免安装版。这里有个容易踩的坑打包机最好和发布目标系统一致如果你在 macOS 上打 Windows 包需要额外配置 Wine 和 Cross 编译环境如果不搞 CI建议直接在 Windows 机器上跑build:win。6.2 常见问题速查表下面这张表是我这几年在 Electron Vue 项目里高频遇到并排查过的坑直接对着看能省很多时间现象原因解决方案打包后白屏Vite base 路径是绝对路径设置base: ./刷新页面白屏使用了 history 路由切换到 hash 路由页面请求接口报跨域渲染进程作为浏览器受同源策略限制请求收口到主进程发Electron 安装失败二进制下载慢或超时配置镜像源窗口显示不出来主进程代码报错但控制台看不到用--enable-logging启动或看终端日志打包后文件体积巨大Electron 内嵌了完整 Chromium换小体积运行时或接受体积做增量更新Node 模块报错Node 版本兼容问题升级到 Node 18可能你还会遇到打包后第一次启动很慢的问题。多数情况下是杀毒软件扫描安装目录导致的跟代码没关系。可以考虑引导用户把应用加入白名单或者做静默启动优化。6.3 实际经验小建议最后分享几条我实操过程中的个人习惯不一定适合所有人但至少能帮你少走弯路第一主进程的代码尽量精简。很多人喜欢把所有业务逻辑都塞到主进程里这种做法短期内没有问题但后面窗口变多、功能变多主进程会变成一团乱麻。我的习惯是主进程只做窗口管理和系统能力调用业务逻辑尽量留在 Vue 层通过 IPC 接口操作。这样主进程代码基本不变大部分开发可以像写网页一样在渲染进程里完成。第二预加载脚本一定要做类型声明。如果你用 TypeScript在src/preload/index.d.ts里声明window.api的接口类型这样 Vue 组件里写window.api的时候会有完整的类型提示效率高很多还不容易写错方法名。第三开发模式和打包模式不要混在一个环境里调试。经常有人在开发模式里设置了--no-sandbox或者用了比较激进的跨域配置最后打包出来出现和开发环境表现不一致的问题。开发和生产配置从一开始就分开并且打包后一定要做一次全功能冒烟测试。现在再看 Electron Vue 这个项目结构其实核心思路就是一句话分清主进程和渲染进程的职责用预加载脚本搭一座安全的桥让 Vue 专注做界面其余的系统能力全部交给主进程。遵循这个结构你会发现桌面应用开发并没有比写网页复杂太多但接手这些代码的人会觉得非常清晰。如果你正准备把一个 Vue 项目包装成桌面应用从这套结构开始后面会顺畅很多。