import-in-the-middle 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.2](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v3.0.1...import-in-the-middle-v3.0.2) (2026-06-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Updated proxying logic for builtins that are used as CJS ([#250](https://github.com/nodejs/import-in-the-middle/issues/250)) ([f69eb8f](https://github.com/nodejs/import-in-the-middle/commit/f69eb8f13c91b7088003e7390b4f54c5076aa50b))
9
+
3
10
  ## [3.0.1](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v3.0.0...import-in-the-middle-v3.0.1) (2026-04-07)
4
11
 
5
12
 
package/create-hook.mjs CHANGED
@@ -265,12 +265,19 @@ async function processModule ({ srcUrl, context, parentGetSource, parentResolve,
265
265
  const objectKey = JSON.stringify(n)
266
266
  const reExportedName = n === 'default' ? n : objectKey
267
267
 
268
+ // For the module.exports synthetic export (Node 23+), fall back to $default
269
+ // when namespace['module.exports'] is not exposed by the native ESM namespace
270
+ // (builtins don't expose it). This ensures the IITM hook proxy returns the
271
+ // actual CJS value (e.g. EventEmitter) when an instrumentor reads
272
+ // capturedExports['module.exports'], rather than undefined.
273
+ const moduleExportsFallback = n === 'module.exports' ? ' ?? $default' : ''
274
+
268
275
  addSetter(n, `
269
276
  let ${variableName}
270
277
  __overridden[${objectKey}] = false
271
278
  let ${variableName}Defer = false
272
279
  try {
273
- ${variableName} = _[${objectKey}] = namespace[${objectKey}]
280
+ ${variableName} = _[${objectKey}] = namespace[${objectKey}]${moduleExportsFallback}
274
281
  } catch (err) {
275
282
  if (!(err instanceof ReferenceError)) throw err
276
283
  ${variableName}Defer = true
@@ -279,11 +286,11 @@ async function processModule ({ srcUrl, context, parentGetSource, parentResolve,
279
286
  if (${variableName}Defer || ${variableName} === undefined) {
280
287
  __pending.push(__makeUpdater(
281
288
  ${objectKey},
282
- () => namespace[${objectKey}],
289
+ () => namespace[${objectKey}]${moduleExportsFallback},
283
290
  (v) => { ${variableName} = _[${objectKey}] = v }
284
291
  ))
285
292
  }
286
- export { ${variableName} as ${reExportedName} }
293
+ ${(n === 'module.exports' && (srcUrl.startsWith('node:') || builtinModules.includes(srcUrl))) ? '' : `export { ${variableName} as ${reExportedName} }`}
287
294
  set[${objectKey}] = (v) => {
288
295
  __overridden[${objectKey}] = true
289
296
  ${variableName} = v
@@ -308,6 +315,14 @@ export function createHook (meta) {
308
315
  const iitmURL = new URL('lib/register.js', meta.url).toString()
309
316
  let includeModules, excludeModules
310
317
 
318
+ // Track CJS module URLs that IITM has wrapped. On Node 24+, CJS modules loaded
319
+ // via loadCJSModule (in an ESM import chain) have their require() calls for
320
+ // builtins routed through the ESM resolver. Without this guard, IITM would
321
+ // intercept those require() calls and return an ESM namespace object instead
322
+ // of the native CJS module value (e.g. EventEmitter constructor), breaking
323
+ // patterns like `class App extends require('events') {}`.
324
+ const cjsInIitmChain = new Set()
325
+
311
326
  async function initialize (data) {
312
327
  if (global.__import_in_the_middle_initialized__) {
313
328
  process.emitWarning("The 'import-in-the-middle' hook has already been initialized")
@@ -405,6 +420,17 @@ export function createHook (meta) {
405
420
  return result
406
421
  }
407
422
 
423
+ // When a CJS module is loaded by an IITM shim, its require() calls for
424
+ // builtins may be routed through the ESM resolver on Node 24+. Skip IITM
425
+ // wrapping in that case so require() returns the native module value.
426
+ // We also propagate the membership to the resolved child so that its own
427
+ // transitive require() calls are likewise skipped (the entire synchronous
428
+ // CJS require chain must remain unwrapped to avoid ERR_VM_MODULE_LINK_FAILURE).
429
+ if (cjsInIitmChain.has(parentURL)) {
430
+ cjsInIitmChain.add(result.url)
431
+ return result
432
+ }
433
+
408
434
  // We don't want to attempt to wrap native modules
409
435
  if (result.url.endsWith('.node')) {
410
436
  return result
@@ -448,6 +474,11 @@ export function createHook (meta) {
448
474
  })
449
475
  // If the module loaded successfully, we can remove the specifier to reduce memory usage early.
450
476
  specifiers.delete(realUrl)
477
+ // Track CJS modules so their transitive require() calls bypass IITM.
478
+ // context.format is set to 'commonjs' by getCjsExports during processModule.
479
+ if (context.format === 'commonjs') {
480
+ cjsInIitmChain.add(realUrl)
481
+ }
451
482
  return {
452
483
  source: `
453
484
  import { register } from '${iitmURL}'
@@ -563,6 +594,24 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpeci
563
594
  return parentLoad(deleteIitm(url), context)
564
595
  }
565
596
 
597
+ // On Node 22+, when a CJS module is loaded through the ESM translator and
598
+ // another loader hook provides its source (instead of leaving source null
599
+ // for Node to read natively), require() calls inside that CJS module for
600
+ // packages using the "module-sync" exports condition fail with
601
+ // ERR_VM_MODULE_LINK_FAILURE. Work around this Node bug by stripping
602
+ // hook-provided source for CJS modules in the synchronous require chain,
603
+ // forcing Node to use its native CJS loader which handles this correctly.
604
+ if (cjsInIitmChain.has(url)) {
605
+ const result = await parentLoad(url, context)
606
+ if (result.format === 'commonjs' && result.source != null) {
607
+ return {
608
+ format: result.format,
609
+ source: undefined
610
+ }
611
+ }
612
+ return result
613
+ }
614
+
566
615
  return parentLoad(url, context)
567
616
  }
568
617
 
@@ -150,7 +150,12 @@ function getExportsForNodeBuiltIn (name) {
150
150
  }
151
151
 
152
152
  if (!exports) {
153
- exports = new Set(addDefault(Object.keys(require(name))))
153
+ // get all properties both enumerable and non-enumerable
154
+ exports = new Set(addDefault(Object.getOwnPropertyNames(require(name))))
155
+ // added in node 23 as alias for default in cjs modules
156
+ if (hasModuleExportsCJSDefault) {
157
+ exports.add('module.exports')
158
+ }
154
159
  BUILT_INS.set(name, exports)
155
160
  }
156
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "import-in-the-middle",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Intercept imports in Node.js",
5
5
  "engines": {
6
6
  "node": ">=18"