恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Electron + Vue 桌面应用项目结构详解与实战

  • 首页
  • 资讯中心
  • /
  • Electron + Vue 桌面应用项目结构详解与实战

相关资讯

数字序列模式识别与应用技术解析 2026/10/14 13:43:54
阿里滑块X82YX5SECua算法Python复现与逆向解析 2026/10/14 13:43:54
QML矢量绘图实战:圆形、矩形、多边形与直尺的动态控制点编辑 2026/10/14 13:43:54

最新资讯

社交平台MFA绕过攻击与防御实战解析
研发团队如何用AI补齐设计短板:五大落地场景与工作流
FFmpeg 4.2.1 Win32 Shared 包使用指南:从解压到集成与避坑
Instagram钓鱼攻击与MFA绕过技术深度解析
Viper 红队实战:execute-assembly 内存执行 C 汇编 —— MSF 与 Cobalt Strike 对比、源码级问题剖析与修复
8G显存跑MiniMax-H3视频生成:720p二采优化实战

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Electron + Vue 桌面应用项目结构详解与实战

发布时间:2026/10/14 13:48:54
Electron + Vue 桌面应用项目结构详解与实战 有段时间我需要快速交付一个跨平台桌面工具团队里全是 Web 背景选型时几乎没怎么纠结就定了 Electron 配 Vue。原因很直接Electron 负责壳Vue 负责界面原本浏览器里的开发体验能原样搬到桌面端一套代码同时覆盖 Windows、macOS 和 Linux。这篇文章就是给第一次用 Electron 构建 Vue 项目的同学准备的我会从最基本的项目结构讲起把主进程、渲染进程、preload 脚本这些概念一次讲透再给出可以直接落地的目录规划、通信方案和打包配置。我尽量不写“教科书”而是以我自己反复搭建这套结构的实际经历为线索省得大家再踩我踩过的坑。1. Electron 与 Vue 的配合方式先搞清楚两个运行时很多人第一次接触 Electron 时心里默认它是一个“能写桌面应用的 Node.js”这时候去套 Vue很容易产生一个错觉Vue 组件里可以直接 require(fs) 读文件。实际并非如此。Electron 应用内部其实同时存在两个运行时一个是 Node.js另一个是 Chromium 浏览器内核它们分别对应不同的进程角色。搞清楚这个基本模型后续所有结构设计都会顺理成章。1.1 主进程、渲染进程与 preload 各管什么Electron 应用启动后至少会拉起三个层面的代码。主进程是入口文件所在的地方整个应用只有一个实例负责创建 BrowserWindow、管理窗口生命周期、处理系统级事件托盘、菜单、全局快捷键也拥有完整的 Node.js 能力。渲染进程每创建一个 BrowserWindow 就会产生一个Vue 应用就跑在这里但它本质上只是一个 Chromium 页面默认不直接拥有 Node.js 权限。preload 脚本则是在页面加载前执行的桥接代码运行在一个特殊环境里可以通过有限的 Node.js 能力暴露安全接口。用生活里的事情类比主进程像餐厅的后厨渲染进程像用餐区preload 像传菜窗口。Vue 只负责把菜摆盘、呈给用户看真正的“下厨”动作比如读文件、写配置、调系统弹窗都需要通过传菜窗口点单给后厨由主进程实际执行。这个模型的意义在于把能力体系按权限边界切开了渲染进程里跑的是用户可见的代码也是攻击面最大的地方所以默认要把 Node 能力关掉避免页面中一旦被注入恶意脚本就直接控制整个系统。1.2 为什么 Vue 只负责渲染层这一半从工程角度看Vue 在 Electron 里的定位和它在浏览器里没有本质区别处理数据、状态、组件、路由、页面交互。真正与操作系统打交道的那部分应该收拢到主进程。我见过不少刚上手的人把“Electron 里能跑 Vue”理解成“Electron 项目等于一个 Vue 项目挂个壳”于是在渲染进程里开 nodeIntegration 去读本地文件。这样做在小 Demo 里确实能跑通但一旦应用需要同时打开多个窗口、处理外部链接跳转、做自动更新所有能力都会堆在渲染进程里权限边界全部打乱。现代 Electron 的默认安全策略正好相反关闭 nodeIntegration开启 contextIsolation把需要的能力通过 preload 显式暴露。原生的安全设计其实已经替我们划好了项目的分界线我们要做的是顺着这条线组织代码而不是逆着它硬塞。所以“Electron 构建 Vue 项目基本结构”这件事本质上是在搭三条轨道主进程代码轨、preload 桥接轨、渲染进程 Vue 轨。三条轨道各有自己的构建目标和运行环境结构是否清晰直接决定了后续开发是顺畅还是混乱。1.3 开发模式下 electron-vite 如何把两边串起来我建议直接使用 electron-vite 这套构建方案而不是手写一堆 webpack 配置。原因是它把 Electron 特有的“多入口、两个运行时”问题封装得很干净。执行开发命令后electron-vite 会同时做三件事先把主进程代码和 preload 代码分别编译到 out/main 和 out/preload再启动一个 Vite dev server 托管 Vue 页面最后拉起 Electron 并加载 dev server 的地址。这样改 Vue 代码时走的是 Vite 热更新改主进程代码时可以通过监听重启 Electron 窗口整个开发体验和平时写 Web 前端几乎一致。用一句话总结electron-vite 把“Vue 构建”和“Electron 构建”两套工具链合并到了一起开发者只需要关心 src 下的业务代码编译分流的事情交给它处理。这个选择还会直接影响后面的目录结构设计因为脚手架默认的 src/main、src/preload、src/renderer 分布就是按 electron-vite 的构建目标划分的保持这个结构后续加配置时就不用反复改构建逻辑。如果你试图用普通 Vite 模板硬改成 Electron 项目通常要额外引入一堆插件和自定义脚本这也是我不建议从空项目手搓的原因。2. 初始化项目脚手架选择与目录规划2.1 直接采用 electron-vite 脚手架的理由创建项目的方式有很多种我实际上对比过三类。第一类是手动创建 package.json安装 electron 和 vue再自己配置 vite 或 webpack。这条路对理解原理最有帮助但对第一次做 Electron 的人来说很容易卡住主进程和渲染进程要两套构建目标还要手动处理 preload 脚本的模块转换工作量不大但特别碎。第二类是直接用通用桌面框架的初始化工具模板确实能跑但模板风格、版本兼容性和我们对 Vue 技术栈的习惯不一定匹配改起来成本反而高。第三类就是使用 electron-vite 提供的 create 脚手架生成 Vue 模板再把示例代码清掉。这是我当前最推荐的方式。生成命令大致是 npm create quick-start/electron过程中按提示选择 Vue 和需要的语言即可。生成之后electron-vite 的配置已经被内置好了后续只需按业务需要调整目录和代码。语言方面团队项目我更倾向 TypeScript因为主进程暴露出来的接口面会越来越大没有类型约束通信参数很容易在调用方写错如果只是快速验证原型用 JavaScript 确实更省事。2.2 生成后的目录骨架逐层拆解打开脚手架生成的项目核心目录基本是这个形态├── src │ ├── main │ │ └── index.js │ ├── preload │ │ └── index.js │ └── renderer │ ├── index.html │ └── src │ ├── main.js │ ├── App.vue │ └── ... ├── resources │ └── icon.png ├── electron-builder.yml └── package.json逐个说清楚。src/main 是主进程代码这里的文件会被 electron-vite 单独编译成一个可在 Node 环境执行的构建产物最终由 Electron 入口加载。src/preload 是 preload 脚本编译成一个独立文件在窗口创建时通过 webPreferences.preload 指定给对应窗口使用。src/renderer 是渲染进程代码内部就是一个常规 Vue 项目有 index.html、入口 main.js、App.vue还能继续细分 views、components、stores 等模块。resources 目录主要放应用图标等打包素材electron-builder.yml 是打包相关配置。这里有一个容易被忽视的点renderer 目录内部有自己的 src外层也有一个 src两者在脚手架里是刻意分开的。Vue 工程里的 src 和 Electron 主进程的 src 不要混在一起否则构建目标会乱掉。我自己维护中还会进一步拆细 src/main比如单独拆出 windows.js 管理窗口创建和复用拆出 ipc.js 集中注册 IPC 处理器拆出 menu.js 管理应用菜单把入口文件保持成非常薄的启动层。这个拆分看起来简单但对后续功能扩张帮助很大。2.3 手动补全基础文件时的注意点如果不使用脚手架而是自己从零搭重点检查三处。第一package.json 中的 main 字段必须指向 Electron 实际加载的入口文件。在 electron-vite 场景下通常是 out/main/index.js因为开发时 electron-vite 会把 src/main 编译到这里生产打包时也依赖这个路径。写错的话应用会启动失败或直接没有任何窗口。第二electron.vite.config.js 里同时配置 main、preload、renderer 三个部分但不要试图在一个配置里硬塞 Vue 插件的所有选项。渲染进程部分的配置和常规 Vite 的 vue 插件配置基本一致主进程部分则要像配置 Node 项目一样注意外部依赖的处理。electron-vite 默认把主进程依赖视为 external所以第三方库不会被打进 out/main 的产物里而是运行时从 node_modules 读取这个机制理解错了后面排查“打包后找不到模块”会很痛苦。第三依赖分类要分清。electron-builder 打包时package.json 里的 dependencies 会被视为运行时依赖打包进应用devDependencies 则只在构建阶段使用。如果你把运行时需要的库写进了 devDependencies打包后的应用很可能启动报错因为模块根本不在应用目录内。到这里一个能跑的结构已经有了接下来最关键的是通信层。3. 打通主进程与 Vue 页面的通信链路3.1 用 contextBridge 暴露受控接口Electron 的通信机制叫 IPC全称是进程间通信。渲染进程和主进程通过 ipcRenderer 与 ipcMain 互相发消息。为了让 Vue 页面用起来安全又顺手我不会直接暴露 ipcRenderer而是通过 preload 脚本里的 contextBridge 把自定义 API 暴露到 window 上。典型的 preload/index.js 写法如下import { contextBridge, ipcRenderer } from electron const api { getSystemInfo: () ipcRenderer.invoke(system:get-info), onUpdate: (callback) { const listener (_event, data) callback(data) ipcRenderer.on(update:push, listener) return () ipcRenderer.removeListener(update:push, listener) } } if (process.contextIsolated) { contextBridge.exposeInMainWorld(api, api) } else { window.api api }这里的核心是 contextBridge.exposeInMainWorld它做了一层对象隔离页面能访问到的只有你定义的几个方法而不是整个 ipcRenderer。如果直接把 ipcRenderer 暴露出去页面里的任何代码都可以向主进程发送任意 IPC 事件等于把整个系统能力的门钥匙交给了前台页面。接口收得越窄越容易维护。写 preload 时还需要注意受 contextIsolation 影响window 对象被隔离成两个世界普通赋值方式可能无效所以脚手架里判断 process.contextIsolated 再分别处理的那段逻辑不要删掉。3.2 渲染进程发起调用与主进程回应的完整示例最常用的通信模式是“渲染进程请求主进程回应”。以查询系统信息为例主进程里注册import { ipcMain } from electron ipcMain.handle(system:get-info, async () { return { platform: process.platform, version: process.versions.electron, memory: process.getSystemMemoryInfo() } })这里使用 ipcMain.handle 而不是 ipcMain.on因为它天然对应 Promise 风格的调用。渲染进程中假设组件里已经引入了 ref 和 onMountedVue 侧只需要这样写const info ref(null) async function loadSystemInfo() { info.value await window.api.getSystemInfo() } onMounted(loadSystemInfo)window.api.getSystemInfo 返回的就是 Promise与 async 函数配合非常自然不需要回调嵌套。如果你用的是 ipcRenderer.send 加 ipcMain.on 那种旧模式得自己维护请求与响应的对应关系并发请求多的时候很容易串号。所以现在的 Electron 版本里invoke/handle 是更推荐的请求-响应模式。要注意的是invoke 传输的数据会被结构化克隆。可以传普通对象、数组、Buffer但不能传函数和带原型链的类实例。所以接口设计上尽量传 JSON 兼容数据别试图把“会话对象”直接透传过去这个习惯能省掉很多怪异的报错。3.3 主进程主动推送事件的订阅与清理另一类常见通信是主进程主动向页面推送消息比如后台任务进度、文件下载状态、系统电量变化。主进程侧推送只需要一行win.webContents.send(update:push, { progress: 42 })页面侧就不能再用 invoke 了因为这是一条单向流。preload 里我习惯把 ipcRenderer.on 包成一个订阅函数具体写法就是 3.1 里 api.onUpdate 那个函数它接收一个回调返回一个取消订阅函数。组件在使用时就能很规范地清理let unsubscribe onMounted(() { unsubscribe window.api.onUpdate((data) { progress.value data.progress }) }) onBeforeUnmount(() { if (unsubscribe) unsubscribe() })为什么强调清理因为渲染进程里的组件销毁后listener 仍然挂在 ipcRenderer 上。如果组件频繁创建销毁弹窗、切换页签listener 会不断堆积导致回调执行越来越慢内存占用持续上涨。真实项目里我还见过因为漏掉清理同一个事件被回调上百次的现象。把 preload 的 API 设计成“返回取消函数”的形态配合 Vue 的生命周期钩子是成本最低且不容易忘的写法。4. 开发与生产的资源路径处理4.1 base 路径与 file 协议之间的关系Vue 项目在普通 Web 部署时构建产物里 JS 和 CSS 的资源路径默认以 / 开头因为页面最终通过 HTTP 协议从域名根路径加载。Electron 生产环境不一样渲染进程加载的是本地 HTML 文件走 file:// 协议。这时候如果资源路径写成 /assets/index.js浏览器会去访问本地磁盘根目录下的 assets结果必然是资源加载失败、页面白屏。解决方式很直接在渲染进程的构建配置里把 base 设置为相对路径。electron-vite 的 Vue 模板通常已配置好这一点但从普通 Vite Vue 项目迁移过来时一定要检查是否设置过 base: / 之类的选项。我见过不少项目本地开发正常一打包就全白根因就是这里。在 Electron 渲染进程中任何资源的加载路径最终都会被映射为本地文件系统路径所以不要用绝对根路径尽量用相对路径或自己掌控路径拼接。4.2 开发模式和生产模式加载入口的分叉主进程创建窗口时加载页面的方式必须区分环境const devUrl process.env[ELECTRON_RENDERER_URL] if (devUrl) { win.loadURL(devUrl) } else { win.loadFile(join(__dirname, ../renderer/index.html)) }开发模式下 electron-vite 会设置 ELECTRON_RENDERER_URL 环境变量指向 dev server 地址所以走 loadURL生产打包后没有这个变量走 loadFile。判断逻辑丢了或者路径写错都会造成失败。这里有一个隐藏细节loadFile 时路径是基于编译后的 out/main/index.js 来解析的所以要用 path.join(__dirname, ../renderer/index.html)。写成相对于项目根目录的路径在开发时也许碰巧有效打包后就不一定了因为应用内的目录结构在 asar 归档里会被重新组织。这个坑我踩过一次排查了一个多小时最后发现只是路径基准点错了。4.3 动态资源与外部文件的加载如果应用不仅要显示界面还需要读取用户本地的图片、数据库文件、导出报表不能指望 Vue 直接通过 fetch 相对路径去读。因为渲染进程的 file 协议访问范围有限而且路径一旦写死换台电脑就失效。比较实用的做法是区分两类资源。一类是随应用安装包一起发布的静态资源比如默认模板、内置样张。这类资源建议放到 resources 目录并在 electron-builder 的 extraResources 中声明运行期通过 process.resourcesPath 拼接访问。resourcesPath 是 Electron 运行时提供的固定目录所有 extraResources 内容都会被复制到这里不会受当前工作目录影响。另一类是用户运行时产生的动态文件。这类资源通常由主进程通过文件对话框打开并读取再把文件内容或可访问地址通过 IPC 返回给渲染进程。如果图片数量大要避免走 IPC 传整个 Buffer更推荐注册一个自定义协议比如 app://在主进程里通过 protocol.handle 把文件读取逻辑收拢页面像访问普通 URL 一样展示图片。当前阶段只要记住“不要用硬编码的相对路径去读运行期文件”就够了。5. 打包发布electron-builder 的基本配置5.1 最小可用的 electron-builder 配置electron-vite 脚手架默认集成了 electron-builderelectron-builder.yml 大致长这样appId: com.example.yourapp productName: YourApp directories: output: release/${version} files: - out/**/* - package.json asar: true win: target: - nsis mac: target: - dmg linux: target: - AppImage几个配置项的作用要理解清楚appId 是应用唯一标识macOS 用作 bundle id最好写成反域名风格productName 是最终安装包和应用菜单里显示的名字directories.output 是安装包输出目录files 决定哪些内容进入安装包上面只放了 out 和 package.json源码和配置文件不会进去asar 开启后应用文件会打包成一个 asar 归档减少文件数量也避免用户轻易翻源码。asar 开启后需要注意运行期需要动态读取的文件不能继续用拼路径的方式直接 fs.readFileSync因为文件在 asar 内部普通路径读取不到。要么把这类动态文件通过 extraResources 放到 resources 目录要么用 Electron 的 asar 解包能力去读取。最简单的规则是代码逻辑文件放 out 走 asar用户数据和大型静态资源走 extraResources。5.2 图标、命名与应用元信息打包时图标是个高频出事点。Windows 需要 .icomacOS 需要 .icnsLinux 需要一组 png 图标。如果只准备了一张 png可以在构建机上用图标转换工具离线转换但 macOS 对图标尺寸要求严格建议用 1024x1024 的源图生成全套图标。另一个容易被忽略的点是 productName 的确定它出现在用户的菜单、任务栏、dock 栏、安装界面里不要在打包前才临时改否则旧的快捷方式会残留一系列问题。还有文件关联和协议关联的声明。桌面应用需要打开某种自定义后缀的文件时要在打包配置里声明 fileAssociations需要从浏览器唤起应用时则配置自定义协议。对基本结构来说是选修课但提前知道位置后面加功能时不至于临时翻文档。另外建议在应用内的 about 面板、默认窗口标题中和 productName 保持一致用户体验会统一很多。5.3 常见打包失败与修复记录我把自己遇到过的打包问题整理成一张表供大家对照现象常见原因处理方式打包时提示下载 Electron 或相关依赖超时构建机访问官方下载源不稳定配置 ELECTRON_MIRROR 镜像源环境变量打包后启动提示找不到某个第三方模块运行时依赖被写进 devDependencies把该依赖移到 dependencies 并重新安装打包失败报错指向某个原生模块Native 模块未针对 Electron ABI 编译在构建机安装编译工具链开启 npmRebuild 或执行对应重编译命令打包成功但页面白屏控制台报资源 404渲染进程构建产生绝对路径检查 vite 配置里的 base 是否已改为相对路径打包后体积明显偏大dependencies 里混入了仅构建期使用的库将只用于构建的依赖移到 devDependencies这张表不需要背下来遇到问题来查就行。核心思路是先确认是构建问题还是运行期问题再检查配置与依赖分类必要时调整依赖源和重编译原生模块。如果碰到表格里没有的新问题我一般先看构建日志里第一个报错绝大多数打包失败都是配置类错误不会太难定位。6. 实测中遇到的几个高频问题与处理思路6.1 页面白屏的第一排查顺序白屏是 Electron Vue 项目里出现频率最高的现象我推荐的排查顺序是固定的。第一步按 F12 打开开发者工具看 Console 和 Network。Console 有 JS 报错说明渲染进程已加载但脚本执行出了问题Network 里 JS/CSS 红了一大片基本是路径或协议问题。第二步检查 preload 是否生效在 Vue 代码里打印 window.api如果它是 undefined说明 preload 路径配置不对或者 contextIsolation 设置影响了注入。常见原因是 BrowserWindow 的 webPreferences.preload 路径写错特别在 electron-vite 下preload 会编译到 out/preload/index.js不是 src/preload/index.js。第三步检查主进程加载分支。确认生产环境确实走到了 loadFile可以在主进程里打印当前加载目标来验证。第四步检查 CSP。有些模板会设置较严格的内容安全策略页面里用了内联脚本或某些第三方库时会被 CSP 拦截控制台通常会明确提示违反了哪个 directive。按这个顺序走大多数白屏都能在几分钟内定位而不是盲目改配置浪费一下午。6.2 开发环境正常但打包后加载失败的根因这类问题本质上是开发与生产两个环境的信息不一致。开发环境走 HTTP dev server页面从网络路径加载资源生产环境走 file 协议从本地文件加载。资源路径、入口判断、协议行为都不同。我在实际项目里遇到三个典型案例。第一是 Vue Router 使用 history 模式。Web 部署时 history 模式需要服务器配合在 Electron 的 file 协议下history 模式刷新页面会直接找不到路径桌面端应用请使用 hash 模式或记忆历史模式。第二是动态 import 的 chunk 路径问题Vite 打包后异步组件会拆成独立 chunk如果 base 不是相对路径这些 chunk 同样 404修复办法是统一设置相对 base。第三是代码里拼接 window.location 来请求接口生产环境 window.location 会变成 file:// 开头直接请求后端自然失败正确做法是把 API 地址写在环境变量里不受页面 URL 影响。这一点在从纯 Web 项目迁移到 Electron 时尤其要改干净。6.3 性能层面的几个基础优化点项目结构搭好之后性能优化通常排在功能后面但有几点建议在早期就建立意识。第一渲染进程不要承担大计算量任务。Vue 页面里的逻辑再顺手它也只是一个浏览器环境遇到 CPU 密集计算应转移到主进程或独立 worker 线程否则窗口会卡顿主线程阻塞影响整个应用交互。第二注意监听器泄漏页面销毁时一定要清理 ipcRenderer 的 listener。对桌面端来说这不是小事窗口可能长时间开着泄漏累积比浏览器标签页更明显。第三窗口创建时避免加载过重资源。如果首页有大量图表或图片可以先创建隐藏窗口等关键资源加载完成后再显示让用户感知到的是秒开而不是白屏等待。第四生产环境关闭调试相关选项不要注册远程调试端口也不要默认打开开发者工具。第五谨慎关闭背景节流。setBackgroundThrottling(false) 能让隐藏窗口继续以满帧渲染但 CPU 占用明显增加除非业务确实需要后台持续渲染否则保持默认节流让系统帮我们省电。这套结构我前后搭过不止一次最深的体会是与其一开始就研究各种高级技巧不如先把 main、preload、renderer 三条轨道的边界划清楚把目录、配置、通信、打包这四件事一次做对。通信层尤其建议一开始就把所有暴露给页面的 API 集中定义在一个文件里命名带上领域前缀后面加功能就是顺着这条主线往两边延伸。如果你正从纯 Web 开发转向桌面端按这个骨架先跑通一个最小可用版本再去扩展业务模块会省掉相当多的弯路。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号