ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Electron+Vue 桌面应用实战:依赖安装、串口接入与打包避坑

Electron+Vue 桌面应用实战:依赖安装、串口接入与打包避坑 简介基于Electron与Vue.js的桌面应用项目模板压缩包内含完整前端工程与构建配置适合具备一定Vue基础、希望上手桌面应用开发或了解Element UI整合方案的开发者可用于学习、演示或二次开发。项目以Vue作为UI层通过Vue Router自动加载路由并借助Element UI实现菜单导航与选项卡联动方便对照学习桌面端常见交互模式的落地方式。压缩包共45个文件主要包括js构建脚本与业务逻辑、vue页面组件、json依赖配置、yml持续集成配置以及字体、图标等静态资源整体约25.91MB结构清晰便于检索。已有465人学习下载。通过解压研究可掌握Electron主进程与渲染进程的目录组织、webpack多环境构建配置、路由自动扫描注册等实际技巧可用于搭建自身项目的起步模板或作为企业级后台管理桌面的参考样例。1. 一份 electron-vue.zip 背后的桌面工程约定“解压 electron-vue.zip 就能把网页变成 exe”这类说法只对了一半。压缩包真正有价值的是里面把 Electron 主进程、Vue 渲染进程和打包脚本串成一条流水线的那套约定。它决定了哪些代码跑在 Node 侧哪些代码跑在 Chromium 侧窗口创建后加载的是开发服务器地址还是打包后的静态文件native 依赖在 asar 压缩时会不会被遗漏。如果拿到 zip 就急着npm install依赖下载阶段就可能被 Electron 二进制卡住更别说打包后白屏这类更难定位的问题。下文按处理 electron-vue 工程最常用的顺序展开确认目录、装依赖、改业务配置、打包验证。适合熟悉 Vue 但第一次交付 Electron 桌面应用的人也适合需要把 Web 端快速封装成 exe 的全栈和运维工程师。2. 解开 electron-vue.zip 先确认目录主进程、渲染进程与构建产物2.1 解压后先看哪几个目录src/main、src/renderer 与 build大部分 electron-vue 模板即使从 vue-cli 换到 Vite目录骨架依然保持同一套约定src/main放主进程代码src/renderer放 Vue 渲染工程build放打包资源。主进程入口常见为src/main/index.js职责是创建 BrowserWindow、注册 IPC handler、响应系统生命周期事件。渲染进程入口是src/renderer/src/main.js职责是创建 Vue 实例并挂载页面。build目录里则是安装包阶段要用的图标、开发者证书或 electron-builder 的补充配置。为什么这两个进程的代码必须分开不干脆写在同一个目录里从上手成本看分层似乎增加了文件数量但从运行时角度看这一步是必要的。主进程跑在 Node.js 环境能用 require 加载系统模块代码最终以脚本形式进入 asar 包渲染进程跑在 Chromium 环境Vue 源码需要经过编译打包才能被加载直接放一起会在解析阶段就开始互相干扰。另一个理由在打包阶段更明显electron-builder 需要把渲染进程产物和主进程产物按不同规则处理目录不区分files 匹配规则就没法写精确这也是很多人打出来的安装包体积失控的原因之一。拿到 zip 后建议依次做三件事。第一在根目录执行ls或dir确认主进程目录、渲染进程目录和 package.json 在同一层。第二打开 package.json找到main字段并确认它指向的文件真实存在。第三找到 BrowserWindow 创建处看loadURL和loadFile的分支写法——开发模式必须走开发服务器地址生产模式必须走本地静态文件分支写错或漏写后果就是开发正常、打包后白屏。这三步做完模板的底细也就基本摸清了。目录或字段常见位置排错时的关注点主进程目录src/main、electron、app确认入口 JS 存在且 main 字段可解析渲染进程目录src/renderer、ui、src/ui确认 Vue 入口文件与构建工具匹配打包资源目录build、buildResources确认图标和打包模板文件不缺失渲染构建输出dist、dist_renderer、out打包前确认输出目录名与 files 字段一致2.2 package.json 的 scripts 与 dependencies 决定安装行为package.json 在 electron-vue 工程里承担着比普通 Web 项目更重要的角色开发命令、打包命令、依赖边界全写在这里。一份典型配置如下{ name: electron-vue-demo, main: src/main/index.js, scripts: { dev: electron-vite dev, build:renderer: electron-vite build, dist: electron-vite build electron-builder }, dependencies: { serialport: ^12.0.0 }, devDependencies: { electron: ^30.3.1, electron-builder: ^24.13.3, vue: ^3.4.0 } }注意这里vue出现在 devDependencies刚接触 electron-vue 的人常会疑惑Vue 不是页面运行时依赖吗这个困惑源于打包模型的差异。在 electron-vite 的构建流程里渲染进程的所有代码会被 bundler 解析并静态编译进产物运行时不再从 node_modules 加载 Vue 包所以它可以安全地放在 devDependencies。而serialport这类 native 模块运行时必须按真实模块被 require必须放在 dependencies。这个边界把握不好最常见的后果有两个一是串口模块被放进 devDependencies打包产物里找不到二是不做区分把整个 node_modules 全部打进 asar安装包变大数倍。scripts 字段里的dist命令通常做了两件事先构建渲染进程再调用 electron-builder 打包。拆开两个阶段的用意是让报错更直观构建渲染进程失败和打包安装包失败定位的日志完全不同。如果你拿到手的 zip 里 scripts 只有start没有dist说明这个模板只覆盖了开发调试自己补上electron-builder命令即可但要注意补配置时把 2.1 节的目录路径和这里的 main 入口对齐。2.3 preload 脚本是连接 Vue 页面和 Node 能力的唯一桥梁electron-vue 工程里最容易被人略过却又必须讲清的是 preload 脚本。Electron 出于安全考虑默认开启contextIsolation渲染进程拿不到 Node 全局对象此时页面若想读取本地文件或调用串口不能直接 require(fs)只能通过 preload 脚本配合contextBridge暴露一个安全的 API。// src/preload/index.js const { contextBridge, ipcRenderer } require(electron) contextBridge.exposeInMainWorld(api, { readConfig: () ipcRenderer.invoke(config:read), onMenuEvent: (callback) ipcRenderer.on(menu:dispatch, callback) })上面这段是在 preload 中向渲染进程注入window.api对象的常规写法。exposeInMainWorld的第一个参数是暴露出的命名空间第二个参数是包含函数声明的对象。ipcRenderer.invoke向主进程发送请求主进程侧用ipcMain.handle回应ipcRenderer.on用于接收主进程主动推送的事件。参数含义在排错时可以对照检查如果页面里window.api是 undefined先确认 preload 脚本是否被写入了 BrowserWindow 的webPreferences.preload路径如果 invoke 没有返回值再检查主进程是否注册了同名的ipcMain.handle。这个桥接层在打包后依然成立因为 preload 文件会在构建时被复制到产物目录。一个值得留意的坑是路径写法开发时用__dirname拼接没问题构建后如果 preload 被单独放到 resources 目录路径要跟着调整。遇到打包后“点击按钮没有任何反应”的情况十有八九是 preload 没加载成功可以在主进程里临时打印win.webContents的did-finish-load事件用webContents.executeJavaScript(typeof window.api)验证注入结果。3. 从 pnpm install 到 dev 模式跑通依赖安装与调试链路3.1 在 electron-vue 工程里优先使用 pnpm 的理由electron-vue 项目安装依赖的阶段比普通 Web 项目敏感得多因为 Electron 包自带 postinstall 脚本需要下载对应平台的二进制文件。安装中途一旦网络中断node_modules 会处于半损坏状态默认情况下npm install不会自动修复。我一般推荐用 pnpm原因是三方面的收益第一pnpm 的全局内容寻址存储会在多个项目间复用 Electron 二进制第二次安装速度快很多第二pnpm 对 peer 依赖校验更严格Vue 3 项目里意外引入 Vue 2 时代的关联包会立刻报冲突把版本问题暴露在安装阶段而不是运行阶段第三安装完成后可以通过pnpm rebuild electron单独触发某个包的 postinstall不用重装全部依赖。# 进入项目根目录确认锁文件类型后执行 pnpm install # Electron 二进制下载失败或被杀毒软件拦截时单独重建 pnpm rebuild electron第一行命令使用 pnpm 生成或复用 pnpm-lock.yaml如果你从没装过 electronpnpm 会从配置好的镜像源下载二进制并校验哈希。第二行的rebuild并不重装包本身而是重新执行依赖的安装脚本对修复 Electron 二进制缺失的场景有效。实际使用时可以把pnpm rebuild electron当作第一排查动作再配合下一步的镜像配置能解决多数环境下的依赖安装问题。3.2 electron 与 electron-builder 的镜像配置参数依赖安装卡住或下载失败最常见的原因是 Electron 二进制和打包工具下载超时。在项目根目录放一份.npmrc能让团队所有人拿到统一配置而不是各自去改全局设置registryhttps://registry.npmmirror.com electron_mirrorhttps://npmmirror.com/mirrors/electron/ electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron-builder-binaries/三个配置项对应三个不同阶段的下载行为。registry负责 npm 包主体的源electron_mirror负责 electron 二进制压缩包electron_builder_binaries_mirror负责打包阶段会额外获取的 winCodeSign、nsis、AppImage 工具。缺第二个安装依赖时 Electron 二进制卡住缺第三个pnpm dist会在生成安装包阶段临时下载工具失败。配置里容易写错的是路径结尾electron_mirror必须以electron/结尾npmmirror 的目录按版本排布路径写错会得到 404之后回退到默认源问题依旧。# 用 curl 验证镜像地址是否可访问 curl -I https://npmmirror.com/mirrors/electron/ | head -5配置项控制的对象缺失时的典型报错registrynpm 包主体socket hang up / ETIMEDOUTelectron_mirrorElectron 二进制Electron failed to install correctlyelectron_builder_binaries_mirror打包工具文件Cannot find nsis / winCodeSign 下载失败3.3 开发模式下两个端口如何衔接electron-vue 开发环境的本质是两个进程并行渲染进程由 Vite 或 Webpack DevServer 提供服务主进程启动 Electron 并创建 BrowserWindow 加载该服务地址。在 electron-vite 的默认配置里开发服务器监听 5173在老版模板里webpack-dev-server 常用 9080。拿到项目后先看.env或构建脚本里实际写的端口再确定排查方向。主进程加载页面的分支代码是开发与生产切换的核心// src/main/index.js function createWindow() { const win new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, ../preload/index.js), contextIsolation: true } }) if (process.env[ELECTRON_RENDERER_URL]) { // 开发模式加载 Vite 开发服务器页面 win.loadURL(process.env[ELECTRON_RENDERER_URL]) } else { // 生产模式加载打包后的 index.html win.loadFile(path.join(__dirname, ../renderer/dist/index.html)) } }webPreferences.preload指定 preload 脚本路径打包后要保持相对路径可解析contextIsolation开启后渲染进程无法直接访问 Node API这是安全基线不建议为图省事关闭。开发模式分支读取ELECTRON_RENDERER_URL该环境变量由 electron-vite 在启动时注入如果模板里用的是VITE_DEV_SERVER_URL在编译配置里二选一即可。开发模式下页面能开但热更新不生效优先检查 DevServer 的 WebSocket 地址是否被 CSP 拦截而不是怀疑 Vue 组件写错。4. 改菜单、接串口、播 m3u8 与 electron-builder 打包参数4.1 Menu 模块构建原生菜单并映射业务事件Electron 桌面应用交付时产品经常要求自定义窗口菜单而不是用 Chromium 默认模板。在 electron-vue 工程里可以新建src/main/menu.js应用 ready 后构建菜单模板并调用Menu.setApplicationMenu// src/main/menu.js const { Menu, dialog } require(electron) function buildMenu() { const template [ { label: 文件, submenu: [ { label: 打开配置, accelerator: CmdOrCtrlO, click: (_item, win) { win.webContents.send(menu:open-config) } }, { type: separator }, { label: 退出, role: quit } ] }, { label: 帮助, submenu: [ { label: 关于, click: () dialog.showMessageBox({ message: electron-vue demo }) } ] } ] Menu.setApplicationMenu(Menu.buildFromTemplate(template)) } module.exports { buildMenu }这段代码的要点集中在菜单项配置上。accelerator声明快捷键CmdOrCtrl在 macOS 上自动映射为 CommandWindows 和 Linux 上映射为 Ctrl比手工判断process.platform更可靠。role字段复用内置行为quit、reload、toggleDevTools这类操作不需要自己实现。click回调接收(menuItem, browserWindow)想给选中窗口发消息时从第二个参数取 webContents。菜单代码写完后需要在主进程入口调用一次buildMenu()。再强调一点菜单点击是主进程到渲染进程的单向消息渲染进程 preload 里要写ipcRenderer.on(menu:open-config, handler)去订阅这和调用 invoke 等待返回值是两个不同的通信模型。若菜单点击后页面无响应先在click里打日志确认回调有没有被触发再检查 preload 的监听器是否在组件销毁时被意外移除。4.2 serialport 串口模块的安装、调用与打包注意点在 electron-vue 工程里集成串口核心难点从来不是函数调用而是 native 模块在打包后的环境中仍能正确加载。实操中要把握三个位置。首先安装时要把串口库放入 dependenciespnpm add serialport其次主进程通过ipcMain.handle把串口能力暴露给渲染进程// src/main/serial.js const { ipcMain } require(electron) const { SerialPort } require(serialport) ipcMain.handle(serial:list, async () { const ports await SerialPort.list() return ports.map((p) ({ path: p.path, manufacturer: p.manufacturer })) }) ipcMain.handle(serial:open, async (_event, options) { const port new SerialPort({ path: options.path, baudRate: options.baudRate || 115200, autoOpen: false }) return new Promise((resolve, reject) { port.open((err) { if (err) reject(err) else resolve(port.isOpen) }) }) })代码把串口操作封装成 Promise渲染进程侧通过window.api.serialOpen({ path, baudRate })调用不必关心底层回调。参数方面path在 Windows 上形如COM3在 Linux 上形如/dev/ttyUSB0不要把枚举范围写死在前端baudRate必须和接入设备一致常见低速设备用 9600高性能设备用 115200两侧不匹配会读到乱码。SerialPort.list()返回的内容包含 vendorId、productId 等字段可以做设备型号的下拉展示。最后是打包配置在 electron-builder 配置里加入{ build: { npmRebuild: true, asarUnpack: [ **/node_modules/serialport/**, **/node_modules/serialport/** ] } }npmRebuild让 electron-builder 在打包前重新编译原生依赖使模块 ABI 与当前 Electron 版本匹配asarUnpack把串口相关文件从 asar 中解出因为 Electron 默认不能从 asar 内加载.node动态库。两个参数缺一不可。出现NODE_MODULE_VERSION报错时几乎都是这两处配置丢失或构建环境不干净所导致。4.3 渲染进程播放 m3u8hls.js 组件与混合内容处理如果 electron-vue 应用需要做直播预览或录像回放且视频源为 m3u8不要指望 Electron 的 Chromium 原生支持 HLS。它和普通浏览器行为一致需要引入 hls.js 来处理切片流。下面是在 Vue 3 组件中的常规接入方式template video refvideoEl controls playsinline/video /template script setup import { ref, onMounted, onBeforeUnmount } from vue import Hls from hls.js const videoEl ref(null) let hls null onMounted(() { if (Hls.isSupported()) { hls new Hls({ maxBufferLength: 30, liveSyncDuration: 5 }) hls.loadSource(https://example.com/live/stream.m3u8) hls.attachMedia(videoEl.value) hls.on(Hls.Events.ERROR, (_event, data) { if (data.fatal) { if (data.type Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad() } }) } else if (videoEl.value.canPlayType(application/vnd.apple.mpegurl)) { // 兜底Safari 可直接播放 m3u8 videoEl.value.src https://example.com/live/stream.m3u8 } }) onBeforeUnmount(() { if (hls) hls.destroy() }) /scriptmaxBufferLength的单位是秒控制 HLS 在内存里预留的缓冲区长度设太大会拉高内存占用设太小遇到瞬时丢帧容易卡顿。liveSyncDuration表示直播滞后于实时边缘的秒数数字越小越接近实时画面但对网络抖动越敏感。hls.on(Hls.Events.ERROR)里的fatal分支是排查播放黑屏的关键网络错误建议直接startLoad重置媒体错误则需要检查 m3u8 引用的 ts 分片是否可访问。如果产品需要完整的控制栏和皮肤改 video.js 会更省事代价是包体明显变大。混合内容问题要单独记一笔渲染进程从http://页面加载https://的 m3u8 没问题但打包后页面走file://协议加载http://的 m3u8 地址时部分版本会触发拦截。更稳的处理是不关webSecurity而是让主进程通过protocol.handle注册自定义协议代理请求绕过跨域限制又不损失安全基线。4.4 electron-builder 打包参数与常见平台差异本地开发跑通后打包出安装包是 electron-vue 交付的收官环节。一份能复用的 electron-builder 配置如下appId: com.example.electronvue productName: DemoApp directories: output: dist_electron files: - dist_renderer/** - src/main/** - package.json asar: true win: target: - nsis artifactName: ${productName}-${version}-${arch}.${ext} nsis: oneClick: false allowToChangeInstallationDirectory: true createDesktopShortcut: truefiles字段决定哪些文件进入安装包精确度直接决定产物体积和可用性。把dist_renderer和src/main包进来再让 electron-builder 自动分析 node_modules 依赖比写成**/*明确得多。artifactName里的${arch}可以是x64、ia32或arm64如果串口库没有对应架构的预编译二进制切架构前要确认模块支持情况。Windows 用nsis目标时oneClick设为 false 会显示安装界面并允许用户选择目录企业内部工具通常采用这种形式createDesktopShortcut为 true 时生成桌面快捷方式交付给非技术人员的项目一般都会开启。目标平台默认输出推荐 target 值环境要求Windowsnsis 安装包nsis、zip、portableWindows 或带 wine 的 Linux/macOSLinuxAppImageAppImage、deb、rpm目标发行版或容器macOSdmgdmg、zip只能在 macOS 上完成签名流程跨平台交叉编译 native 模块并不安全涉及 serialport 时更是如此。最佳路径是在目标平台或对应 CI 容器内执行安装和打包而不是试图在开发机上一条命令出三平台安装包。5. 用 asar 检查构建产物定位打包后的两类高频问题5.1 验证渲染进程产物是否进入 app.asar打包完成后不要急着双击安装包。先打开输出目录dist_electron正常的 Windows 交付物会有win-unpacked目录和安装程序。很多白屏问题在 asar 阶段就能被拦截方法是直接检查 app.asar 中是否有渲染进程产物# 列出 asar 内的文件确认 index.html 与 assets 存在 npx asar list dist_electron/win-unpacked/resources/app.asar | grep -E index.html|assets # 只看第一层判断目录结构是否符合预期 npx asar list dist_electron/win-unpacked/resources/app.asar | head -20grep是常用过滤方式如果输出为空说明files匹配规则没覆盖到渲染进程构建产物。此时回到第 2.1 节的表格对照dist_renderer的实际目录名修改 files 配置。另一个高频项是检查 serialport 的.node文件是否在 asar 内被正确解包npx asar list dist_electron/win-unpacked/resources/app.asar | grep serialport # 确认产物目录下存在 app.asar.unpacked 且其中包含串口模块 ls dist_electron/win-unpacked/resources/5.2 安装包体积异常与签名问题的快速检查如果安装包体积明显偏大大概率是files里混入了不必要的目录。在 electron-vue 里最常被误入的是node_modules/.vite缓存、源码里的测试目录和.git文件夹。还有一种少见却隐蔽的情况渲染进程的public目录没有做产物裁剪把开发时的调试页面和 mock 数据一起带了进去。处理方式是收紧 files 白名单并定期用 asar list 对比前后两次构建的包内容差异。Windows 上如果没有企业证书electron-builder 会在打包时尝试签名并报错。此时在 win 配置里显式关闭签名相关字段或不配置 repository 信息工具会输出未签名版本。未签名 exe 在目标机器上运行会触发 SmartScreen 警告这是预期行为。验证产物时不要只跑安装包优先执行win-unpacked目录下的可执行文件把 stdout 和 stderr 重定向到日志文件日志里若出现Uncaught或Cannot find module再回到前两章对应位置定位。5.3 一次干净的构建验证脚本最后分享一个我常用的发版前验证流程把 node_modules 和锁文件全部删除按 3.2 节的镜像配置重新安装再执行完整打包命令。rm -rf node_modules pnpm-lock.yaml pnpm install pnpm dist这条链路能同时检验镜像配置、依赖声明和构建历史遗留问题。真正的桌面交付项目里很多“开发机正常、其他电脑白屏”的案例都源于构建环境不干净而这条重装命令恰好把这类问题暴露在发版之前。本文还有配套的精品资源点击获取
返回列表