调试是 Electron 开发中的重要环节,掌握有效的调试技巧和工具可以大大提高开发效率。本文将详细介绍 Electron 的调试方法和工具。
开发者工具
1. 启用开发者工具
javascript// main.js const { app, BrowserWindow } = require('electron') let mainWindow app.whenReady().then(() => { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { devTools: true // 确保启用开发者工具 } }) mainWindow.loadFile('index.html') // 自动打开开发者工具 if (process.env.NODE_ENV === 'development') { mainWindow.webContents.openDevTools() } })
2. 快捷键
Electron 提供了默认的快捷键来打开开发者工具:
- Windows/Linux:
Ctrl+Shift+I - macOS:
Cmd+Option+I
3. 自定义快捷键
javascript// main.js const { app, BrowserWindow, globalShortcut } = require('electron') app.whenReady().then(() => { // 注册自定义快捷键 globalShortcut.register('CommandOrControl+Shift+D', () => { const focusedWindow = BrowserWindow.getFocusedWindow() if (focusedWindow) { if (focusedWindow.webContents.isDevToolsOpened()) { focusedWindow.webContents.closeDevTools() } else { focusedWindow.webContents.openDevTools() } } }) }) app.on('will-quit', () => { // 注销所有快捷键 globalShortcut.unregisterAll() })
主进程调试
1. 使用 VS Code 调试
创建 .vscode/launch.json 文件:
json{ "version": "0.2.0", "configurations": [ { "name": "Debug Main Process", "type": "node", "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", "windows": { "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" }, "args": ["."], "outputCapture": "std" } ] }
2. 使用 Chrome DevTools
javascript// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') // 在主进程中使用 console.log console.log('Main process started') console.log('Electron version:', process.versions.electron) console.log('Node.js version:', process.versions.node) console.log('Chrome version:', process.versions.chrome) })
3. 使用 debugger 语句
javascript// main.js const { app, BrowserWindow } = require('electron') function initializeApp() { debugger // 在这里设置断点 const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') } app.whenReady().then(initializeApp)
渲染进程调试
1. 使用 Chrome DevTools
渲染进程可以直接使用 Chrome DevTools 进行调试:
javascript// renderer.js console.log('Renderer process started') // 使用 debugger 语句 function handleClick() { debugger // 在这里设置断点 console.log('Button clicked') } document.getElementById('myButton').addEventListener('click', handleClick)
2. React DevTools
bashnpm install --save-dev react-devtools
javascript// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { // 安装 React DevTools const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer') try { installExtension(REACT_DEVELOPER_TOOLS) console.log('React DevTools installed') } catch (error) { console.error('Failed to install React DevTools:', error) } const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') })
3. Vue DevTools
bashnpm install --save-dev vue-devtools
javascript// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { // 安装 Vue DevTools const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer') try { installExtension(VUEJS_DEVTOOLS) console.log('Vue DevTools installed') } catch (error) { console.error('Failed to install Vue DevTools:', error) } const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') })
性能分析
1. 使用 Performance 面板
javascript// renderer.js // 开始性能记录 performance.mark('start') // 执行一些操作 function performOperation() { // 复杂的计算或渲染操作 } // 结束性能记录 performance.mark('end') // 测量性能 performance.measure('operation', 'start', 'end') const measure = performance.getEntriesByName('operation')[0] console.log(`Operation took ${measure.duration}ms`)
2. 使用 Chrome Performance API
javascript// renderer.js // 使用 Performance Observer const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(`${entry.name}: ${entry.duration}ms`) } }) observer.observe({ entryTypes: ['measure', 'navigation', 'resource'] }) // 测量特定操作 function measureFunction(fn) { const start = performance.now() fn() const end = performance.now() console.log(`Function took ${end - start}ms`) }
3. 内存分析
javascript// main.js // 监控内存使用 setInterval(() => { const memoryUsage = process.memoryUsage() console.log('Memory Usage:', { rss: `${Math.round(memoryUsage.rss / 1024 / 1024)} MB`, heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`, heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`, external: `${Math.round(memoryUsage.external / 1024 / 1024)} MB` }) }, 30000)
javascript// renderer.js // 使用 Chrome Memory Profiler function takeHeapSnapshot() { if (window.performance && window.performance.memory) { console.log('Memory Info:', { usedJSHeapSize: `${Math.round(window.performance.memory.usedJSHeapSize / 1024 / 1024)} MB`, totalJSHeapSize: `${Math.round(window.performance.memory.totalJSHeapSize / 1024 / 1024)} MB`, jsHeapSizeLimit: `${Math.round(window.performance.memory.jsHeapSizeLimit / 1024 / 1024)} MB` }) } } // 定期检查内存 setInterval(takeHeapSnapshot, 10000)
网络调试
1. 使用 Network 面板
javascript// main.js const { session } = require('electron') app.whenReady().then(() => { // 监听网络请求 session.defaultSession.webRequest.onBeforeRequest((details, callback) => { console.log('Request:', details.url) callback({}) }) session.defaultSession.webRequest.onCompleted((details) => { console.log('Response:', details.url, details.statusCode) }) session.defaultSession.webRequest.onErrorOccurred((details) => { console.error('Error:', details.url, details.error) }) })
2. 拦截和修改请求
javascript// main.js const { session } = require('electron') app.whenReady().then(() => { // 拦截请求 session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { // 添加自定义请求头 details.requestHeaders['X-Custom-Header'] = 'CustomValue' callback({ requestHeaders: details.requestHeaders }) }) // 修改响应 session.defaultSession.webRequest.onHeadersReceived((details, callback) => { // 添加自定义响应头 details.responseHeaders['X-Custom-Response'] = ['CustomResponse'] callback({ responseHeaders: details.responseHeaders }) }) })
日志记录
1. 使用 electron-log
bashnpm install electron-log
javascript// main.js const log = require('electron-log') // 配置日志 log.transports.file.level = 'debug' log.transports.console.level = 'debug' // 记录日志 log.info('Application started') log.debug('Debug information') log.warn('Warning message') log.error('Error occurred') // 记录对象 log.info('User data:', { name: 'John', age: 30 }) // 记录错误堆栈 try { // 可能出错的代码 } catch (error) { log.error('Error:', error) }
2. 自定义日志系统
javascript// logger.js const fs = require('fs').promises const path = require('path') const { app } = require('electron') class Logger { constructor() { this.logPath = path.join(app.getPath('userData'), 'logs') this.currentLogFile = path.join(this.logPath, this.getLogFileName()) } getLogFileName() { const date = new Date() return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.log` } async log(level, message, data = null) { const timestamp = new Date().toISOString() const logEntry = `[${timestamp}] [${level}] ${message}` if (data) { console.log(logEntry, data) await this.writeToFile(logEntry + ' ' + JSON.stringify(data)) } else { console.log(logEntry) await this.writeToFile(logEntry) } } async writeToFile(content) { try { await fs.mkdir(this.logPath, { recursive: true }) await fs.appendFile(this.currentLogFile, content + '\n') } catch (error) { console.error('Failed to write log:', error) } } info(message, data) { this.log('INFO', message, data) } debug(message, data) { this.log('DEBUG', message, data) } warn(message, data) { this.log('WARN', message, data) } error(message, data) { this.log('ERROR', message, data) } } module.exports = new Logger()
错误处理
1. 全局错误处理
javascript// main.js const { app, dialog } = require('electron') // 捕获未处理的异常 process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error) dialog.showErrorBox('Error', `An error occurred: ${error.message}`) // 记录错误 log.error('Uncaught Exception:', error) }) // 捕获未处理的 Promise 拒绝 process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason) dialog.showErrorBox('Error', `An error occurred: ${reason}`) // 记录错误 log.error('Unhandled Rejection:', reason) }) // 捕获渲染进程错误 app.on('render-process-gone', (event, webContents, details) => { console.error('Render process gone:', details) dialog.showErrorBox('Error', 'The application encountered an error and needs to restart.') // 重新加载页面 if (webContents) { webContents.reload() } })
2. 渲染进程错误处理
javascript// renderer.js // 捕获全局错误 window.addEventListener('error', (event) => { console.error('Global error:', event.error) // 发送错误到主进程 const { ipcRenderer } = require('electron') ipcRenderer.send('renderer-error', { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack }) }) // 捕获未处理的 Promise 拒绝 window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason) // 发送错误到主进程 const { ipcRenderer } = require('electron') ipcRenderer.send('renderer-error', { reason: event.reason }) })
javascript// main.js const { ipcMain } = require('electron') ipcMain.on('renderer-error', (event, errorData) => { console.error('Renderer error:', errorData) // 记录错误 log.error('Renderer error:', errorData) // 显示错误对话框 const { dialog } = require('electron') dialog.showErrorBox('Error', 'An error occurred in the renderer process') })
测试工具
1. 使用 Spectron
bashnpm install --save-dev spectron
javascript// test/app.test.js const Application = require('spectron').Application const path = require('path') describe('Application launch', function() { this.timeout(10000) beforeEach(async function() { this.app = new Application({ path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'), args: [path.join(__dirname, '..')] }) await this.app.start() }) afterEach(async function() { if (this.app && this.app.isRunning()) { await this.app.stop() } }) it('shows an initial window', async function() { const windowCount = await this.app.client.getWindowCount() assert.equal(windowCount, 1) }) it('window title is correct', async function() { const title = await this.app.client.getTitle() assert.equal(title, 'My Electron App') }) })
2. 使用 Playwright
bashnpm install --save-dev @playwright/test
javascript// test/app.spec.js const { test, expect } = require('@playwright/test') const { _electron: electron } = require('playwright') test('launch app', async () => { const app = await electron.launch({ path: require('electron') }) const window = await app.firstWindow() const title = await window.title() expect(title).toBe('My Electron App') await app.close() })
最佳实践
1. 开发环境配置
javascript// config.js const isDevelopment = process.env.NODE_ENV === 'development' module.exports = { isDevelopment, devTools: isDevelopment, logLevel: isDevelopment ? 'debug' : 'info' }
2. 条件编译
javascript// main.js const { isDevelopment } = require('./config') app.whenReady().then(() => { const mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { devTools: isDevelopment } }) mainWindow.loadFile('index.html') if (isDevelopment) { mainWindow.webContents.openDevTools() } })
3. 错误上报
javascript// main.js const Sentry = require('@sentry/electron') Sentry.init({ dsn: 'your-sentry-dsn', environment: process.env.NODE_ENV }) // 自动捕获错误 Sentry.captureException(error)
常见问题
Q: 如何在主进程中使用 console.log?A: 主进程中的 console.log 会输出到终端,可以在终端中查看日志。
Q: 如何调试多个窗口?A: 每个窗口都有独立的 DevTools,可以分别为每个窗口打开 DevTools。
Q: 如何在生产环境中禁用 DevTools?A: 设置 webPreferences.devTools 为 false,并移除打开 DevTools 的代码。
Q: 如何远程调试 Electron 应用?A: 使用 --remote-debugging-port 参数启动应用,然后使用 Chrome 连接到该端口。