Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to be able to set up breakpoints correctly when using VS Code to debug a TypeScript app run using ts-node in a Docker container?

Our app is written in TypeScript and uses Docker, and to avoid round-tripping through .js files, we're running it with ts-node to load the .ts files directly.

Unfortunately this seems to make VSCode confused as to where valid lines of code are for setting breakpoints.

This problem manifests with the following setup:

/package.json

{
  "scripts": {
    "start": "node --inspect=0.0.0.0 --require ts-node/register src/index.ts"
  },
  "dependencies": {
    "@types/node": "^10.1.2",
    "ts-node": "^6.0.3",
    "typescript": "^2.8.3"
  }
}

/tsconfig.json

{
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs", 
    "outDir": "./dist",
    "rootDir": "./src",    
    "esModuleInterop": true
  }
}

/Dockerfile

FROM node

RUN mkdir /home/node/app
WORKDIR /home/node/app
COPY package.json /home/node/app
RUN npm install && npm cache clean --force
COPY . /home/node/app
CMD [ "npm", "start" ]

/docker-compose.yml

version: "3.4"

services:
  http:
    build: .
    ports:
      - "8000:8000"
      - "9229:9229"

/.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Attach",
            "address": "localhost",
            "port": 9229,
            "protocol": "inspector",
            "localRoot": "${workspaceFolder}/src",
            "remoteRoot": "/home/node/app/src"
        }
    ]
}

/src/index.ts

import {createServer} from "http";








const server = createServer((msg, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain'})
    res.end(msg.url)
    debugger
})

server.listen(8000)

(The blank lines are significant for reasons I'll show later, about ten of them does the job.)

You can also fetch the whole thing here: https://github.com/millimoose/ts-node-breakpoints

I run this with docker-compose --up, then attach to that with the debugger using the above launch configuration. When I try to set breakpoints in /src/index.ts on any of the lines inside the createServer() call, they're reported as invalid; while I can set breakpoints in the blank lines. This is presumably because TypeScript compilation strips the blank lines, and for some reason, VSCode will only recognize line numbers from the generated JS as valid:

line number mismatch between TS and JS

This is a contrived example for ease of reproduction, but in general there'll be a mismatch between where I think I'm setting breakpoints, and where they're actually set.

However, when I break on the debugger statement, VSCode fetches the TypeScript file (the tab says something along the lines of "read-only inlined from sourcemap" when newly opened) from the server, and I can then set breakpoints correctly in it:

breakpoints in local vs. remote file

This is an unsatisfying situation for reasons I shouldn't have to explain: juggling a local file I can edit and a remote file where breakpoints work is a hassle, and adding debugger statements would involve reloading the app everytime I need a new breakpoint.

I've searched around for the issue, but the keywords give me at least ten lengthy GitHub issues ranging as far as years back. Since I'm not intimately familiar with the internals of ts-node, transpilation, and sourcemaps, I'm having a hard time reasoning through what's going on here, much less how to fix it. From what I understand, what happens is that ts-node compiles TS to JS and generates sourcemaps in temporary files inside the Docker container where VSCode can't access them. (This is why I've no idea how to set e.g. outFiles.) There were also some allusions to my scenario being already supported if set up correctly in closed issues, but no clue as to how to do it.

Is there a way to get this working so that I can actually set a breakpoint in my local sources when remote-debugging, and have them hit in said files, without having to revert to precompiling TS to JS and sourcemaps so I have the latter available locally?

like image 617
millimoose Avatar asked May 19 '18 22:05

millimoose


1 Answers

Your are not generating source map for the code. So I updated your tsconfig.json like below

{
  "compilerOptions": {
    /* Basic Options */
    "target": "ES2017",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "outDir": "./dist",                        /* Redirect output structure to the directory. */
    "rootDir": "./src",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    "sourceMap": true,
    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
  }
}

and now it works like a charm

Debugging working

like image 141
Tarun Lalwani Avatar answered Sep 23 '22 04:09

Tarun Lalwani