Node.js调试都经历了什么?我们可以使用ChromeDevtools调试Node.js代码,也可以使用VSCode进行调试。调试工具是Node.js开发的基础工具。但是现在好用的调试工具一开始并不是这样的,它经历了一系列的演变过程。今天我们就来说说Node.js调试工具背后的故事。相信有的同学还不知道如何调试Node.js代码,那么我们先来了解一下如何调试Node.js代码:DebuggingNode.jsCode准备一段简单的Node.js代码用于调试:constos=require('os');functionfunc(a,b){returna+b;}console.log(func(1,2));console.log(os.cpus());它的逻辑是先做一个加法,然后打印cpu的核心。直接执行是这样的:打印出1+2的结果,为3,同时打印出CPU核数,8核M1芯片。断点调试怎么样?执行时加一个--inspect参数,调试服务就会启动:指定--inspect-brk参数也会停在第一行。可以看到启动了一个WebSocket服务器,就是调试服务。只需使用调试工具客户端连接即可:调试客户端可以是ChromeDevtools或VSCode。以ChromeDevtools为例,使用ChromeDevtools连接是这样的:打开chrome://inspect的url,会看到这个可以连接的target:点击inspect连接到ws服务器进行调试:可以看到调用右边的栈,上下文变量,单步执行,断点等。控制台会输出打印信息:VSCode如果使用VSCode,需要在项目根目录下添加一个.vscode/launch.json文件进行调试,类型选择attachtoprocess:很好理解,就是连接到目标进程的ws服务:端口为9229,也就是我们调试服务启动的端口。然后点击调试面板上的调试按钮开始:这样也会在断点处停止,可以单步运行,可以看到调用栈和上下文信息:不知道有没有同学觉得这是太麻烦了。每次都要启动一个ws调试服务,然后attach,难道不能把这两个步骤合二为一,自动完成吗?是的,确实可以合二为一,就是启动一个ws服务,然后自动附加:调试配置选择启动程序:只需要指定要调试的Node.js模块的地址,然后点击启动,这样就可以调试了:注意,如果要达到和--inspect-brk一样的效果,第一行就断了,这里需要执行stopOnEntry为true。效果是一样的:这比直接启动ws调试服务再attach少了一步。如何调试Node.js说完,你是不是觉得这样调试还是挺方便的?但实际上,初期调试并没有那么好用。接下来我们再看看之前的调试:Node.jsDebugger的历史从之前的实践我们也可以发现调试的原理还是挺清晰的:启动一个WebSocket服务器提供各种运行时信息。该服务由JSRuntime,即Node.js提供。启动一个WebSocket客户端实现调试UI,包括调用栈、上下文显示、断点、单步运行等功能,比如我们用过的ChromeDevtools和VSCodeDebugger。中间传递的消息是调试协议:我们知道Node.js是基于V8的,而V8本身就有一个调试协议V8DebugProtocol,所以Node.js最早的调试协议也是V8DebugProtocol。当时调试是这样的:通过nodedebug运行js文件,第一行就断了:然后可以用run,cont,next,step等命令实现单步调试,打印通过backtrace调用栈,通过setBreakPoint等设置断点。Point:比如使用setBreakPoint(sb)命令在第四行设置断点:然后cont(c)命令继续执行,backtrace(bt)打印调用栈:虽然有一些调试功能,这样调试起来还是比较困难的。我们如何使用UI来调试而不是命令行调试?当时Node瞄准的是ChromeDevtools,它的调试UI非常好。不过ChromeDevtools的调试协议是ChromeDevtoolsProtocol,与V8DebugProtocol还是有些区别的。怎么使用ChromeDevtools这个调试工具来调试Node呢?其实很容易想到,就是加一个中间服务做转换:这个服务由node-inspector包提供。所以运行完nodedebug服务后,还得再运行一个node-inspector服务,这样Node.js代码才能用chromedevtools调试。后来维护Node.js的人觉得这样太麻烦了,要不Node.js提供的调试协议直接兼容ChromeDevtoolsProtocol。当时有这样一个PR将v8inspector集成到Node.js中:这个v8inspector是从chromekernelblink中剥离出来的部分,让v8支持chromedevtools协议。显然这需要v8团队的配合,所以Node.js的发展还是非常依赖v8团队的支持。之后Node.js在v6.3中增加了这个功能:成熟后去掉了对v8调试协议的支持,也就是放弃了nodedebug命令,改为nodeinspect。启动ws服务的方式是node--inspect或者node--inspect-brk。当然,之前作为两种协议之间的中转服务的服务节点-检查器也退出了历史舞台。所以今天,我们可以使用ChromeDevtools轻松调试Node.js代码,如本文开头所示。当然这里只是说ChromeDevtools调试Node.js。VSCode调试Node.js还有一个小故事:我们已经知道了调试的原理,就是ws客户端和服务端的通信,然后根据调试协议不同的功能来完成。Node.js如此,其他语言也是如此。VSCode是一个支持多种语言的通用编辑器,即它的调试UI支持多种调试协议。同一个调试工具同时支持不同的协议是不现实的,那怎么办呢?可以加一个中间层,VSCode的调试UI只需要支持这个中间调试协议,其余的调试协议都是适配这个调试出来的协议:这个就是DAP协议,调试器适配器协议。Node.js将调试工具的协议替换为兼容ChromeDevtoolsProtocol的协议后,只要实现一个DAP适配器,就可以连接VSCode的调试工具。这样我们就可以在VSCode中调试Node.js了。Node.js调试的故事讲完了,我们总结一下:综上所述,现在可以使用ChromeDevtools或者VSCode来调试Node.js,非常方便。但是一开始并没有那么好用。我们讲过它之前的故事:调试的原理是Node启动ws的调试服务,调试客户端(chromedevtools、vscode等)连接调试服务,实现交互UI,完成基于调试的调试关于传输调试协议。一开始Node.js的调试协议是v8调试协议,只能通过命令行调试。为了直接使用ChromeDevtools的UI进行调试,实现了node-inspector中转服务,实现v8调试协议到chromedevtools协议的协议转换。这样还是太麻烦了,所以后来Node.js和v8团队合作实现了v8-inspector,可以让Node.js提供的调试协议直接兼容ChromeDevtoolsProtocol。这样我们就可以直接使用node--inspect启动ws调试服务,然后使用ChromeDevtools进行连接调试。为了在同一个调试UI中支持不同语言的调试,VSCode设计了一个中间调试协议DebugApapterProtocol。如果你想在VSCode中调试Node.js,只需要实现相应的适配器即可。今天,我们可以使用ChromeDevtools或VSCodeDebugger轻松调试Node.js代码。其实这背后还有一段有趣的故事。
