Skip to content

Commit b40cc25

Browse files
committed
Allow for multiple install calls
This allows for multiple libraries to utilize this without stepping on each other’s toes. This makes the retrieve helpers additive, giving priority to the last retrieve method registered. In the event of a miss, the next method is called up to and including the default behavior. The `overrideRetriveFile` and `overrideRetrieveSourceMap` flags are added if this behavior is not desired but this is something that most libraries should avoid to be good citizens. Flagged behaviors will now be on if any of the install calls dictate that they would be. Fix for #91
1 parent 793a35a commit b40cc25

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)