require-in-the-middle
Advanced tools
Comparing version 6.0.0 to 7.0.0
89
index.js
@@ -7,5 +7,10 @@ 'use strict' | ||
const debug = require('debug')('require-in-the-middle') | ||
const parse = require('module-details-from-path') | ||
const moduleDetailsFromPath = require('module-details-from-path') | ||
const assert = require('assert') | ||
// Using the default export is discouraged, but kept for backward compatibility. | ||
// Use this instead: | ||
// const { Hook } = require('require-in-the-middle') | ||
module.exports = Hook | ||
module.exports.Hook = Hook | ||
@@ -32,2 +37,44 @@ /** | ||
// Cache `onrequire`-patched exports for modules. | ||
// | ||
// Exports for built-in (a.k.a. "core") modules are stored in an internal Map. | ||
// Exports for non-core modules are stored on a private field on the `Module` | ||
// object in `require.cache`. This allows users to delete from `require.cache` | ||
// to trigger a re-load (and re-run of the hook's `onrequire`) of a module the | ||
// next time it is required. | ||
// https://nodejs.org/docs/latest/api/all.html#all_modules_requirecache | ||
class ExportsCache { | ||
constructor () { | ||
this._exportsFromBuiltinId = new Map() | ||
this._kRitmExports = Symbol('RitmExports') | ||
} | ||
has (filename, isBuiltin) { | ||
if (isBuiltin) { | ||
return this._exportsFromBuiltinId.has(filename) | ||
} else { | ||
const mod = require.cache[filename] | ||
return !!(mod && this._kRitmExports in mod) | ||
} | ||
} | ||
get (filename, isBuiltin) { | ||
if (isBuiltin) { | ||
return this._exportsFromBuiltinId.get(filename) | ||
} else { | ||
const mod = require.cache[filename] | ||
return (mod && mod[this._kRitmExports]) | ||
} | ||
} | ||
set (filename, exports, isBuiltin) { | ||
if (isBuiltin) { | ||
this._exportsFromBuiltinId.set(filename, exports) | ||
} else { | ||
assert(filename in require.cache, `unexpected that there is no Module entry for "${filename}" in require.cache`) | ||
require.cache[filename][this._kRitmExports] = exports | ||
} | ||
} | ||
} | ||
function Hook (modules, options, onrequire) { | ||
@@ -50,3 +97,4 @@ if ((this instanceof Hook) === false) return new Hook(modules, options, onrequire) | ||
this.cache = new Map() | ||
this._cache = new ExportsCache() | ||
this._unhooked = false | ||
@@ -84,3 +132,16 @@ this._origRequire = Module.prototype.require | ||
} else { | ||
filename = Module._resolveFilename(id, this) | ||
try { | ||
filename = Module._resolveFilename(id, this) | ||
} catch (resolveErr) { | ||
// If someone *else* monkey-patches before this monkey-patch, then that | ||
// code might expect `require(someId)` to get through so it can be | ||
// handled, even if `someId` cannot be resolved to a filename. In this | ||
// case, instead of throwing we defer to the underlying `require`. | ||
// | ||
// For example the Azure Functions Node.js worker module does this, | ||
// where `@azure/functions-core` resolves to an internal object. | ||
// https://github.com/Azure/azure-functions-nodejs-worker/blob/v3.5.2/src/setupCoreModule.ts#L46-L54 | ||
debug('Module._resolveFilename("%s") threw %j, calling original Module.require', id, resolveErr.message) | ||
return self._origRequire.apply(this, arguments) | ||
} | ||
} | ||
@@ -93,5 +154,5 @@ | ||
// return known patched modules immediately | ||
if (self.cache.has(filename) === true) { | ||
if (self._cache.has(filename, core) === true) { | ||
debug('returning already patched cached module: %s', filename) | ||
return self.cache.get(filename) | ||
return self._cache.get(filename, core) | ||
} | ||
@@ -130,3 +191,3 @@ | ||
} else { | ||
const stat = parse(filename) | ||
const stat = moduleDetailsFromPath(filename) | ||
if (stat === undefined) { | ||
@@ -175,13 +236,11 @@ debug('could not parse filename: %s', filename) | ||
// only call onrequire the first time a module is loaded | ||
if (self.cache.has(filename) === false) { | ||
// ensure that the cache entry is assigned a value before calling | ||
// onrequire, in case calling onrequire requires the same module. | ||
self.cache.set(filename, exports) | ||
debug('calling require hook: %s', moduleName) | ||
self.cache.set(filename, onrequire(exports, moduleName, basedir)) | ||
} | ||
// ensure that the cache entry is assigned a value before calling | ||
// onrequire, in case calling onrequire requires the same module. | ||
self._cache.set(filename, exports, core) | ||
debug('calling require hook: %s', moduleName) | ||
const patchedExports = onrequire(exports, moduleName, basedir) | ||
self._cache.set(filename, patchedExports, core) | ||
debug('returning module: %s', moduleName) | ||
return self.cache.get(filename) | ||
return patchedExports | ||
} | ||
@@ -188,0 +247,0 @@ } |
{ | ||
"name": "require-in-the-middle", | ||
"version": "6.0.0", | ||
"version": "7.0.0", | ||
"description": "Module to hook into the Node.js require function", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -20,6 +20,6 @@ # require-in-the-middle | ||
const path = require('path') | ||
const Hook = require('require-in-the-middle') | ||
const { Hook } = require('require-in-the-middle') | ||
// Hook into the express and mongodb module | ||
Hook(['express', 'mongodb'], function (exports, name, basedir) { | ||
new Hook(['express', 'mongodb'], function (exports, name, basedir) { | ||
const version = require(path.join(basedir, 'package.json')).version | ||
@@ -41,3 +41,3 @@ | ||
### `hook = Hook([modules][, options], onrequire)` | ||
### `hook = new Hook([modules][, options], onrequire)` | ||
@@ -44,0 +44,0 @@ When called a `hook` object is returned. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14577
222