Debuggable Maven-style TypeScript Node.js Project Set-up for Visual Studio Code

This is not the typical Yet Another Introduction to Using TypeScript in VS Code!

What we want to achieve:

  • Ability to set breakpoints in TypeScript source files. This requirement is of paramount importance.
  • Separation of source code (TypeScript) and target code (JavaScript). We will use the typical Maven project structure. How can anyone bear with seeing index.ts, index.js.map, and index.js next to each other? (Although many tutorials either oversees or avoids solving this problem.)

We will start with an empty project and work our way toward a complete template project (download the complete code on GitHub).

Create a directory named node-ts/. Initialize using npm init however you want. The VS Code debugger doesn’t care about the package.json file. The configuration files important for our purpose are tsconfig.json.vscode/launch.json, and .vscode/task.json.

We now set up the directory structure of our template project. Create some directories as follows

node-ts/
├── package.json
├── resources/
├── src/
└── target/

Firstly, we need to ask TypeScript to

  1. Read the*.ts files from thesrc/directory as input.
  2. Output the *.js and *.map.js files into the target/ directory.
  3. Watch any changes to the *.ts files in src/ and re-compile when there are any changes.

All of above can be done by creating a file named node-ts/tsconfig.json as follows

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "sourceMap": true, 
    "outDir": "target",
    "rootDir": "src",
    "watch": true
  }
}

Now we add a task to VS Code to execute tsc using the config above, by creating the file .vscode/tasks.json which has the following content:

{
  "version": "0.1.0",
  "command": "tsc",
  "isShellCommand": true,
  "args": ["-p", "."]
}

Let’s test if the task is set properly by running it. Create a simple TypeScript file (such as main.ts) inside src/. Then press Cmd+Shift+P, and type in “run task”. Select “Tasks: Run Task” from the suggestion list. Then choose the tsc task. You should immediately see main.js and main.js.map spring into life under target/. You should also see a spinning indicator at the bottom-left corner of VS Code window, and in the task output, you should see

Compilation complete. Watching for file changes.

You can try to modify main.ts, and hit save. The output should read

File change detected. Starting incremental compilation...

To stop the task, press Cmd+Shift+P again, and type in “task terminate” and choose “Tasks: Terminate Running Task”.

Now let’s get back to configuring the debugger. Create the file .vscode/launch.js which has the following content

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceRoot}/target/main.js",
      "sourceMaps": true,
      "outFiles": ["${workspaceRoot}/target/*.js"]
    }
  ]
}

What this configuration does:

  1. Specifies a Node.js debugger configuration.
  2. Sets main.js as the main program. This file will be generated by our tsc task.
  3. Enable source map. This allows us to set breakpoints in main.ts.
  4. Specifies the pattern that matches all the generated code.

Done!

Now, whenever you want to do some debugging, start the tsc task at the very beginning, then you can leave it alone.

Some people may suggest using preLaunchTask to automate the tsc task, but VS Code is very buggy at this moment with launching a persisting background task (e.g., it will prompt that the task is already running from the second time you debug after the tsc task is first ran).