Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Emitting TypeScript Source Maps

July 14, 2020
typescript

Source maps enable us to debug TypeScript code. A source map file maps from the transpiled JavaScript file to the original TypeScript file. This allows the original TypeScript code to be reconstructed while debugging.

In this post, we will go through how to generate source maps for TypeScript code and explore different ways we can configure the generated source maps.

Generating source map files

We can generate a basic source map using the sourceMap compilation option in tsconfig.json:

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

Let’s say we have a breakpoint in some TypeScript code or maybe a debugger statement:

import { Product } from "./product";

const table = new Product("Table", 70);
const chair = new Product("Chair", 20);
debugger;
console.log(table, chair);

We can debug this code in Visual Studio Code by carrying out the following steps:

  • First, we need to open the file that contains the code we want to debug.
  • Then we go to run view by clicking the Run icon in the activity bar on the left-hand side.

Run debugger

  • We then click Run and Debug in the panel that appears.

Run debug button

  • Lastly, we select Node.js from the list that appears.

Select Nodejs

The code is compiled, and the debugger starts. The execution breaks on the debugger statement:

debugger

The code in the debug window is the TypeScript code rather than the transpiled JavaScript. The source map for this file has enabled us to debug the code in TypeScript.

Nice!

We can stop the debugger by clicking the red square icon.

Understanding a source map

Let’s have a closer look at the source map file.

If we look in the folder that has the transpiled JavaScript, we will see the source maps. These are files with a .map extension. If we open a source map file we will see it is in JSON format:

{
  "version": 3,
  "file": "index.js",
  "sourceRoot": "",
  "sources": [
    "../src/index.ts"
  ],
  "names": [],
  "mappings": ";;AAAA,uCAAoC;....""
}
`}

Here are some key points about the source map:

  • The reference to the JavaScript file is defined in a file field.
  • The reference to the TypeScript file is in a sources field. Notice that it references the file in the project structure.
  • The sourceRoot field is the root path of the TypeScript files.
  • The version field defines which version of the source map spec is being used.
  • The names field is a list of identifiers used in the source code that were changed or removed from the output.
  • The mappings field contains mappings for every position in the JavaScript code back to positions in the TypeScript code. These are base64 encoded variable-length quantities.

Let’s open the transpiled JavaScript file. We will notice the comment at the bottom of it:

//# sourceMappingURL=index.js.map

This is the reference to the source map.

Including source maps in generated JavaScript

There is a compilation option called inlineSourceMap that will put the source map inside the transpiled JavaScript file. Let’s explore this.

Let’s replace the sourceMap with inlineSourceMap in tsconfig.json:

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

Let’s run the TypeScript compiler:

npx -p typescript tsc

We will notice that the compiler hasn’t generated any .map files. Instead, the source maps are at the bottom of the JavaScript files.

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJm...

Including TypeScript code in source maps

By default, the TypeScript code isn’t included in the generated source maps. Instead, the source maps reference the TypeScript code in the project. This is fine for local development, but not ideal if you are trying to debug a problem when the code has been deployed to a web server. We can use the inlineSources option to change this behavior.

Let’s remove inlineSourceMap and add the following to tsconfig.json:

{
  "compilerOptions": {
    ...
    "sourceMap": true,    "inlineSources": true  },
  ...
}

The inlineSources option can be used with sourceMap or inlineSourceMap.

Let’s rerun the TypeScript compiler:

npx -p typescript tsc

If we look at the source maps, they contain the TypeScript code rather than referencing it. The TypeScript code is contained within the sourcesContent field in the JSON.

{ ... "sourcesContent":[ "import { Product } from \"./product\";\n\nconst table = new Product(\"Table\", 70);\nconst chair = new Product(\"Chair\", 20);\ndebugger;\nconsole.log(table, chair);\n" ] }

Cool!

Changing the location of source maps

By default, the transpiled JavaScript expects source maps to be in the same directory. The behavior can be changed with the mapRoot option.

Let’s add this option to tsconfig.json:

{
  "compilerOptions": {
    ...
    "mapRoot": "maps"  },
  ...
}

Let’s rerun the TypeScript compiler:

npx -p typescript tsc

The location of the source maps hasn’t changed as we might have expected - they are still next to the JavaScript. The mapRoot option doesn’t change where the source maps are placed - it changes how the JavaScript file references the source map.

Let’s open a JavaScript file and look at source map reference at the bottom.

//# sourceMappingURL=../src/maps/index.js.map

The source map is now expected to be in a maps folder in this example.

Changing the source map reference for TypeScript code

As mentioned earlier, a source map references the TypeScript code in the project structure by default. The location of the TypeScript code can be changed using a sourceRoot option.

Let’s remove the mapRoot and inlineSources options and add the following to tsconfig.json:

{
  "compilerOptions": {
    ...
    "sourceMap": true,    "sourceRoot": "ts"  },
  ...
}

Let’s rerun the TypeScript compiler:

npx -p typescript tsc

The sourceRoot field within the source map is now updated with the path we specified.

{
  ...
  "sourceRoot":"ts/",
  ...
}

Wrap up

Source maps allow TypeScript code to be debugged and switched on using the sourceMap or inlineSourceMap options.

inlineSources is useful when debugging code on a deployed server.

mapRoot is useful with sourceMap when debugging code on a deployed server, and the map files aren’t next to the JavaScript files.

sourceRoot is useful with sourceMap when debugging code on a deployed server, and the TypeScript files are in a different relative location to the local project.

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about TypeScript, you may find my free TypeScript course useful:

Take a look