What are JavaScript source maps? A detailed guide from Raygun
Posted Jan 8, 2018 | 5 min. (942 words){
"file": "example.min.js",
"mappings": "AAAA,GAAIA,QAASC,QAAQ,SAErB,IAAIC,KAEC,KAAM,IAAIC,OAAM,iBAOf",
"names": [
"apple",
"banana",
"chicken",
"document",
"init"
],
"sources": [
"example.js"
]
"version": 3
}
A source map validator checks whether the source map has the required fields and allows you to see that the source map contains the mappings you want to have. Sometimes it will even allow you to validate mappings from minified code to source.
It’s important to validate source maps when you are first setting them up to ensure they were correctly generated before using them in a production environment. Typically you will only validate source maps when first implementing or updating your JavaScript minification process.
You can validate your source maps first hand in most modern web browsers. If the source map is available to use often times the browser will automatically present a source mapped copy of the minified file to the user when the user clicks through from an error notification. Ensuring the source mapping aligns with what you would expect given your knowledge of the source code and an error message.
In addition, various validators exist online. When provided with your source map, they can provide some information about which files the map pertains to, as well as allowing you to run map queries.
Raygun provides a free JavaScript source map validator tool which allows you to enter the URL of a known source map or upload a source map to be checked.
How do I create a source map?
Source maps are created by the same utility or library you use to minify your JavaScript code. They will usually be output if you specify a specific option when creating the minified file. For example, the popular minifier UglifyJS2 creates a source map if an option is provided and a filename is specified:
uglifyjs example.js --source-map example.js.map
Source maps in action
To give an example of how source maps can be useful here is a small demonstration.
Let’s say we have the source code below which we wish to deploy on our website:
var apiKey = 'BxyGQtWjGVMsgRxMoJsJxw==';
function profit() { } function swingPickaxe() { } function openEyes() { brain.trigger(“eyes.open”); } function checkForCreepers() { console.log(‘Checking for those horrid green dudes’); openEyes(); } function scanForDiamonds() { var i; i = 0; checkForCreepers(i); swingPickaxe(); // … profit(9001); } function dig() { var foo, bar; var baz; foo = “foo!”; scanForDiamonds(foo); } // Attach Raygun to send me details of any errors Raygun.init(apiKey, { allowInsecureSubmissions: true }).attach(); // Now when I am down in the mines looking for diamond I need to.. dig();
We give this file as an input to our minification tool:
uglifyjs example.js --source-map example.js.map
And we are given two new files, a minified file example.min.js:
var apiKey="BxyGQtWjGVMsgRxMoJsJxw==";function profit(){}function swingPickaxe(){}function openEyes(){brain.trigger("eyes.open")}function checkForCreepers(){console.log("Checking for those horrid green dudes");openEyes()}function scanForDiamonds(){var i;i=0;checkForCreepers(i);swingPickaxe();profit(9001)}function dig(){var foo,bar;var baz;foo="foo!";scanForDiamonds(foo)}Raygun.init(apiKey,{allowInsecureSubmissions:true}).attach();dig();
//# sourceMappingURL=sm.js.map
and our map file:
{"version":3,"file":"sm.min.js","sources":["sm.js"],"names":["apiKey","profit","swingPickaxe","openEyes","brain","trigger","checkForCreepers","console","log","scanForDiamonds","i","dig","foo","bar","baz","Raygun","init","allowInsecureSubmissions","attach"],"mappings":"AAAA,GAAIA,QAAS,0BAEb,SAASC,WAIT,QAASC,iBAIT,QAASC,YACPC,MAAMC,QAAQ,aAGhB,QAASC,oBACPC,QAAQC,IAAI,wCACZL,YAGF,QAASM,mBACR,GAAIC,EAEJA,GAAI,CACJJ,kBAAiBI,EACjBR,eAEAD,QAAO,MAGR,QAASU,OACR,GAAIC,KAAKC,GACT,IAAIC,IACJF,KAAM,MACNH,iBAAgBG,KAIjBG,OAAOC,KAAKhB,QAAUiB,yBAA0B,OAAQC,QAGxDP","sourcesContent":["var apiKey = 'BxyGQtWjGVMsgRxMoJsJxw==';\r\n\r\nfunction profit()\r\n{\r\n}\r\n\r\nfunction swingPickaxe()\r\n{\r\n}\r\n\r\nfunction openEyes() {\r\n brain.trigger(\"eyes.open\");\r\n}\r\n\r\nfunction checkForCreepers() {\r\n console.log('Checking for those horrid green dudes');\r\n openEyes();\r\n}\r\n\r\nfunction scanForDiamonds() {\r\n var i;\r\n \r\n i = 0; \r\n checkForCreepers(i);\r\n swingPickaxe();\r\n // ...\r\n profit(9001);\r\n}\r\n\r\nfunction dig() {\r\n var foo, bar;\r\n var baz; \r\n foo = \"foo!\";\r\n scanForDiamonds(foo);\r\n}\r\n\r\n// Attach Raygun to send me details of any errors\r\nRaygun.init(apiKey, { allowInsecureSubmissions: true }).attach();\r\n\r\n// Now when I am down in the mines looking for diamond I need to..\r\ndig();\r\n"]}
We can now deploy the minified version of our code to our website.
Unfortunately the code has a bug. After it’s deployed you see an error message in your console:
Uncaught ReferenceError: brain is not defined
at openEyes (example.min.js:1) at checkForCreepers (example.min.js:1) at scanForDiamonds (example.min.js:1) at dig (example.min.js:1) at example.min.js:1
All the line numbers are the same. This is because the minified code only has a single line. When you refer back to your source code you might be able to spot the origin of the code just based on the method names, however sometimes even this is impossible especially if your method names are local and are minified to single characters in which case your error message might look more like this:
Uncaught ReferenceError: a is not defined
at b (example.min.js:1) at c (example.min.js:1) at d (example.min.js:1) at e (example.min.js:1) at example.min.js:1
To determine where the bug has occurred you could hunt through the source code based on the vague clues given, but the better option is to utilize the source map that was generated along with the minified code.
Making the source map available to your internet browser by serving it alongside the minified file allows Chrome to automatically retrieve the map file and provide a mapped stacktrace for you.
The error message now looks like this:
Uncaught ReferenceError: brain is not defined
at openEyes (example.js:12) at checkForCreepers (example.js:17) at scanForDiamonds (example.js:24) at dig (example.js:34) at example.js:41
Line numbers now reference the source code instead of the minified file, this makes finding the bug very easy:
function openEyes() {
brain.trigger("eyes.open"); // causes reference error, we need to ensure brain is initialized before calling
}
Other software can also make use of source maps to make errors more readable. For instance Raygun Crash Reporting can automatically fetch source map files for minified files allowing you to debug errors reported from clients all over the world. It even provides a means to store source maps privately if you don’t want to expose your JavaScript source code to the public.
**Hopefully this article has answered your question “what are JavaScript source maps?” Feel free to utilize our JavaScript source map validator here once you have written your code to double check your formatting. **
Have any questions about JavaScript source maps? Leave a comment below.