Skip to content

Commit c7db746

Browse files
authored
Merge pull request #23 from ejose19/ej/sourceMapSupportRedirect
refactor: redirect subsequent imports of "source-map-support" to this…
2 parents a1abb8c + a4e1648 commit c7db746

File tree

3 files changed

+121
-2
lines changed

3 files changed

+121
-2
lines changed

source-map-support.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ export interface Options {
3131
overrideRetrieveSourceMap?: boolean | undefined;
3232
retrieveFile?(path: string): string;
3333
retrieveSourceMap?(source: string): UrlAndMap | null;
34+
/**
35+
* Set false to disable redirection of require / import `source-map-support` to `@cspotcode/source-map-support`
36+
*/
37+
redirectConflictingLibrary?: boolean;
38+
/**
39+
* Callback will be called every time we redirect due to `redirectConflictingLibrary`
40+
* This allows consumers to log helpful warnings if they choose.
41+
* @param parent NodeJS.Module which made the require() or require.resolve() call
42+
*/
43+
onConflictingLibraryRedirect?: (request: string, parent: any, isMain: boolean, redirectedRequest: string) => void;
3444
}
3545

3646
export interface Position {

source-map-support.js

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ var sharedData = initializeSharedData({
7777
errorPrepareStackTraceHook: undefined,
7878
/** @type {HookState} */
7979
processEmitHook: undefined,
80+
/** @type {HookState} */
81+
moduleResolveFilenameHook: undefined,
82+
83+
/** @type {Array<(request: string, parent: any, isMain: boolean, redirectedRequest: string) => void>} */
84+
onConflictingLibraryRedirectArr: [],
8085

8186
// If true, the caches are reset before a stack trace formatting operation
8287
emptyCacheBetweenOperations: false,
@@ -633,6 +638,47 @@ exports.install = function(options) {
633638
}
634639
}
635640

641+
// Use dynamicRequire to avoid including in browser bundles
642+
var Module = dynamicRequire(module, 'module');
643+
644+
// Redirect subsequent imports of "source-map-support"
645+
// to this package
646+
const {redirectConflictingLibrary = true, onConflictingLibraryRedirect} = options;
647+
if(redirectConflictingLibrary) {
648+
if (!sharedData.moduleResolveFilenameHook) {
649+
const originalValue = Module._resolveFilename;
650+
const moduleResolveFilenameHook = sharedData.moduleResolveFilenameHook = {
651+
enabled: true,
652+
originalValue,
653+
installedValue: undefined,
654+
}
655+
Module._resolveFilename = sharedData.moduleResolveFilenameHook.installedValue = function (request, parent, isMain, options) {
656+
if (moduleResolveFilenameHook.enabled) {
657+
// Match all source-map-support entrypoints: source-map-support, source-map-support/register
658+
let requestRedirect;
659+
if (request === 'source-map-support') {
660+
requestRedirect = './';
661+
} else if (request === 'source-map-support/register') {
662+
requestRedirect = './register';
663+
}
664+
665+
if (requestRedirect !== undefined) {
666+
const newRequest = require.resolve(requestRedirect);
667+
for (const cb of sharedData.onConflictingLibraryRedirectArr) {
668+
cb(request, parent, isMain, options, newRequest);
669+
}
670+
request = newRequest;
671+
}
672+
}
673+
674+
return originalValue.call(this, request, parent, isMain, options);
675+
}
676+
}
677+
if (onConflictingLibraryRedirect) {
678+
sharedData.onConflictingLibraryRedirectArr.push(onConflictingLibraryRedirect);
679+
}
680+
}
681+
636682
// Allow sources to be found by methods other than reading the files
637683
// directly from disk.
638684
if (options.retrieveFile) {
@@ -655,8 +701,6 @@ exports.install = function(options) {
655701

656702
// Support runtime transpilers that include inline source maps
657703
if (options.hookRequire && !isInBrowser()) {
658-
// Use dynamicRequire to avoid including in browser bundles
659-
var Module = dynamicRequire(module, 'module');
660704
var $compile = Module.prototype._compile;
661705

662706
if (!$compile.__sourceMapSupport) {
@@ -738,6 +782,17 @@ exports.uninstall = function() {
738782
}
739783
sharedData.errorPrepareStackTraceHook = undefined;
740784
}
785+
if (sharedData.moduleResolveFilenameHook) {
786+
// Disable behavior
787+
sharedData.moduleResolveFilenameHook.enabled = false;
788+
// If possible, remove our hook function. May not be possible if subsequent third-party hooks have wrapped around us.
789+
var Module = dynamicRequire(module, 'module');
790+
if(Module._resolveFilename === sharedData.moduleResolveFilenameHook.installedValue) {
791+
Module._resolveFilename = sharedData.moduleResolveFilenameHook.originalValue;
792+
}
793+
sharedData.moduleResolveFilenameHook = undefined;
794+
}
795+
sharedData.onConflictingLibraryRedirectArr.length = 0;
741796
}
742797

743798
exports.resetRetrieveHandlers = function() {

test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Note: some tests rely on side-effects from prior tests.
22
// You may not get meaningful results running a subset of tests.
33

4+
const Module = require('module');
45
const priorErrorPrepareStackTrace = Error.prepareStackTrace;
56
const priorProcessEmit = process.emit;
7+
const priorResolveFilename = Module._resolveFilename;
68
const underTest = require('./source-map-support');
79
var SourceMapGenerator = require('source-map').SourceMapGenerator;
810
var child_process = require('child_process');
@@ -704,16 +706,52 @@ it('supports multiple instances', function(done) {
704706
]);
705707
});
706708

709+
describe('redirects require() of "source-map-support" to this module', function() {
710+
it('redirects', function() {
711+
assert.strictEqual(require.resolve('source-map-support'), require.resolve('.'));
712+
assert.strictEqual(require.resolve('source-map-support/register'), require.resolve('./register'));
713+
assert.strictEqual(require('source-map-support'), require('.'));
714+
});
715+
716+
it('emits notifications', function() {
717+
let onConflictingLibraryRedirectCalls = [];
718+
let onConflictingLibraryRedirectCalls2 = [];
719+
underTest.install({
720+
onConflictingLibraryRedirect(request, parent, isMain, redirectedRequest) {
721+
onConflictingLibraryRedirectCalls.push([...arguments]);
722+
}
723+
});
724+
underTest.install({
725+
onConflictingLibraryRedirect(request, parent, isMain, redirectedRequest) {
726+
onConflictingLibraryRedirectCalls2.push([...arguments]);
727+
}
728+
});
729+
require.resolve('source-map-support');
730+
assert.strictEqual(onConflictingLibraryRedirectCalls.length, 1);
731+
assert.strictEqual(onConflictingLibraryRedirectCalls2.length, 1);
732+
for(const args of [onConflictingLibraryRedirectCalls[0], onConflictingLibraryRedirectCalls2[0]]) {
733+
const [request, parent, isMain, options, redirectedRequest] = args;
734+
assert.strictEqual(request, 'source-map-support');
735+
assert.strictEqual(parent, module);
736+
assert.strictEqual(isMain, false);
737+
assert.strictEqual(options, undefined);
738+
assert.strictEqual(redirectedRequest, require.resolve('.'));
739+
}
740+
});
741+
});
742+
707743
describe('uninstall', function() {
708744
this.beforeEach(function() {
709745
underTest.uninstall();
710746
process.emit = priorProcessEmit;
711747
Error.prepareStackTrace = priorErrorPrepareStackTrace;
748+
Module._resolveFilename = priorResolveFilename;
712749
});
713750

714751
it('uninstall removes hooks and source-mapping behavior', function() {
715752
assert.strictEqual(Error.prepareStackTrace, priorErrorPrepareStackTrace);
716753
assert.strictEqual(process.emit, priorProcessEmit);
754+
assert.strictEqual(Module._resolveFilename, priorResolveFilename);
717755
normalThrowWithoutSourceMapSupportInstalled();
718756
});
719757

@@ -773,4 +811,20 @@ describe('uninstall', function() {
773811
process.emit('foo');
774812
assert(peInvocations >= 1);
775813
});
814+
815+
it('uninstall preserves third-party module._resolveFilename hooks installed after us', function() {
816+
installSms();
817+
const wrappedResolveFilename = Module._resolveFilename;
818+
let peInvocations = 0;
819+
function thirdPartyModuleResolveFilename() {
820+
peInvocations++;
821+
return wrappedResolveFilename.apply(this, arguments);
822+
}
823+
Module._resolveFilename = thirdPartyModuleResolveFilename;
824+
underTest.uninstall();
825+
assert.strictEqual(Module._resolveFilename, thirdPartyModuleResolveFilename);
826+
normalThrowWithoutSourceMapSupportInstalled();
827+
Module._resolveFilename('repl');
828+
assert(peInvocations >= 1);
829+
});
776830
});

0 commit comments

Comments
 (0)