diff --git a/README.md b/README.md index 3194160..14eae8f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,11 @@ time. ## Usage -See the Typescript definition file for API docs. The API for -`require-in-the-middle` is followed as closely as possible. +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. You can modify anything exported from any given ESM or CJS module that's imported in ESM files, regardless of whether they're imported statically or diff --git a/index.d.ts b/index.d.ts index 736fdd6..0e63933 100644 --- a/index.d.ts +++ b/index.d.ts @@ -23,7 +23,7 @@ export type Options = { internals?: boolean } -export declare class Hook { +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 @@ -37,7 +37,9 @@ export declare class Hook { * they are mentioned specifically in the modules array. * @param {HookFunction} hookFn The function to be run on each module. */ - constructor (modules?: Array, options?: Options, hookFn: HookFunction) + constructor (modules: Array, options: Options, hookFn: HookFunction) + constructor (modules: Array, hookFn: HookFunction) + constructor (hookFn: HookFunction) /** * Disables this hook. It will no longer be run against any subsequently @@ -46,9 +48,32 @@ export declare class Hook { 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. + */ +export type LLHookFunction = (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. + * + * 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. + */ +export declare function addHook(hookFn: HookFunction): void + /** * 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 +export declare function removeHook(hookFn: LLHookFunction): void diff --git a/index.js b/index.js index 07189b0..4895146 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,18 @@ const proxyHandler = { } } +function addHook(hook) { + importHooks.push(hook) + toHook.forEach(([name, namespace]) => hook(name, namespace)) +} + +function removeHook(hook) { + const index = importHooks.indexOf(hook) + if (index > -1) { + importHooks.splice(index, 1) + } +} + function _register(name, namespace, set, specifier) { specifiers.set(name, specifier) setters.set(namespace, set) @@ -80,17 +92,14 @@ function Hook(modules, options, hookFn) { } } - importHooks.push(this._iitmHook) - toHook.forEach(([name, namespace]) => this._iitmHook(name, namespace)) + addHook(this._iitmHook) } Hook.prototype.unhook = function () { - const index = importHooks.indexOf(this._iitmHook) - if (index > -1) { - importHooks.splice(index, 1) - } + removeHook(this._iitmHook) } module.exports = Hook - module.exports._register = _register +module.exports.addHook = addHook +module.exports.removeHook = removeHook diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..21803ca --- /dev/null +++ b/test/README.md @@ -0,0 +1,13 @@ +These tests have the following nomenclature: + +* Prefixed with `hook-` if they use the `Hook` class. +* Prefixed with `ll-` if they use the "low-level" API, `addHook` and + `removeHook`. + +The tests should be run with the `runtest` command found in this directory. If +the command exits with a non-zero code, then it's a test failure. + +Running of all the tests can be done with `npm test`. + +Coverage must be 100% according to `c8`. If you don't have 100% coverage, you +can run `npm run coverage` to get coverage data in HTML form. diff --git a/test/dynamic-import-default.js b/test/hook-dynamic-import-default.js similarity index 100% rename from test/dynamic-import-default.js rename to test/hook-dynamic-import-default.js diff --git a/test/dynamic-import-default.mjs b/test/hook-dynamic-import-default.mjs similarity index 100% rename from test/dynamic-import-default.mjs rename to test/hook-dynamic-import-default.mjs diff --git a/test/dynamic-import.js b/test/hook-dynamic-import.js similarity index 100% rename from test/dynamic-import.js rename to test/hook-dynamic-import.js diff --git a/test/dynamic-import.mjs b/test/hook-dynamic-import.mjs similarity index 100% rename from test/dynamic-import.mjs rename to test/hook-dynamic-import.mjs diff --git a/test/remove.mjs b/test/hook-remove.mjs similarity index 100% rename from test/remove.mjs rename to test/hook-remove.mjs diff --git a/test/static-import-default.mjs b/test/hook-static-import-default.mjs similarity index 100% rename from test/static-import-default.mjs rename to test/hook-static-import-default.mjs diff --git a/test/static-import-disabled.mjs b/test/hook-static-import-disabled.mjs similarity index 100% rename from test/static-import-disabled.mjs rename to test/hook-static-import-disabled.mjs diff --git a/test/static-import-package-internals-enabled.mjs b/test/hook-static-import-package-internals-enabled.mjs similarity index 100% rename from test/static-import-package-internals-enabled.mjs rename to test/hook-static-import-package-internals-enabled.mjs diff --git a/test/static-import-package-internals.mjs b/test/hook-static-import-package-internals.mjs similarity index 100% rename from test/static-import-package-internals.mjs rename to test/hook-static-import-package-internals.mjs diff --git a/test/static-import-package.mjs b/test/hook-static-import-package.mjs similarity index 100% rename from test/static-import-package.mjs rename to test/hook-static-import-package.mjs diff --git a/test/static-import.mjs b/test/hook-static-import.mjs similarity index 100% rename from test/static-import.mjs rename to test/hook-static-import.mjs diff --git a/test/ll-dynamic-import-default.js b/test/ll-dynamic-import-default.js new file mode 100644 index 0000000..8d20c45 --- /dev/null +++ b/test/ll-dynamic-import-default.js @@ -0,0 +1,23 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +const { addHook } = require('../index.js') +const { strictEqual } = require('assert') + +addHook((name, exports) => { + if (name.match(/something\.m?js/)) { + const orig = exports.default + exports.default = function bar() { + return orig() + 15 + } + } +}) + +;(async () => { + const { default: barMjs } = await import('./fixtures/something.mjs') + const { default: barJs } = await import('./fixtures/something.js') + + strictEqual(barMjs(), 57) + strictEqual(barJs(), 57) +})() diff --git a/test/ll-dynamic-import-default.mjs b/test/ll-dynamic-import-default.mjs new file mode 100644 index 0000000..bcc9b7c --- /dev/null +++ b/test/ll-dynamic-import-default.mjs @@ -0,0 +1,23 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { addHook } from '../index.js' +import { strictEqual } from 'assert' + +addHook((name, exports) => { + if (name.match(/something\.m?js/)) { + const orig = exports.default + exports.default = function bar() { + return orig() + 15 + } + } +}) + +;(async () => { + const { default: barMjs } = await import('./fixtures/something.mjs') + const { default: barJs } = await import('./fixtures/something.js') + + strictEqual(barMjs(), 57) + strictEqual(barJs(), 57) +})() diff --git a/test/ll-dynamic-import.js b/test/ll-dynamic-import.js new file mode 100644 index 0000000..75feed8 --- /dev/null +++ b/test/ll-dynamic-import.js @@ -0,0 +1,25 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +const { addHook } = require('../index.js') +const { strictEqual } = require('assert') + +addHook((name, exports) => { + if (name.match(/something\.m?js/)) { + exports.foo += 15 + } + if (name.match('os')) { + exports.freemem = () => 47 + } +}) + +;(async () => { + const { foo: fooMjs } = await import('./fixtures/something.mjs') + const { foo: fooJs } = await import('./fixtures/something.js') + const { freemem } = await import('os') + + strictEqual(fooMjs, 57) + strictEqual(fooJs, 57) + strictEqual(freemem(), 47) +})() diff --git a/test/ll-remove.mjs b/test/ll-remove.mjs new file mode 100644 index 0000000..2e0d8dc --- /dev/null +++ b/test/ll-remove.mjs @@ -0,0 +1,25 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { addHook, removeHook } from '../index.js' +import { strictEqual } from 'assert' + +const hook = (name, exports) => { + if (name.match(/something\.m?js/)) { + exports.foo += 15 + } +} + +addHook(hook) + +;(async () => { + const { foo: fooMjs } = await import('./fixtures/something.mjs') + + removeHook(hook) + + const { foo: fooJs } = await import('./fixtures/something.js') + + strictEqual(fooMjs, 57) + strictEqual(fooJs, 42) +})() diff --git a/test/ll-static-import-default.mjs b/test/ll-static-import-default.mjs new file mode 100644 index 0000000..722637c --- /dev/null +++ b/test/ll-static-import-default.mjs @@ -0,0 +1,20 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { addHook } from '../index.js' +import barMjs from './fixtures/something.mjs' +import barJs from './fixtures/something.js' +import { strictEqual } from 'assert' + +addHook((name, exports) => { + if (name.match(/something\.m?js/)) { + const orig = exports.default + exports.default = function bar() { + return orig() + 15 + } + } +}) + +strictEqual(barMjs(), 57) +strictEqual(barJs(), 57) diff --git a/test/ll-static-import-disabled.mjs b/test/ll-static-import-disabled.mjs new file mode 100644 index 0000000..382c99d --- /dev/null +++ b/test/ll-static-import-disabled.mjs @@ -0,0 +1,15 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { addHook } from '../index.js' +import { foo as fooMjs } from './fixtures/something.mjs' +import { foo as fooJs } from './fixtures/something.js' +import { strictEqual, fail } from 'assert' + +addHook(() => { + fail('should not have been called at all') +}) + +strictEqual(fooMjs, 42) +strictEqual(fooJs, 42) diff --git a/test/ll-static-import.mjs b/test/ll-static-import.mjs new file mode 100644 index 0000000..180fc6b --- /dev/null +++ b/test/ll-static-import.mjs @@ -0,0 +1,22 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { addHook } from '../index.js' +import { foo as fooMjs } from './fixtures/something.mjs' +import { foo as fooJs } from './fixtures/something.js' +import { freemem } from 'os' +import { strictEqual } from 'assert' + +addHook((name, exports) => { + if (name.match(/something\.m?js/)) { + exports.foo += 15 + } + if (name.match('os')) { + exports.freemem = () => 47 + } +}) + +strictEqual(fooMjs, 57) +strictEqual(fooJs, 57) +strictEqual(freemem(), 47)