Home > Software design >  Typescript breakpoints with VSCode
Typescript breakpoints with VSCode

Time:06-07

I've this Yeoman scaffolded project from the generator-express-no-stress-typescript template. I need to debug it, as in "step through the typescript code" with Visual Studio Code (and no, console.log() is not enough for me).

As per the docs I only need to issue

npm run dev:debug

and then attach VSCode. Here is the problem: if I do that, VSCode can't bind the breakpoints. If I make the debugger stop on the first execution line ("stopOnEntry": true), it stops in the generated JavaScript code instead of the source TypeScript code, and/but the filename in the tab title shows "index.ts" (not .js) in italics.

Here is the dev:debug script target in package.json:

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

and here is the "Attach" configuration in my launch.json:

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

Since this did not work, I tried a few other tutorials out there, and one (I can't remember which one) made me add the following configuration to my 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}",           
   }

This seemed promising at first, but then, when I tried it, it stepped through JS code instead of TS, just like the "Debug (Attach)" configuration above.

Here is the generated JS code that the debugger uses to trace execution. Please note that it contains the sourcemap in the commment at its end.

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

I decoded the base64 sourcemap and it seems to contain the correct paths to my sources and even a copy of the source code:

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

Here is my tsconfig.json, just in case it matters:

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

And here is the output of npx ts-node --showConfig (after beautifulcoder's comment to his answer)

{
  "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
  }
}

I've tried to scaffhold a new minimal generator-express-no-stress-typescript project, but that gave me another rather obscure and unrelated error (which is good for a different question), so here is a copy of my whole project, which is actually quite minimal, since it's only a basic CRUD for the time being, and you can safely ignore most of it, because a simple breakpoint at the first meaningful line of server/index.ts is all you need to check out the problem.

So now I don't know what to do next in order to debug my code... any clues?

CodePudding user response:

My current theory is you have the debug script running behind nodemon. This is a node process monitor meant for production and not for debugging purposes on local.

Try:

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

CodePudding user response:

I think the outFiles property in your Debug (Attach) configuration is likely the problem. ts-node doesn't actually write its generated files and source maps to disk, so if VS Code is looking for them there then it won't find them. You should just be able to remove this property.

I have a number of codebases that use ts-node and nodemon in basically exactly the way you're using them here, and source mapping works fine. The only difference I can see is that my debug configurations don't have outFiles set.


EDIT: since the outFiles thing didn't work, the only other thing I can think of is that it's something in tsconfig.json. Here's one of mine that works. My guess is that it might be the "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"
  ]
}
  • Related