Node-js 調試一路走來經歷了什麼

做爲前端開發,想必大家都寫過 Node.js 的代碼,也大概率用 debugger 斷點調試過。

我們可以用 Chrome Devtools 調試 Node.js 代碼,也可以用 VSCode 來調試它。調試工具是 Node.js 開發的基礎工具了。

但現在好用的調試工具也不是一開始就這樣的,它經歷了一系列的演變過程。今天我們就來聊聊 Node.js 調試工具背後的故事吧。

相信還是有部分同學不知道 Node.js 代碼怎麼調試的,所以我們先來過一遍怎麼調試 Node.js 代碼:

調試 Node.js 代碼

準備一段簡單的 Node.js 代碼用來調試:

const os = require('os');

function func(a, b) {
    return a + b;
}
console.log(func(1,2));

console.log(os.cpus());

它的邏輯就是執行了一個加法,然後打印了 cpu 的核心的情況。

直接執行是這樣的:

打印了 1 + 2 的結果,也就是 3 ,也打印了 CPU 核心的情況,8 核的 M1 芯片。

那怎麼斷點調試呢?

執行的時候加上一個 --inspect 的參數,就會啓動調試服務了:

指定 --inspect-brk 參數還會在首行斷住。

可以看到啓動了一個 WebSocket 的服務端,這就是調試服務,用某個調試工具客戶端連上就行了:

調試客戶端可以是 Chrome Devtools 也可以是 VSCode。

Chrome Devtools

比如用 Chrome Devtools 來連上是這樣的:

打開 chrome://inspect 的 url 就會看到這個可以連接的 target:

點擊 inspect 就是連上這個 ws 服務端來做調試:

右邊可以看到調用棧、上下文的變量,可以單步執行、可以打斷點等。

打印信息會輸出在 console:

VSCode

用 VSCode 調試的話需要在項目根目錄下加一個 .vscode/launch.json 的文件,類型選擇 attach to process:

很容易理解,就是連接到目標進程的 ws 服務的意思:

端口是 9229,也就是我們調試服務啓動的端口。

然後點擊調試面板的調試按鈕來啓動:

這樣也會在斷點處斷住,可以單步運行、可以看調用棧、上下文的信息:

看到這裏不知道有沒有同學會覺得這樣太麻煩了,每次都要起一個 ws 調試服務,然後再 attach,不能把這兩步合併到一塊自動給做了嗎?

沒錯,確實可以合併到一塊,也就是起一個 ws 服務,然後自動 attach 上:

調試配置選擇 launch program:

只需要指定要調試的 Node.js 模塊的地址,然後點擊啓動,這樣就可以調試了:

注意,想達到和 --inspect-brk 一樣的首行斷住的效果,這裏要執行 stopOnEntry 爲 true。

效果是一樣的:

這樣比直接啓動 ws 調試服務,然後再 attach 還少了一步。

怎麼調試 Node.js 講完了,大家是不是覺得這樣調試還是蠻方便的呢?

但其實最開始的調試並沒有這麼好用,接下來我們看下之前的調試都是咋樣的吧:

Node.js Debugger 的歷史

從前面的實踐中我們也能發現,調試的原理還是蠻清晰的:

啓動一個 WebSocket 服務端來提供各種運行時的信息,這個服務是 JS Runtime 提供的,也就是 Node。

啓動一個 WebSocket 客戶端來實現調試的 UI,包括調用棧、上下文的顯示、打斷點、單步運行等功能,比如我們用過的 Chrome Devtools、VSCode Debugger。

中間傳輸的消息就是調試協議:

我們知道 Node.js 是基於 V8 的,V8 本身有調試協議 V8 Debug Protocol,所以 Node.js 最早的調試協議也就是 V8 Debug Protocol。

當時調試是這樣的:

通過 node debug 來跑 js 文件,會在首行斷住:

然後可以通過 run、cont、next、step 等命令來實現單步調試,通過 backtrace 打印調用棧,通過 setBreakPoint 等設置斷點:

比如用 setBreakPoint(sb)命令在第四行打個斷點:

然後 cont(c) 命令繼續執行,backtrace(bt) 打印調用棧:

雖然該有的調試功能都有,但是這樣調試還是比較費勁的。

怎麼能不用命令行調試,而是用 UI 來調試呢?

當時 Node 就瞄準了 Chrome Devtools,它的調試 UI 就很不錯。

但是 Chrome Devtools 的調試協議是 Chrome Devtools Protocol,和 V8 Debug Protocol 還是有些差距的,怎麼能用上 Chrome Devtools 的調試工具來調試 Node 呢?

其實還挺容易想到的,就是加一箇中間的服務來做轉換:

這個服務是 node-inspector 這個包提供的。

所以當時 node debug 服務跑起來之後,還要要再跑一個 node-inspector 服務,這樣才能用 chrome devtools 來調試 Node.js 代碼。

後來維護 Node.js 的那些人覺得這樣也太麻煩了,要不讓 Node.js 提供的調試協議就直接就是兼容 Chrome Devtools Protocol 的吧。

當時就有了這樣一個 pr,把 v8 inspector 集成到 Node.js 中:

這個 v8 inspector 就是從 chrome 的內核 blink 裏剝離出來的讓 v8 支持 chrome devtools protocol 的部分。

很明顯這需要 v8 團隊的配合,所以說 Node.js 的發展還是很依賴 v8 團隊的支持的。

之後 Node.js 就在 v6.3 中加入了這個功能:

並且在成熟之後去掉了對 v8 debug protocol 的支持,也就是廢棄了 node debug 命令,改爲了 node inspect。

啓動 ws 服務的方式就是 node --inspect 或者 node --inspect-brk。

當然,之前作爲兩個協議的中轉的服務 node-inspector 也就退出了歷史舞臺。

所以今天,我們可以輕易的用 Chrome Devtools 來調試 Node.js 代碼,就如本文開始展示的那樣。

當然,這裏只是說 Chrome Devtools 調試 Node.js,在 VSCode 裏調試 Node.js 的話還有另一段小故事:

調試的原理我們已經知道了,就是 ws 客戶端和服務器的通信,然後基於調試協議來完成不同的功能。Node.js 是這樣,其他語言也是這樣。

VSCode 是一個通用的編輯器,是要支持多種語言的,也就是它的調試 UI 要支持多種調試協議。

要同一個調試工具同時支持不同的協議有點不太現實,那怎麼辦呢?

可以加一箇中間層,VSCode 的調試 UI 只要支持這個中間的調試協議就可以了,其餘的調試協議適配到這個調試協議上來:

這就是 DAP 協議,debugger adpater protocol。

Node.js 在把調試工具的協議換成兼容 Chrome Devtools Protocol 的協議之後,只要實現個 DAP 的 adapter 就可以對接到 VSCode 的調試工具了。

這樣我們就可以在 VSCode 裏調試 Node.js 了。

Node.js 調試的故事講完了,我們來總結下:

總結

現在 Node.js 的調試可以用 Chrome Devtools 也可以用 VSCode,都是挺方便的。

但是它不是一開始就這麼好用的,我們聊了下它之前的故事:

調試的原理就是 Node 啓動 ws 的調試服務,調試客戶端(chrome devtools、vscode 等)對接這個調試服務並實現交互的 UI,基於傳輸的調試協議來完成調試。

最開始 Node.js 的調試協議是 v8 debug protocol,只能用命令行調試。

爲了直接用 Chrome Devtools 的 UI 來調試,就實現了 node-inspector 的中轉服務來實現 v8 debug protocol 到 chrome devtools protocol 的協議轉換。

這樣還是太麻煩了,所以後來 Node.js 和 v8 團隊合作實現了 v8-inspector,可以讓 Node.js 提供的調試協議是直接兼容 Chrome Devtools Protocol 的。

這樣我們就可以直接用 node --inspect 起 ws 調試服務,然後用 Chrome Devtools 連接調試了。

VSCode 爲了同一個調試 UI 支持不同語言的調試,設計了中間的調試協議 Debug Apapter Protocol。Node.js 想在 VSCode 裏調試的話只要實現對應的 adapter 即可。

今天我們用 Chrome Devtools 或者 VSCode Debugger 都可以輕鬆調試 Node.js 代碼,其實這背後還是有一段挺有意思的故事的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5d5yiDpQ_gUTWGyx2lcCYQ