JavaScript Source Maps Demystified

JavaScript source maps are a powerful tool that can be used to debug and troubleshoot JavaScript code. Essentially, a source map is a file that maps the original code of a JavaScript application to the code that has been minified or transpiled for production use.

All three spellings ā€œsource-mapā€ (Webpack, UglifyJS ), ā€œsourcemapā€ (Babel, Rollup), and ā€œsource mapsā€ (general term) are correct and widely used interchangeably in the context of JavaScript source maps.

This mapping allows developers to view and debug the original source code, even when the production code has been compressed and optimized for performance.

Many JavaScript tools can generate and consume source maps, including:

Most modern browsers also support source maps, which means that you can debug your code directly in the browser using the original source code, even if it has been transpiled or minified.

Source maps specification

While the source map format is intended to be language and platform agnostic, it is useful to have a some conventions for the expected use-case of web server hosted javascript.

The entire file is a single JSON object:

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "bar.js"],
  "sourcesContent": [null, null],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}
  • version: File version (always the first entry in the object) and must be a positive integer.
  • file: An optional name of the generated code that this source map is associated with.
  • sourceRoot: An optional source root, useful for relocating source files on a server or removing repeated values in the ā€œsourcesā€ entry. This value is prepended to the individual entries in the ā€œsourceā€ field.
  • sources: A list of original sources used by the ā€œmappingsā€ entry.
  • sourcesContent: An optional list of source content, useful when the ā€œsourceā€ canā€™t be hosted. The contents are listed in the same order as the sources in line 5. ā€œnullā€ may be used if some original sources should be retrieved by name.
  • names: A list of symbol names used by the ā€œmappingsā€ entry.
  • mappings: A string with the encoded mapping data.

The mappings field is the most important part of the source map file. It contains a base64 VLQ-encoded string that maps each character in the output file to its original location in the source files. The mappings string can be quite complex, as it needs to take into account line numbers, column numbers, and file names for each character.

Optionally, a source map will have the same name as the generated file but with a ā€œ.mapā€ extension. For example, for ā€œpage.jsā€ a source map named ā€œpage.js.mapā€ would be generated.

Example using Webpack

In this example, we have a simple JavaScript function that adds two numbers together and logs the result to the console. Weā€™re using Webpack to bundle this code and generate a source map file.

  1. JavaScript code
function add(a, b) {
  return a + b;
}

console.log(add(2, 3));
  1. Webpack configuration

The Webpack configuration tells Webpack to use a source-map devtool, which instructs it to generate a separate source map file.

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'source-map'
};
  1. Bundled code

In this case, the bundler has used UglifyJS to minify and compress the code, which has resulted in some changes to the variable names and formatting, but the functionality of the code remains the same.

bundled code

As you can see, the original function code has been transformed and included in the bundled code. This can make it difficult to debug the code, since the line numbers and variable names in the bundled code may be different from those in the original source code.

  1. Source map

Thatā€™s where the source map file comes in - it provides a way to map the code in the bundled file back to its original source code, making it much easier to debug.

{
  "version": 3,
  "file": "bundle.js",
  "sources": [
    "webpack:///./src/index.js"
  ],
  "sourcesContent": [
    "function add(a, b) {\n  return a + b;\n}\n\nconsole.log(add(2, 3));\n"
  ],
  "mappings": "AAAA,IAAI,EAAG,IAAI,EAAG,IAAI,EAAG,IAAI;AACd,IAAI",
  "names": [
    "add",
    "a",
    "b",
    "console",
    "log"
  ],
  "sourceRoot": ""
}

The output source map file includes information about the original source code (sources and sourcesContent), the names of variables and functions used in the code (names), and the mapping between the original source code and the bundled code (mappings).

The sources field in the source map file indicates that the original source code can be found at webpack:///./src/index.js, which means that Webpack is using a virtual file system to load the original source code. The sourcesContent field contains the actual content of the original source file.

When a browser loads the bundled JavaScript code, it can use the source map file to map each line and column in the bundled code back to its corresponding location in the original source code. This makes it much easier to debug the code, since the developer can see the original source code and set breakpoints and step through the code as if they were working with the original source.