Bun 使用 WebKit Inspector Protocol,因此你可以使用交互式調試器調試代碼。為了演示,考慮以下簡單的 Web 服務器。
調試 JavaScript 和 TypeScript
Bun.serve({
fetch(req) {
console.log(req.url);
return new Response("Hello, world!");
},
});--inspect
要在運行 Bun 代碼時啟用調試,使用 --inspect 標志。這會自動在可用端口上啟動 WebSocket 服務器,可用於檢查運行的 Bun 進程。
bun --inspect server.ts------------------ Bun Inspector ------------------
Listening at:
ws://localhost:6499/0tqxs9exrgrm
Inspect in browser:
https://debug.bun.sh/#localhost:6499/0tqxs9exrgrm
------------------ Bun Inspector --------------------inspect-brk
--inspect-brk 標志的行為與 --inspect 相同,不同之處在於它會在執行腳本的第一行自動注入斷點。這對於調試快速運行並立即退出的腳本很有用。
--inspect-wait
--inspect-wait 標志的行為與 --inspect 相同,不同之處在於代碼在調試器附加到運行進程之前不會執行。
設置調試器的端口或 URL
無論你使用哪個標志,都可以選擇指定端口號、URL 前綴或兩者。
bun --inspect=4000 server.ts
bun --inspect=localhost:4000 server.ts
bun --inspect=localhost:4000/prefix server.ts調試器
各種調試工具可以連接到此服務器以提供交互式調試體驗。
debug.bun.sh
Bun 在 debug.bun.sh 提供基於 Web 的調試器。它是 WebKit Web Inspector Interface 的修改版本,Safari 用戶會感到熟悉。
在瀏覽器中打開提供的 debug.bun.sh URL 以開始調試會話。在此界面中,你可以查看運行文件的源代碼、查看和設置斷點,並使用內置控制台執行代碼。
讓我們設置一個斷點。導航到 Sources 選項卡;你應該會看到前面的代碼。點擊行號 3 在我們的 console.log(req.url) 語句上設置斷點。
然後在瀏覽器中訪問 http://localhost:3000。這將向我們的 localhost Web 服務器發送 HTTP 請求。頁面似乎沒有加載。為什麼?因為程序在我們之前設置的斷點處暫停了執行。
注意 UI 如何變化。
此時我們可以做很多事情來檢查當前執行環境。我們可以使用底部的控制台在程序的上下文中運行任意代碼,完全訪問斷點處作用域內的變量。
在 Sources 窗格的右側,我們可以看到當前作用域內的所有局部變量,並深入查看它們的屬性和方法。這裡,我們正在檢查 req 變量。
在 Sources 窗格的左上角,我們可以控制程序的執行。
以下是控制流按鈕功能的速查表。
- 繼續腳本執行 — 繼續運行程序直到下一個斷點或異常。
- 單步跳過 — 程序將繼續到下一行。
- 單步進入 — 如果當前語句包含函數調用,調試器將"進入"被調用的函數。
- 單步跳出 — 如果當前語句是函數調用,調試器將完成調用執行,然後"跳出"函數到調用位置。
Visual Studio Code 調試器
實驗性的 Bun 腳本調試支持可在 Visual Studio Code 中使用。要使用它,你需要安裝 Bun VSCode 擴展。
調試網絡請求
BUN_CONFIG_VERBOSE_FETCH 環境變量允許你自動記錄使用 fetch() 或 node:http 發出的網絡請求。
| 值 | 描述 |
|---|---|
curl | 將請求打印為 curl 命令。 |
true | 打印請求和響應信息 |
false | 不打印任何內容。默認值 |
將 fetch 和 node:http 請求打印為 curl 命令
Bun 還支持通過將環境變量 BUN_CONFIG_VERBOSE_FETCH 設置為 curl 來將 fetch() 和 node:http 網絡請求打印為 curl 命令。這會將 fetch 請求打印為單行 curl 命令,以便你可以復制粘貼到終端中復制請求。
process.env.BUN_CONFIG_VERBOSE_FETCH = "curl";
await fetch("https://example.com", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ foo: "bar" }),
});[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/1.3.3" -H "Accept: */*" -H "Host: example.com" -H "Accept-Encoding: gzip, deflate, br" --compressed -H "Content-Length: 13" --data-raw "{\"foo\":\"bar\"}"
[fetch] > HTTP/1.1 POST https://example.com/
[fetch] > content-type: application/json
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br
[fetch] > Content-Length: 13
[fetch] < 200 OK
[fetch] < Accept-Ranges: bytes
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT
[fetch] < Etag: "3147526947"
[fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: EOS (vny/044F)
[fetch] < Content-Length: 1256帶有 [fetch] > 的行是來自本地代碼的請求,帶有 [fetch] < 的行是來自遠程服務器的響應。
BUN_CONFIG_VERBOSE_FETCH 環境變量在 fetch() 和 node:http 請求中都受支持,所以它應該可以直接使用。
要不帶 curl 命令打印,將 BUN_CONFIG_VERBOSE_FETCH 設置為 true。
process.env.BUN_CONFIG_VERBOSE_FETCH = "true";
await fetch("https://example.com", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ foo: "bar" }),
});[fetch] > HTTP/1.1 POST https://example.com/
[fetch] > content-type: application/json
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br
[fetch] > Content-Length: 13
[fetch] < 200 OK
[fetch] < Accept-Ranges: bytes
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT
[fetch] < Etag: "3147526947"
[fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: EOS (vny/044F)
[fetch] < Content-Length: 1256堆棧跟蹤和 Source Map
Bun 轉譯每個文件,這聽起來像是你在控制台中看到的堆棧跟蹤會無用地指向轉譯後的輸出。為了解決這個問題,Bun 自動為其轉譯的每個文件生成並提供 source map。當你在控制台中看到堆棧跟蹤時,你可以點擊文件路徑並被帶到原始源代碼,即使它是用 TypeScript 或 JSX 編寫的,或應用了其他轉換。
Bun 自動加載 source map,無論是在運行時按需轉譯文件時,還是在使用 bun build 提前預編譯文件時。
語法高亮的源代碼預覽
為了幫助調試,當發生未處理的異常或拒絕時,Bun 會自動打印一小段源代碼預覽。你可以通過調用 Bun.inspect(error) 來模擬此行為:
// 創建錯誤
const err = new Error("Something went wrong");
console.log(Bun.inspect(err, { colors: true }));這會打印錯誤發生處的語法高亮源代碼預覽,以及錯誤消息和堆棧跟蹤。
1 | // 創建錯誤
2 | const err = new Error("Something went wrong");
^
error: Something went wrong
at file.js:2:13V8 堆棧跟蹤
Bun 使用 JavaScriptCore 作為其引擎,但大部分 Node.js 生態系統和 npm 期望 V8。JavaScript 引擎在 error.stack 格式化方面有所不同。Bun 旨在成為 Node.js 的即插即用替代品,這意味著即使引擎不同,我們也要確保堆棧跟蹤盡可能相似。
這就是為什麼當你在 Bun 中記錄 error.stack 時,error.stack 的格式與 Node.js 的 V8 引擎相同。當你使用期望 V8 堆棧跟蹤的庫時,這特別有用。
V8 堆棧跟蹤 API
Bun 實現了 V8 Stack Trace API,這是一組允許你操作堆棧跟蹤的函數。
Error.prepareStackTrace
Error.prepareStackTrace 函數是一個全局函數,允許你自定義堆棧跟蹤輸出。此函數使用錯誤對象和 CallSite 對象數組調用,允許你返回自定義堆棧跟蹤。
Error.prepareStackTrace = (err, stack) => {
return stack.map(callSite => {
return callSite.getFileName();
});
};
const err = new Error("Something went wrong");
console.log(err.stack);
// [ "error.js" ]CallSite 對象具有以下方法:
| 方法 | 返回 |
|---|---|
getThis | 函數調用的 this 值 |
getTypeName | typeof this |
getFunction | 函數對象 |
getFunctionName | 函數名稱(字符串) |
getMethodName | 方法名稱(字符串) |
getFileName | 文件名或 URL |
getLineNumber | 行號 |
getColumnNumber | 列號 |
getEvalOrigin | undefined |
getScriptNameOrSourceURL | 源 URL |
isToplevel | 如果函數在全局作用域中則返回 true |
isEval | 如果函數是 eval 調用則返回 true |
isNative | 如果函數是原生的則返回 true |
isConstructor | 如果函數是構造函數則返回 true |
isAsync | 如果函數是 async 則返回 true |
isPromiseAll | 尚未實現。 |
getPromiseIndex | 尚未實現。 |
toString | 返回調用點的字符串表示 |
在某些情況下,Function 對象可能已經被垃圾回收,因此其中一些方法可能返回 undefined。
Error.captureStackTrace(error, startFn)
Error.captureStackTrace 函數允許你在代碼的特定點捕獲堆棧跟蹤,而不是在拋出錯誤的位置。
當你有回調或異步代碼使得難以確定錯誤來源時,這很有用。Error.captureStackTrace 的第二個參數是你希望堆棧跟蹤開始的函數。
例如,以下代碼將使 err.stack 指向調用 fn() 的代碼,即使錯誤是在 myInner 中拋出的。
const fn = () => {
function myInner() {
throw err;
}
try {
myInner();
} catch (err) {
console.log(err.stack);
console.log("");
console.log("-- captureStackTrace --");
console.log("");
Error.captureStackTrace(err, fn);
console.log(err.stack);
}
};
fn();Error: here!
at myInner (file.js:4:15)
at fn (file.js:8:5)
at module code (file.js:17:1)
at moduleEvaluation (native)
at moduleEvaluation (native)
at <anonymous> (native)
-- captureStackTrace --
Error: here!
at module code (file.js:17:1)
at moduleEvaluation (native)
at moduleEvaluation (native)
at <anonymous> (native)