Bun

调试器

Bun 使用 WebKit Inspector 协议,因此您可以使用交互式调试器调试您的代码。为了演示目的,请考虑以下简单的 Web 服务器。

调试 JavaScript 和 TypeScript

server.ts
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 以启动调试会话。通过此界面,您将能够查看正在运行的文件的源代码、查看和设置断点以及使用内置控制台执行代码。

Screenshot of Bun debugger, Console tab

让我们设置一个断点。导航到 “Sources” 选项卡;您应该看到之前的代码。单击行号 3 以在我们的 console.log(req.url) 语句上设置断点。

screenshot of Bun debugger

然后访问 http://localhost:3000 在您的 Web 浏览器中。这将向我们的 localhost Web 服务器发送 HTTP 请求。页面似乎没有加载。为什么?因为程序已在我们之前设置的断点处暂停执行。

请注意 UI 的变化。

screenshot of Bun debugger

此时,我们可以做很多事情来自省当前的执行环境。我们可以使用底部的控制台在程序的上下文中运行任意代码,并完全访问断点处作用域内的变量。

在 “Sources” 窗格的右侧,我们可以看到当前作用域内的所有局部变量,并深入查看它们的属性和方法。在这里,我们正在检查 req 变量。

在 “Sources” 窗格的左上角,我们可以控制程序的执行。

这是一个备忘单,解释了控制流按钮的功能。

  • 继续脚本执行 — 继续运行程序,直到下一个断点或异常。
  • 单步跳过 — 程序将继续执行到下一行。
  • 单步进入 — 如果当前语句包含函数调用,调试器将 “单步进入” 被调用的函数。
  • 单步跳出 — 如果当前语句是函数调用,调试器将完成执行调用,然后 “单步跳出” 函数到调用它的位置。

Visual Studio Code 调试器

Visual Studio Code 中提供了对调试 Bun 脚本的实验性支持。要使用它,您需要安装 Bun VSCode 扩展

调试网络请求

BUN_CONFIG_VERBOSE_FETCH 环境变量允许您自动记录使用 fetch()node:http 发出的网络请求。

描述
curl将请求打印为 curl 命令。
true打印请求和响应信息
false不打印任何内容。默认

Bun 还支持通过将环境变量 BUN_CONFIG_VERBOSE_FETCH 设置为 curl,将 fetch()node:http 网络请求打印为 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 命令,让您可以复制粘贴到终端中以复制请求。

[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/1.2.5" -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.2.5
[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.2.5
[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

堆栈跟踪 & 源码映射

Bun 会转译每个文件,这听起来意味着您在控制台中看到的堆栈跟踪会无益地指向转译后的输出。为了解决这个问题,Bun 会自动为它转译的每个文件生成和提供源码映射文件。当您在控制台中看到堆栈跟踪时,您可以单击文件路径并跳转到原始源代码,即使它是用 TypeScript 或 JSX 编写的,或者应用了其他一些转换。

Bun 在运行时按需转译文件时,以及在使用 bun build 提前预编译文件时,都会自动加载源码映射。

语法高亮源代码预览

为了帮助调试,当发生未处理的异常或拒绝时,Bun 会自动打印小型的源代码预览。您可以通过调用 Bun.inspect(error) 来模拟此行为

// Create an error
const err = new Error("Something went wrong");
console.log(Bun.inspect(err, { colors: true }));

这将打印发生错误的源代码的语法高亮预览,以及错误消息和堆栈跟踪。

1 | // Create an error
2 | const err = new Error("Something went wrong");
                ^
error: Something went wrong
      at file.js:2:13

V8 堆栈跟踪

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 堆栈跟踪 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
getTypeNamethis 的 typeof
getFunction函数对象
getFunctionName函数名称,字符串形式
getMethodName方法名称,字符串形式
getFileName文件名或 URL
getLineNumber行号
getColumnNumber列号
getEvalOriginundefined
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 的第二个参数是您希望堆栈跟踪开始的函数。

例如,即使错误是在 myInner 处抛出的,以下代码也会使 err.stack 指向调用 fn() 的代码。

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)