-
Notifications
You must be signed in to change notification settings - Fork 52
Description
Let's hook import('got') -- a type=module package -- using internals: true:
import { Hook } from 'import-in-the-middle'
register('import-in-the-middle/hook.mjs', import.meta.url)
Hook(['got'], { internals: true }, (exports, name, baseDir) => {
console.log('HIT: name=%s', name)
})
await import('got')The result:
HIT: name=got/dist/source/core/errors.js
...
HIT: name=got/dist/source/core/utils/is-unix-socket-url.js
HIT: name=got/dist/source/core/diagnostics-channel.js
HIT: name=got/dist/source/core/index.js
HIT: name=got/dist/source/as-promise/types.js
HIT: name=got/dist/source/as-promise/index.js
HIT: name=got/dist/source/create.js
HIT: name=got/dist/source/types.js
HIT: name=got/dist/source/index.js <--- This one differs from RITM
With RITM the callback name value for the loaded specifier is the specifier, rather than the internal path. E.g. with ioredis:
const { Hook } = require('require-in-the-middle')
Hook(['ioredis'], { internals: true }, (exports, name, baseDir) => {
console.log('HIT: name=%s', name)
return exports
})
require('ioredis')The result:
HIT: name=ioredis/built/utils/lodash.js
HIT: name=ioredis/built/utils/debug.js
...
HIT: name=ioredis/built/DataHandler.js
HIT: name=ioredis/built/redis/event_handler.js
HIT: name=ioredis/built/redis/RedisOptions.js
HIT: name=ioredis/built/Redis.js
HIT: name=ioredis <--- This one differs from IITM
RITM is preferring the specifier/id match over the internals: true-match.
My motivation
This currently breaks OTel's attempt to use internals: true with its IITM hook. I want to use internals: true as an alternative to @isaacs' open-telemetry/opentelemetry-js#6246 (I don't want to move to using Hook arguments that match entry-points because of some issues with that that I haven't yet written up on that issue.) The motivation for that PR to OTel is to support hooking @langchain/langgraph/prebuilt with OTel instrumentation (see https://github.com/getsentry/sentry-javascript/pull/18403/files#diff-e6fb561aa706da86d96d54111d486f4472ac8c4c069453a29d456da4c8f3ffcbR35).
I might try to change OTel's IITM usage to work around this, but I haven't done that yet.
Option 1: Change IITM to match RITM here
Assuming #236 and then #237 get merged, the change would be to swap the specifier/id-check and the internals-check:
diff --git a/index.js b/index.js
index 975deac..f206f25 100644
--- a/index.js
+++ b/index.js
@@ -151,9 +151,6 @@ function Hook (modules, options, hookFn) {
if (!baseDir) {
// built-in module (or unexpected non file:// name?)
callHookFn(hookFn, namespace, name, baseDir)
- } else if (internals) {
- const internalPath = name + path.sep + path.relative(baseDir, fileURLToPath(filename))
- callHookFn(hookFn, namespace, internalPath, baseDir)
} else if (baseDir.endsWith(specifiers.get(filename))) {
// An import of the top-level module (e.g. `import 'ioredis'`).
// Note: Slight behaviour difference from RITM. RITM uses
@@ -161,6 +158,9 @@ function Hook (modules, options, hookFn) {
// main file, which will catch `require('ioredis/built/index.js')`.
// The check here will not catch `import 'ioredis/built/index.js'`.
callHookFn(hookFn, namespace, name, baseDir)
+ } else if (internals) {
+ const internalPath = name + path.sep + path.relative(baseDir, fileURLToPath(filename))
+ callHookFn(hookFn, namespace, internalPath, baseDir)
}
} else if (matchArg === specifier) {
callHookFn(hookFn, namespace, specifier, baseDir)Option 2: do that, but also add a 4th subPath arg to the callback
With this option the callback would be:
function onImport(mod, name, baseDir, modPath)
where modPath is the ${moduleName}/${subPath}, e.g. ioredis/built/index.js.
I haven't prototyped this. That modPath could be added just for internals: true, or in all cases.
This would allow users of RITM and IITM to be more specific about which implementation files to monkey-patch:
- not "when user code loads 'ioredis'",
- but "whenever the file that implements that we want to monkey-patch is loaded"
This is the API that orchestrion-js has: module: { name: 'pkg1', verisonRange: '>=1.0.0', filePath: 'index.js' }, (excluding the versionRange, of course, though that could be interesting).
Thoughts?
Option 3: leave it alone
... and document the subtlety.