6

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 scaffolded a new minimal example generator-express-no-stress-typescript project, following the documented procedure again. e.g.

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

and choosing "OpenAPI 3" over "Swagger 2" when asked, which is what I did with the real project when I scaffolded it the first time. If you need a minimal example showing the problem you can do that too or you can find mine at GitHub, if you prefer. I've only added my launch.json to it and you only need to set a breakpoint at the first meaningful line of code.

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

2
  • Have you tried nuking nodemon? Commented Jun 3, 2022 at 14:30
  • Not yet, I did not know it could help. What should I do exactly? Commented Jun 3, 2022 at 15:17

8 Answers 8

5
+100

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"
  ]
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks, I've tried to remove outFiles from launch.json, but nothing changed. At the very least, I've understood what outFiles is supposed to do.
Ok, I've added my tsconfig.json to the answer in case that helps.
Edited my question. Please note the comment in the npx ts-node --showConfig output. I don't know why it is still showing false.
I even tried your tsconfig.json but it did not solve the problem in my case.
Adding "inlineSourceMap": true solved it for me. I was dealing with the same problem, trying to debug strapi 4.8.1 + typescript inside docker container
|
2

The problem was being caused (the real bug was being triggered) by a symlink in the path where I store my project, e.g.

/home/lucio/myapp -> /home/lucio/workspace/vscode/myapp

The real bug is in VSCode, here.

The straghtforward workaround is to avoid symlinks in the project folder path.

Comments

1

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

6 Comments

Just tried, thanks but no dice: it traces the generated JS code instead of the source TS, e.g. just the same problem as above.
I bet ts-node doesn't spit out source maps. There is probably a way to do this.
I've edited my question adding the output of npx ts-node --showConfig. I don't know what I should look for in that output, except "sourceMap": true which is already there.
Yes, this is correct and it looks good. I'm stumped, can you post minimal reproducible codes on GitHub? I'm actually morbidly curious.
|
1

As I just figured out - this might be due to a VSCode console not utilizing proper node version.

In case you are using nvm (or something similar) - this can easily be the case.

Setup wasn't working anyhow, with any options enumerated here or in any other similar threads (meaning it did started, but breakpoints where not hit).

But as soon as I added proper version to my launch.json (in my case "runtimeVersion": "12.22.12") - everything started to work with simplest defaults...

For the referrence:

My "default" node version was 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 Comment

Thanks in advance, adding "runtimeVersion" in launch.json was the only thing that solved my problem. I'm using VSCode on a Mac, and i use nvm to manage multiple node versions and i just could not make the debugger to run and stop any breakpoint.
0

I checked all the solutions and none of them worked for my case. at last, I cleared the "outFiles" line in the launch.json and it worked!

just comment out these lines in the launch.json:

  "outFiles": [
      "${workspaceFolder}/dist/**/*.js"
  ]

Comments

0

Typically I debug npm scripts using the "Javascript Debug Terminal" which will auto-attach and bind breakpoints when using npm run [script] commands.

If you're using a package like tsx or ts-node to strip types then you need to make sure source maps are enabled in your tsconfig.json by setting inlineSourceMap to true.

I've also run into issues getting the breakpoints to bind correctly when I load node-options from my .npmrc file. The breakpoints bind correctly without .npmrc, but as a workaround using node --run [script] instead of npm run [script] with node v22+ worked as well.

Edit

Apparently vs code requires its own NODE_OPTIONS variables for debugging: https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_how-can-i-set-nodeoptions

So you need to append to it in your shell configuration file:

export NODE_OPTIONS="$NODE_OPTIONS --no-warnings"

or in your project's .npmrc file:

node-options="${NODE_OPTIONS} --no-warnings"

Edit 2

You can now run node (v22.7+) with --experimental-strip-types (simple type stripping) or --experimental-transform-types (transpiles enums etc.) to run typescript. So if you add the following to .npmrc:

node-options="${NODE_OPTIONS} --experimental-strip-types"

Then you can just run node normally in package.json:

{
  ...
  "scripts": {
    ...
    "dev:debug": "node server/index.ts",
   }
}

Finally open up a Javascript Debug Terminal in VS Code and just run npm run dev:debug

Comments

0

I faced this issue in vscode version 1.96.2. Rolling back nodejs to a previous version worked for me. I was using nodejs version 23 and after rolling back to version 22 it starts to work. So one solution I can recommend is that check the help -> about option in vs code. And use a version nodejs that is relatively recent to the version mentioned in the vscode help -> about.

Comments

0

ts-node performs Just In Time compilation from TypeScript into JavaScript (as David Alexander mentions), but you can request a build in the command nodemon triggers, e.g. with a build script in package.json and npm run build as a part of the command in the launch.json's args. Enabling either inlineSourceMap or sourceMap in tsconfig.json should work.

launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "args": "--exec 'npm run build && npx ts-node server/index.ts'",
      "console": "integratedTerminal",
      "env": {
        "DEBUG": "*",
        "NODE_OPTIONS": "--unhandled-rejections=strict"
      },
      "internalConsoleOptions": "neverOpen",
      "name": "Debug",
      "request": "launch",
      "restart": true,
      "runtimeExecutable": "nodemon",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "type": "node"
    },
  ]
}

package.json

{
  "dependencies": {
    ...
  },
  "devDependencies": {
    "nodemon": "3.1.9",
    "typescript": "5.7.3"
    ...
  },
  "nodemonConfig": {
    "delay": 2500,
    "env": {
      "NODE_PATH": "."
    },
    "ext": "ts",
    "ignore": [
      "dist",
      "*.js"
    ],
    "legacyWatch": true,
    "watch": "*.ts"
  },
  "scripts": {
    "build": "tsc --project tsconfig.json",
    ...
  }
}

tsconfig.json

{
  "compilerOptions": {
    "inlineSourceMap": true,
    ...
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.