Skip to content

Commit

Permalink
Merge pull request #23 from ejose19/ej/sourceMapSupportRedirect
Browse files Browse the repository at this point in the history
refactor: redirect subsequent imports of "source-map-support" to this…
  • Loading branch information
cspotcode authored Oct 6, 2021
2 parents a1abb8c + a4e1648 commit c7db746
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 2 deletions.
10 changes: 10 additions & 0 deletions source-map-support.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ export interface Options {
overrideRetrieveSourceMap?: boolean | undefined;
retrieveFile?(path: string): string;
retrieveSourceMap?(source: string): UrlAndMap | null;
/**
* Set false to disable redirection of require / import `source-map-support` to `@cspotcode/source-map-support`
*/
redirectConflictingLibrary?: boolean;
/**
* Callback will be called every time we redirect due to `redirectConflictingLibrary`
* This allows consumers to log helpful warnings if they choose.
* @param parent NodeJS.Module which made the require() or require.resolve() call
*/
onConflictingLibraryRedirect?: (request: string, parent: any, isMain: boolean, redirectedRequest: string) => void;
}

export interface Position {
Expand Down
59 changes: 57 additions & 2 deletions source-map-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ var sharedData = initializeSharedData({
errorPrepareStackTraceHook: undefined,
/** @type {HookState} */
processEmitHook: undefined,
/** @type {HookState} */
moduleResolveFilenameHook: undefined,

/** @type {Array<(request: string, parent: any, isMain: boolean, redirectedRequest: string) => void>} */
onConflictingLibraryRedirectArr: [],

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

// Use dynamicRequire to avoid including in browser bundles
var Module = dynamicRequire(module, 'module');

// Redirect subsequent imports of "source-map-support"
// to this package
const {redirectConflictingLibrary = true, onConflictingLibraryRedirect} = options;
if(redirectConflictingLibrary) {
if (!sharedData.moduleResolveFilenameHook) {
const originalValue = Module._resolveFilename;
const moduleResolveFilenameHook = sharedData.moduleResolveFilenameHook = {
enabled: true,
originalValue,
installedValue: undefined,
}
Module._resolveFilename = sharedData.moduleResolveFilenameHook.installedValue = function (request, parent, isMain, options) {
if (moduleResolveFilenameHook.enabled) {
// Match all source-map-support entrypoints: source-map-support, source-map-support/register
let requestRedirect;
if (request === 'source-map-support') {
requestRedirect = './';
} else if (request === 'source-map-support/register') {
requestRedirect = './register';
}

if (requestRedirect !== undefined) {
const newRequest = require.resolve(requestRedirect);
for (const cb of sharedData.onConflictingLibraryRedirectArr) {
cb(request, parent, isMain, options, newRequest);
}
request = newRequest;
}
}

return originalValue.call(this, request, parent, isMain, options);
}
}
if (onConflictingLibraryRedirect) {
sharedData.onConflictingLibraryRedirectArr.push(onConflictingLibraryRedirect);
}
}

// Allow sources to be found by methods other than reading the files
// directly from disk.
if (options.retrieveFile) {
Expand All @@ -655,8 +701,6 @@ exports.install = function(options) {

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

if (!$compile.__sourceMapSupport) {
Expand Down Expand Up @@ -738,6 +782,17 @@ exports.uninstall = function() {
}
sharedData.errorPrepareStackTraceHook = undefined;
}
if (sharedData.moduleResolveFilenameHook) {
// Disable behavior
sharedData.moduleResolveFilenameHook.enabled = false;
// If possible, remove our hook function. May not be possible if subsequent third-party hooks have wrapped around us.
var Module = dynamicRequire(module, 'module');
if(Module._resolveFilename === sharedData.moduleResolveFilenameHook.installedValue) {
Module._resolveFilename = sharedData.moduleResolveFilenameHook.originalValue;
}
sharedData.moduleResolveFilenameHook = undefined;
}
sharedData.onConflictingLibraryRedirectArr.length = 0;
}

exports.resetRetrieveHandlers = function() {
Expand Down
54 changes: 54 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Note: some tests rely on side-effects from prior tests.
// You may not get meaningful results running a subset of tests.

const Module = require('module');
const priorErrorPrepareStackTrace = Error.prepareStackTrace;
const priorProcessEmit = process.emit;
const priorResolveFilename = Module._resolveFilename;
const underTest = require('./source-map-support');
var SourceMapGenerator = require('source-map').SourceMapGenerator;
var child_process = require('child_process');
Expand Down Expand Up @@ -704,16 +706,52 @@ it('supports multiple instances', function(done) {
]);
});

describe('redirects require() of "source-map-support" to this module', function() {
it('redirects', function() {
assert.strictEqual(require.resolve('source-map-support'), require.resolve('.'));
assert.strictEqual(require.resolve('source-map-support/register'), require.resolve('./register'));
assert.strictEqual(require('source-map-support'), require('.'));
});

it('emits notifications', function() {
let onConflictingLibraryRedirectCalls = [];
let onConflictingLibraryRedirectCalls2 = [];
underTest.install({
onConflictingLibraryRedirect(request, parent, isMain, redirectedRequest) {
onConflictingLibraryRedirectCalls.push([...arguments]);
}
});
underTest.install({
onConflictingLibraryRedirect(request, parent, isMain, redirectedRequest) {
onConflictingLibraryRedirectCalls2.push([...arguments]);
}
});
require.resolve('source-map-support');
assert.strictEqual(onConflictingLibraryRedirectCalls.length, 1);
assert.strictEqual(onConflictingLibraryRedirectCalls2.length, 1);
for(const args of [onConflictingLibraryRedirectCalls[0], onConflictingLibraryRedirectCalls2[0]]) {
const [request, parent, isMain, options, redirectedRequest] = args;
assert.strictEqual(request, 'source-map-support');
assert.strictEqual(parent, module);
assert.strictEqual(isMain, false);
assert.strictEqual(options, undefined);
assert.strictEqual(redirectedRequest, require.resolve('.'));
}
});
});

describe('uninstall', function() {
this.beforeEach(function() {
underTest.uninstall();
process.emit = priorProcessEmit;
Error.prepareStackTrace = priorErrorPrepareStackTrace;
Module._resolveFilename = priorResolveFilename;
});

it('uninstall removes hooks and source-mapping behavior', function() {
assert.strictEqual(Error.prepareStackTrace, priorErrorPrepareStackTrace);
assert.strictEqual(process.emit, priorProcessEmit);
assert.strictEqual(Module._resolveFilename, priorResolveFilename);
normalThrowWithoutSourceMapSupportInstalled();
});

Expand Down Expand Up @@ -773,4 +811,20 @@ describe('uninstall', function() {
process.emit('foo');
assert(peInvocations >= 1);
});

it('uninstall preserves third-party module._resolveFilename hooks installed after us', function() {
installSms();
const wrappedResolveFilename = Module._resolveFilename;
let peInvocations = 0;
function thirdPartyModuleResolveFilename() {
peInvocations++;
return wrappedResolveFilename.apply(this, arguments);
}
Module._resolveFilename = thirdPartyModuleResolveFilename;
underTest.uninstall();
assert.strictEqual(Module._resolveFilename, thirdPartyModuleResolveFilename);
normalThrowWithoutSourceMapSupportInstalled();
Module._resolveFilename('repl');
assert(peInvocations >= 1);
});
});

0 comments on commit c7db746

Please sign in to comment.