In today's modern workflow, the code that we author in our development environments is considerably different from the production code, after running it through compilation, minification, concatenation, or various other optimization processes.
This is where source maps come into play, by pointing out the exact mapping in our production code to the original authored code. In this introductory tutorial, we'll take a simple project, and run it through various JavaScript compilers for the purposes of playing with source maps in the browser.
What are Source Maps?
Source maps offer a language-agnostic way of mapping production code to the original code that was authored.
Source maps offer a language-agnostic way of mapping production code to the original code that was authored in your development environment. When we ultimately look at the code-base, generated and prepared for production, it becomes very challenging to locate exactly where the line mapping to our original authored code is. However, during compilation, a source map stores this information, so that, when we query a line section, it will return the exact location in the original file to us! This offers a huge advantage for the developer, as the code then becomes readable - and even debuggable!
In this tutorial, we'll take a very simple bit of JavaScript and SASS code, run them through various compilers, and then view our original files in the browser with the help of source maps. Go ahead and download the demo files and let's get started!
Browsers
Please note that, while writing this article, Chrome (Version 23) supports JavaScript Source Maps, and even SASS Source Maps. Firefox should also gain support in the near future, as it's currently in an active stage of development. With that word of caution out of the way, let's now see how we can take advantage of source maps in the browser!
Source Maps in Chrome
First, we must enable support in Chrome, using the following simple steps:
- Open Chrome Developer Tools: View -> Developer -> Developer Tools
- Click the "Settings" cog in the bottom-right corner
- Choose "General," and select "Enable source maps"
Setup
If you'd like to work along with this tutorial, download the demo and open the "start" directory. The files and directory structure is quite basic, with some simple JavaScript in scripts/script.js
. You should be able to open index.html
and even add some CSS color names or hex values to amend the background color.
$ tree . ├── index.html ├── scripts │ ├── jquery.d.ts │ ├── script.coffee.coffee │ ├── script.js │ └── script.typescript.ts └── styles ├── style.css └── style.sass
Have a look through the simple script files in plain JavaScript, TypeScript or CoffeeScript. Using various JavaScript compilers, we'll create a production-ready version, as well as generate the corresponding source maps.
In the following sections, we'll use five different ways to generate a compiled and minified script.js
, along with the associated source map. You can either choose to test out all of the options, or simply go with the compiler that you are already familiar with. These options include:
Option A: Closure Compiler
Closure Compiler, by Google, is a tool for optimizing JavaScript. It does this by analyzing your code, removing irrelevant bits, and then minifying the rest. On top of that, it can also generate source maps.
Let's use the following steps to create an optimized version of script.js
, using the Closure compiler:
- Download the latest Closure compiler.
- Transfer the file,
compiler.jar
, to the directory,scripts
. - Navigate to the directory,
scripts
, from the command line, and execute the following, so that an optimized, production-readyscript.closure.js
file will be created:java -jar compiler.jar --js script.js --js_output_file script.closure.js
- Ensure that
index.html
is now linked with the newly created file,scripts/script.closure.js
, by uncommenting Option A.
When we open index.html
within the browser and navigate to the Source Panel in the developer tools, only the optimized version of script.closure.js
is referenced; we have no way of making a relation back to our original, properly indented. Let's next create the source map file by executing the following command in the scripts
directory:
java -jar compiler.jar --js script.js --create_source_map script.closure.js.map --source_map_format=V3 --js_output_file script.closure.js
Notice that Closure Compiler takes in two options, --create_source_map
and --source_map_format
, to create a source map file, script.closure.js.map
, with source map version 3. Next, append the source mapping url to the end of the compiled script file, script.closure.js
, so that the optimized file contains the source map location information:
//@ sourceMappingURL=script.closure.js.map
Now, when we view the project in the browser, the "scripts" directory, under the Source Panel of the developer tools, will show both the original file as well as the optimized version, script.closure.js
. Although the browser is of course using the optimized file that we originally referenced in index.html
, source maps allow us to create a connection to the original file.
Also, do try it out with breakpoints for debugging, but keep in mind that watch expressions and variables are not yet available with source maps. Hopefully, they will be in the future!
Option B: GruntJS Task for JSMin
If you already use Grunt.js for build processes, then the Grunt plugin for JSMin source maps will come in handy. Not only will it optimize your code, but it will also create the source map!
The following steps will demonstrate how to create an optimized version of script.js
with the Grunt JSMin plugin:
- install Grunt.js and initiate a gruntfile,
grunt.js
, within the root of the "start" directory:$ npm install -g grunt $ npm view grunt version npm http GET https://registry.npmjs.org/grunt npm http 200 https://registry.npmjs.org/grunt 0.3.17 $ grunt init:gruntfile
- Install the Grunt plugin grunt-jsmin-sourcemap; when you do, a directory, called
node_modules/grunt-jsmin-sourcemap
will be created:$ npm install grunt-jsmin-sourcemap
- Edit the newly created
grunt.js
file to only contain thejsmin-sourcemap
task - to keep things as simple as possible.module.exports = function(grunt) { grunt.loadNpmTasks('grunt-jsmin-sourcemap'); grunt.initConfig({ 'jsmin-sourcemap': { all: { src: ['scripts/script.js'], dest: 'scripts/script.jsmin-grunt.js', destMap: 'scripts/script.jsmin-grunt.js.map' } } }); grunt.registerTask('default', 'jsmin-sourcemap'); };
- Return to the command line, and run
grunt
; this will execute the jsmin-sourcemap task, as the default task is stated as such within the grunt.js file:$ grunt Running "jsmin-sourcemap:all" (jsmin-sourcemap) task Done, without errors.
- In the newly created source map file,
script.grunt-jsmin.js.map
, ensure that the source is"sources":["script.js"].
- Uncomment Option B to link to the newly created file,
script.grunt-jsmin.js
, withinindex.html
, and open up in the browser.
With Grunt and the plugin, jsmin-sourcemap, the build process created two files: the optimized script file with the source mapping url at the bottom, as well as a source map. You will need both of these in order to view all of them in the browser.
Option C: UglifyJS
UglifyJS2 is another JavaScript parser, minfier and compressor. Similar to the two alternatives above, UglifyJS2 will create an optimized script file, appended with a source mapping url as well as a source map file that will contain the mapping to the original file. To use UglifyJS, execute the following in the command line of the "start" directory:
- Install the NPM module,
uglify-js
, locally; a directory, callednocde_module/uglify-js
, will be created.$ npm install uglify-js $ npm view uglify-js version 2.2.3 $ cd scripts/
- Within the "scripts" directory, we'll execute the command to create an optimized version, as well as a source file with the options,
--source-map
and--output
, to name the output file.uglifyjs --source-map script.uglify.js.map --output script.uglify.js script.js
- Lastly, ensure that
index.html
is correctly linked to the script,script.uglify.js
Option D: CoffeeScript Redux
For the previous three options, we only required a one-step optimization, from the original code to the optimized JavaScript. However, for languages like CoffeeScript, we need a two-step process: CoffeeScript > JavaScript > optimised JavaScript. In this section, we will explore how to create Multi-Level Source Maps with CoffeeScript and the CoffeeScript Redux compiler.
Step 1: CoffeeScript to Plain JavaScript
Navigate to the directory, "start," in the command line. In the following steps, we will map the optimized script file back to the CoffeeScript:
- Install CoffeeScript as a global npm package
- Compile the CoffeeScript file,
script.coffee.coffee
, to create a plain JavaScript version, using the following command:$ coffee -c scripts/script.coffee.coffee
- Install CoffeeScript Redux:
$ git clone https://github.com/michaelficarra/CoffeeScriptRedux.git coffee-redux $ cd coffee-redux $ npm install $ make -j test $ cd ..
- Next, we will create a source map file,
script.coffee.js.map
, that will hold the mapping information from the generated JavaScript back to the CoffeeScript file:$ coffee-redux/bin/coffee --source-map -i scripts/script.coffee.coffee > scripts/script.coffee.js.map
- Ensure that the generated JavaScript file,
script.coffee.js
, has the source mapping url right at the end with the following line://@ sourceMappingURL=script.coffee.js.map
- Ensure that the source map file,
script.coffee.js.map
, has the correct reference file as"file":"script.coffee.coffee"
, and source file as"sources":["script.coffee.coffee"]
Step 2: Plain JavaScript to Minified JavaScript
- Finally, we will use UglifyJS once again to minify the generated JavaScript, as well as create a source map. This time, it will take in a source map so that we can refer back to the original CoffeeScript file. Execute the following command in the "scripts" directory:
$ cd scripts/ $ uglifyjs script.coffee.js -o script.coffee.min.js --source-map script.coffee.min.js.map --in-source-map script.coffee.js.map
- Finally, ensure that the source map file,
script.coffee.min.js.map
, has the correct reference file as"file":"script.coffee.min.js"
, and the correct sources as"sources":["script.coffee.coffee"]
.
Option E: TypeScript
TypeScript, just like CoffeeScript, also requires a two-step process: TypeScript > Plain JavaScript > Minified JavaScript. Because the script uses a jQuery plugin, we need two TypeScript files, which are already provided: script.typescript.ts
and jquery.d.ts
.
Step 1: TypeScript to Plain JavaScript
Navigate to the "scripts" directory from the command line, and execute the following command:
$ tsc script.typescript.ts -sourcemap
The above command will create a new JavaScript file, called script.typescript.js
, with the source mapping url at the bottom: //@ sourceMappingURL=script.typescript.js.map
. With this single command, it will also create the map file, script.typescript.js.map
.
Step 2: Plain JavaScript to Minified JavaScript
As with the CoffeeScript example, the next step is to use UglifyJS.
$ uglifyjs script.typescript.js -o script.typescript.min.js --source-map script.typescript.min.js.map --in-source-map script.typescript.js.map
Finally, ensure that index.html
links to the correct script file, scripts/script.typescript.min.js
, and open it up in the browser!
Source Maps for SASS
Beyond JavaScript, currently, Chrome also supports SASS or SCSS source maps. For SASS source mapping, let's amend a few settings in Chrome, and then compile SASS to CSS with debug parameters:
- Before changing any settings, notice that, upon inspecting an element from developer tools, it will only show us the CSS file reference. This isn't overly helpful.
- Go to chrome://flags/.
- Enable Developer Tools experiments.
- Open Dev Tools > Setting > Experiments > Check "Support for SASS".
- Compile SASS with the follow debug parameters in the "styles" directory. This will prepend each CSS Ruleset with
@media -sass-debug-info
that will have the information on the filename and the line number.$ cd styles/ $ sass --debug-info --watch style.sass:style.css
- Be sure to restart the developer tools, and refresh the page.
- Now, when we inspect an element, we can access the original SASS file!
Beyond simply viewing the SASS file, if you are running LiveReload in the background and make changes to the SASS file, the page will also update to reflect the changes. For example, let's open up the project in Firefox, and inspect the page, using the Firebug extension.
Information Within a Source Map
If we view any of the *.map
files, it will contain the mapping information from the original file to the optimised file. The structure of a source map is typically in the JSON format, using the Version 3 specifications. It will usually contain the following five properties:
- version: Version number of the source map - typically "3."
- file: Name of the optimized file.
- sources: Names of the original files.
- names: Symbols used for mapping.
- mappings: Mapping data.
Additional Resources
Source maps are still very much under active development, but, already, there are some great resources available around the web. Be sure to consider the following, if you'd like to learn more.
- Introduction to JavaScript Source Maps by Ryan Seddon, HTML5 Rocks
- The Breakpoint Episode 3: JavaScript Source Maps by the Google Developer Team
- The Breakpoint Episode 2: SASS Source Maps by the Google Developer Team
- Source Maps wiki on languages, tools, articles on Source Maps
- Multi Level Source Maps with CoffeeScript and TypeScript by Ryan Seddon
- Source Maps Version 3 proposal
Conclusion
I hope that the above walk-through, using multiple compilers, has demonstrated the potential of source maps. Although the functionality is currently limited, hopefully, in the future, we'll have full debugging capability, including access to variables and expressions.
Comments