Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: redirect subsequent imports of "source-map-support" to this… #23

Merged
merged 12 commits into from Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions source-map-support.d.ts
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
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) {
ejose19 marked this conversation as resolved.
Show resolved Hide resolved
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
@@ -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);
});
});