ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VS Code 调试 TypeScript 断点不生效?source map 配置与排查指南

VS Code 调试 TypeScript 断点不生效?source map 配置与排查指南 简介这份资源面向希望在 Visual Studio Code 中高效调试 TypeScript 的开发者尤其适合刚接触 TS 调试配置、需要打通断点与源码映射环节的初中级前端或 Node.js 工程师。内容围绕 tsconfig.json 编译选项、.vscode/launch.json 调试配置、断点设置、变量监视以及 Chrome 前端调试等核心场景展开帮助读者理解 sourceMap 在源码级调试中的作用。资源包共 10 个文件以 5 个 json 配置与依赖清单、2 个 ts 示例源码为主另含 readme 说明、LICENSE 与 .gitignore整体约 5KB结构轻量可直接作为调试配置模板参考。目前已有 1979 人学习下载。通过其中的示例工程与配置读者能快速对照搭建可运行的调试环境掌握断点、调用栈、作用域查看与 Console 执行等实用技巧并迁移到自己的本地应用或 Web 项目中。1. 在 VS Code 里调试 TypeScript为什么你的断点总是灰的很多人第一次在 VS Code 里给 TypeScript 打断点看到的是一个空心灰圈鼠标悬停提示 “Breakpoint set but not yet bound”。代码明明在跑日志也照常输出断点就是不生效。这不是玄学是调试器根本没把编译后的 JavaScript 和你的.ts源文件对上号。这个标题要解决的就是这件事让 VS Code 的调试器真正停在你的 TypeScript 源码行上能看变量、能单步、能看调用栈。它适合两类人——刚把项目从 JS 迁到 TS、发现console.log已经不够用的开发者以及接手了一个 TS 工程、想搞清楚 launch.json 里那一堆字段到底在干什么的人。核心链路只有一条VS Code 通过调试适配器启动 NodeNode 执行编译产物source map 把执行位置映射回.ts。这条链上任何一环断了断点就灰。下面按“先跑通最小例子再拆配置再排坑”的顺序讲。2. 先跑通一个最小可调试的 TypeScript 工程2.1 三个文件就能验证整条链路不要一上来就在大项目里调先建一个空目录只放三个文件。这样出问题时变量最少能快速定位是配置问题还是工程问题。mkdir ts-debug-demo cd ts-debug-demo npm init -y npm install --save-dev typescript ts-node types/node npx tsc --inittsc --init会生成tsconfig.json。默认生成的配置里sourceMap是注释掉的这是后面断点变灰的头号原因先手动打开。接着建源码文件// src/index.ts interface User { id: number; name: string; } function greet(user: User): string { const label user-${user.id}; // 在这行打断点 return ${label}: ${user.name}; } const u: User { id: 7, name: A同学 }; console.log(greet(u));然后在.vscode/launch.json里写第一版配置{ version: 0.2.0, configurations: [ { name: Debug TS (ts-node), type: node, request: launch, runtimeArgs: [-r, ts-node/register], args: [${workspaceFolder}/src/index.ts], cwd: ${workspaceFolder}, sourceMaps: true, skipFiles: [node_internals/**] } ] }按 F5程序应该跑起来并在const label那行停住。如果停住了说明链路是通的可以往下看配置细节如果没停先检查tsconfig.json里sourceMap是否为true这是最高频的原因。2.2 ts-node 方案和预编译方案怎么选上面用的是ts-node/register它在运行时即时编译 TS省去了手动tsc的步骤。优点是改完代码直接 F5适合开发期快速迭代。缺点是启动稍慢且某些依赖原生模块或特殊 loader 的场景会出兼容问题。另一种常见做法是先编译再调试配置长这样{ name: Debug TS (compiled), type: node, request: launch, program: ${workspaceFolder}/dist/index.js, preLaunchTask: tsc: build, outFiles: [${workspaceFolder}/dist/**/*.js], sourceMaps: true }preLaunchTask指向.vscode/tasks.json里定义的一个 tsc 构建任务每次 F5 前自动编译。outFiles告诉调试器去哪里找编译产物和对应的.map文件这个字段不写source map 可能加载不上。选哪个我的习惯是日常开发用 ts-node省事需要调试构建产物行为、或者项目本身构建流程复杂比如有 bundler时用预编译方案因为它更接近真实运行环境。两者不要混用混用容易出现“源码改了但调试的是旧产物”的翻车现场。2.3 tsconfig 里和调试强相关的四个字段tsconfig.json字段很多但直接影响调试的其实就几个配错了断点必灰。字段推荐值作用配错的后果sourceMaptrue生成 .js.map 映射文件断点无法绑定到 .tsoutDir./dist编译产物目录产物和源码混在一起outFiles 难写inlineSourcestrue把源码内容嵌进 map调试时看不到源码只能看编译结果rootDir./src源码根目录map 里的路径错位映射失败inlineSources容易被忽略。默认情况下 source map 只记录路径调试器需要自己去磁盘读.ts文件。如果路径对不上比如构建机器和本地目录结构不同就看不到源码。打开inlineSources后源码内容直接写进 map路径问题就绕过去了。3. launch.json 里那几个必调参数到底在干什么3.1 type、request、program 三件套type: node表示用 Node 调试适配器这是内置的不需要额外装扩展。request有两个值launch是自己启动一个进程来调试attach是连到一个已经在跑的进程。新手先用launchattach后面讲。program指向入口 JS 文件。用 ts-node 方案时它不写program而是用runtimeArgs加args组合用预编译方案时program指向dist/index.js。这里有个常见误解有人把program直接指向.ts文件然后不加 ts-node结果 Node 报语法错误——Node 本身不认识 TS 语法必须有个编译环节。3.2 sourceMaps 和 outFiles 的配合关系sourceMaps: true是开关outFiles是告诉调试器去哪里扫描.map文件。用 ts-node 时编译在内存里发生map 也是即时的所以outFiles可以不写。用预编译方案时outFiles必须写而且 glob 要能匹配到实际的.js文件。outFiles: [${workspaceFolder}/dist/**/*.js]如果产物目录是build而不是dist这里要同步改。outFiles写错的表现是程序能跑断点也能停但停的位置错位——比如你打在.ts第 10 行它停在编译后.js的第 8 行。这就是 map 没被正确关联调试器退回到了按 JS 行号定位。3.3 skipFiles 和 smartStep 减少无谓单步调试时最烦的是单步进入 Node 内部或第三方库。skipFiles用 glob 指定跳过的文件skipFiles: [ node_internals/**, ${workspaceFolder}/node_modules/** ]node_internals/**是内置变量代表 Node 核心模块。加上这两条后F11 单步不会跳进fs.js或某个 npm 包的源码里。smartStep: true是另一个省事开关它会自动跳过那些没有对应 source map 的编译代码。比如你用了一个编译过的第三方库单步进去只能看到编译结果smartStep会直接跨过去。这两个参数不配也能调试但配上之后体验差别很大属于“用过就回不去”的那类。4. 断点不生效、映射错位、attach 连不上排查清单4.1 断点灰色不绑定现象断点是空心灰圈程序跑完也不停。原因调试器没有拿到有效的 source map无法把.ts行号映射到运行中的 JS。解决按顺序检查——tsconfig.json里sourceMap是否为truelaunch.json里sourceMaps是否为true预编译方案下outFiles是否匹配到实际产物产物目录里是否存在.js.map文件。四步走完基本能解决。4.2 断点停在了错误的行现象断点绑定了但停的位置和源码对不上偏移几行。原因通常是outFiles配置错误或者存在多份 map 文件冲突。比如dist目录里有上次构建的旧 map和新编译的混在一起。解决先清空产物目录重新构建排除旧文件干扰。然后确认outFiles的 glob 只匹配当前构建产物。如果用了 bundler如 esbuild、webpack检查它是否生成了正确的 source map有些 bundler 默认不生成或生成的是eval形式的 mapNode 调试器认不出来。4.3 attach 模式连不上进程现象用request: attach时提示连接被拒绝或超时。原因目标进程没有以调试模式启动或者端口不匹配。解决attach 要求目标进程启动时带上--inspect参数。比如node --inspect9229 dist/index.js然后launch.json里对应配置{ name: Attach to Node, type: node, request: attach, port: 9229, restart: true }restart: true表示目标进程重启后自动重新连接适合配合 nodemon 这类热重载工具使用。端口默认 9229如果被占用可以改成别的但两边要一致。4.4 ts-node 下断点偶尔失效现象同样的配置有时能停有时不能重启 VS Code 后又好了。原因ts-node 的编译缓存和 VS Code 的调试会话状态不同步属于偶发问题。解决在tsconfig.json里打开ts-node: { transpileOnly: true }跳过类型检查加速编译减少缓存不一致的概率。另外在launch.json里加outputCapture: std确保输出被正确捕获。如果还是偶发在 F5 前手动删掉node_modules/.cache下的 ts-node 缓存目录这是血泪经验换来的土办法但确实管用。5. 把调试配置推进到多文件工程和远程场景5.1 多入口和 monorepo 下的配置拆分单入口工程一个配置就够了但真实项目往往有多个入口——比如一个 CLI 加一个服务端。这时候在launch.json的configurations数组里写多个配置用name区分F5 时下拉选择。monorepo 场景下每个子包有自己的tsconfig.json和产物目录。常见做法是在根目录的launch.json里用${workspaceFolder}变量配合子路径{ name: Debug pkg-a, type: node, request: launch, program: ${workspaceFolder}/packages/pkg-a/dist/index.js, outFiles: [${workspaceFolder}/packages/pkg-a/dist/**/*.js], sourceMaps: true, cwd: ${workspaceFolder}/packages/pkg-a }关键是cwd要设到子包目录否则程序运行时读取相对路径的配置文件会找不到。outFiles也要精确到子包不要用根目录的大 glob否则不同子包的 map 会互相干扰。5.2 用条件断点和日志点替代 console.log断点打多了每次 F5 都停反而拖慢节奏。两个进阶技巧条件断点和日志点。条件断点右键断点 → “编辑断点” → 输入表达式比如user.id 7。只有条件为真时才停。适合在循环里定位特定一次迭代。日志点右键断点 → “添加日志点” → 输入id is {user.id}。它不会暂停程序只是在调试控制台打印一条消息。本质上是不改代码的console.log调试完不用删代码也不会污染提交记录。这个功能用过之后我在开发期几乎不再往代码里写临时console.log了。5.3 验证调试配置是否正确的三个检查动作配置写完别急着说“应该没问题”做三个动作验证第一在源码里打一个断点F5确认能停。第二停在断点后在调试侧边栏看调用栈确认显示的.ts文件名和行号正确而不是.js。第三在调试控制台输入一个变量名确认能打印出值。三个都通过说明 source map、断点绑定、变量作用域三条链路都正常。如果第三步打印出来是undefined但代码逻辑上该有值检查是不是被编译优化掉了——某些 bundler 在 production 模式下会做 tree-shaking 或变量重命名调试时要用 development 模式构建。5.4 一个我常犯的错误早期我总把launch.json当成一次性配置写完就不管了。后来发现项目结构一变——比如改了outDir、换了构建工具、加了路径别名——调试配置就得跟着改否则断点又开始变灰。现在我的习惯是每次调整tsconfig.json的outDir或rootDir顺手检查一遍launch.json里的program和outFiles是否还对得上。路径别名paths尤其容易出问题因为 Node 运行时不认识 TS 的paths需要额外配tsconfig-paths或在 bundler 里处理否则调试时模块解析会失败。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进