VS Code 中的 Node.js 调试

Visual Studio Code 编辑器内置了对 Node.js 运行时的调试支持,可以调试 JavaScript、TypeScript 以及许多其他转译为 JavaScript 的语言。VS Code 提供了适当的启动配置默认值和代码片段,使得设置 Node.js 调试项目变得十分简单。

在 VS Code 中调试 Node.js 程序有几种方法:

自动附加 (Auto Attach)

如果启用了自动附加 (Auto Attach) 功能,Node 调试器会自动附加到从 VS Code 集成终端启动的特定 Node.js 进程。要启用此功能,请使用命令面板中的切换自动附加 (Toggle Auto Attach) 命令(⇧⌘P(Windows、Linux 为 Ctrl+Shift+P),或者如果该功能已激活,则使用状态栏上的自动附加项。

自动附加有三种模式,您可以在弹出的“快速选择”菜单中进行选择,也可以通过 debug.javascript.autoAttachFilter 设置进行配置:

  • smart - 如果您在 node_modules 文件夹之外执行脚本,或使用常见的“运行器”脚本(如 mocha 或 ts-node),则会调试该进程。您可以使用自动附加智能模式 (Auto Attach Smart Pattern) 设置 (debug.javascript.autoAttachSmartPattern) 来配置“运行器”脚本的允许列表。
  • always - 在集成终端中启动的所有 Node.js 进程都将被调试。
  • onlyWithFlag - 仅调试使用 --inspect--inspect-brk 标志启动的进程。

启用自动附加后,您需要通过点击终端右上角的 ⚠ 图标来重启终端,或者直接创建一个新终端。之后,调试器应会在一秒钟内附加到您的程序。

Auto Attach

当开启自动附加时,自动附加项会出现在 VS Code 窗口底部的状态栏中。点击它可以更改自动附加模式,或暂时将其关闭。如果您正在运行一些不需要调试的一次性程序,但又不想完全禁用此功能,那么暂时关闭自动附加会很有用。

其他配置

其他启动配置属性

您可以将 通常在 launch.json 中找到的其他属性 应用于 debug.javascript.terminalOptions 设置中的自动附加功能。例如,要将 node 内部模块添加到 skipFiles 中,您可以将以下内容添加到用户或工作区设置中:

  "debug.javascript.terminalOptions": {
    "skipFiles": [
      "<node_internals>/**"
    ]
  },

自动附加智能模式

smart 自动附加模式下,VS Code 会尝试附加到您的代码,而不附加到您无需调试的构建工具。它通过将主脚本与 glob 模式 列表进行匹配来实现这一点。Glob 模式可以在 debug.javascript.autoAttachSmartPattern 设置中配置,其默认值为:

[
  '!**/node_modules/**', // exclude scripts in node_modules folders
  '**/$KNOWN_TOOLS$/**' // but include some common tools
];

$KNOWN_TOOLS$ 会被替换为常见的“代码运行器”列表,如 ts-nodemochaava 等。如果这些设置不起作用,您可以修改此列表。例如,要排除 mocha 并包含 my-cool-test-runner,您可以添加两行配置:

[
  '!**/node_modules/**',
  '**/$KNOWN_TOOLS$/**',
  '!**/node_modules/mocha/**', // use "!" to exclude all scripts in "mocha" node modules
  '**/node_modules/my-cool-test-runner/**' // include scripts in the custom test runner
];

JavaScript 调试终端

自动附加 类似,JavaScript 调试终端会自动调试您在其中运行的任何 Node.js 进程。您可以通过命令面板(kbs(workbench.action.showCommands))运行 调试: 创建 JavaScript 调试终端 (Debug: Create JavaScript Debug Terminal) 命令,或者从终端切换器下拉菜单中选择 创建 JavaScript 调试终端 来创建调试终端。

Create Debug Terminal

其他配置

其他启动配置属性

您可以将 通常在 launch.json 中找到的其他属性 应用于 debug.javascript.terminalOptions 设置中的调试终端。例如,要将 node 内部模块添加到 skipFiles 中,您可以将以下内容添加到用户或工作区设置中:

"debug.javascript.terminalOptions": {
  "skipFiles": [
    "<node_internals>/**"
  ]
},

启动配置 (Launch Configuration)

启动配置是在 VS Code 中设置调试的传统方式,它为您运行复杂应用程序提供了最多的配置选项。

在本节中,我们将更详细地探讨高级调试场景的配置和功能。您将找到有关使用 源映射 (source maps)跳过外部代码 (stepping over external code)、进行 远程调试 等方面的说明。

如果您想观看入门视频,请参阅 VS Code 调试入门

注意:如果您刚刚开始使用 VS Code,可以在 调试 (Debugging) 主题中了解常规调试功能和如何创建 launch.json 配置文件。

启动配置属性

调试配置存储在位于工作区 .vscode 文件夹中的 launch.json 文件里。有关创建和使用调试配置文件的介绍,请参阅常规的 调试 (Debugging) 文章。

以下是 Node.js 调试器特有的常见 launch.json 属性参考。您可以在 vscode-js-debug 选项 文档中查看完整的选项集合。

以下属性在 launchattach 类型的启动配置中均受支持:

这些属性仅适用于请求类型为 launch 的启动配置:

  • program - 要调试的 Node.js 程序的绝对路径。
  • args - 传递给要调试程序的参数。此属性为数组类型,期望将单个参数作为数组元素。
  • cwd - 在此目录中启动要调试的程序。
  • runtimeExecutable - 要使用的运行时可执行文件的绝对路径。默认为 node。请参阅 对 npm 和其他工具的启动配置支持 章节。
  • runtimeArgs - 传递给运行时可执行文件的可选参数。
  • runtimeVersion - 如果使用 "nvm"(或 "nvm-windows")或 "nvs" 来管理 Node.js 版本,此属性可用于选择特定的 Node.js 版本。请参阅下方的 多版本支持 章节。
  • env - 可选的环境变量。此属性期望环境变量为字符串类型的键/值对列表。
  • envFile - 包含环境变量定义文件的可选路径。请参阅下方的 从外部文件加载环境变量 章节。
  • console - 启动程序的控制台(internalConsoleintegratedTerminalexternalTerminal)。请参阅下方的 Node 控制台 章节。
  • outputCapture - 如果设置为 std,来自进程 stdout/stderr 的输出将显示在调试控制台中,而不是通过调试端口监听输出。这对于直接写入 stdout/stderr 流而不是使用 console.* API 的程序或日志库非常有用。

此属性仅适用于请求类型为 attach 的启动配置:

  • restart - 终止时重启连接。请参阅 自动重启调试会话 章节。
  • port - 要使用的调试端口。请参阅 附加到 Node.js远程调试 章节。
  • address - 调试端口的 TCP/IP 地址。请参阅 附加到 Node.js远程调试 章节。
  • processId - 调试器在发送 USR1 信号后尝试附加到此进程。通过此设置,调试器可以附加到已运行但未以调试模式启动的进程。使用 processId 属性时,调试端口是根据 Node.js 版本(及使用的协议)自动确定的,无法显式配置。因此,请勿指定 port 属性。
  • continueOnAttach - 附加到进程时,如果进程已暂停,是否继续执行。如果您使用 --inspect-brk 启动程序,此选项非常有用。

常见场景的启动配置

您可以在 launch.json 文件中触发 IntelliSense(⌃Space(Windows、Linux 为 Ctrl+Space),以查看常用 Node.js 调试场景的启动配置代码片段。

Launch configuration snippets for Node.js

您还可以通过 launch.json 编辑器窗口右下角的添加配置... (Add Configuration...) 按钮调出代码片段。

Add Configuration button

以下代码片段可用:

  • 启动程序 (Launch Program):以调试模式启动 Node.js 程序。
  • 通过 npm 启动 (Launch via npm):通过 npm 'debug' 脚本启动 Node.js 程序。如果 npm debug 脚本已在 package.json 中定义,您可以在启动配置中使用它。npm 脚本中使用的调试端口必须与代码片段中指定的端口相对应。
  • 附加 (Attach):附加到本地运行的 Node.js 程序的调试端口。请确保要调试的 Node.js 程序已在调试模式下启动,并且所使用的调试端口与代码片段中指定的端口相同。
  • 附加到远程程序 (Attach to Remote Program):附加到在 address 属性指定的主机上运行的 Node.js 程序的调试端口。请确保要调试的 Node.js 程序已在调试模式下启动,并且所使用的调试端口与代码片段中指定的端口相同。为了帮助 VS Code 在您的工作区和远程主机的文件系统之间映射源文件,请确保为 localRootremoteRoot 属性指定正确的路径。
  • 按进程 ID 附加 (Attach by Process ID):打开进程选择器以选择要调试的 node 或 gulp 进程。使用此启动配置,即使是未以调试模式启动的 node 或 gulp 进程,您也可以进行附加。
  • Nodemon 设置 (Nodemon Setup):使用 nodemon 在 JavaScript 源代码发生更改时自动重启调试会话。请确保已全局安装 nodemon。请注意,终止调试会话只会终止要调试的程序,不会终止 nodemon 本身。要终止 nodemon,请在集成终端中按 Ctrl+C
  • Mocha 测试 (Mocha Tests):调试项目 test 文件夹中的 mocha 测试。请确保项目已在 node_modules 文件夹中安装了 'mocha'。
  • Yeoman 生成器 (Yeoman generator):调试 yeoman 生成器。此片段会要求您指定生成器的名称。请确保项目已在 node_modules 文件夹中安装了 'yo',并且通过在项目文件夹中运行 npm link,生成的项目已安装以便进行调试。
  • Gulp 任务 (Gulp task):调试 gulp 任务。请确保项目已在 node_modules 文件夹中安装了 'gulp'。
  • Electron 主进程 (Electron Main):调试 Electron 应用程序的主 Node.js 进程。该片段假定 Electron 可执行文件已安装在工作区的 node_modules/.bin 目录中。

Node 控制台

默认情况下,Node.js 调试会话在内置的 VS Code 调试控制台中启动目标。由于调试控制台不支持需要从控制台读取输入的程序,您可以通过在启动配置中将 console 属性分别设置为 externalTerminalintegratedTerminal 来启用外部终端或使用 VS Code 集成终端。默认值为 internalConsole

在外部终端中,您可以通过 terminal.external.windowsExecterminal.external.osxExecterminal.external.linuxExec 设置来配置要使用的终端程序。

对 'npm' 和其他工具的启动配置支持

除了直接使用 node 启动 Node.js 程序外,您还可以直接从启动配置中使用 'npm' 脚本或其他任务运行工具。

  • 您可以对 runtimeExecutable 属性使用 PATH 中可用的任何程序(例如 'npm'、'mocha'、'gulp' 等),参数可以通过 runtimeArgs 传递。
  • 如果您的 npm 脚本或其他工具隐式指定了要启动的程序,则无需设置 program 属性。

让我们看一个 'npm' 示例。如果您的 package.json 有一个 'debug' 脚本,例如:

  "scripts": {
    "debug": "node myProgram.js"
  },

相应的启动配置如下所示:

{
  "name": "Launch via npm",
  "type": "node",
  "request": "launch",
  "cwd": "${workspaceFolder}",
  "runtimeExecutable": "npm",
  "runtimeArgs": ["run-script", "debug"]
}

多版本支持

如果您使用 'nvm'(或 'nvm-windows')来管理 Node.js 版本,可以在启动配置中指定 runtimeVersion 属性来选择特定的 Node.js 版本。

{
  "type": "node",
  "request": "launch",
  "name": "Launch test",
  "runtimeVersion": "14",
  "program": "${workspaceFolder}/test.js"
}

如果您使用 'nvs' 来管理 Node.js 版本,可以使用 runtimeVersion 属性来选择特定的版本、架构和风格的 Node.js,例如:

{
  "type": "node",
  "request": "launch",
  "name": "Launch test",
  "runtimeVersion": "chackracore/8.9.4/x64",
  "program": "${workspaceFolder}/test.js"
}

请确保已安装要与 runtimeVersion 属性一起使用的 Node.js 版本,因为该功能不会自动下载并安装版本。例如,如果您计划在启动配置中添加 "runtimeVersion": "7.10.1",则必须从集成终端运行类似于 nvm install 7.10.1nvs add 7.10.1 的命令。

如果您省略次版本号和修订版本号,例如使用 "runtimeVersion": "14",则会使用系统中安装的最新 14.x.y 版本。

从外部文件加载环境变量

VS Code Node 调试器支持从文件加载环境变量并将其传递给 Node.js 运行时。要使用此功能,请将 envFile 属性添加到您的启动配置中,并指定包含环境变量的文件的绝对路径:

   //...
   "envFile": "${workspaceFolder}/.env",
   "env": { "USER": "john doe" }
   //...

env 字典中指定的任何环境变量都会覆盖从文件中加载的变量。

以下是 .env 文件的示例:

USER=doe
PASSWORD=abc123

# a comment

# an empty value:
empty=

# new lines expanded in quoted strings:
lines="foo\nbar"

附加到 Node.js

如果您想将 VS Code 调试器附加到外部 Node.js 程序,请按如下方式启动 Node.js:

node --inspect program.js

或者,如果程序不应立即运行,而必须等待调试器附加:

node --inspect-brk program.js

将调试器附加到程序的方法

  • 打开一个“进程选择器”,列出所有潜在的候选进程并让您选择一个,或者
  • 创建一个显式指定所有配置选项的“附加 (attach)”配置,然后按 F5

让我们详细了解这些选项。

“附加到 Node 进程”操作

命令面板(⇧⌘P(Windows、Linux 为 Ctrl+Shift+P)中的 附加到 Node 进程 (Attach to Node Process) 命令会打开一个“快速选择”菜单,列出所有可供 Node.js 调试器使用的潜在进程。

Node.js Process picker

选择器中列出的各个进程会显示调试端口和进程 ID。一旦您在该列表中选择了 Node.js 进程,Node.js 调试器就会尝试附加到它。

除了 Node.js 进程外,选择器还会显示其他以各种形式的 --inspect 参数启动的程序。这使得附加到 Electron 或 VS Code 的辅助进程成为可能。

设置“附加”配置

此选项需要更多工作,但与前两个选项相比,它允许您显式配置各种调试配置选项。

最简单的“附加”配置如下所示:

{
  "name": "Attach to Process",
  "type": "node",
  "request": "attach",
  "port": 9229
}

端口 9229--inspect--inspect-brk 选项的默认调试端口。要使用不同的端口(例如 12345),请将其添加到选项中,如下所示:--inspect=12345--inspect-brk=12345,并修改启动配置中的 port 属性以保持一致。

要附加到未以调试模式启动的 Node.js 进程,您可以将 Node.js 进程的进程 ID 指定为字符串:

{
  "name": "Attach to Process",
  "type": "node",
  "request": "attach",
  "processId": "53426"
}

为了避免在启动配置中重复输入新的进程 ID,Node 调试支持 PickProcess 命令变量,它将打开上述进程选择器。

使用 PickProcess 变量,启动配置如下所示:

{
  "name": "Attach to Process",
  "type": "node",
  "request": "attach",
  "processId": "${command:PickProcess}"
}

停止调试

使用 调试: 停止 (Debug: Stop) 操作(在调试工具栏中或通过命令面板提供)可停止调试会话。

如果调试会话以“附加”模式启动(调试工具栏中的红色终止按钮显示叠加的“插头”图标),按下 停止 将断开 Node.js 调试器与被调试程序的连接,随后程序会继续执行。

如果调试会话处于“启动 (launch)”模式,按下 停止 会执行以下操作:

  1. 当第一次按下 停止 时,通过发送 SIGINT 信号请求被调试程序优雅地关闭。被调试程序可以拦截此信号并根据需要清理资源,然后关闭。如果该关闭代码中没有断点(或问题),被调试程序和调试会话将终止。

  2. 但是,如果调试器在关闭代码中命中断点,或者被调试程序未能自行正确终止,则调试会话不会结束。在这种情况下,再次按下 停止 将强制终止被调试程序及其子进程 (SIGKILL)。

如果您发现按下红色 停止 按钮时调试会话没有结束,请再次按下该按钮以强制关闭被调试程序。

在 Windows 上,按下 停止 会强制杀死被调试程序及其子进程。

源映射 (Source maps)

VS Code 的 JavaScript 调试器支持源映射,有助于调试转译语言(例如 TypeScript 或最小化/混淆过的 JavaScript)。使用源映射,可以在原始源代码中进行单步执行或设置断点。如果原始源代码不存在源映射,或者源映射损坏且无法成功在源代码和生成的 JavaScript 之间进行映射,则断点会显示为未验证(灰色空心圆)。

默认为 truesourceMaps 属性用于控制源映射功能。调试器总是尝试使用源映射(如果能找到的话),因此您甚至可以在 program 属性中指定源文件(例如 app.ts)。如果由于某种原因需要禁用源映射,可以将 sourceMaps 属性设置为 false

工具配置

由于源映射并非总是自动创建的,您应确保配置转译器来创建它们。例如:

TypeScript

对于 TypeScript,您可以通过向 tsc 传递 --sourceMap,或在 tsconfig.json 文件中添加 "sourceMap": true 来启用源映射。

tsc --sourceMap --outDir bin app.ts

Babel

对于 Babel,您需要将 sourceMaps 选项设置为 true,或在编译代码时传递 --source-maps 选项。

npx babel script.js --out-file script-compiled.js --source-maps

Webpack

Webpack 有 许多 源映射选项。我们建议在 webpack.config.js 中设置属性 devtool: "source-map" 以获得最佳保真度,尽管您可以尝试其他设置(可能会导致构建变慢)。

此外,如果您的 Webpack 中有额外的编译步骤(例如使用 TypeScript 加载器),您还需要确保这些步骤也配置为生成源映射。否则,Webpack 生成的源映射将映射回加载器生成的已编译代码,而不是真正的源文件。

源映射发现

默认情况下,VS Code 将搜索您的整个工作区(不包括 node_modules)来寻找源映射。在大型工作区中,此搜索可能会很慢。您可以通过在 launch.json 中设置 outFiles 属性来配置 VS Code 搜索源映射的位置。例如,此配置仅发现 bin 文件夹中 .js 文件的源映射:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch TypeScript",
      "type": "node",
      "request": "launch",
      "program": "app.ts",
      "outFiles": ["${workspaceFolder}/bin/**/*.js"]
    }
  ]
}

请注意,outFiles 应匹配您的 JavaScript 文件,而不是源映射文件(后缀可能是 .map 而不是 .js)。

源映射解析

默认情况下,仅解析 outFiles 中的源映射。此行为旨在防止依赖项干扰您设置的断点。例如,如果您有一个文件 src/index.ts,而某个依赖项的源映射引用了 webpack:///./src/index.ts,则该源映射会错误地解析为您的源文件,并可能导致意外结果。

您可以通过设置 resolveSourceMapLocations 选项来配置此行为。如果设置为 null,则会解析每个源映射。例如,此配置将允许额外解析 node_modules/some-dependency 中的源映射:

  "resolveSourceMapLocations": [
    "out/**/*.js",
    "node_modules/some-dependency/**/*.js",
  ]

智能步进 (Smart stepping)

在启动配置中将 smartStep 属性设置为 true,VS Code 将在调试器步进代码时自动跳过“无关代码”。“无关代码”是指通过转译过程生成的代码,且没有被源映射覆盖,因此无法映射回原始源文件。当您在调试器中逐步执行源代码时,这些代码会阻碍您的操作,因为它们会让调试器在原始源代码和您不感兴趣的生成代码之间来回切换。smartStep 会自动步过未被源映射覆盖的代码,直到到达再次被源映射覆盖的位置。

智能步进对于 TypeScript 中的 async/await 降级编译等情况特别有用,因为编译器会注入未被源映射覆盖的辅助代码。

smartStep 功能仅适用于从源文件生成并因此具有源映射的 JavaScript 代码。对于没有源文件的 JavaScript,智能步进选项无效。

JavaScript 源映射提示

使用源映射调试时的一个常见问题是:设置断点后,断点变为灰色。如果将鼠标悬停在上面,您会看到以下消息:"Breakpoint ignored because generated code not found (source map problem?)"。现在该怎么办?导致此问题的因素很多。首先,简单解释一下 Node 调试适配器如何处理源映射。

当您在 app.ts 中设置断点时,调试适配器必须确定 app.js(您的 TypeScript 文件的转译版本,即 Node 中实际运行的版本)的路径。但是,从 .ts 文件出发并没有直接的方法来确定这一点。因此,调试适配器使用 launch.json 中的 outFiles 属性来查找所有已转译的 .js 文件,并对其进行解析以寻找包含其关联 .ts 文件位置的源映射。

当您在 TypeScript 中启用源映射构建 app.ts 文件时,它会生成一个 app.js.map 文件,或者将源映射作为 base64 编码的字符串内联在 app.js 文件底部的注释中。为了查找与此映射关联的 .ts 文件,调试适配器会查看源映射中的两个属性:sourcessourceRootsourceRoot 是可选的,如果存在,它会被预置到 sources 中的每个路径(路径数组)之前。结果是一个指向 .ts 文件的绝对或相对路径数组。相对路径相对于源映射进行解析。

最后,调试适配器会在该 .ts 文件列表中搜索 app.ts 的完整路径。如果有匹配项,则它找到了在将 app.ts 映射到 app.js 时使用的源映射文件。如果没有匹配项,则无法绑定断点,断点会变为灰色。

当您的断点变灰时,可以尝试以下操作:

  • 调试时,运行 调试: 诊断断点问题 (Debug: Diagnose Breakpoint Problems) 命令。此命令将从命令面板(⇧⌘P(Windows、Linux 为 Ctrl+Shift+P)启动一个工具,为您提供帮助解决问题的提示。
  • 您构建时是否启用了源映射?确保有 .js.map 文件,或者 .js 文件中存在内联源映射。
  • 源映射中的 sourceRootsources 属性是否正确?它们能否组合成正确的 .ts 文件路径?
  • 您打开文件夹时是否使用了错误的大小写?例如通过命令行以 code FOO 方式打开 foo/ 文件夹,这种情况下源映射可能无法正确解析。
  • 尝试在 Stack Overflow 上搜索有关您特定设置的帮助,或者在 GitHub 上提交问题。
  • 尝试添加一条 debugger 语句。如果在那里断入了 .ts 文件,但该位置的断点无法绑定,这是一个非常有用的信息,可以包含在 GitHub 问题中。

覆盖源映射路径

调试器使用 sourceMapPathOverrides 来实现自定义的源映射到磁盘路径映射。大多数工具都有合理的默认设置,但在高级情况下,您可能需要对其进行自定义。默认路径覆盖是一个如下所示的对象映射:

{
  'webpack:///./~/*': "${workspaceFolder}/node_modules/*",
  'webpack:////*': '/*',
  'webpack://@?:*/?:*/*': "${workspaceFolder}/*",
  // and some more patterns...
}

它将源映射中的路径或 URL 从左侧映射到右侧。模式 ?:* 是非贪婪非捕获匹配,而 * 是贪婪捕获匹配。随后,调试器会将右侧模式中相应的 * 替换为从源映射路径中捕获的片段。例如,上述示例中的最后一个模式会将 webpack://@my/package/foo/bar 映射到 ${workspaceFolder}/foo/bar

请注意,对于浏览器调试,默认的 sourceMapPathOverrides 中使用 webRoot 代替 workspaceFolder

远程调试

注意: VS Code 现在具备通用的 远程开发功能。使用 远程开发 (Remote Development) 扩展,远程场景和容器中的 Node.js 开发与本地设置没有任何区别。这是远程调试 Node.js 程序的推荐方式。请查看 入门 (Getting started) 章节和 远程教程 (Remote tutorials) 以了解更多信息。

如果您无法使用任何远程开发扩展来调试 Node.js 程序,下面是如何从本地 VS Code 实例调试远程 Node.js 程序的指南。

Node.js 调试器支持远程调试,您可以附加到在另一台机器或容器中运行的进程。通过 address 属性指定远程主机。例如:

{
  "type": "node",
  "request": "attach",
  "name": "Attach to remote",
  "address": "192.168.148.2", // <- remote address here
  "port": 9229
}

默认情况下,VS Code 会将调试的源文件从远程 Node.js 文件夹流式传输到本地 VS Code,并在只读编辑器中显示。您可以单步执行此代码,但不能修改它。如果您希望 VS Code 改为从您的工作区打开可编辑的源文件,您可以设置远程和本地位置之间的映射。localRootremoteRoot 属性可用于映射本地 VS Code 项目与(远程)Node.js 文件夹之间的路径。这在同一系统上甚至跨不同操作系统时都适用。当需要将代码路径从远程 Node.js 文件夹转换为本地 VS Code 路径时,remoteRoot 路径会从原路径中剥离并替换为 localRoot。反之,localRoot 路径会被替换为 remoteRoot

{
  "type": "node",
  "request": "attach",
  "name": "Attach to remote",
  "address": "TCP/IP address of process to be debugged",
  "port": 9229,
  "localRoot": "${workspaceFolder}",
  "remoteRoot": "C:\\Users\\username\\project\\server"
}

访问已加载的脚本

如果您需要在不属于工作区的脚本中设置断点,并且无法通过正常的 VS Code 文件浏览轻松定位和打开,则可以通过运行和调试 (Run and Debug) 视图中的已加载脚本 (LOADED SCRIPTS) 视图来访问它们。

Loaded Scripts Explorer

已加载脚本 (LOADED SCRIPTS) 视图允许您通过输入脚本名称快速选择脚本,或者在开启输入时启用过滤 (Enable Filter on Type) 时筛选列表。

脚本被加载到只读编辑器中,您可以在其中设置断点。这些断点会在调试会话之间保留,但您只能在调试会话运行时访问脚本内容。

编辑源代码时自动重启调试会话

启动配置的 restart 属性控制 Node.js 调试器在调试会话结束后是否自动重启。如果您使用 nodemon 在文件更改时重启 Node.js,此功能非常有用。将启动配置属性 restart 设置为 true,使 node 调试器在 Node.js 终止后自动尝试重新附加到 Node.js。

如果您已经在命令行中通过 nodemon 启动了程序 server.js,如下所示:

nodemon --inspect server.js

您可以使用以下启动配置将 VS Code 调试器附加到它:

{
  "name": "Attach to node",
  "type": "node",
  "request": "attach",
  "restart": true,
  "port": 9229
}

或者,您可以直接通过启动配置使用 nodemon 启动程序 server.js 并附加 VS Code 调试器:

{
  "name": "Launch server.js via nodemon",
  "type": "node",
  "request": "launch",
  "runtimeExecutable": "nodemon",
  "program": "${workspaceFolder}/server.js",
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}

提示: 按下 停止 按钮会停止调试会话并断开与 Node.js 的连接,但 nodemon(和 Node.js)将继续运行。要停止 nodemon,您必须从命令行将其终止(如果您像上面那样使用 integratedTerminal,这很容易做到)。

提示: 如果存在语法错误,nodemon 在错误修复前将无法成功启动 Node.js。在这种情况下,VS Code 会持续尝试附加到 Node.js,但最终会放弃(10 秒后)。为避免这种情况,您可以通过添加一个值更大(单位为毫秒)的 timeout 属性来增加超时时间。

重启帧 (Restart frame)

Node 调试器支持在堆栈帧处重新开始执行。当您在源代码中发现了问题并希望用修改后的输入值重新运行一小部分代码时,这很有用。停止然后重新启动整个调试会话可能很耗时。重启帧 (Restart Frame) 操作允许您在通过设置值 (Set Value) 操作更改变量后重新进入当前函数:

Restart frame

重启帧不会回滚函数外部状态的变异,因此它可能并不总是按预期工作。

Breakpoints

条件断点

条件断点是仅在表达式返回真值时才暂停的断点。您可以通过右键单击行号旁边的侧边栏并选择“条件断点 (Conditional Breakpoint)”来创建一个断点。

Conditional breakpoint

日志点

有时您只想在代码到达某个位置时记录消息或值,而不是暂停。您可以使用日志点 (logpoints) 来实现这一点。日志点不会暂停,而是在命中断点时将消息记录到调试控制台。在 JavaScript 调试器中,您可以使用花括号将表达式插入到消息中,例如 current value is: {myVariable.property}

您可以通过右键单击行号旁边的侧边栏并选择“日志点 (Logpoint)”来创建一个日志点。例如,这可能会记录类似于 location is /usr/local 的信息。

Logpoint

命中次数断点 (Hit count breakpoints)

“命中次数条件”控制断点在“中断”执行前需要被命中的次数。您可以通过右键单击行号旁边的侧边栏,选择“条件断点”,然后切换到“命中次数 (Hit Count)”来放置一个命中次数断点。

Hit count breakpoint

Node.js 调试器支持的命中次数语法是一个整数,或者是运算符 <<===>>=% 后跟一个整数。

一些示例

  • >10 在 10 次命中后总是中断
  • <3 仅在前两次命中时中断
  • 10>=10 相同
  • %2 每隔一次命中中断一次

触发断点

触发断点是一种在另一个断点命中后自动启用的断点。当代码中出现仅在特定先决条件发生后才出现的故障情况时,它们非常有用。

可以通过右键单击字形边距,选择 添加触发断点,然后选择哪个其他断点启用此断点来设置触发断点。

断点验证

出于性能原因,Node.js 在首次访问时会延迟解析 JavaScript 文件中的函数。因此,断点在 Node.js 尚未看到(解析)的源代码区域中不起作用。

由于这种行为对于调试来说并不理想,VS Code 会自动向 Node.js 传递 --nolazy 选项。这可以防止延迟解析,并确保在运行代码前可以验证断点(因此它们不会再“跳动”)。

由于 --nolazy 选项可能会显着增加调试目标的启动时间,您可以通过将 --lazy 作为 runtimeArgs 属性传递来轻松选择退出。

执行此操作后,您会发现一些断点不会“粘”在请求的行上,而是“跳”到已解析代码中的下一行。为避免混淆,VS Code 总是会在 Node.js 认为断点所在的位置显示断点。在 断点 (BREAKPOINTS) 部分,这些断点显示为请求行号和实际行号之间的箭头:

Breakpoints View

这种断点验证发生在会话启动且断点向 Node.js 注册时,或者在会话正在运行且设置了新断点时。在这种情况下,断点可能会“跳”到不同的位置。在 Node.js 解析完所有代码(例如通过运行代码)后,可以使用 断点 (BREAKPOINTS) 部分标题中的 重新应用 (Reapply) 按钮将断点轻松地重新应用到请求的位置。这应该使断点“跳回”到请求的位置。

Breakpoint Actions

跳过无关代码

VS Code Node.js 调试具有一项避免进入您不想步进的源代码的功能(也称为“仅我的代码”)。可以通过启动配置中的 skipFiles 属性启用此功能。skipFiles 是用于跳过的脚本路径的 glob 模式 数组。

例如,使用:

  "skipFiles": [
    "${workspaceFolder}/node_modules/**/*.js",
    "${workspaceFolder}/lib/**/*.js"
  ]

项目中 node_moduleslib 文件夹中的所有代码都将被跳过。skipFiles 也适用于调用 console.log 和类似方法时显示的位置:第一个未跳过的堆栈位置将显示在调试控制台中的输出旁边。

Node.js 的内置 核心模块glob 模式 中可以通过“魔术名称” <node_internals> 来引用。以下示例跳过了所有内部模块:

  "skipFiles": [
     "<node_internals>/**/*.js"
   ]

确切的“跳过”规则如下:

  • 如果您步入已跳过的文件,则不会停在那里,而是会在下一个不在已跳过文件中的执行行停下。
  • 如果您已设置在抛出异常时中断的选项,则除非异常冒泡到非跳过的文件中,否则不会在已跳过文件中抛出的异常处中断。
  • 如果您在已跳过的文件中设置了断点,则会在该断点处停止,并且可以一直步进,直到步出该文件,此时将恢复正常的跳过行为。
  • 来自跳过文件内部的控制台消息的位置将显示为调用堆栈中的第一个非跳过位置。

已跳过的源代码在调用堆栈视图中以“变暗”样式显示。

Skipped source is dimmed in call stack view

将鼠标悬停在变暗的条目上可以解释堆栈帧为何变暗。

调用堆栈上的上下文菜单项 切换跳过此文件 (Toggle skipping this file) 使您能够轻松在运行时跳过文件,而无需将其添加到启动配置中。此选项仅在当前调试会话期间保留。您也可以使用它来停止跳过一个被启动配置中 skipFiles 选项跳过的文件。

注意: legacy 协议调试器支持负向 glob 模式,但它们必须跟在正向模式之后:正向模式添加到已跳过文件集,而负向模式从该集合中减去。

在以下(仅限 legacy 协议)示例中,除 'math' 模块外,所有内容都被跳过:

"skipFiles": [
    "${workspaceFolder}/node_modules/**/*.js",
    "!${workspaceFolder}/node_modules/math/**/*.js"
]

注意: legacy 协议调试器必须模拟 skipFiles 功能,因为 V8 调试器协议 本身不支持它。这可能会导致步进性能变慢。

调试 WebAssembly

如果编译后的 WebAssembly 代码包含 DWARF 调试信息,JavaScript 调试器就可以对其进行调试。许多工具链都支持发出此信息:

  • C/C++ 与 Emscripten:使用 -g 标志进行编译以发出调试信息。
  • Zig:在“Debug”构建模式下会自动发出 DWARF 信息。
  • Rust:Rust 会发出 DWARF 调试信息。然而,wasm-pack 目前尚未在构建期间保留它。因此,常见 wasm-bindgen/wasm-pack 库的用户不应运行 wasm-pack build,而应使用两个命令手动构建:
    1. 运行 cargo install wasm-bindgen-cli 一次以安装必要的命令行工具。
    2. 运行 cargo build --target wasm32-unknown-unknown 来构建您的库。
    3. 运行 wasm-bindgen --keep-debug --out-dir pkg ./target/wasm32-unknown-unknown/debug/<library-name>.wasm <extra-arguments> 以生成 WebAssembly 绑定,将 <library-name> 替换为 Cargo.toml 中的名称,并根据需要配置 <extra-arguments>

构建代码后,您需要安装 WebAssembly DWARF 调试 (WebAssembly DWARF Debugging) 扩展。它作为单独的扩展提供,以保持 VS Code 核心的“精简”。安装后,重启任何活动的调试会话,本地代码应能在调试器中进行映射!您应该会在 已加载源 (Loaded Sources) 视图中看到源代码,并且断点应该能起作用。

在下图中,调试器停在创建 Mandelbrot 分形的 C++ 源代码断点处。调用堆栈清晰可见,包含了从 JavaScript 代码到 WebAssembly,再到已映射 C++ 代码的帧。您还可以看到 C++ 代码中的变量,以及对与 int32 height 变量关联的内存的编辑。

Debugger stopped on a breakpoint in C++ source code

虽然接近同等功能,但调试 WebAssembly 与普通 JavaScript 略有不同:

  • 变量 (Variables) 视图中的变量不能直接编辑。但是,您可以选择变量旁边的 查看二进制数据 (View Binary Data) 操作来编辑其关联的内存。
  • 调试控制台 (Debug Console)监视 (Watch) 视图中的基本表达式求值由 lldb-eval 提供。这与普通 JavaScript 表达式不同。
  • 未映射到源代码的位置将以反汇编的 WebAssembly 文本格式显示。对于 WebAssembly,禁用源映射步进 (Disable Source Map Stepping) 命令将导致调试器仅在反汇编代码中步进。

VS Code 的 WebAssembly 调试基于 Chromium 作者提供的 C/C++ 调试扩展

支持的类 Node.js 运行时

当前的 VS Code JavaScript 调试器支持 8.x 或更高版本的 Node、较新的 Chrome 版本以及较新的 Edge 版本(通过 msedge 启动类型)。

后续步骤

如果您还没有阅读 Node.js 章节,请看看:

  • Node.js - 带有示例应用程序的端到端 Node 场景

要查看关于 VS Code 调试基础知识的教程,请查看此视频:

要了解 VS Code 的任务运行支持,请转到:

  • 任务 (Tasks) - 使用 Gulp、Grunt 和 Jake 运行任务。显示错误和警告

要编写自己的调试器扩展,请访问:

常见问题

可以。如果您为项目内的文件夹创建了符号链接(例如使用 npm link),则可以通过让 Node.js 运行时保留符号链接路径来调试这些链接源。在启动配置的 runtimeArgs 属性中使用 node.exe 的 --preserve-symlinks 开关runtimeArgs 是一个字符串数组,会传递给调试会话的运行时可执行文件(默认为 node.exe)。

{
  "runtimeArgs": ["--preserve-symlinks"]
}

如果您的主脚本位于符号链接路径内,那么您还需要添加 "--preserve-symlinks-main" 选项。此选项仅在 Node 10+ 中可用。

如何调试 ECMAScript 模块?

如果您使用 esm,或者为了使用 ECMAScript 模块向 Node.js 传递 --experimental-modules,可以通过 launch.jsonruntimeArgs 属性传递这些选项:

如何设置 NODE_OPTIONS?

调试器使用特殊的 NODE_OPTIONS 环境变量来设置应用程序的调试功能,覆盖它将阻止调试正常工作。您应该在其中追加内容,而不是覆盖它。例如,.bashrc 文件可能包含如下内容:

export NODE_OPTIONS="$NODE_OPTIONS --some-other-option=here"
© . This site is unofficial and not affiliated with Microsoft.