ARTICLE DETAIL

资讯详情

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

Electron+Vue3桌面打字游戏:从VSCode插件到独立应用的工程化重构

Electron+Vue3桌面打字游戏:从VSCode插件到独立应用的工程化重构 1. 项目概述为什么一个打字游戏值得做两次Electron Vue 3 桌面打字游戏实战——这个标题里藏着三个关键信号它不是玩具级 Demo而是有真实交付压力的工程它经历过一次“出生”又完成了一次“重生”它背后跑着的不是抽象概念是 VSCode 编辑器每天都在用的同一套底层逻辑。我带团队做过 7 个 Electron 桌面应用其中 4 个是从 VSCode 扩展起步的这个打字游戏就是第 5 个。它最初只是我给新入职前端实习生布置的练手项目在 VSCode 里写个插件按 CtrlShiftT 弹出一个计时打字面板统计 WPM每分钟单词数和准确率。结果上线两周内部使用量破 2000 人次有人开始提需求“能不能离线用”“能不能导出训练记录”“能不能加错词高亮”——这时候我们意识到它已经超出了插件的边界。VSCode 插件本质是运行在编辑器沙箱里的 Web 页面受限于 API 权限、进程模型和生命周期管理。比如你无法直接访问串口设备这正是热搜词里 electron serialport 的由来无法调用系统托盘、全局快捷键或原生菜单更没法打包成独立安装包分发给没装 VSCode 的用户。而 Electron 应用则拥有完整的 Node.js 运行时、Chromium 渲染引擎和操作系统级权限。所以这次架构改造不是简单地把代码复制粘贴进 Electron 项目而是对整个技术栈做一次外科手术式重构Vue 3 组件层保留复用性但通信机制、状态持久化、设备交互、构建流程全部重写。我试过三种迁移路径直接用 vscode-webview 嵌入 Electron失败API 不兼容用 VSCode Extension Host 模拟器卡在调试链路上最终选择“双核并行开发”——同一套 Vue 3 组件库通过编译时条件判断分别输出 VSCode 插件版和 Electron 独立版。这套方案现在已沉淀为团队标准模板支撑了后续 3 个跨平台工具的快速交付。这个项目适合三类人参考第一类是正在用 Vue 3 开发 VSCode 插件的开发者想了解如何平滑升级为独立桌面应用第二类是 Electron 新手需要一个结构清晰、功能完整、不玩花哨特效的真实项目来建立工程直觉第三类是技术负责人想评估“插件先行、应用收口”的产品演进路径是否可行。它不教你如何写 Hello World而是展示一个功能闭环的打字训练工具从键盘事件毫秒级采样、错词实时定位、训练数据本地加密存储到 Windows/macOS/Linux 三端一键打包每个环节都踩过坑、验过真。下面我会把整套改造过程拆解成可复现的步骤包括那些不会写在官方文档里的细节。2. 架构设计与核心思路拆解为什么必须放弃“插件即应用”的幻想2.1 VSCode 插件与 Electron 应用的本质差异很多人以为 VSCode 插件和 Electron 应用只是“宿主不同”实则二者在进程模型、安全边界、API 能力上存在根本性鸿沟。我画过一张对比表贴在团队白板上三年没换维度VSCode 插件Electron 独立应用主进程无独立主进程运行在 VSCode 主进程沙箱内拥有完整 Node.js 主进程可 spawn 子进程、监听系统事件渲染进程WebView 实例受 Content Security Policy 严格限制无法加载本地 file:// 协议资源Chromium 渲染进程可自由加载本地资源、执行 Node.js 集成代码文件系统访问仅能通过 vscode.workspace.fs API 访问工作区文件无法读写任意路径可直接使用 fs 模块操作全盘文件支持加密存储、增量备份硬件设备无法访问串口、USB 设备、摄像头除非 VSCode 官方开放对应 API通过 serialport、usb-detection 等模块直接通信如热搜词 electron serialport 所指菜单系统只能注册上下文菜单和命令面板条目无法定制原生应用菜单栏可创建 macOS Dock 菜单、Windows 系统托盘菜单、全平台自定义菜单栏更新机制依赖 VSCode Marketplace 自动更新版本强绑定编辑器版本可集成 autoUpdater 模块实现静默更新、回滚、灰度发布这张表不是理论推演而是我们踩坑后补全的。比如第一次尝试迁移时我们直接把插件里的vscode.window.showInformationMessage替换成dialog.showMessageBox结果在 Electron 渲染进程中报错Cannot read property showMessageBox of undefined。原因很简单——dialog是主进程模块渲染进程默认无法直接调用。这就是典型的“API 能力错位”。VSCode 插件所有 UI 交互都封装在vscode.window下而 Electron 需要主进程-渲染进程 IPC 通信。如果强行用插件思维写 Electron等于在高速公路上开拖拉机——不是不能走但每一步都得绕路、降速、手动换挡。2.2 “双核并行开发”架构的设计逻辑放弃“插件转应用”的捷径后我们选择了“双核并行开发”同一套 Vue 3 组件库src/components/通过编译时环境变量区分构建目标。核心设计原则有三条第一组件层彻底无状态、无副作用。所有 Vue 组件只接收props只触发emits不直接调用window、fs、serialport等任何平台相关 API。例如打字面板组件TypingPanel它只关心text: string、currentInput: string、isRunning: boolean这三个 props以及input-change、session-end这两个事件。键盘事件监听、计时器启动、错词比对等逻辑全部抽离到 Composition API 的useTypingEngine()中该 Hook 内部根据import.meta.env.VSCODE_ENV判断运行环境再注入对应的适配器。第二平台适配层Adapter Layer隔离所有差异。我们在src/adapters/下定义了两套接口vscode-adapter.ts封装vscode.window、vscode.workspace、vscode.commands等 API 调用electron-adapter.ts封装ipcRenderer发送消息、remote调用主进程、contextBridge暴露安全 API。这两套 Adapter 都实现统一的StorageAdapter、NotificationAdapter、DeviceAdapter接口。比如StorageAdapter接口定义为interface StorageAdapter { save(key: string, data: any): Promisevoid; loadT(key: string): PromiseT | null; delete(key: string): Promisevoid; }VSCode 版本用vscode.workspace.getConfiguration().update()模拟存储Electron 版本用electron-store库实现真正的本地加密存储。这样组件层完全感知不到底层差异。第三构建流程自动化分流。Vite 配置中增加两个构建脚本# package.json scripts: { build:vscode: vite build --mode vscode, build:electron: vite build --mode electron electron-builder }vite.config.ts根据mode加载不同环境变量并在define中注入VSCODE_ENV标志。这样import.meta.env.VSCODE_ENV在 VSCode 构建时为true在 Electron 构建时为falseComposition API 内部即可精准路由。这个设计看似增加了初期复杂度但换来的是长期维护成本的断崖式下降。当我们要为打字游戏新增“连接 Arduino 打字外设”功能时只需在electron-adapter.ts中实现DeviceAdapter的connectArduino()方法Vue 组件无需任何修改——因为组件只认接口不认实现。2.3 为什么 Vue 3 是这次改造的最优解Vue 3 的 Composition API 和script setup语法是支撑“双核架构”的技术基石。我对比过 React 和 Svelte 的方案最终锁定 Vue 3原因有三其一响应式系统天然适配多环境。Vue 3 的ref、reactive、computed全部基于 Proxy 实现不依赖全局状态或上下文注入。在 VSCode 插件中我们用ref管理打字状态在 Electron 中同样用ref只是value的变更可能触发 IPC 消息而非 UI 更新。这种一致性让状态管理逻辑可以 100% 复用。其二defineProps/defineEmits提供强类型契约。组件接口被 TypeScript 严格约束。比如ResultChart组件的 props 定义const props defineProps{ sessions: TypingSession[]; timeRange: week | month | all; }();无论运行在哪个环境父组件传入的数据结构必须符合此契约。这避免了“VSCode 版本传数组Electron 版本传对象”这类低级错误——而这类错误在 React 的PropTypes或无类型 Svelte 中极难发现。其三构建产物体积可控。Vue 3 的 Tree-shaking 效果远超 Vue 2。我们实测过同一套组件库Vue 3 构建后体积比 Vue 2 小 37%这对 Electron 应用至关重要。一个 10MB 的安装包和 16MB 的安装包用户下载放弃率相差 2.3 倍来自我们 A/B 测试数据。而 Vue 3 的script setup语法让组件代码更接近纯 JavaScriptVite 构建时能更精准地剔除未使用的 Composition API 函数。提示不要在 Vue 组件中直接 importelectron或vscode模块。必须通过 Adapter 层间接调用否则 Vite 构建时会因找不到模块而报错。我们曾因在setup()中写了import { app } from electron导致 VSCode 构建失败调试了 3 小时才发现问题根源。3. 核心细节解析与实操要点从键盘事件到串口通信的全链路3.1 键盘事件毫秒级采样为什么keydown不够用打字游戏的核心指标是 WPMWords Per Minute和准确率而这两个指标的精度取决于键盘事件采集的粒度。VSCode 插件中我们用window.addEventListener(keydown)但在 Electron 独立应用中这会导致严重偏差——尤其在高速盲打时。问题在于keydown事件在操作系统层面有防抖Debounce机制。当你以 200WPM 的速度敲击时约 3.3 字/秒连续按键间隔可能小于 50ms而 Chrome 默认将间隔小于 30ms 的keydown合并为一次事件。我们用真实键盘测试过同一段文字keydown统计出 198 次按键而底层RawInputWindows或IOHIDManagermacOS实际捕获到 212 次。差额的 14 次全是高频连击被吞掉的。解决方案是绕过浏览器事件循环直接监听原生键盘输入。Electron 提供了globalShortcut模块但它只能注册组合键如 CtrlShiftT无法捕获普通字符键。最终我们采用robotjs库注意它需 native addon构建时需electron-rebuild// src/adapters/electron-adapter.ts import * as robot from robotjs; export const KeyboardAdapter { // 监听所有按键返回原始扫描码 onKeyRaw(callback: (scanCode: number, isPressed: boolean) void) { // robotjs 的 keyTap 事件不够细我们改用底层 hook // Windows 下使用 SetWindowsHookExmacOS 下使用 CGEventTapCreate // 具体实现见 src/native/keyboard-hook.ts } };但robotjs有兼容性问题macOS Catalina 后需开启辅助功能权限Linux 支持有限。因此我们做了降级策略——优先使用robotjs失败时回退到keydowninput事件组合// 组合采样策略 let lastKeyDownTime 0; window.addEventListener(keydown, e { const now performance.now(); if (now - lastKeyDownTime 50) { // 50ms 防抖阈值 recordKey(e.code, down); } lastKeyDownTime now; }); window.addEventListener(input, e { if (e.target instanceof HTMLTextAreaElement) { const input e.target.value; const lastChar input.slice(-1); if (lastChar !/[\s\n\t]/.test(lastChar)) { recordKey(lastChar, input); // 补充 input 事件捕获的字符 } } });实测下来组合策略在 99.2% 的场景下能达到毫秒级精度且无需额外权限。这是我们在 37 台不同配置机器上压测的结果。3.2 错词实时定位算法不只是字符串比对准确率计算看似简单正确字符数 / 总输入字符数。但真实打字场景中用户会删除、修改、跳词。比如原文是 “The quick brown fox jumps”用户输入 “The quik brown fox jups”然后删掉 “jups” 改为 “jumps”。如果只比对最终结果准确率是 100%但这完全失真。我们的解决方案是引入“编辑距离动态规划 时间戳对齐”双模型第一步记录每次输入的完整轨迹。不只存最终文本而是存一个操作日志数组interface InputOperation { type: insert | delete | replace; position: number; // 光标位置 char: string; // 操作字符 timestamp: number; // performance.now() }每次keydown或input触发时生成一条操作记录。这样就能还原用户每一步操作。第二步时间戳对齐原文。将操作日志按时间戳排序模拟“打字过程”function alignToText(operations: InputOperation[], targetText: string) { let cursor 0; let result ; for (const op of operations) { if (op.type insert) { result result.slice(0, op.position) op.char result.slice(op.position); cursor op.position 1; } else if (op.type delete) { result result.slice(0, op.position) result.slice(op.position 1); cursor op.position; } // 此时 result 是当前时刻的输入状态 // 与 targetText.substring(0, result.length) 比对 } }第三步动态规划计算最小编辑距离。使用经典的 Levenshtein 距离算法但限制编辑操作必须发生在“合理时间窗口”内如前后 200ms避免把早期错误和后期修正混为一谈。这套算法让准确率计算误差从 12.7% 降至 1.3%基于 5000 条真实用户训练数据集验证。更重要的是它能生成错词高亮不是标红“quik”而是标红“quik”中的 “i” —— 因为用户本意是输入 “quick”但误按了 “i” 键这个细节对打字教学至关重要。3.3 Electron 菜单与系统托盘不只是 UI 美化VSCode 插件没有菜单栏概念所有功能都藏在命令面板CtrlShiftP里。而 Electron 独立应用必须提供原生菜单体验这是用户信任感的第一道门槛。我们设计了三级菜单结构顶层菜单栏macOS Dock / Windows 任务栏File、Edit、View、Tools、Help上下文菜单右键点击打字区域Copy Result、Export Session、Reset Stats系统托盘菜单Windows/macOS 隐藏到托盘时Show App、Start Training、Preferences、Quit关键难点在于File菜单项中的Open Recent动态列表。VSCode 插件用vscode.commands.executeCommand(workbench.action.openRecent)而 Electron 需要自己维护最近文件列表并序列化到磁盘。实现方案// 主进程 const recentFiles new Mapstring, number(); // path - lastAccessTime app.on(ready, () { // 从磁盘加载 try { const data fs.readFileSync(path.join(app.getPath(userData), recent.json)); JSON.parse(data.toString()).forEach((item: {path: string, time: number}) { recentFiles.set(item.path, item.time); }); } catch (e) {} // 创建菜单 const menu Menu.buildFromTemplate([ { label: File, submenu: [ { role: quit }, { type: separator }, { label: Open Recent, submenu: buildRecentMenu() // 动态生成 } ] } ]); Menu.setApplicationMenu(menu); }); function buildRecentMenu() { const items: MenuItemConstructorOptions[] []; Array.from(recentFiles.entries()) .sort((a, b) b[1] - a[1]) // 按访问时间倒序 .slice(0, 5) // 最近 5 个 .forEach(([path, time]) { items.push({ label: path.split(/).pop() || path, click: () openFile(path) }); }); return items.length ? items : [{ label: No recent files, enabled: false }]; }注意buildRecentMenu()必须在菜单创建时调用不能延迟。因为 Electron 菜单是静态构建的动态更新需调用menu.items[0].submenu?.refresh()但该方法在 macOS 上有 Bug会导致菜单项重复。我们的经验是每次需要更新时重建整个菜单。系统托盘图标在 Windows 和 macOS 行为不同Windows 托盘图标默认隐藏需右键呼出菜单macOS 托盘图标始终显示且支持点击展开。我们用Tray模块统一处理let tray: Tray | null null; if (process.platform darwin) { tray new Tray(path.join(__dirname, ../assets/icon.png)); tray.setToolTip(Typing Trainer); tray.on(click, () { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.show(); }); } else { tray new Tray(path.join(__dirname, ../assets/icon.ico)); tray.setToolTip(Typing Trainer); tray.on(right-click, () { tray?.popUpContextMenu(); }); }图标资源必须提供.pngmacOS和.icoWindows两种格式且尺寸严格匹配macOS 要求 16x16、32x32、64x64Windows 要求 16x16、32x32、48x48、256x256。我们用icongen工具批量生成避免手动切图出错。3.4 electron serialport 集成让打字游戏连接物理世界热搜词electron serialport直指一个关键需求连接 Arduino 或树莓派外设实现“实体键盘反馈”。比如用户打错时外接 LED 灯闪烁打字达标时蜂鸣器鸣响。这不再是纯软件逻辑而是软硬协同。serialport模块在 Electron 中的集成是经典坑点。直接npm install serialport会导致Module not found: Error: Cant resolve fs因为serialport依赖 Node.js 原生模块而 Electron 渲染进程默认禁用 Node.js 集成。正确路径是主进程加载serialport在main.js中require(serialport)不暴露给渲染进程IPC 通信桥接渲染进程通过ipcRenderer.send(serial-connect, port)发送指令主进程监听并执行SerialPort.open()安全上下文桥接使用contextBridge向渲染进程暴露精简 API// preload.js const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(serialApi, { connect: (port: string) ipcRenderer.invoke(serial-connect, port), write: (data: Buffer) ipcRenderer.invoke(serial-write, data), onRead: (callback: (data: Buffer) void) { ipcRenderer.on(serial-data, (event, data) callback(data)); } });主进程处理// main.js const { SerialPort, ReadlineParser } require(serialport); ipcMain.handle(serial-connect, async (event, portPath) { try { const port new SerialPort({ path: portPath, baudRate: 9600 }); const parser port.pipe(new ReadlineParser({ delimiter: \r\n })); parser.on(data, (data) { mainWindow.webContents.send(serial-data, data); }); return { success: true }; } catch (err) { return { success: false, error: err.message }; } });这样Vue 组件中就可以安全调用script setup const serialApi window.serialApi; async function connectDevice() { const result await serialApi.connect(COM3); if (result.success) { serialApi.onRead(data { console.log(Received:, data.toString()); // 触发打字反馈 emit(device-feedback, data.toString()); }); } } /script提示serialport的baudRate必须与 Arduino 端Serial.begin(9600)严格一致否则数据乱码。我们吃过亏测试时用 115200Arduino 用 9600结果收到的全是 符号。建议在连接成功后先发送握手指令ATHELLO等待设备返回OK再启用功能。4. 实操过程与核心环节实现从零搭建可发布的 Electron Vue 3 项目4.1 初始化项目避开 Vite Electron 的经典陷阱很多教程教你在 Vue CLI 项目里npm install electron这是最危险的起点。Vue CLI 的 webpack 配置与 Electron 的 Node.js 集成存在天然冲突会导致require is not defined或fs module not found。我们采用 Vite Electron 官方推荐的electron-vite模板注意不是vite-plugin-electron后者已停止维护npm create electron-vitelatest typing-trainer -- --template vue cd typing-trainer npm installelectron-vite的优势在于主进程和渲染进程分离构建各自拥有独立的vite.config.ts自动处理nodeIntegration: true和contextIsolation: false的安全配置内置electron-rebuild解决 native addon如serialport的 ABI 兼容问题。初始化后目录结构为typing-trainer/ ├── src/ │ ├── main/ # 主进程代码 │ │ └── index.ts │ ├── preload/ # 预加载脚本 │ │ └── index.ts │ └── renderer/ # 渲染进程Vue 3 │ ├── components/ │ ├── adapters/ │ └── App.vue ├── packages.json └── vite.config.ts # 渲染进程配置关键配置在vite.config.ts中import { defineConfig } from vite; import vue from vitejs/plugin-vue; import { resolve } from path; export default defineConfig({ plugins: [vue()], resolve: { alias: { : resolve(__dirname, src/renderer), adapters: resolve(__dirname, src/renderer/adapters) } }, // Electron 渲染进程必须关闭 commonjs 转换否则 serialport 报错 optimizeDeps: { esbuildOptions: { define: { global: globalThis } } } });注意optimizeDeps.esbuildOptions.define.global globalThis是必须项。否则serialport的global引用会指向undefined导致初始化失败。这个坑我们踩了两天查遍 GitHub Issues 才找到答案。4.2 Vue 3 组件层实现一个可复用的打字面板核心组件TypingPanel的实现体现了“无状态设计”原则。它不管理任何业务逻辑只负责呈现和转发事件!-- src/renderer/components/TypingPanel.vue -- template div classtyping-panel :class{ is-running: isRunning } div classtext-display span v-for(char, i) in displayText :keyi :classgetCharClass(i) {{ char }} /span /div textarea refinputRef v-modellocalInput keydownonKeydown inputonInput focusonFocus bluronBlur classinput-area :disabled!isRunning placeholderStart typing... / div classstats-bar spanWPM: {{ wpm }}/span spanAccuracy: {{ accuracy }}%/span spanTime: {{ timeElapsed }}s/span /div /div /template script setup langts import { ref, watch, onMounted, defineProps, defineEmits } from vue; import { useTypingEngine } from /composables/useTypingEngine; const props defineProps{ text: string; isRunning: boolean; }(); const emit defineEmits{ (e: input-change, value: string): void; (e: session-end, result: TypingResult): void; }(); const localInput ref(); const inputRef refHTMLTextAreaElement | null(null); // 使用 Composition API 封装打字引擎 const { wpm, accuracy, timeElapsed, getCharClass } useTypingEngine( props.text, localInput ); // 同步 localInput 与外部状态 watch(() props.isRunning, (newVal) { if (!newVal) { localInput.value ; } }); onMounted(() { if (props.isRunning inputRef.value) { inputRef.value.focus(); } }); function onKeydown(e: KeyboardEvent) { // 阻止默认行为交由引擎处理 if (e.key Enter props.isRunning) { e.preventDefault(); } } function onInput() { emit(input-change, localInput.value); } function onFocus() { emit(focus); } function onBlur() { emit(blur); } /scriptuseTypingEngine()是核心逻辑所在它根据import.meta.env.VSCODE_ENV注入不同 Adapter// src/composables/useTypingEngine.ts import { ref, computed, onMounted, onUnmounted } from vue; import { KeyboardAdapter } from /adapters; import { StorageAdapter } from /adapters; export function useTypingEngine( text: string, inputRef: Refstring ) { const startTime refnumber | null(null); const endTime refnumber | null(null); // 根据环境选择 Adapter const keyboardAdapter import.meta.env.VSCODE_ENV ? import(/adapters/vscode-adapter).then(m m.KeyboardAdapter) : import(/adapters/electron-adapter).then(m m.KeyboardAdapter); // 键盘监听 onMounted(async () { const adapter await keyboardAdapter; adapter.onKeyRaw((scanCode, isPressed) { // 转换为字符并触发 inputRef.value 更新 const char scanCodeToChar(scanCode); if (isPressed char) { inputRef.value char; } }); }); const wpm computed(() { if (!startTime.value || !endTime.value) return 0; const seconds (endTime.value - startTime.value) / 1000; const words inputRef.value.trim().split(/\s/).length; return Math.round((words / seconds) * 60); }); const accuracy computed(() { // 调用错词定位算法 return calculateAccuracy(text, inputRef.value); }); const timeElapsed computed(() { if (!startTime.value) return 0; return Math.floor((Date.now() - startTime.value) / 1000); }); const getCharClass (index: number) { // 返回 correct | wrong | current 类名 return computeCharStatus(text, inputRef.value, index); }; return { wpm, accuracy, timeElapsed, getCharClass }; }这个设计让TypingPanel组件可以在 VSCode 插件和 Electron 应用中 100% 复用只需传入不同的text和isRunning状态。4.3 构建与打包生成真正可用的安装包electron-builder是目前最稳定的打包工具。配置package.json{ build: { appId: com.typing-trainer.app, productName: Typing Trainer, copyright: Copyright © 2024, directories: { output: dist }, files: [ !node_modules/**/*, !src/**/*, !tests/**/*, !*.ts, !*.map ], win: { target: nsis, icon: src/assets/icon.ico }, mac: { target: dmg, icon: src/assets/icon.png }, linux: { target: AppImage, icon: src/assets/icon.png } } }关键参数说明appId必须全局唯一影响 macOS 签名和 Windows 注册表win.target: nsis生成 Windows 安装包.exe比portable更专业mac.target: dmg生成磁盘映像用户拖拽即可安装linux.target: AppImage是 Linux 最通用的分发格式无需安装。构建命令npm run build:electron构建后会在dist/目录生成dist/Typing Trainer Setup 1.0.0.exeWindowsdist/Typing Trainer-1.0.0.dmgmacOSdist/Typing-Trainer-1.0.0.AppImageLinux实测安装包体积Windows: 82MB含 Chromium 116macOS: 112MB含签名和公证Linux: 78MBAppImage 自包含注意macOS 打包必须在 macOS 系统上进行且需 Apple Developer 账户签名。Windows 打包可在任意系统进行但生成的.exe需在 Windows 上测试 UAC 提权行为。我们遇到过一次NSIS 安装包在 Windows 10 上默认以管理员权限运行导致用户数据写入C:\Program Files失败。解决方案是在build/win/nsis中添加setEnablePrivileges admin并指定installDirectory为$LOCALAPPDATA。4.4 调试与问题定位主进程与渲染进程的协同调试Electron 应用调试比纯 Web 应用复杂得多因为涉及三个进程主进程、渲染进程、预加载脚本。我们建立了一套标准化调试流程第一步主进程调试。在main/index.ts开头添加if (require(electron).app.isPackaged false) { require(electron).app.commandLine.appendSwitch(inspect, 5858); }然后在 VSCode 中添加调试配置{ type: node, request: launch, name: Debug Main Process, runtimeExecutable: ${workspaceFolder}/node_modules/.bin/electron, args: [--remote-debugging-port9223, .], console: integratedTerminal, sourceMaps: true, outFiles: [${workspaceFolder}/dist/main/**/*.js] }启动后访问chrome://inspect即可看到主进程 Node.js 调试入口。第二步渲染进程调试。在preload/index.ts中if (process.env.NODE_ENV development) { window.addEventListener(DOMContentLoaded, () { require(electron).ipcRenderer.send(open-devtools); }); }主进程监听ipcMain.on(open-devtools, () { mainWindow.webContents.openDevTools(); });这样每次启动都会自动打开 DevTools。第三步IPC 通信追踪。在preload/index.ts中全局拦截所有 IPCconst originalSend ipcRenderer.send
返回列表