Source maps for Emacs
Source maps turn out to be a handy tool not only for debugging minified code, but also for debugging the minifier ;-) My use case is this: (1) minify a big JS code, but request indented output. Then open that uglified code in Emacs and look at it, tryin' to figure out if compression is correct or wrong, and how could I make it better. It's extremely useful at this point if I could quickly jump to the original source, at the location where the thing under the cursor is coming from, starting from the minified file.
So I wrote a quick'n'dirty script that takes a source map and outputs information about the original location at-a-given-point. Here's the script, I'm calling it source-map-query.js:
#! /usr/bin/env node
// requires fitzgen's source-map library, available via NPM
var fs = require("fs");
// discard stderr since node warns about API change and this disturbs
// the emacs side. ("path.existsSync is now called `fs.existsSync`.")
var devnull = fs.createWriteStream("/dev/null");
process.__defineGetter__("stderr", function(){
return devnull;
});
var SM = require("source-map");
var sys = require("util");
var FILE = process.argv[2];
var LINE = parseInt(process.argv[3], 10);
var COL = parseInt(process.argv[4], 10);
var map = fs.readFileSync(FILE, "utf8");
var reader = new SM.SourceMapConsumer(map);
var info = reader.originalPositionFor({
line: LINE,
column: COL
});
sys.print(JSON.stringify(info));
It takes 3 arguments: the name of the source map file and two integers — the line and column in the minified file that we want to query.
Now, to make it easily available in Emacs I added the following function in my ~/.emacs:
(defun query-source-map ()
(interactive)
(let* ((name (buffer-name))
(mapname (format "%s.map" name)))
(if (file-exists-p mapname)
(let* ((line (line-number-at-pos))
(col (current-column))
(json (shell-command-to-string
(format "source-map-query.js %s %s %s"
mapname line col)))
(info (json-read-from-string json))
(file (cdr (assoc 'source info)))
(line (cdr (assoc 'line info)))
(col (cdr (assoc 'column info)))
(name (cdr (assoc 'name info))))
(if file
(progn
(find-file file)
(goto-line line)
(forward-char col))
(message "Cannot determine original location")))
(message "Source map (%s) not found" mapname))))
Then I've bound it to C-M-.:
(define-key js-mode-map (kbd "C-M-.") 'query-source-map)
I minify my test code like this:
uglifyjs2 -b --source-map jq.js.map -o jq.js jquery-1.8.1.js
This generates the compressed code in jq.js and the source map in jq.js.map. Next I open jq.js in Emacs, go to some random position and press C-M-., and it takes me (close) to the original location of the token under the cursor in jquery-1.8.1.js.
This turns out to be useful for debugging my source map generation too.
Enjoy!