在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 窗口底部的状态栏中。点击它可以更改自动附加模式,或暂时关闭它。如果你正在运行一些不需要调试的一次性程序,但又不想完全禁用该功能,暂时关闭自动附加功能会很有用。

额外配置

其他启动配置属性

您可以将通常在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进程。您可以通过从命令面板运行调试:创建JavaScript调试终端命令(kbs(workbench.action.showCommands)),或从终端切换器下拉菜单中选择创建JavaScript调试终端来创建调试终端。

创建调试终端

额外配置

其他启动配置属性

您可以在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 - 当重新启动会话时,在此毫秒数后放弃。参见章节 Attaching to Node.js
  • stopOnEntry - 程序启动时立即中断。
  • localRoot - VS Code 的根目录。请参阅下面的远程调试部分。
  • remoteRoot - Node的根目录。请参阅下面的远程调试部分。
  • smartStep - 尝试自动跳过不映射到源文件的代码。参见章节 智能步进
  • skipFiles - 自动跳过由这些glob模式覆盖的文件。参见章节跳过不感兴趣的代码
  • trace - 启用诊断输出。

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

  • program - 要调试的 Node.js 程序的绝对路径。
  • args - 传递给程序进行调试的参数。此属性为数组类型,并期望将各个参数作为数组元素。
  • cwd - 在此目录中启动要调试的程序。
  • runtimeExecutable - 要使用的运行时可执行文件的绝对路径。默认为 node。请参阅章节 Launch configuration support for 'npm' and other tools
  • runtimeArgs - 传递给运行时可执行文件的可选参数。
  • runtimeVersion - 如果使用"nvm"(或"nvm-windows")或"nvs"来管理Node.js版本,此属性可用于选择特定版本的Node.js。请参阅下面的多版本支持部分。
  • env - 可选的环境变量。此属性期望环境变量作为字符串类型的键/值对列表。
  • envFile - 可选路径,指向包含环境变量定义的文件。请参阅下面的从外部文件加载环境变量部分。
  • console - 用于启动程序的控制台(internalConsole, integratedTerminal, externalTerminal)。请参阅下面的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调试场景的启动配置片段。

Node.js的启动配置片段

你也可以通过launch.json编辑器窗口右下角的添加配置...按钮来调出代码片段。

添加配置按钮

以下代码片段可用:

  • 启动程序: 在调试模式下启动一个Node.js程序。
  • 通过 npm 启动: 通过 npm 的 'debug' 脚本启动一个 Node.js 程序。如果您的 package.json 中定义了 npm 调试脚本,您可以从启动配置中使用它。npm 脚本中使用的调试端口必须与代码片段中指定的端口相对应。
  • 附加: 附加到本地运行的Node.js程序的调试端口。确保要调试的Node.js程序已在调试模式下启动,并且使用的调试端口与代码片段中指定的端口相同。
  • 附加到远程程序:附加到由address属性指定的主机上运行的Node.js程序的调试端口。确保要调试的Node.js程序已在调试模式下启动,并且使用的调试端口与代码片段中指定的端口相同。为了帮助VS Code在工作区和远程主机的文件系统之间映射源文件,请确保为localRootremoteRoot属性指定正确的路径。
  • 通过进程ID附加:打开进程选择器以选择要调试的节点或gulp进程。使用此启动配置,您甚至可以附加到未在调试模式下启动的节点或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' 和其他工具的启动配置支持

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

  • 您可以使用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

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

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

让我们详细看看这些选项:

附加到节点进程操作

从命令面板中选择附加到节点进程命令(⇧⌘P (Windows, Linux Ctrl+Shift+P))会打开一个快速选择菜单,列出Node.js调试器可用的所有潜在进程:

Node.js 进程选择器

选择器中列出的各个进程显示调试端口和进程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 之间进行映射,则断点将显示为未验证(灰色空心圆圈)。

sourceMaps 属性默认为 true,用于控制源映射功能。调试器总是尝试使用源映射(如果它能找到任何源映射),因此,你甚至可以使用 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",
  ]

智能步进

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

智能步进对于像TypeScript中的异步/等待向下编译这样的情况特别有用,在这种情况下,编译器会注入一些不被源映射覆盖的辅助代码。

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

JavaScript 源映射技巧

在使用源映射进行调试时,一个常见的问题是您设置了一个断点,但它会变成灰色。如果将光标悬停在它上面,您会看到消息"由于未找到生成的代码,断点被忽略(源映射问题?)"。现在该怎么办?有许多问题可能导致这种情况。首先,快速解释一下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中的每个路径前,sources是一个路径数组。结果是一个指向.ts文件的绝对或相对路径数组。相对路径是相对于源映射解析的。

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

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

  • 在调试时,运行调试:诊断断点问题命令。此命令将打开一个工具,可以提供提示以帮助您解决命令面板中的任何问题(⇧⌘P (Windows, Linux Ctrl+Shift+P))。
  • 你是否启用了源映射构建?确保你的.js文件中有.js.map文件或内联的源映射。
  • 你的源映射中的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 视图允许您通过输入脚本名称快速选择脚本,或在启用输入时过滤开启时过滤列表。

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

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

启动配置中的restart属性控制Node.js调试器在调试会话结束后是否自动重启。如果您使用nodemon在文件更改时重启Node.js,此功能非常有用。将启动配置属性restart设置为true会使节点调试器在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调试器支持在堆栈帧处重新启动执行。这在您发现源代码中的问题并希望使用修改后的输入值重新运行一小部分代码时非常有用。停止然后重新启动完整的调试会话可能非常耗时。重新启动帧操作允许您在通过设置值操作更改变量后重新进入当前函数:

重新启动帧

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

断点

条件断点

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

条件断点

日志点

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

您可以通过在行号旁边的装订线中右键单击并选择“Logpoint”来创建一个。例如,这可能会记录类似location is /usr/local的内容:

Logpoint

命中计数断点

'命中计数条件'控制断点在被触发多少次后才会中断执行。您可以通过在行号旁边的装订线中右键单击,选择“条件断点”,然后切换到“命中计数”来设置命中计数断点。

命中计数断点

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 部分中,这些断点会在请求的行号和实际行号之间显示一个箭头:

断点视图

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

断点操作

跳过不感兴趣的代码

VS Code 的 Node.js 调试功能有一个特性,可以避免进入你不想逐步执行的源代码(也称为“仅我的代码”)。这个功能可以通过在你的启动配置中使用 skipFiles 属性来启用。skipFiles 是一个包含 glob 模式的数组,用于指定要跳过的脚本路径。

例如,使用:

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

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

Node.js 的内置核心模块可以通过在glob 模式中使用 'magic name' 来引用。以下示例跳过了所有内部模块:

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

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

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

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

在调用堆栈视图中跳过的源被调暗

悬停在变暗的条目上可以解释为什么堆栈框架变暗。

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

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

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

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

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

调试WebAssembly

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

  • C/C++ with 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/.wasm 来生成 WebAssembly 绑定,将 替换为你的 Cargo.toml 中的名称,并根据需要配置

在构建完代码后,您需要安装WebAssembly DWARF Debugging扩展。这个扩展是作为一个独立的扩展发布的,以保持VS Code核心的“精简”。安装完成后,重新启动任何活动的调试会话,原生代码应该在调试器中被映射!您应该会在已加载的源视图中看到您的源代码,并且断点应该可以正常工作。

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

调试器在C++源代码的断点处停止

虽然接近对等,调试WebAssembly与普通的JavaScript有些不同:

  • Variables 视图中的变量无法直接编辑。但是,您可以选择变量旁边的 View Binary Data 操作来编辑其关联的内存。
  • 调试控制台监视视图中提供的基本表达式评估由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的任务运行支持,请访问:

  • 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或向Node.js传递--experimental-modules以使用ECMAScript模块,你可以通过launch.jsonruntimeArgs属性传递这些选项:

如何设置NODE_OPTIONS?

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

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