使用VSCode进行Typescript断点调试

5

我有一个从 Yeoman 脚手架生成的项目,使用了generator-express-no-stress-typescript 模板。我需要调试它,即“逐步执行typescript代码”,并使用 Visual Studio Code(不,console.log()对我来说不够用)。

根据文档,我只需要输入以下命令:

npm run dev:debug

然后附加VSCode。问题在于:如果这样做,VSCode 无法绑定断点。如果我使调试器停止在第一行执行 ("stopOnEntry": true),它会停在生成的 JavaScript 代码上而不是源 TypeScript 代码上,但是选项卡标题中的文件名显示为 "index.ts"(不是 .js),并且以斜体字显示。

这是 package.json 中 dev:debug 脚本目标:

"dev:debug": "nodemon --exec \"node -r ts-node/register --inspect-brk\" server/index.ts | pino-pretty",

这是我的launch.json文件中与附件(Attach)相关的配置:

    {
        "name": "Debug (Attach)",
        "port": 9229,
        "request": "attach",
        "cwd": "${workspaceFolder}",
        "sourceMaps": true,
        "skipFiles": ["<node_internals>/**"],
        "type": "node",
        // "outFiles": ["${workspaceFolder}/dist/**/*.js"],
    },

由于这种方法无效,我尝试了一些其他的教程,其中一个(我不记得是哪一个了)让我在launch.json中加入以下配置:

   {
       "name": "Run and debug",
       "program": "${workspaceFolder}/server/index.ts",
       "request": "launch",
       "skipFiles": [
           "<node_internals>/**"
       ],
       "type": "node",
       // "outFiles": ["${workspaceFolder}/dist/**/*.js"],
       "runtimeArgs": ["-r", "ts-node/register", "--preserve-symlinks"],
       "runtimeExecutable": "node",
       "args": ["--inspect", "${workspaceFolder}/server/index.ts"],
       "cwd": "${workspaceFolder}",           
   }

一开始看起来很有前途,但后来我尝试时却发现它在调试代码时跳过了TS而是进入了JS中,就像上面的“Debug (Attach)”配置一样。

下面是调试器用于追踪执行的生成JS代码。请注意,它在末尾的注释中包含了源映射。

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
require("./common/env");
const server_1 = __importDefault(require("./common/server"));
const routes_1 = __importDefault(require("./routes"));
const models_1 = __importDefault(require("./api/models"));
const port = parseInt((_a = process.env.PORT) !== null && _a !== void 0 ? _a : '3000');
const syncdb = ((_b = process.env.SYNC_DB_SCHEMA_ON_STARTUP) !== null && _b !== void 0 ? _b : 'false') === 'true';
if (syncdb) {
    models_1.default.sequelize.sync({ force: true }).then(() => {
        console.log("DB Aggiornato");
    }).catch((err) => {
        console.log("Errore", err);
    });
}
exports.default = new server_1.default().router(routes_1.default).listen(port);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiL2hvbWUvbHVjaW8vbXlhcHAvc2VydmVyL2luZGV4LnRzIiwic291cmNlcyI6WyIvaG9tZS9sdWNpby9teWFwcC9zZXJ2ZXIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsd0JBQXNCO0FBQ3RCLDZEQUFxQztBQUNyQyxzREFBOEI7QUFDOUIsMERBQThCO0FBQzlCLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxNQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxtQ0FBSSxNQUFNLENBQUMsQ0FBQztBQUdsRCxNQUFNLE1BQU0sR0FBRyxDQUFDLE1BQUEsT0FBTyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsbUNBQUksT0FBTyxDQUFDLEtBQUssTUFBTSxDQUFDO0FBRTdFLElBQUksTUFBTSxFQUFFO0lBQ1IsZ0JBQUUsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRTtRQUN6QyxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0lBQ2pDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQVEsRUFBRSxFQUFFO1FBQ2xCLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLEdBQUcsQ0FBQyxDQUFBO0lBQzlCLENBQUMsQ0FBQyxDQUFDO0NBQ047QUFHRCxrQkFBZSxJQUFJLGdCQUFNLEVBQUUsQ0FBQyxNQUFNLENBQUMsZ0JBQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAnLi9jb21tb24vZW52JztcbmltcG9ydCBTZXJ2ZXIgZnJvbSAnLi9jb21tb24vc2VydmVyJztcbmltcG9ydCByb3V0ZXMgZnJvbSAnLi9yb3V0ZXMnO1xuaW1wb3J0IGRiIGZyb20gJy4vYXBpL21vZGVscyc7XG5jb25zdCBwb3J0ID0gcGFyc2VJbnQocHJvY2Vzcy5lbnYuUE9SVCA/PyAnMzAwMCcpO1xuXG5cbmNvbnN0IHN5bmNkYiA9IChwcm9jZXNzLmVudi5TWU5DX0RCX1NDSEVNQV9PTl9TVEFSVFVQID8/ICdmYWxzZScpID09PSAndHJ1ZSc7XG5cbmlmIChzeW5jZGIpIHtcbiAgICBkYi5zZXF1ZWxpemUuc3luYyh7IGZvcmNlOiB0cnVlIH0pLnRoZW4oKCkgPT4ge1xuICAgICAgICBjb25zb2xlLmxvZyhcIkRCIEFnZ2lvcm5hdG9cIik7XG4gICAgfSkuY2F0Y2goKGVycjogYW55KSA9PiB7XG4gICAgICAgIGNvbnNvbGUubG9nKFwiRXJyb3JlXCIsIGVycilcbiAgICB9KTtcbn1cblxuXG5leHBvcnQgZGVmYXVsdCBuZXcgU2VydmVyKCkucm91dGVyKHJvdXRlcykubGlzdGVuKHBvcnQpOyJdfQ==

我解码了base64的sourcemap,它似乎包含了正确的路径到我的源代码,甚至还有源代码的一个副本。
{"version":3,"file":"/home/lucio/myapp/server/index.ts","sources":["/home/lucio/myapp/server/index.ts"],"names":[],"mappings":";;;;;;AAAA,wBAAsB;AACtB,6DAAqC;AACrC,sDAA8B;AAC9B,0DAA8B;AAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAA,OAAO,CAAC,GAAG,CAAC,IAAI,mCAAI,MAAM,CAAC,CAAC;AAGlD,MAAM,MAAM,GAAG,CAAC,MAAA,OAAO,CAAC,GAAG,CAAC,yBAAyB,mCAAI,OAAO,CAAC,KAAK,MAAM,CAAC;AAE7E,IAAI,MAAM,EAAE;IACR,gBAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;QACzC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE;QAClB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAC;CACN;AAGD,kBAAe,IAAI,gBAAM,EAAE,CAAC,MAAM,CAAC,gBAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC","sourcesContent":["import './common/env';\nimport Server from './common/server';\nimport routes from './routes';\nimport db from './api/models';\nconst port = parseInt(process.env.PORT ?? '3000');\n\n\nconst syncdb = (process.env.SYNC_DB_SCHEMA_ON_STARTUP ?? 'false') === 'true';\n\nif (syncdb) {\n    db.sequelize.sync({ force: true }).then(() => {\n        console.log(\"DB Aggiornato\");\n    }).catch((err: any) => {\n        console.log(\"Errore\", err)\n  

这是我的tsconfig.json,以防有所需要:

{
  "compileOnSave": false,
  "compilerOptions": {
    "inlineSourceMap": true, // added after answer below, still doesn't work
    "target": "ES2019",
    "lib": ["ES2020"],
    "strict": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "declaration": true,
    "moduleResolution": "node",
    "useUnknownInCatchVariables": false,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "noUnusedParameters": true,
    "noUnusedLocals": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "strictPropertyInitialization":false,
    "alwaysStrict": true,
    "outDir": "dist",
    "typeRoots": ["node_modules/@types"],
    "resolveJsonModule": true,
    "baseUrl": "."

  },
  "include": ["server/**/*.ts", "server/api/models/index.ts"],
  "exclude": ["node_modules", "./test/", "./dist"]
}

以下是 npx ts-node --showConfig 的输出结果(在beautifulcoder对他的答案发表评论后):

{
  "ts-node": {
    "cwd": "/home/lucio/myapp",
    "projectSearchDir": "/home/lucio/myapp",
    "project": "/home/lucio/myapp/tsconfig.json"
  },
  "compilerOptions": {
    "target": "es2019",
    "lib": [
      "es2020"
    ],
    "strict": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "useUnknownInCatchVariables": false,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "noUnusedParameters": true,
    "noUnusedLocals": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "strictPropertyInitialization": false,
    "alwaysStrict": true,
    "outDir": "./.ts-node",
    "typeRoots": [
      "/home/lucio/myapp/node_modules/@types"
    ],
    "resolveJsonModule": true,
    "baseUrl": "./",
    "inlineSourceMap": false, // PLEASE NOTE: this is false even after I added `"inlineSourceMap": true` in tsconfig.json above
    "inlineSources": true,
    "noEmit": false
  }
}

我已经搭建了一个新的最小化范例 generator-express-no-stress-typescript 项目,遵循文档中的步骤。例如:

$ npm install -g yo generator-express-no-stress
$ yo express-no-stress ts-debug-test

当被问及在“Swagger 2”和“OpenAPI 3”之间进行选择时,选择“OpenAPI 3”,这是我第一次构建真实项目时所做的选择。如果您需要一个展示问题的最小化示例,也可以这样做,或者您可以在GitHub上找到我的示例。我只添加了我的launch.json文件,您只需在代码的第一行设置断点即可。

现在我不知道下一步该怎么做才能调试我的代码...有什么提示吗?


你试过彻底卸载 nodemon 吗? - beautifulcoder
还没有,我不知道它能帮助我。我应该具体做什么? - Lucio Crusca
4个回答

4
我认为你的Debug (Attach)配置中的outFiles属性很可能是问题所在。由于ts-node实际上不会将其生成的文件和源代码映射写入磁盘,因此如果VS Code在那里查找它们,则无法找到它们。你只需删除这个属性即可。
我有许多使用ts-nodenodemon的代码库基本上都以你在这里使用它们的方式使用,并且源映射正常工作。我唯一看到的区别是我的调试配置没有设置outFiles
编辑:既然outFiles这个东西没用,我能想到的唯一其他事情就是tsconfig.json中的某些内容。以下是我的一个可以工作的tsconfig.json文件。我猜测可能是"inlineSourceMap": true造成的。
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "inlineSourceMap": true,
    "noImplicitAny": false,
    "types": [
      "webpack-env"
    ],
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "../interface/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

谢谢,我已经尝试从launch.json中删除outFiles,但没有任何变化。至少,我已经理解了outFiles的作用。 - Lucio Crusca
好的,我已经将我的 tsconfig.json 添加到答案中,以便有所帮助。 - David Alexander
修改了我的问题。请注意 npx ts-node --showConfig 输出中的注释。我不知道为什么它仍然显示为 false - Lucio Crusca
谢谢!抱歉我没能提供更多的帮助。 - David Alexander
添加 "inlineSourceMap": true 对我来说解决了这个问题。我正在处理相同的问题,尝试在docker容器中调试strapi 4.8.1 + typescript。 - Artem Melnichenko
显示剩余2条评论

2
问题是由我存储项目的路径中的符号链接引起的(实际BUG被触发),例如:
/home/lucio/myapp -> /home/lucio/workspace/vscode/myapp

真正的问题出现在VSCode中,这里

直接的解决方法是避免在项目文件夹路径中使用符号链接。


1

正如我刚刚发现的那样 - 这可能是由于 VSCode 控制台未使用正确的 Node 版本。

如果您正在使用 nvm(或类似工具) - 这很容易发生。

无论是在此处还是在任何其他类似的线程中枚举的任何选项,设置都无法正常工作(这意味着它已经启动,但断点没有被命中)。

但是,一旦我在我的 launch.json 中添加了正确的版本(在我的情况下是 "runtimeVersion": "12.22.12"),一切都开始以最简单的默认值工作...

供参考:

我的“默认”node版本是8.x.x

launch.json

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "runtimeVersion": "12.22.12",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/src/server.ts",
            "preLaunchTask": "tsc: build - tsconfig.json",
            "sourceMaps": true,
            "smartStep": true,
            "internalConsoleOptions": "openOnSessionStart",
            "runtimeExecutable": "node",
            "outFiles": [
                "${workspaceFolder}/dist/**/*.js"
            ]
        }
    ]
}

tsconfig.json

{
    "extends": "@tsconfig/node12/tsconfig.json",
    "compilerOptions": {
        "outDir": "dist",
        "sourceMap": true
    },
    "include": ["src"],
    "exclude": ["node_modules"]
}

1
非常感谢,将"runtimeVersion"添加到launch.json中是解决我的问题的唯一方法。我在Mac上使用VSCode,并使用nvm来管理多个Node版本,但无法使调试器运行并停止任何断点。 - Arthur Mastropietro

1

我的当前理论是你正在nodemon后面运行调试脚本。这是一个针对生产环境而不是用于本地调试的Node进程监视器。

尝试:

{"dev:debug": "node -r ts-node/register --inspect-brk server/index.ts"}

刚试了一下,谢谢但不行:它跟踪生成的JS代码而不是源TS代码,例如与上述问题相同。 - Lucio Crusca
我敢打赌 ts-node 不会输出源代码映射。可能有一种方法可以做到这一点。 - beautifulcoder
https://www.npmjs.com/package/ts-node#configuration-1 - beautifulcoder
我编辑了我的问题,添加了 npx ts-node --showConfig 命令的输出。除了已经存在的 "sourceMap": true,我不知道在那个输出中应该寻找什么。 - Lucio Crusca
是的,这是正确的,看起来很好。我被难住了,你能在GitHub上发布最小可复现代码吗?我实际上非常好奇。 - beautifulcoder
我在这里发布了一个最小化的示例: https://github.com/lucrus73/ts-debug-test 并相应地更新了我的问题。 - Lucio Crusca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接