import-in-the-middle
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -6,12 +6,70 @@ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. | ||
/** | ||
* Property bag representing a module's exports, including the `default` if | ||
* present. | ||
*/ | ||
export type Namespace = { [key: string]: any } | ||
/** | ||
* A hook function to be run against loaded modules. | ||
* Not to be confused with `HookFunction`, used by the lower-level API. | ||
* @param {exported} { [string]: any } An object representing the exported | ||
* items of a module. | ||
* @param {name} string The name of the module. If it is (or is part of) a | ||
* package in a node_modules directory, this will be the path of the file | ||
* starting from the package name. | ||
* @param {baseDir} string The absolute path of the module, if not provided in | ||
* `name`. | ||
* @return any A value that can will be assigned to `exports.default`. This is | ||
* equivalent to doing that assignment in the body of this function. | ||
*/ | ||
export type HookFn = (exported: Namespace, name: string, baseDir: string|void) => any | ||
export type Options = { | ||
internals?: boolean | ||
} | ||
declare class Hook { | ||
/** | ||
* Creates a hook to be run on any already loaded modules and any that will | ||
* be loaded in the future. It will be run once per loaded module. If | ||
* statically imported, any variables bound directly to exported items will | ||
* be re-bound if those items are re-assigned in the hook function. | ||
* @param {Array<string>} [modules] A list of modules to run this hook on. If | ||
* omitted, it will run on every import everywhere. | ||
* @param {Options} [options] An options object. If omitted, the default is | ||
* `{ internals: false }`. If internals is true, then the hook will operate | ||
* on internal modules of packages in node_modules. Otherwise it will not, | ||
* unless they are mentioned specifically in the modules array. | ||
* @param {HookFunction} hookFn The function to be run on each module. | ||
*/ | ||
constructor (modules: Array<string>, options: Options, hookFn: HookFn) | ||
constructor (modules: Array<string>, hookFn: HookFn) | ||
constructor (hookFn: HookFn) | ||
/** | ||
* Disables this hook. It will no longer be run against any subsequently | ||
* loaded modules. | ||
*/ | ||
unhook(): void | ||
} | ||
export default Hook | ||
/** | ||
* A hook function to be run against loaded modules. To be used with the | ||
* lower-level APIs `addHook` and `removeHook`. | ||
* @param {url} string The absolute path of the module, as a `file:` URL string. | ||
* @param {exported} { [string]: any } An object representing the exported items of a module. | ||
* @param {exported} { [string]: any } An object representing the exported | ||
* items of a module. | ||
*/ | ||
export type HookFunction = (url: string, exported: { [string]: any }) => void | ||
export type HookFunction = (url: string, exported: Namespace) => void | ||
/** | ||
* Adds a hook to be run on any already loaded modules and any that will be loaded in the future. | ||
* It will be run once per loaded module. If statically imported, any variables bound directly to | ||
* exported items will be re-bound if those items are re-assigned in the hook. | ||
* Adds a hook to be run on any already loaded modules and any that will be | ||
* loaded in the future. It will be run once per loaded module. If statically | ||
* imported, any variables bound directly to exported items will be re-bound if | ||
* those items are re-assigned in the hook. | ||
* | ||
* This is the lower-level API for hook creation. It will be run on every | ||
* single imported module, rather than with any filtering. | ||
* @param {HookFunction} hookFn The function to be run on each module. | ||
@@ -22,6 +80,9 @@ */ | ||
/** | ||
* Removes a hook that has been previously added with `addHook`. It will no longer be run against | ||
* any subsequently loaded modules. | ||
* Removes a hook that has been previously added with `addHook`. It will no | ||
* longer be run against any subsequently loaded modules. | ||
* | ||
* This is the lower-level API for hook removal, and cannot be used with the | ||
* `Hook` class. | ||
* @param {HookFunction} hookFn The function to be removed. | ||
*/ | ||
export declare function removeHook(hookFn: HookFunction): void |
93
index.js
@@ -5,22 +5,13 @@ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. | ||
const importHooks = [] // TODO should this be a Set? | ||
const path = require('path') | ||
const parse = require('module-details-from-path') | ||
const { fileURLToPath } = require('url') | ||
const setters = new WeakMap() | ||
const { | ||
importHooks, | ||
specifiers, | ||
toHook | ||
} = require('./lib/register') | ||
const toHook = [] | ||
const proxyHandler = { | ||
set(target, name, value) { | ||
return setters.get(target)[name](value) | ||
} | ||
} | ||
exports._register = function _register(name, namespace, set) { | ||
setters.set(namespace, set) | ||
const proxy = new Proxy(namespace, proxyHandler) | ||
importHooks.forEach(hook => hook(name, proxy)) | ||
toHook.push([name, proxy]) | ||
} | ||
exports.addHook = function addHook(hook) { | ||
function addHook(hook) { | ||
importHooks.push(hook) | ||
@@ -30,3 +21,3 @@ toHook.forEach(([name, namespace]) => hook(name, namespace)) | ||
exports.removeHook = function removeHook(hook) { | ||
function removeHook(hook) { | ||
const index = importHooks.indexOf(hook) | ||
@@ -37,1 +28,65 @@ if (index > -1) { | ||
} | ||
function callHookFn(hookFn, namespace, name, baseDir) { | ||
const newDefault = hookFn(namespace, name, baseDir) | ||
if (newDefault && newDefault !== namespace) { | ||
namespace.default = newDefault | ||
} | ||
} | ||
function Hook(modules, options, hookFn) { | ||
if ((this instanceof Hook) === false) return new Hook(modules, options, hookFn) | ||
if (typeof modules === 'function') { | ||
hookFn = modules | ||
modules = null | ||
options = null | ||
} else if (typeof options === 'function') { | ||
hookFn = options | ||
options = null | ||
} | ||
const internals = options ? options.internals === true : false | ||
this._iitmHook = (name, namespace) => { | ||
const filename = name | ||
const isBuiltin = name.startsWith('node:') | ||
let baseDir | ||
if (isBuiltin) { | ||
name = name.replace(/^node:/, '') | ||
} else { | ||
name = name.replace(/^file:\/\//, '') | ||
const details = parse(name) | ||
if (details) { | ||
name = details.name | ||
baseDir = details.basedir | ||
} | ||
} | ||
if (modules) { | ||
for (const moduleName of modules) { | ||
if (moduleName === name) { | ||
if (baseDir) { | ||
if (internals) { | ||
name = name + path.sep + path.relative(baseDir, fileURLToPath(filename)) | ||
} else { | ||
if (!baseDir.endsWith(specifiers.get(filename))) continue | ||
} | ||
} | ||
callHookFn(hookFn, namespace, name, baseDir) | ||
} | ||
} | ||
} else { | ||
callHookFn(hookFn, namespace, name, baseDir) | ||
} | ||
} | ||
addHook(this._iitmHook) | ||
} | ||
Hook.prototype.unhook = function () { | ||
removeHook(this._iitmHook) | ||
} | ||
module.exports = Hook | ||
module.exports.addHook = addHook | ||
module.exports.removeHook = removeHook |
{ | ||
"name": "import-in-the-middle", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Intercept imports in Node.js", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "c8 --check-coverage --lines 100 imhotap --runner test/runtest --files test/*.*js" | ||
"test": "c8 --check-coverage --lines 100 imhotap --runner test/runtest --files test/{hook,low-level}/*.*js", | ||
"coverage": "c8 --reporter html imhotap --runner test/runtest --files test/{hook,low-level}/*.*js && echo '\nNow open coverage/index.html\n'" | ||
}, | ||
@@ -30,3 +31,6 @@ "repository": { | ||
"imhotap": "^1.1.0" | ||
}, | ||
"dependencies": { | ||
"module-details-from-path": "^1.0.3" | ||
} | ||
} |
@@ -10,3 +10,7 @@ # import-in-the-middle | ||
See the Typescript definition file for API docs. | ||
The API for | ||
`require-in-the-middle` is followed as closely as possible as the default | ||
export. There are lower-level `addHook` and `removeHook` exports available which | ||
don't do any filtering of modules, and present the full file URL as a parameter | ||
to the hook. See the Typescript definition file for detailed API docs. | ||
@@ -18,12 +22,10 @@ You can modify anything exported from any given ESM or CJS module that's | ||
```js | ||
import { addHook } from 'import-in-the-middle' | ||
import { foo } from './module-i-want-to-modify.mjs' | ||
import Hook from 'import-in-the-middle' | ||
import { foo } from 'package-i-want-to-modify' | ||
console.log(foo) // whatever that module exported | ||
addHook((url, exported) => { | ||
Hook(['package-i-want-to-modify'], (exported, name, baseDir) => { | ||
// `exported` is effectively `import * as exported from ${url}` | ||
if (url.match(/module-i-want-to-modify.mjs$/)) { | ||
exported.foo += 1 | ||
} | ||
exported.foo += 1 | ||
}) | ||
@@ -38,3 +40,3 @@ | ||
``` | ||
--loader=/path/to/import-in-the-middle/hook.mjs | ||
--loader=import-in-the-middle/hook.mjs | ||
``` | ||
@@ -41,0 +43,0 @@ |
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
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
25497
32
561
47
1
1
1
+ Addedmodule-details-from-path@1.0.3(transitive)