在 VS Code 中试用

在 VS Code 中调试 Node.js

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

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

自动附加

如果启用了自动附加功能,Node 调试器将自动附加到从 VS Code 的集成终端启动的某些 Node.js 进程。要启用该功能,可以使用命令面板(⇧⌘P (Windows, Linux Ctrl+Shift+P))中的切换自动附加命令,或者,如果它已经激活,使用状态栏中的自动附加项。

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

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

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

Auto Attach

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

附加配置

其他启动配置属性

您可以在 debug.javascript.terminalOptions 设置中,将 通常在 launch.json 中找到的其他属性应用于自动附加。例如,要将 Node.js 内部文件添加到您的 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 调试终端 命令,或从终端切换器下拉菜单中选择创建 JavaScript 调试终端 来创建一个调试终端。

Create Debug Terminal

附加配置

其他启动配置属性

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

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

启动配置

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

在本节中,我们将更详细地介绍更高级调试场景的配置和功能。您将找到使用源映射调试、跳过外部代码、进行远程调试等的说明。

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

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

启动配置属性

调试配置存储在您工作区 .vscode 文件夹中的 launch.json 文件中。关于创建和使用调试配置文件的介绍可以在通用调试文章中找到。

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

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

  • outFiles - 用于定位生成的 JavaScript 文件的glob 模式数组。参见源映射一节。
  • resolveSourceMapLocations - 应该解析源映射的位置的glob 模式数组。参见源映射一节。
  • timeout - 重新启动会话时,在此毫秒数后放弃。参见附加到 Node.js 一节。
  • stopOnEntry - 程序启动时立即中断。
  • localRoot - VS Code 的根目录。参见下面的远程调试一节。
  • remoteRoot - Node 的根目录。参见下面的远程调试一节。
  • smartStep - 尝试自动跳过不映射到源文件的代码。参见智能步进一节。
  • skipFiles - 自动跳过这些glob 模式覆盖的文件。参见跳过不相关的代码一节。
  • trace - 启用诊断输出。

这些属性仅适用于请求类型为 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 button

提供以下代码片段:

  • 启动程序:以调试模式启动 Node.js 程序。
  • 通过 npm 启动:通过 npm 'debug' 脚本启动 Node.js 程序。如果您的 package.json 中定义了 npm debug 脚本,则可以从您的启动配置中使用它。npm 脚本中使用的调试端口必须与代码片段中指定的端口一致。
  • 附加:附加到本地运行的 Node.js 程序的调试端口。请确保要调试的 Node.js 程序已以调试模式启动,并且使用的调试端口与代码片段中指定的端口相同。
  • 附加到远程程序:附加到在 address 属性指定的宿主机上运行的 Node.js 程序的调试端口。请确保要调试的 Node.js 程序已以调试模式启动,并且使用的调试端口与代码片段中指定的端口相同。为了帮助 VS Code 在您的工作区和远程宿主机的 文件系统 之间映射源文件,请确保为 localRootremoteRoot 属性指定正确的路径。
  • 按进程 ID 附加:打开进程选择器,选择一个 node 或 gulp 进程进行调试。通过此启动配置,您甚至可以附加到未以调试模式启动的 node 或 gulp 进程。
  • Nodemon 设置:使用 nodemon 在 JavaScript 源代码更改时自动重新启动调试会话。请确保已全局安装 nodemon。请注意,终止调试会话仅终止要调试的程序,而不是 nodemon 本身。要终止 nodemon,请在集成终端中按Ctrl+C
  • Mocha 测试:调试项目中 test 文件夹中的 mocha 测试。请确保您的项目已在其 node_modules 文件夹中安装了 'mocha'。
  • Yeoman 生成器:调试 Yeoman 生成器。该代码片段会要求您指定生成器的名称。请确保您的项目已在其 node_modules 文件夹中安装了 'yo',并且已通过在项目文件夹中运行 npm link 来安装您的生成项目以进行调试。
  • Gulp 任务:调试 Gulp 任务。请确保您的项目已在其 node_modules 文件夹中安装了 'gulp'。
  • Electron 主进程:调试 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' 和其他工具的启动配置支持

您可以使用 'npm' 脚本或其他任务运行器工具直接从启动配置启动,而无需直接使用 node 启动 Node.js 程序:

  • 您可以将 PATH 上的任何程序(例如 'npm'、'mocha'、'gulp' 等)用于 runtimeExecutable 属性,并通过 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

附加调试器到您的程序的选项:

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

让我们详细介绍这些选项:

附加到 Node 进程操作

命令面板(⇧⌘P (Windows, Linux Ctrl+Shift+P))中的附加到 Node 进程 命令会打开一个快速选择菜单,列出 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}"
}

停止调试

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

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

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

  1. 第一次按下停止时,通过发送 SIGINT 信号请求被调试程序正常关闭。被调试程序可以自由拦截此信号,并在必要时清理任何内容,然后关闭。如果在该关闭代码中没有断点(或问题),被调试程序和调试会话将终止。

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

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

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

源映射

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

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

工具配置

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

TypeScript

对于 TypeScript,您可以通过将 --sourceMap 传递给 tsc,或在您的 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",
  ]

智能步进

在启动配置中将 smartStep 属性设置为 true 时,VS Code 会在调试器中单步执行代码时自动跳过“不感兴趣的代码”。“不感兴趣的代码”是指由转译过程生成但未被源映射覆盖的代码,因此无法映射回原始源。当你在调试器中单步执行源代码时,这类代码会妨碍你,因为它会使调试器在原始源代码和你不需要关注的生成代码之间切换。smartStep 会自动单步跳过没有源映射覆盖的代码,直到它再次到达一个被源映射覆盖的位置。

智能单步调试对于 TypeScript 中 async/await 的向下编译等情况特别有用,在这种情况下,编译器会注入未被源映射覆盖的帮助器代码。

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

JavaScript 源映射技巧

使用源映射调试时的一个常见问题是,当你设置断点时,它会变成灰色。将光标悬停在其上时,你会看到消息 "忽略断点,因为未找到生成的代码(源映射问题?)"。现在怎么办?有很多可能导致这种情况的问题。首先,快速解释一下 Node 调试适配器如何处理源映射。

当你在 app.ts 中设置断点时,调试适配器必须找出 app.js(你的 TypeScript 文件的转译版本)的路径,这才是 Node 中实际运行的内容。但是,从 .ts 文件开始并没有直接的方法可以解决这个问题。相反,调试适配器使用 launch.json 中的 outFiles 属性来查找所有转译的 .js 文件,并解析它们以查找源映射,源映射包含其关联的 .ts 文件的位置。

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

最后,调试适配器在此结果 .ts 文件列表中搜索 app.ts 的完整路径。如果匹配,则表示找到了用于将 app.ts 映射到 app.js 的源映射文件。如果不匹配,则无法绑定断点,断点会变成灰色。

当你的断点变成灰色时,可以尝试以下一些方法

  • 调试时,运行 调试:诊断断点问题 命令。此命令会打开一个工具,可以提供提示来帮助你解决来自命令面板 (⇧⌘P (Windows、Linux Ctrl+Shift-P)) 的任何问题。
  • 你构建时是否启用了源映射?确保存在 .js.map 文件,或在你的 .js 文件中有内联的源映射。
  • 你的源映射中的 sourceRootsources 属性是否正确?它们是否可以组合以获得正确的 .ts 文件路径?
  • 你在 VS Code 中打开文件夹时是否使用了不正确的字母大小写?可以从命令行打开文件夹 foo/,例如 code 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 现在拥有通用的 远程开发能力。使用 远程开发 扩展,在远程场景和容器中的 Node.js 开发与本地设置中的 Node.js 开发没有区别。这是远程调试 Node.js 程序的推荐方法。查看 入门 部分和 远程教程 了解更多信息。

如果你无法使用任何远程开发扩展来调试 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 文件浏览找到并打开的脚本中设置断点,你可以通过 运行和调试 视图中的 已加载脚本 视图访问已加载的脚本

Loaded Scripts Explorer

已加载脚本 视图允许你通过键入其名称快速选择脚本,或在 启用键入时过滤 开启时过滤列表。

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

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

启动配置的 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 属性来增加超时时间。

重启帧

Node 调试器支持在堆栈帧重新启动执行。这在你发现了源代码中的问题并想使用修改后的输入值重新运行一小部分代码的情况下非常有用。停止然后重新启动完整的调试会话可能很耗时。重新启动帧 操作允许你在使用 设置值 操作更改变量后重新进入当前函数

Restart frame

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

断点

条件断点

条件断点是仅当表达式返回真值时才暂停的断点。你可以通过右键单击行号旁边的边距并选择“条件断点”来创建一个

Conditional breakpoint

日志点

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

你可以通过右键单击行号旁边的边距并选择“日志点”来创建一个。例如,这可能会记录类似 location is /usr/local 的内容。

Logpoint

命中计数断点

“命中计数条件”控制断点需要在中断执行之前被命中多少次。你可以通过右键单击行号旁边的边距,选择“条件断点”,然后切换到“命中计数”来设置命中计数断点。

Hit count breakpoint

Node.js 调试器支持的命中计数语法是一个整数,或后跟一个整数的以下运算符之一:<, <=, ==, >, >=, %

一些示例:

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

触发的断点

触发的断点是一种在命中另一个断点后自动启用的断点。在诊断仅在满足特定前置条件后才会发生的代码中的失败情况时,它们非常有用。

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

断点验证

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

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

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

当这样做时,你会发现你的一些断点不会“粘滞”到请求的行,而是会“跳跃”到已解析代码中下一个可能的行。为了避免混淆,VS Code 始终在 Node.js 认为断点所在的位置显示断点。在 断点 部分中,这些断点会在请求行号和实际行号之间带有箭头显示

Breakpoints View

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

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"
   ]

具体的“跳过”规则如下:

  • 如果你单步进入一个跳过的文件,你不会在那里停止 - 你会在不在跳过文件中的下一条执行行上停止。
  • 如果你设置了在抛出的异常处断点的选项,那么你不会在从跳过文件抛出的异常处断点,除非这些异常冒泡到未跳过的文件中。
  • 如果你在跳过文件中设置断点,你会在该断点处停止,并且能够单步执行它,直到你单步跳出它,此时正常的跳过行为将恢复。
  • 来自跳过文件内部的 console 消息的位置将显示为调用堆栈中第一个未跳过的位置。

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

Skipped source is dimmed in call stack view

将鼠标悬停在变暗的条目上会解释该堆栈帧变暗的原因。

调用堆栈上的一个上下文菜单项,切换跳过此文件,使你可以在运行时轻松跳过一个文件,而无需将其添加到你的启动配置中。此选项仅在当前调试会话期间持续有效。你也可以使用它来停止跳过在你的启动配置中由 skipFiles 选项跳过的文件。

注意: legacy 协议调试器支持否定的 glob 模式,但它们必须 跟随 一个肯定的模式:肯定的模式将文件添加到跳过集合中,而否定的模式从该集合中减去文件。

在以下(仅 legacy 协议)示例中,除了“math”模块外,其他所有模块都被跳过:

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

注意: legacy 协议调试器必须模拟 skipFiles 功能,因为 V8 调试器协议 不原生支持它。这可能会导致单步执行性能缓慢。

调试 WebAssembly

如果包含 DWARF 调试信息,JavaScript 调试器可以调试编译为 WebAssembly 的代码。许多工具链支持生成此信息:

  • 使用 Emscripten 的 C/C++:使用 -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 Debugging 扩展。这作为一个单独的扩展发布,以保持 VS Code 核心的“精简”。安装完成后,重启所有活动的调试会话,然后原生代码应该可以在调试器中被映射!你应该会看到你的源代码出现在 已加载的源 视图中,并且断点应该会工作。

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

Debugger stopped on a breakpoint in C++ source code

尽管功能接近,但调试 WebAssembly 与普通 JavaScript 有点不同:

  • 变量 视图中的变量无法直接编辑。然而,你可以选择变量旁边的 查看二进制数据 操作来编辑它们关联的内存。
  • 调试控制台监视 视图中的基本表达式求值由 lldb-eval 提供。这与普通的 JavaScript 表达式不同。
  • 未映射到源代码的位置将以反汇编的 WebAssembly 文本格式显示。对于 WebAssembly,命令 禁用源映射单步调试 将使调试器仅在反汇编的代码中单步执行。

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

支持的类 Node 运行时

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

下一步

如果你还没有阅读 Node.js 部分,请查看:

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

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

要了解 VS Code 的任务运行支持,请前往:

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

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

  • 调试器扩展 - 从模拟示例开始创建 VS Code 调试扩展的步骤

常见问题

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

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

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

如何调试 ECMAScript 模块?

如果你使用 esm 或将 --experimental-modules 传递给 Node.js 以使用 ECMAScript 模块,可以通过 launch.jsonruntimeArgs 属性传递这些选项:

如何设置 NODE_OPTIONS?

调试器使用特殊的 NODE_OPTIONS 环境变量来设置你的应用程序的调试,覆盖它将阻止调试正常工作。你应该附加到它而不是覆盖它。例如,一个 .bashrc 文件可能包含类似如下内容:

export NODE_OPTIONS="$NODE_OPTIONS --some-other-option=here"