Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

const path = require('path')
const parse = require('module-details-from-path')
const moduleDetailsFromPath = require('module-details-from-path')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const { fileURLToPath } = require('url')
const { MessageChannel } = require('worker_threads')

Expand Down Expand Up @@ -128,9 +128,9 @@ function Hook (modules, options, hookFn) {
}

this._iitmHook = (name, namespace, specifier) => {
const filename = name
const isNodeUrl = name.startsWith('node:')
let baseDir
const loadUrl = name
const isNodeUrl = loadUrl.startsWith('node:')
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer note: The name change to isNodeUrl is to avoid the name isBuiltin for the actual isBuiltin function from the node:module module in a separate PR (https://github.com/nodejs/import-in-the-middle/pull/240/files).

let filePath, baseDir

if (isNodeUrl) {
// Normalize builtin module name to *not* have 'node:' prefix, unless
Expand All @@ -140,38 +140,43 @@ function Hook (modules, options, hookFn) {
if (isBuiltin(unprefixed)) {
name = unprefixed
}
} else {
if (name.startsWith('file://')) {
const stackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
try {
name = fileURLToPath(name)
} catch (e) {}
Error.stackTraceLimit = stackTraceLimit
}
const details = parse(name)
if (details) {
name = details.name
baseDir = details.basedir
} else if (loadUrl.startsWith('file://')) {
const stackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
try {
filePath = fileURLToPath(name)
name = filePath
} catch (e) {}
Error.stackTraceLimit = stackTraceLimit

if (filePath) {
const details = moduleDetailsFromPath(filePath)
Copy link
Copy Markdown
Contributor Author

@trentm trentm Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer note: This section changes to only do the moduleDetailsFromPath() parsing if we have a file:// URL that was successfully converted to a path.

if (details) {
name = details.name
baseDir = details.basedir
}
}
}

if (modules) {
for (const matchArg of modules) {
if (matchArg === name) {
if (filePath && matchArg === filePath) {
// abspath match
callHookFn(hookFn, namespace, filePath, undefined)
} else if (matchArg === name) {
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))) {
} else if (baseDir.endsWith(specifiers.get(loadUrl))) {
// An import of the top-level module (e.g. `import 'ioredis'`).
// Note: Slight behaviour difference from RITM. RITM uses
// `require.resolve(name)` to see if `filename` is the module
// `require.resolve(name)` to see if filename is the module
// 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, filePath)
callHookFn(hookFn, namespace, internalPath, baseDir)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer note: This part of the change (that moves the } else if (internals) {-block to be after the else if (baseDir.endsWith(specifier..-block) is for the first/primary fix in this PR: the fix the name in the callback to match what RITM does.

The rest of the changes in "index.js" are for the absolute-paths fix.

}
} else if (matchArg === specifier) {
callHookFn(hookFn, namespace, specifier, baseDir)
Expand Down
39 changes: 39 additions & 0 deletions test/hook/absolute-paths.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import path from 'path'
import { fileURLToPath } from 'url'
import { deepStrictEqual } from 'assert'
import Hook from '../../index.js'

const hooked = []
const TOP = path.resolve(fileURLToPath(import.meta.url), '../../../')
Hook([
`${TOP}/test/fixtures/index.js`,
`${TOP}/test/fixtures/something.js`,
`${TOP}/test/fixtures/node_modules/some-external-module/index.mjs`,
`${TOP}/test/fixtures/node_modules/some-external-module/sub.mjs`
], (_, name, baseDir) => {
hooked.push([name, baseDir])
})

;(async () => {
// Import an absolute path.
await import(`${TOP}/test/fixtures/index.js`)

// Import a relative path.
await import('../fixtures/something.js')

// Absolute path, note the file happens to be in a `node_modules` dir.
await import(`${TOP}/test/fixtures/node_modules/some-external-module/index.mjs`)

// Relative path, note the file happens to be in a `node_modules` dir.
await import('../fixtures/node_modules/some-external-module/sub.mjs')

deepStrictEqual(
hooked,
[
[`${TOP}/test/fixtures/index.js`, undefined],
[`${TOP}/test/fixtures/something.js`, undefined],
[`${TOP}/test/fixtures/node_modules/some-external-module/index.mjs`, undefined],
[`${TOP}/test/fixtures/node_modules/some-external-module/sub.mjs`, undefined]
]
)
})()
4 changes: 2 additions & 2 deletions test/hook/v22-duplicate-entries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ await import('../fixtures/load-external-modules.mjs')
deepStrictEqual(hits, [
'some-external-module/sub.mjs',
'some-external-module/sub.mjs',
'some-external-module/index.mjs',
'some-external-module/index.mjs'
'some-external-module',
'some-external-module'
])
2 changes: 1 addition & 1 deletion test/hook/v22-hook-module-and-specifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ await import('../fixtures/load-external-modules.mjs')
deepStrictEqual(hits, [
'some-external-module/sub.mjs', // module name match, internals:true
'some-external-module/sub', // specifier match
'some-external-module/index.mjs' // module name match, internals:true
'some-external-module' // module name match, internals:true
])
8 changes: 4 additions & 4 deletions test/low-level/v22-esm-internal-spec-match.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ const hookModuleURL = String(pathToFileURL(
register(hookModuleURL, import.meta.url, registerOptions)

Hook(['some-external-module'], { internals: true }, (exported, name) => {
equal(name, 'some-external-module/index.mjs')
equal(name, 'some-external-module')
exported.foo += '-mutated'
})

Hook(['some-external-cjs-module'], { internals: true }, (exported, name) => {
equal(name, 'some-external-cjs-module/index.js')
equal(name, 'some-external-cjs-module')
exported.foo += '-mutated (cjs)'
})

Hook(['@scope/some-scoped-module'], { internals: true }, (exported, name) => {
equal(name, '@scope/some-scoped-module/index.mjs')
equal(name, '@scope/some-scoped-module')
exported.foo += '-mutated'
})

Hook(['@scope/some-scoped-cjs-module'], { internals: true }, (exported, name) => {
equal(name, '@scope/some-scoped-cjs-module/index.js')
equal(name, '@scope/some-scoped-cjs-module')
exported.foo += '-mutated (cjs)'
})

Expand Down