Source maps let you debug minified, transpiled production code as if it were your original source.
Here’s a look at a simple Webpack setup and how source maps work in practice.
Imagine a small React app.
src/App.js:
import React from 'react';
function App() {
const message = "Hello, world!";
console.log(message);
return (
<div>
<h1>Welcome</h1>
</div>
);
}
export default App;
And src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Without source maps, your browser’s developer tools would show you something like this when you inspect the console.log:
console.log("Hello, world!"); // Inlined at
Or if it’s bundled and minified:
console.log("Hello, world!") // a1b2c3d4e5f6g7h8i9j0
You’re looking at generated code, not your original src/App.js.
Now, let’s configure Webpack to generate source maps.
webpack.config.js:
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devtool: 'source-map', // This is the key!
};
When you run npx webpack, it will produce dist/bundle.js and dist/bundle.js.map.
The bundle.js.map file is a JSON document. It contains mappings between the generated code’s locations and the original source code’s locations.
Here’s a snippet of what bundle.js.map might look like (simplified):
{
"version": 3,
"file": "bundle.js",
"sourceRoot": "",
"sources": [
"webpack://my-app/src/App.js",
"webpack://my-app/src/index.js"
],
"names": [
"React",
"App",
"message",
"console",
"log",
"ReactDOM",
"render",
"document",
"getElementById",
"root"
],
"mappings": "AAAA,OAAO,KAAA,QAAA,OAAA,KAAA;AAEP,SAAS,GAAA,KAAA;IACF,MAAM,WAAA,GAAA,iBAAA;IACV,OAAO,WAAA,GAAA,KAAA;IACV,OAAO,KAAA;AACP,KAAC,EAAC,KAAA,EAAC,KAAA,EAAC,KAAA,EAAC,KAAA;AACP,EAAA"
}
The mappings field is a Base64VLQ encoded string. It’s a compact way to represent a sequence of transformations. Each transformation describes a shift in column, line, source file index, and name index.
When you open dist/index.html (which links to dist/bundle.js) in your browser and inspect the console.log, the browser’s developer tools will:
- Load
bundle.js. - See a comment at the end of
bundle.jslike//# sourceMappingURL=bundle.js.map. - Load
bundle.js.map. - Parse the source map.
- Use the mappings to translate the current execution location in
bundle.jsback to the originalsrc/App.jsfile and line number.
Now, in your browser’s developer tools, you can see App.js and set breakpoints directly on the console.log(message) line within your original source code.
The devtool option in webpack.config.js controls the type of source map generated. 'source-map' is the most common and robust option for production. Other options like 'eval-source-map' or 'inline-source-map' are generally not recommended for production due to security or performance implications, but are useful during development.
The key benefit of 'source-map' is that it generates a separate .map file. This keeps your bundle.js file cleaner and allows browsers to fetch the source map only when needed (e.g., when you open the developer tools and try to inspect a specific line).
The original source files themselves (e.g., src/App.js) are not included in the bundle.js or the .map file. The .map file only contains the instructions on how to reconstruct the path back to your original source. This is crucial for keeping production bundles small and for security.
The sourceRoot property in the source map can be used to specify a base URL for the sources paths. If it’s empty, the paths in sources are relative to the source map file itself.
When debugging, ensure your web server is configured to serve .map files with the correct MIME type (application/json). Most modern static file servers handle this automatically.
The most surprising thing is how the mappings string, despite looking like gibberish, precisely encodes every character’s origin. It’s a compressed, serializable representation of a 2D transformation matrix applied to character positions.
The next concept you’ll likely encounter is optimizing your production builds further, perhaps by exploring different code splitting strategies or advanced tree-shaking techniques in Webpack.