大家好,我是徐徐。今天讲讲如何在 Electron 中进行调试。
前言
Electron 应用不同于传统的前端项目,里面除了传统的 Web 端的调试,还有主进程的调试。本文将会详细讲解Electron 渲染进程调试方法,开发者工具扩展,结合 Vscode 调试主进程,结合 Chrome 调试主进程,通用的内存消耗监控方法。
调试渲染进程
手动打开调试面板
如果你的 Electron 应用没有自定义菜单的话,可以用在菜单里面 View->Toogle Developer Tools 打开调试面板。
代码打开调试面板
有很多时候我们可能会进行自定义 Electron 的菜单,我们就需要用编程的方式来打开调试面板,如下所示。
typescript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow()
win.webContents.openDevTools()
这个方法的具体的参数可以参考
www.electronjs.org/zh/docs/lat...
如果你对如何使用 chrome 的 devtool 还不够了解的话,建议参考如下文档
developer.chrome.com/docs/devtoo...
打开 devtool 后你就可以像调试常规 Web 应用那样去调试你 Electron 应用的渲染进程了。
开发者工具扩展
我们在开发 Electron 应用的时候,devtool 是没有集成比如 React Developer Tools,Vue.js devtools 这些扩展插件的,这样就不能很好的支持现在比较流行的前端框架的调试,所以我们需要集成一些开发者工具到 Electorn 的调试面板中,下面介绍两种扩展方式。
使用 electron-devtools-installer
安装
typescript
yarn add electron-devtools-installer -D
使用
typescript
import { installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer';
const initElectronDevtoolsInstaller = () => {
installExtension([REACT_DEVELOPER_TOOLS,REDUX_DEVTOOLS],{loadExtensionOptions: { allowFileAccess: true }})
.then(([react,redux]) => Log4.info(`已添加扩展: ${react.name} ${redux.name}`))
.catch((err) => Log4.error('发生错误: ', err));
}
app.whenReady().then(() => {
initElectronDevtoolsInstaller()
});
正常情况下会 devtools 会出现如下界面。
不过 react develop 插件在某些 electron 版本中首次加载的时候并不能正常使用,需要 reload 一下整个应用才能成功使用。这里有相应的 issue:github.com/electron/el...
因为我是用的 react 框架,所以这里只演示了 react develop 插件和 redux 插件的使用。其他更多的使用可参考:github.com/MarshallOfS...
手动扩展
如果你不用 npm 工具包扩展的话,electron 也支持手动扩展插件。依然以 React Developer Tools 为例子,大概步骤如下:
- 在 Google Chrome 中安装该扩展。
- 导航到 chrome://extensions,并找到其扩展 ID: fmkadmapgofadopljbjfkapdkoienih
- 找到 Chrome 用于存储扩展的文件系统位置:
- 在 Windows 上是 %LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions
- 在 Linux 上可能是:
- ~/.config/google-chrome/Default/Extensions/
- ~/.config/google-chrome-beta/Default/Extensions/
- ~/.config/google-chrome-canary/Default/Extensions/
- ~/.config/chromium/Default/Extensions/
- 在 macOS 上是 ~/Library/Application Support/Google/Chrome/Default/Extensions
- 然后利用
session.defaultSession.loadExtension
导入扩展,下面是 Mac 上的例子:
typescript
import { app, session } from "electron";
import path from "path";
import os from "os";
const initInstallDevtoolsInstallerBySession = () => {
const reactDevToolsPath = path.join(
os.homedir(),
'/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/6.0.1_0'
)
Log4.info(reactDevToolsPath)
session.defaultSession.loadExtension(reactDevToolsPath).then(({name}) => {
Log4.info(`已添加扩展: ${name}`)
}).catch((err) => {
Log4.error('发生错误: ', err)
})
}
app.whenReady().then(() => {
initInstallDevtoolsInstallerBySession()
});
注意事项
- loadExtension 返回一个包含扩展元数据的 Promise 对象。在加载页面之前需要确保此 Promise 已解析(例如使用 await 表达式),否则无法保证扩展加载完成。
- loadExtension 不能在 app 模块的 ready 事件发出之前调用,也不能在内存中的(非持久性)会话上调用。
- 如果您希望扩展在每次应用启动时都加载,则必须在每次启动时调用 loadExtension。
开发者工具扩展插件之后,对开发效率有非常大提升,要想做一个出色的应用,工具链得齐全,调试工具更是必不可少的。
结合 Vscode 调试主进程
有些比较接近底层的应用,我们可能很多时候需要调试主进程,结合 Vscode 调试 Electorn 也是非常方便的,它可以让你去打断点一步步调试。大概需要一下三个步骤:
1:创建 .vscode
目录和 launch.json
文件
- 在项目根目录下创建一个
.vscode
目录。 - 在
.vscode
目录中创建一个launch.json
文件,并添加以下内容:
json
{
"version": "0.1.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Start",
"program": "${workspaceFolder}/scripts/dev.js",
"cwd": "${workspaceFolder}"
}
]
}
2:启用 Vite 的 Source Map
- 打开
vite.config.js
文件。 - 在
build
选项中添加sourcemap: true
:
javascript
// vite.config.js
export default {
// 其他配置
build: {
sourcemap: true
}
}
3:在 Vscode 中运行调试
打开 Vscode,打开调试试图,选择start,点击绿色的"播放"按钮(或按 F5
)启动调试。
结合 Chrome 调试主进程
虽然 VSCode 给我了我们调试代码的能力,但是有的时候我们还是需要像使用 Chrome 内置的调试工具调试Electron 主进程的代码,然后去看一些内存的情况以及性能指标,这样可以更加全方位的调试整个应用。主要核心逻辑就是使用 electron --inspect
在 Electron 中启用调试模式,以便通过 Chrome DevTools 调试 Electron 的主进程代码。具体地说,它会启动一个调试服务器,允许开发者在 Chrome 中连接并调试主进程的 JavaScript 代码。在我的演示项目里就是这样的:
- 启用调试模式 : 在
vite\service.js
文件的createMainServer
方法中,使用spawn
命令启动 Electron,并添加--inspect
参数。这会让 Electron 的主进程在一个特定端口上监听调试连接。 - 启动开发环境 : 通过运行
npm run dev
启动开发环境。 - 连接到调试工具 : 打开 Chrome 浏览器,进入
chrome://inspect/#devices
,点击 "Open dedicated DevTools for Node"。这样就可以打开 Chrome DevTools 并连接到 Electron 主进程进行调试。
内存消耗监控方法
我们在开发 Electron 应用的时候,我们最最害怕的就是应用内存泄漏,因为 Electorn 这个框架本身就消耗内存,如果在开发的过程中不注意内存的监控,发布生产之后可能会产生一些奇奇怪怪的现象,比如程序跑一段时间后整个电脑卡顿,程序卡顿,程序崩溃等,这些现象的大部分原因是内存泄漏,所以如何监控内存消耗是非常重要的,下面我们就展开说说。
在 Electorn 的调试面板中,不管是渲染进程还是通过主进程都有 Memory 面板。下面左侧是渲染进程的 Memory 调试面板,右侧是通过 --inspect
后在 Chrome 另外派生出来的主进程的 Memory 调试面板。有了这两个面板你就可以非常仔细的监控整个应用的内存消耗情况了。
面板中提供了四种监控内存的方式。
- Heap snapshot:用于打印内存堆快照,堆快照用于分析页面JavaScript对象和相关DOM节点之间的内存分布情况。
- Allocation on timeline:用于从时间维度观察内存的变化情况。
- Allocation sampling:用于采样内存分配信息,由于其开销较小,可以用于长时间记录内存的变化情况。
- Detached Element:用于检测分离 DOM 元素的内存泄漏问题,帮助开发者定位和分析由于未正确移除 DOM 引用导致的内存占用。不过这个主进程里面是没法用的。
这里我们主要讲讲 Heap snapshot 和 Allocation on timeline的使用,这也是我最经常使用的监控方式。
Heap snapshot
选择 Heap snapshot,并点击Take snapshot按钮,截取当前应用内存堆栈的快照信息,生成的快照信息可以通过三个维度查看。
- Summary:以构造函数分类显示堆栈使用情况。
- Containment:以GC路径(深度)分类显示堆栈使用情况。
- Statistics:以饼状图显示内存占用信息。
Summary 模式
这个界面下有四列内容:
Constructor:构造函数名,例如 Object、Module、Socket、Array、string 等,构造函数名后 x59627 代表此行信息存在 59627 个该类型的实例。
Distance:指当前对象到根对象的距离,对于 Electron 应用来说,根对象有可能是 window 对象也有可能是global 对象。此值越大,说明引用层次越深。
Shallow Size:指对象自身的大小,不包括它所引用的对象,也就是该对象自有的布尔类型、数字类型和字符串类型所占内存之和。
Retained Size:指对象的总大小,包括它所引用对象的大小,同样也是该对象被 GC 之后所能回收的内存的大小。一般情况下开发者会依据 Retained Size 降序展示以分析内存消耗情况。
选中一行内存消耗较高的记录后,视图的下方将会出现与这行内存占用有关的业务逻辑信息,开发者可以通过此视图内的链接切换到业务代码中观察是哪行代码导致此处内存消耗增加了。需要注意的是 Constructor 列中值为(closure)的项,这里记录着因闭包函数引用而未被释放的内存,这类内存泄漏问题是开发时最容易忽略、调优时最难发现的问题,遇到这种需要注意注意再注意。
Containment 模式
这个模式跟 Summary 差不多,只是分类的话变成了对象。这个视图可以探测堆的具体内容,它提供了一个更适合的视图来查看对象结构,有助于分析对象的引用情况,使用它可以分析闭包和进行更深层次的对象分析。可以支持 JS VM 内部的对象查看,展示 JavaScript 引擎内部的对象表示,显示对象在内存中的实际存储方式,包括隐藏的内部属性和元数据,让你更加深入底层分析内存的存储方式。
Statistics 模式
这个就是一个统计图,列出了源码脚本、字符串对象、数组对象等所消耗内存的占比及数量,可以让你直观地看到不同类型的资源的内存占用情况,然后针对性的优化占比较高的资源。
Allocation on timeline
这种模式最大的优势就是开发者可以截取某一小段时间来观察内存的消长情况,我们需要特别关注的是蓝色的线条,如果你发现蓝色的线条很长一段时间都存在的话,并且没有消失,你需要警惕是不是存在内存长期没有回收的情况。这时候我们就需要点击到蓝色线条上去观察这段时间创建的变量,对象,数组等实例,然后就可以具体分析其原因了。
getAppMetrics
当然,我们上面讲到的是基于代码层面的应用内存分析,有时候我们还需要监控应用在设备上的整体内存占用情况,我们就需要用到 <font style="color:rgb(28, 30, 33);">app.getAppMetrics()</font>
这个方法去获取整个应用的内存情况,监控整个内存的走势。
具体信息可查看:www.electronjs.org/zh/docs/lat...
结语
调试 Electron 应用是开发 Electron 中必不可少的一环,掌握必要的调试技巧可以让你事半功倍,应用内存的占用情况是开发一个应用的关键指标,如果能准确的监控内存的回收情况,应用的健壮性也会大大提高,合理运用一些调试监控方法会让你及时发现一些潜在的隐患。好了,讲了这么多,希望能帮助到你。