Skip to content

Commit 553e2da

Browse files
committed
Merge pull request #96 from kpdecker/chained-retrieve
Allow for multiple install calls
2 parents 793a35a + b40cc25 commit 553e2da

File tree

2 files changed

+100
-28
lines changed

2 files changed

+100
-28
lines changed

source-map-support.js

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ var path = require('path');
33
var fs = require('fs');
44

55
// Only install once if called multiple times
6-
var alreadyInstalled = false;
6+
var errorFormatterInstalled = false;
7+
var uncaughtShimInstalled = false;
78

89
// If true, the caches are reset before a stack trace formatting operation
910
var emptyCacheBetweenOperations = false;
@@ -20,6 +21,10 @@ var sourceMapCache = {};
2021
// Regex for detecting source maps
2122
var reSourceMap = /^data:application\/json[^,]+base64,/;
2223

24+
// Priority list of retrieve handlers
25+
var retrieveFileHandlers = [];
26+
var retrieveMapHandlers = [];
27+
2328
function isInBrowser() {
2429
if (environment === "browser")
2530
return true;
@@ -32,7 +37,21 @@ function hasGlobalProcessEventEmitter() {
3237
return ((typeof process === 'object') && (process !== null) && (typeof process.on === 'function'));
3338
}
3439

35-
function retrieveFile(path) {
40+
function handlerExec(list) {
41+
return function(arg) {
42+
for (var i = 0; i < list.length; i++) {
43+
var ret = list[i](arg);
44+
if (ret) {
45+
return ret;
46+
}
47+
}
48+
return null;
49+
};
50+
}
51+
52+
var retrieveFile = handlerExec(retrieveFileHandlers);
53+
54+
retrieveFileHandlers.push(function(path) {
3655
// Trim the path to make sure there is no extra whitespace.
3756
path = path.trim();
3857
if (path in fileContentsCache) {
@@ -60,7 +79,7 @@ function retrieveFile(path) {
6079
}
6180

6281
return fileContentsCache[path] = contents;
63-
}
82+
});
6483

6584
// Support URLs relative to a directory, but be careful about a protocol prefix
6685
// in case we are in the browser (i.e. directories may start with "http://")
@@ -106,7 +125,8 @@ function retrieveSourceMapURL(source) {
106125
// there is no source map. The map field may be either a string or the parsed
107126
// JSON object (ie, it must be a valid argument to the SourceMapConsumer
108127
// constructor).
109-
function retrieveSourceMap(source) {
128+
var retrieveSourceMap = handlerExec(retrieveMapHandlers);
129+
retrieveMapHandlers.push(function(source) {
110130
var sourceMappingURL = retrieveSourceMapURL(source);
111131
if (!sourceMappingURL) return null;
112132

@@ -131,7 +151,7 @@ function retrieveSourceMap(source) {
131151
url: sourceMappingURL,
132152
map: sourceMapData
133153
};
134-
}
154+
});
135155

136156
function mapSourcePosition(position) {
137157
var sourceMap = sourceMapCache[position.source];
@@ -393,7 +413,7 @@ function shimEmitUncaughtException () {
393413
}
394414

395415
return origEmit.apply(this, arguments);
396-
}
416+
};
397417
}
398418

399419
exports.wrapCallSite = wrapCallSite;
@@ -402,33 +422,50 @@ exports.mapSourcePosition = mapSourcePosition;
402422
exports.retrieveSourceMap = retrieveSourceMap;
403423

404424
exports.install = function(options) {
405-
if (!alreadyInstalled) {
406-
alreadyInstalled = true;
407-
Error.prepareStackTrace = prepareStackTrace;
425+
options = options || {};
408426

409-
// Configure options
410-
options = options || {};
411-
var installHandler = 'handleUncaughtExceptions' in options ?
412-
options.handleUncaughtExceptions : true;
413-
427+
if (options.environment) {
428+
environment = options.environment;
429+
if (["node", "browser", "auto"].indexOf(environment) === -1) {
430+
throw new Error("environment " + environment + " was unknown. Available options are {auto, browser, node}")
431+
}
432+
}
433+
434+
// Allow sources to be found by methods other than reading the files
435+
// directly from disk.
436+
if (options.retrieveFile) {
437+
if (options.overrideRetrieveFile) {
438+
retrieveFileHandlers.length = 0;
439+
}
440+
441+
retrieveFileHandlers.unshift(options.retrieveFile);
442+
}
443+
444+
// Allow source maps to be found by methods other than reading the files
445+
// directly from disk.
446+
if (options.retrieveSourceMap) {
447+
if (options.overrideRetrieveSourceMap) {
448+
retrieveMapHandlers.length = 0;
449+
}
450+
451+
retrieveMapHandlers.unshift(options.retrieveSourceMap);
452+
}
453+
454+
// Configure options
455+
if (!emptyCacheBetweenOperations) {
414456
emptyCacheBetweenOperations = 'emptyCacheBetweenOperations' in options ?
415457
options.emptyCacheBetweenOperations : false;
458+
}
416459

417-
if (options.environment) {
418-
environment = options.environment;
419-
if (["node", "browser", "auto"].indexOf(environment) === -1)
420-
throw new Error("environment " + environment + " was unknown. Available options are {auto, browser, node}")
421-
}
422-
423-
// Allow sources to be found by methods other than reading the files
424-
// directly from disk.
425-
if (options.retrieveFile)
426-
retrieveFile = options.retrieveFile;
460+
// Install the error reformatter
461+
if (!errorFormatterInstalled) {
462+
errorFormatterInstalled = true;
463+
Error.prepareStackTrace = prepareStackTrace;
464+
}
427465

428-
// Allow source maps to be found by methods other than reading the files
429-
// directly from disk.
430-
if (options.retrieveSourceMap)
431-
retrieveSourceMap = options.retrieveSourceMap;
466+
if (!uncaughtShimInstalled) {
467+
var installHandler = 'handleUncaughtExceptions' in options ?
468+
options.handleUncaughtExceptions : true;
432469

433470
// Provide the option to not install the uncaught exception handler. This is
434471
// to support other uncaught exception handlers (in test frameworks, for
@@ -438,6 +475,7 @@ exports.install = function(options) {
438475
// generated JavaScript code will be shown above the stack trace instead of
439476
// the original source code.
440477
if (installHandler && hasGlobalProcessEventEmitter()) {
478+
uncaughtShimInstalled = true;
441479
shimEmitUncaughtException();
442480
}
443481
}

test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ it('missing source maps should also be cached', function(done) {
388388
' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
389389
'}',
390390
'require("./source-map-support").install({',
391+
' overrideRetrieveSourceMap: true,',
391392
' retrieveSourceMap: function(name) {',
392393
' if (/\\.generated.js$/.test(name)) count++;',
393394
' return null;',
@@ -405,6 +406,39 @@ it('missing source maps should also be cached', function(done) {
405406
]);
406407
});
407408

409+
it('should consult all retrieve source map providers', function(done) {
410+
compareStdout(done, createSingleLineSourceMap(), [
411+
'',
412+
'var count = 0;',
413+
'function foo() {',
414+
' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
415+
'}',
416+
'require("./source-map-support").install({',
417+
' retrieveSourceMap: function(name) {',
418+
' if (/\\.generated.js$/.test(name)) count++;',
419+
' return undefined;',
420+
' }',
421+
'});',
422+
'require("./source-map-support").install({',
423+
' retrieveSourceMap: function(name) {',
424+
' if (/\\.generated.js$/.test(name)) {',
425+
' count++;',
426+
' return ' + JSON.stringify({url: '.original.js', map: createMultiLineSourceMapWithSourcesContent().toJSON()}) + ';',
427+
' }',
428+
' }',
429+
'});',
430+
'process.nextTick(foo);',
431+
'process.nextTick(foo);',
432+
'process.nextTick(function() { console.log(count); });',
433+
], [
434+
'Error: this is the error',
435+
/^ at foo \(.*\/original.js:1004:5\)$/,
436+
'Error: this is the error',
437+
/^ at foo \(.*\/original.js:1004:5\)$/,
438+
'1', // The retrieval should only be attempted once
439+
]);
440+
});
441+
408442
/* The following test duplicates some of the code in
409443
* `compareStackTrace` but appends a charset to the
410444
* source mapping url.

0 commit comments

Comments
 (0)