Giter VIP home page Giter VIP logo

import-in-the-middle's Introduction

import-in-the-middle

import-in-the-middle is an module loading interceptor inspired by require-in-the-middle, but specifically for ESM modules. In fact, it can even modify modules after loading time.

Usage

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 dynamically.

import { Hook } from 'import-in-the-middle'
import { foo } from 'package-i-want-to-modify'

console.log(foo) // whatever that module exported

Hook(['package-i-want-to-modify'], (exported, name, baseDir) => {
  // `exported` is effectively `import * as exported from ${url}`
  exported.foo += 1
})

console.log(foo) // 1 more than whatever that module exported

This requires the use of an ESM loader hook, which can be added with the following command-line option.

--loader=import-in-the-middle/hook.mjs

Limitations

  • You cannot add new exports to a module. You can only modify existing ones.
  • While bindings to module exports end up being "re-bound" when modified in a hook, dynamically imported modules cannot be altered after they're loaded.
  • Modules loaded via require are not affected at all.

import-in-the-middle's People

Contributors

bengl avatar carlesdd avatar fardjad avatar jackwhelpton avatar jsumners-nr avatar khanayan123 avatar luxaritas avatar mohd-akram avatar nwalters512 avatar rochdev avatar timfish avatar tlhunter avatar trentm avatar uurien avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

import-in-the-middle's Issues

Support TypeScript extensions

Expected Behavior

I have put together an example of chain-loading dd-trace and ts-node/esm in a native ESM project without compiling TS to JS here. However, to make it work, I had to patch import-in-the-middle.

Actual Behavior

Without the patch, MITM does not recognize TypeScript extensions (i.e. .ts, .mts, .cts).

Steps to Reproduce the Problem

  1. Clone the example repo
  2. Install the dependencies by running npm i --ignore-scripts
  3. Run npm test

Specifications

  • Version: 1.2.1
  • Platform: N/A
  • Subsystem: N/A

`renamedExport` handling incorrectly adds ES module exports

The renamedExport handling from #53 incorrectly adds ES module exports. Given these three small test files:

% cat default-and-star-a.mjs
export default function funcA() {
  console.log('hi from a');
}
export const valueA = 'a';

% cat default-and-star-b.mjs
export * from './default-and-star-a.mjs';
export const valueB = 'b';

% cat default-and-star-main.mjs
import Hook from './index.js'
Hook((exports, name, baseDir) => {
  if (!name.includes('default-and-star')) return;
  console.log('Hooked name=%s exports:', name, exports)
})

import * as mod from './default-and-star-b.mjs'
console.log('default-and-star-b mod is:', mod)

When run without IITM, we expect the import of './default-and-star-b.mjs' to only have the valueA and valueB exports:

% node default-and-star-main.mjs
default-and-star-b mod is: [Module: null prototype] { valueA: 'a', valueB: 'b' }

However, when running with IITM (at tip of current "main") a renamedExport for the export * from './default-and-star-a.mjs'; statement in "default-and-star-b.mjs" is incorrectly added:

% node --no-warnings --experimental-loader=./hook.mjs default-and-star-main.mjs
Hooked name=/Users/trentm/tm/import-in-the-middle6/default-and-star-a.mjs exports: { default: [Function: funcA], valueA: 'a' }
Hooked name=/Users/trentm/tm/import-in-the-middle6/default-and-star-b.mjs exports: { valueA: 'a', valueB: 'b', defaultAndStarA: [Function: funcA] }
default-and-star-b mod is: [Module: null prototype] {
  defaultAndStarA: [Function: funcA],
  valueA: 'a',
  valueB: 'b'
}
Hooked name=/Users/trentm/tm/import-in-the-middle6/default-and-star-main.mjs exports: {}

Was this possibly a misunderstanding of import behaviour while working on #53?
The #53 description says:

I have since learned that ESM doesn't actually allow exporting default multiple times. Transitive default exports get mapped to some other name for the module that has imported them [...]

Where did the impression that "default exports get mapped to some other name for the module that has imported them" come from? Or perhaps I'm not following what the is saying.

Also the change to "hook.js" includes this comment:

      // When we encounter modules that re-export all identifiers from other
      // modules, it is possible that the transitive modules export a default
      // identifier. Due to us having to merge all transitive modules into a
      // single common namespace, we need to recognize these default exports
      // and remap them to a name based on the module name. [...]

Is this possibly a misunderstanding as well? Taking the "default-and-star-b.mjs" example from above:

export * from './default-and-star-a.mjs';
export const valueB = 'b';

That export * ... statement does not re-export the default export from "default-and-star-a.mjs".
Therefore, there should not be any need for import-in-the-middle to be adding some normalization of that property name to the hooked namespace or have a setter for it.


Assuming we agree this is a bug that should be fixed (i.e. that this was a misunderstanding),
I'll have a draft PR soonish to fix this, along with some simplifications in getSource and processModule.

`getFullCjsExports` does not resolve re-exports from external dependencies

This had been fixed for ESM but not for CJS.

The following code should result in a default export
test.cjs

module.exports = require('util').deprecate;

Instead it results in the following error:

node:internal/process/esm_loader:40
      internalBinding('errors').triggerUncaughtException(
                                ^
Error: ENOENT: no such file or directory, open '/Users/tim/Documents/Repositories/import-in-the-middle/test/check-exports/util'
    at Object.readFileUtf8 (node:internal/fs/sync:25:18)
    at Object.readFileSync (node:fs:441:19)
    at getExports (/Users/tim/Documents/Repositories/import-in-the-middle/lib/get-exports.js:69:17)
    at async Promise.all (index 0)
    at async getFullCjsExports (/Users/tim/Documents/Repositories/import-in-the-middle/lib/get-exports.js:23:9)
    at async processModule (/Users/tim/Documents/Repositories/import-in-the-middle/hook.js:131:23)
    at async getSource (/Users/tim/Documents/Repositories/import-in-the-middle/hook.js:249:23)
    at async load (/Users/tim/Documents/Repositories/import-in-the-middle/hook.js:280:26)
    at async nextLoad (node:internal/modules/esm/hooks:833:22)
    at async Hooks.load (node:internal/modules/esm/hooks:416:20) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/Users/tim/Documents/Repositories/import-in-the-middle/test/check-exports/util'
}

I think this is due to this code which doesn't support resolving bare specifiers or node built-ins?

const ex = getCjsExports(source)
const full = Array.from(new Set([
...addDefault(ex.exports),
...(await Promise.all(ex.reexports.map(re => getExports(
(/^(..?($|\/|\\))/).test(re)
? pathToFileURL(require.resolve(fileURLToPath(new URL(re, url)))).toString()
: pathToFileURL(require.resolve(re)).toString(),
context,
parentLoad
)))).flat()
]))

Doesn't handle exports with invalid identifiers

CommonJs supports exports keyed with arbitrary strings which are not valid identifiers:

exports["unsigned short"] = "something"

This means the webidl-conversions package results in the following error due to invalid iitm code:

file:///Users/tim/Documents/Repositories/import-in-the-middle/test/check-exports/node_modules/webidl-conversions/lib/index.js?iitm=true:69
      let $unsigned short = _.unsigned short
                    ^^^^^
SyntaxError: Unexpected identifier 'short'
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:118:18)
    at callTranslator (node:internal/modules/esm/loader:273:14)
    at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:278:30)

Regression between import-in-the-middle v1.7.1 and v1.7.2

@opentelemetry debug output in v1.7.2:

Loading instrumentation for @opentelemetry/instrumentation-aws-sdk
Loading instrumentation for @opentelemetry/instrumentation-bunyan
Loading instrumentation for @opentelemetry/instrumentation-cassandra-driver
Loading instrumentation for @opentelemetry/instrumentation-connect
Loading instrumentation for @opentelemetry/instrumentation-cucumber
Loading instrumentation for @opentelemetry/instrumentation-dataloader
Loading instrumentation for @opentelemetry/instrumentation-dns
Loading instrumentation for @opentelemetry/instrumentation-express
Loading instrumentation for @opentelemetry/instrumentation-fastify
Loading instrumentation for @opentelemetry/instrumentation-fs
Loading instrumentation for @opentelemetry/instrumentation-generic-pool
Loading instrumentation for @opentelemetry/instrumentation-graphql
Loading instrumentation for @opentelemetry/instrumentation-grpc
Loading instrumentation for @opentelemetry/instrumentation-hapi
Loading instrumentation for @opentelemetry/instrumentation-http
Loading instrumentation for @opentelemetry/instrumentation-ioredis
Loading instrumentation for @opentelemetry/instrumentation-knex
Loading instrumentation for @opentelemetry/instrumentation-koa
Loading instrumentation for @opentelemetry/instrumentation-lru-memoizer
Loading instrumentation for @opentelemetry/instrumentation-memcached
Loading instrumentation for @opentelemetry/instrumentation-mongodb
Loading instrumentation for @opentelemetry/instrumentation-mongoose
Loading instrumentation for @opentelemetry/instrumentation-mysql2
Loading instrumentation for @opentelemetry/instrumentation-mysql
Loading instrumentation for @opentelemetry/instrumentation-nestjs-core
Loading instrumentation for @opentelemetry/instrumentation-net
Loading instrumentation for @opentelemetry/instrumentation-pg
Loading instrumentation for @opentelemetry/instrumentation-pino
Loading instrumentation for @opentelemetry/instrumentation-redis
Loading instrumentation for @opentelemetry/instrumentation-redis-4
Loading instrumentation for @opentelemetry/instrumentation-restify
Loading instrumentation for @opentelemetry/instrumentation-router
Loading instrumentation for @opentelemetry/instrumentation-socket.io
Loading instrumentation for @opentelemetry/instrumentation-tedious
Loading instrumentation for @opentelemetry/instrumentation-winston
(node:57158) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
Applying patch for dns
patch lookup function
patch lookup function
Applying patch for dns
patch lookup function
patch lookup function
@opentelemetry/instrumentation-fs Applying patch for fs
@opentelemetry/instrumentation-fs Applying patch for fs
Applying patch for [email protected]
Applying patch for [email protected]

VS

v1.7.1:

Loading instrumentation for @opentelemetry/instrumentation-aws-sdk
Loading instrumentation for @opentelemetry/instrumentation-bunyan
Loading instrumentation for @opentelemetry/instrumentation-cassandra-driver
Loading instrumentation for @opentelemetry/instrumentation-connect
Loading instrumentation for @opentelemetry/instrumentation-cucumber
Loading instrumentation for @opentelemetry/instrumentation-dataloader
Loading instrumentation for @opentelemetry/instrumentation-dns
Loading instrumentation for @opentelemetry/instrumentation-express
Loading instrumentation for @opentelemetry/instrumentation-fastify
Loading instrumentation for @opentelemetry/instrumentation-fs
Loading instrumentation for @opentelemetry/instrumentation-generic-pool
Loading instrumentation for @opentelemetry/instrumentation-graphql
Loading instrumentation for @opentelemetry/instrumentation-grpc
Loading instrumentation for @opentelemetry/instrumentation-hapi
Loading instrumentation for @opentelemetry/instrumentation-http
Loading instrumentation for @opentelemetry/instrumentation-ioredis
Loading instrumentation for @opentelemetry/instrumentation-knex
Loading instrumentation for @opentelemetry/instrumentation-koa
Loading instrumentation for @opentelemetry/instrumentation-lru-memoizer
Loading instrumentation for @opentelemetry/instrumentation-memcached
Loading instrumentation for @opentelemetry/instrumentation-mongodb
Loading instrumentation for @opentelemetry/instrumentation-mongoose
Loading instrumentation for @opentelemetry/instrumentation-mysql2
Loading instrumentation for @opentelemetry/instrumentation-mysql
Loading instrumentation for @opentelemetry/instrumentation-nestjs-core
Loading instrumentation for @opentelemetry/instrumentation-net
Loading instrumentation for @opentelemetry/instrumentation-pg
Loading instrumentation for @opentelemetry/instrumentation-pino
Loading instrumentation for @opentelemetry/instrumentation-redis
Loading instrumentation for @opentelemetry/instrumentation-redis-4
Loading instrumentation for @opentelemetry/instrumentation-restify
Loading instrumentation for @opentelemetry/instrumentation-router
Loading instrumentation for @opentelemetry/instrumentation-socket.io
Loading instrumentation for @opentelemetry/instrumentation-tedious
Loading instrumentation for @opentelemetry/instrumentation-winston
(node:57196) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
@opentelemetry/instrumentation-http Applying patch for [email protected]
Applying patch for dns
patch lookup function
patch lookup function
Applying patch for dns
patch lookup function
patch lookup function
@opentelemetry/instrumentation-fs Applying patch for fs
@opentelemetry/instrumentation-fs Applying patch for fs
Applying patch for [email protected]
Applying patch for [email protected]
@opentelemetry/instrumentation-fastify Applying patch for [email protected]
@opentelemetry/instrumentation-fastify Patching fastify constructor function
@opentelemetry/instrumentation-fastify Applying patch for [email protected]
@opentelemetry/instrumentation-fastify Patching fastify constructor function

Looks like v1.7.2 makes so that fastify dependency can no longer be picked up.

IITM Throws Error When an NPM Package is Not Installed in the Root Folder

I am encountering an issue while using prisma module in my test folder along with the import-in-the-middle. When attempting to import the prisma module from a subdirectory, I receive the following error:

node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
^
Error: Cannot find module '.prisma/client/default'
Require stack:
/prismabug/node_modules/import-in-the-middle/lib/get-exports.js
/prismabug/node_modules/import-in-the-middle/hook.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1134:15)
at Function.resolve (node:internal/modules/helpers:188:19)
at /prismabug/node_modules/import-in-the-middle/lib/get-exports.js:26:33)
at Array.map ()
at getFullCjsExports (/prismabug/node_modules/import-in-the-middle/lib/get-exports.js:23:40)
at getExports (/prismabug/node_modules/import-in-the-middle/lib/get-exports.js:76:12)
at async processModule (/prismabug/node_modules/import-in-the-middle/hook.js:131:23)
at async getSource (/prismabug/node_modules/import-in-the-middle/hook.js:249:23)
at async load (/prismabug/node_modules/import-in-the-middle/hook.js:280:26)
at async nextLoad (node:internal/modules/esm/hooks:864:22) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'/prismabug/node_modules/import-in-the-middle/lib/get-exports.js',
'/prismabug/node_modules/import-in-the-middle/hook.js'
]

This occurs when I install and use the prisma package in a subdirectory. Notably, the issue only arises with Node.js version 18.19 and above. Versions 18 and below do not exhibit this problem. Additionally, the issue does not occur when the import-in-the-middle hook is not used.

If I add the prismato the root package.json, the error does not occur. However, due to project compliance, I am not allowed to include the the package in the root directory.

Expected Behavior

The prisma package should be successfully imported and executed without errors.
When I run the command node --loader ./hook.mjs packages/a/test/prisma/app.mjs it should not throw any error.

Actual Behavior

The error Cannot find module '.prisma/client/default' is thrown.

Steps to Reproduce the Problem

  1. Unzip prismabug.zip.
  2. Run npm i.
  3. Navigate to the Prisma test directory: cd packages/a/test/prisma/
  4. Run npm i within this directory.
  5. Return to the root directory: cd ../../../../
  6. Execute the following command
    node --loader ./hook.mjs packages/a/test/prisma/app.mjs
    or
    node --import ./register packages/a/test/prisma/app.mjs
    both are throwing errors.

Specifications

  • Version: 1.8.0
  • Platform: arm64
  • Subsystem: Node 18.19

Fails to resolve `exports` defined submodule when reexported by another package

node_modules/has-submodule/package.json

{
  "name": "has-submodule",
  "type": "module",
  "exports": {
    "./sub": "./sub.js"
  }
}

node_modules/has-submodule/sub.js

export const foo = 'bar'
console.log('loaded submodule')
cat: node_modules/reexport-submodule/sub.js: No such file or directory

node_modules/reexport-submodule/package.json

{
  "name": "reexport-submodule",
  "exports": {
    ".": "./index.js"
  },
  "type": "module"
}

node_modules/reexport-submodule/index.js

export * from 'has-submodule/sub'

load-sub.mjs

import { foo } from 'reexport-submodule'
console.log(foo)

esm-loader.mjs

export * from 'import-in-the-middle/hook.mjs'

Expected Behavior

$ node load-sub.mjs
loaded submodule
bar
$ node --loader=./esm-loader.mjs load-sub.mjs
loaded submodule
bar

Actual Behavior

$ node --loader=./esm-loader.mjs load-sub.mjs
(node:36249) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("./esm-loader.mjs", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)

node:internal/process/esm_loader:40
      internalBinding('errors').triggerUncaughtException(
                                ^
[Error: ENOENT: no such file or directory, open '/Users/isaacs/dev/tapjs/esm-tap-repro/node_modules/reexport-submodule/has-submodule/sub'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/Users/isaacs/dev/tapjs/esm-tap-repro/node_modules/reexport-submodule/has-submodule/sub'
}

Node.js v20.9.0

Steps to Reproduce the Problem

Shown above.

Loader breaks in Node 16.17.0 when using node internals (Changes backported from Node 18)

Expected Behavior

The loader does not throw when using node internals in Node 16.17.0.

Actual Behavior

Any usage of node internals, e.g.: fs, will cause the program to throw in Node 16.17.0 (LTS as of this writing).

Loader changes from Node 18 were backported to Node 16.17.0 https://github.com/nodejs/node/releases/tag/v16.17.0

Steps to Reproduce the Problem

  1. Install/upgrade to Node 16.17.0.
  2. Add an import to a node internal. E.g: import * as fs from 'fs'; in an ESM project.
  3. Run node with --loader point to the hook.mjs file.

Specifications

  • Version: 16.17.0
  • Platform: all
  • Subsystem: ?

Node 18 is expected to be LTS by October 2022, which will make the NODE_MAJOR check work again, but otherwise, the check should be updated to account 16.17+ as well.

`getExports` used for node v20, v18.19.0 doesn't handle reexports

import-in-the-middle hooking of a CommonJS module that has reexports breaks usage of that module. For example:

% node --version
v20.2.0

% cat foo.mjs
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
console.log('hi');

% node foo.mjs
hi

% node --experimental-loader=import-in-the-middle/hook.mjs foo.mjs
(node:91931) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
file:///Users/trentm/tmp/iitm-node20-exports/foo.mjs:1
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
                   ^^^^^^^^^^^^^^^^^^
SyntaxError: The requested module '@aws-sdk/client-s3' does not provide an export named 'ListBucketsCommand'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:122:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:188:5)

Node.js v20.2.0

Steps to Reproduce the Problem

  1. Save this "package.json":
{
  "name": "iitm-node20-exports",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "@aws-sdk/client-s3": "^3.363.0",
    "import-in-the-middle": "^1.4.1"
  }
}
  1. Save this "foo.mjs":
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
console.log('hi');
  1. Run this (using node v20):
node --experimental-loader=import-in-the-middle/hook.mjs foo.js

details

Adding this console.log to import-in-the-middle/lib/get-exports.js:

  if (format === 'commonjs') {
    console.log('XXX IITM getCjsExports of url %s\n-- source --\n%s\n-- parsed --\n%o\n--', url, source, getCjsExports(source))
    return addDefault(getCjsExports(source).exports)
  }
and re-running:
% node --experimental-loader=import-in-the-middle/hook.mjs foo.mjs
(node:96950) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
XXX IITM getCjsExports of url file:///Users/trentm/tmp/iitm-node20-exports/node_modules/@aws-sdk/client-s3/dist-cjs/index.js
-- source --
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.S3ServiceException = void 0;
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./S3Client"), exports);
tslib_1.__exportStar(require("./S3"), exports);
tslib_1.__exportStar(require("./commands"), exports);
tslib_1.__exportStar(require("./pagination"), exports);
tslib_1.__exportStar(require("./waiters"), exports);
tslib_1.__exportStar(require("./models"), exports);
var S3ServiceException_1 = require("./models/S3ServiceException");
Object.defineProperty(exports, "S3ServiceException", { enumerable: true, get: function () { return S3ServiceException_1.S3ServiceException; } });

-- parsed --
{
  exports: [ '__esModule', 'S3ServiceException', [length]: 2 ],
  reexports: [
    './S3Client',
    './S3',
    './commands',
    './pagination',
    './waiters',
    './models',
    [length]: 6
  ]
}
--
XXX IITM getCjsExports of url file:///Users/trentm/tmp/iitm-node20-exports/node_modules/@aws-sdk/client-s3/dist-cjs/index.js
-- source --
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.S3ServiceException = void 0;
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./S3Client"), exports);
tslib_1.__exportStar(require("./S3"), exports);
tslib_1.__exportStar(require("./commands"), exports);
tslib_1.__exportStar(require("./pagination"), exports);
tslib_1.__exportStar(require("./waiters"), exports);
tslib_1.__exportStar(require("./models"), exports);
var S3ServiceException_1 = require("./models/S3ServiceException");
Object.defineProperty(exports, "S3ServiceException", { enumerable: true, get: function () { return S3ServiceException_1.S3ServiceException; } });

-- parsed --
{
  exports: [ '__esModule', 'S3ServiceException', [length]: 2 ],
  reexports: [
    './S3Client',
    './S3',
    './commands',
    './pagination',
    './waiters',
    './models',
    [length]: 6
  ]
}
--
file:///Users/trentm/tmp/iitm-node20-exports/foo.mjs:1
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
                   ^^^^^^^^^^^^^^^^^^
SyntaxError: The requested module '@aws-sdk/client-s3' does not provide an export named 'ListBucketsCommand'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:122:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:188:5)

Node.js v20.2.0

That shows the "reexports" I'm referring to.

I was kind of surprised that cjs-module-lexer recognized tslib_1.__exportStar(require("./S3Client"), exports); and similar as a "reexport". Does it have particular smarts about tslib or is it parsing and/or executing tslib?

Does import-in-the-middle want/need to get into recursively handling these "reexports"?

SyntaxError on importing some packages when using register()

I'm using the ts-rest library. Without iitm it works just fine, but when i add iitm with register or --loader it throws SyntaxError: The requested module '@ts-rest/core' does not provide an export named 'initContract'.

Steps to Reproduce the Problem

Here is the reproduction repo.
There are three scripts:

  • start - regular launch with Node
  • start:hook - launch with hook import
  • start:register - launch with importing file with register call

Specifications

Library version: 1.8.0
Node version: 20.11.1

Using outdated dependency flags dependency confusion attack [PR available]

Expected Behavior

Prior to v1.9.0, acorn-import-attributes (then called acorn-import-assertions) used an implicit/not fully qualified reference to a dependency (test262).

This causes security scanning tools to flag a possible dependency confusion attack.

Actual Behavior

No security warning

Steps to Reproduce the Problem

  1. Run security scan (e.g. Orca) on code using this repo

Specifications

  • Version: 1.7.3
  • Platform: (all)
  • Subsystem: (all)

Loader fails when circular dependencies exist within an application

Expected Behavior

When running an application that has circular deps with import-in-the-middle, it should handle this accordingly.

Actual Behavior

It silently fails in pre Node 20, and in Node 20 errors with

file:///Users/revans/code/cyclic-loader-issue/dep-2.js:5
  dep1()
  ^

ReferenceError: Cannot access 'dep1' before initialization
    at dep2 (file:///Users/revans/code/cyclic-loader-issue/dep-2.js:5:3)
    at file:///Users/revans/code/cyclic-loader-issue/dep-1.js:7:1
    at ModuleJob.run (node:internal/modules/esm/module_job:217:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:308:24)
    at async loadESM (node:internal/process/esm_loader:42:7)
    at async handleMainPromise (node:internal/modules/run_main:66:12)

Steps to Reproduce the Problem

  1. git clone https://github.com/bizob2828/cyclic-loader-issue
  2. npm i && npm run start

Specifications

  • Version: Node 16,18,20.
  • Platform: All
  • Subsystem: All

`@@toStringTag` property not present on modules passed to `hookFn`

Expected Behavior

Using Hook, the module passed to hookFn includes @@toStringTag property as was the case up until [email protected]

Actual Behavior

Using Hook, the module passed to hookFn does not include the @@toStringTag property.

Steps to Reproduce the Problem

  1. npm init
  2. npm install --save-exact [email protected] (used as an example)
  3. npm install --save-exact [email protected]
  4. create index.mjs
// index.mjs
import Hook from 'import-in-the-middle'
import * as koa from 'koa';

Hook(['koa'], (exported, name, baseDir) => {
  // Expect "Module"
  if(exported[Symbol.toStringTag] !== "Module"){
      throw new Error('Expected module')
  }
})

console.log('Everything as expected');
  1. node --loader=import-in-the-middle/hook.mjs index.mjs This fails

However, doing the same with [email protected] works as expected:

  1. npm install --save-exact [email protected]
  2. node --loader=import-in-the-middle/hook.mjs index.mjs This succeeds

Additional info

I'm not certain that this is safe, but adding this to the generated code in hook.js after L312 includes the property from the primary namespace and seems to fix the issue.

for (const k of Object.getOwnPropertySymbols(primary)) {
   _[k] = primary[k]
 }

Specifications

  • Version: Node.js v18.19.0
  • Platform: Ubuntu 22.04

Identifier '$Completions' has already been declared

Getting error:

file:///Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/openai/resources/index.mjs?iitm=true:55
    let $Completions = namespace.Completions
        ^

SyntaxError: Identifier '$Completions' has already been declared
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:168:18)
    at callTranslator (node:internal/modules/esm/loader:279:14)
    at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:285:30)

The file contents:

// File generated from our OpenAPI spec by Stainless.
export * from "./chat/index.mjs";
export * from "./shared.mjs";
export { Audio } from "./audio/audio.mjs";
export { Beta } from "./beta/beta.mjs";
export { Completions, } from "./completions.mjs";
export { Embeddings } from "./embeddings.mjs";

After taking a closer look, the problem seems to be that ./chat/index.mjs exports:

// File generated from our OpenAPI spec by Stainless.
export { Chat } from "./chat.mjs";
export { Completions, } from "./completions.mjs";
//# sourceMappingURL=index.mjs.map

which end up conflicting with export { Completions, } from "./completions.mjs";

Use `es-module-lexer` to parse ESM

@AbhiPrasad suggested we use es-module-lexer father than acorn:

  • It's specifically designed for the task of parsing imports and exports
  • 8x smaller than acorn (4KiB vs 32KiB gzipped)
  • 20x faster than acorn

A very small single JS file (4KiB gzipped) that includes inlined Web Assembly for very fast source analysis of ECMAScript module syntax only.

For an example of the performance, Angular 1 (720KiB) is fully parsed in 5ms, in comparison to the fastest JS parser, Acorn which takes over 100ms.

Comprehensively handles the JS language grammar while remaining small and fast. - ~10ms per MB of JS cold and ~5ms per MB of JS warm, see benchmarks for more info.

I gave it a test and the entire getEsmExports can be replaced with this:

const { init, parse } = require('es-module-lexer')

async function getEsmExports (moduleSource) {
  await init
  const srcString = moduleSource.toString()
  const [imports, exports] = parse(srcString)

  const reexports = imports
    .map(i => [srcString.slice(i.s, i.e), srcString.slice(i.ss, i.se)])
    .filter(([, full]) => full.match(/export\s*\*\s*from/))
    .map(([file]) => `* from ${file}`)

  const exportNames = exports.map(e => e.n)
  return [...exportNames, ...reexports]
}

A couple of tests fail due to a couple of missing parser features:
guybedford/es-module-lexer#175
guybedford/es-module-lexer#176

Don't use parser for Node v22?

Testing on top of PR #85, I found that for Node v22, the parser doesn't appear to be required and we can revert back to the faster path of import(srcUrl).then(Object.keys).

We would need the parser to support v18.19 -> v21.

We could reduce the potential bundle size caused by the parser (~100KB) by:

  • Moving the parser to it's own package (eg. 'import-in-the-middle-parser')
  • Set package.json#engines to the Node versions where it's required
  • Make it an optionalDependency of import-in-the-middle
  • import('import-in-the-middle-parser') in hook.js in a try/catch

When installed with Node versions requiring the parser, the optional dependency will be installed and used.
When installed with Node v22+, the optional dependency will be missing and will not be included in any bundle

usage of `import` causes odd side effects/timing

Due to import() causing evaluation getting the namespaces of modules can cause double init since the loader module cache != the app module cache and can cause out of order evaluation.

A nasty intentional runtime error with a nested cycle could let you get a hold of a module namespace without executing it potentially / sadly.

// inspector
import 'will-explode';
import * as ns from 'to-inspect';
export function keys() {
  return Object.keys(ns);
}
// will-explode
import {keys} from 'inspector';
throw keys();

Hooking a Module that Exports a Function Does not Hook a Callable Function

Expected Behavior

When hooking a module that exports a function, the exported object passed to the hook will also be a function.

Actual Behavior

When hooking a module that exports a function, the exported object passed to the hook is not a function.

Steps to Reproduce the Problem

We've noticed that, unlike require-in-the-middle, if you're trying to import a module that exports a function object by default that the function won't be callable.

That is -- if we install fastify and import-in-the-middle in a type="module" project

% npm install import-in-the-middle
% npm install fastify

and have a simple sample program that looks like this

% cat index.js 
import Fastify from 'fastify'
import Hook from 'import-in-the-middle'

Hook(['fastify'], (exported, name, baseDir) => {
  const fastifyInstance = exported()
})

const fastifyInstance = Fastify({
  logger:true
})
console.log(fastifyInstance)

We get an error when running the program.

% node --loader=import-in-the-middle/hook.mjs index.js
//...
  const fastifyInstance = exported()
                          ^

TypeError: exported is not a function

With a require-in-the-middle hook, the exported variable is the callable function exported by the fastify module.

Is this something we could get import-in-the-middle doing? Or are there ๐Ÿ‘‹ reasons ๐Ÿ‘‹ this isn't possible with either ESM modules or loader hooks?

For context -- This is a small isolated example of a larger problem we're trying to solve. We have a piece of software with a large number of require-in-the-middle hooks. We'd like to be able to provide our users with the same functionality if they happen to be using import statements while still supporting folks who are sticking with CommondJS modules at the same time. The closer import-in-the-middle's behavior is to require-in-the-middle the less we need branching code or refactoring of our existing hooks.

Specifications

  • Version: 1.3.0
  • Platform: all
  • Subsystem: ?

no such file or directory

Expected Behavior

getExports is throwing an error:

{
  context: { format: 'module', importAttributes: {} },
  error: Error: ENOENT: no such file or directory, open '/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-yoga/esm/@graphql-yoga/logger'
      at async open (node:internal/fs/promises:633:25)
      at async readFile (node:internal/fs/promises:1242:14)
      at async getSource (node:internal/modules/esm/load:46:14)
      at async defaultLoad (node:internal/modules/esm/load:137:34)
      at async nextLoad (node:internal/modules/esm/hooks:750:22)
      at async getExports (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js:68:17)
      at async processModule (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:134:23)
      at async processModule (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:160:20)
      at async getSource (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:269:60)
      at async load (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:334:26) {
    errno: -2,
    code: 'ENOENT',
    syscall: 'open',
    path: '/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-yoga/esm/@graphql-yoga/logger'
  },
  url: 'file:///Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-yoga/esm/@graphql-yoga/logger'
}

I added the following code to catch the above error.

let parentCtx;

try {
  parentCtx = await parentLoad(url, context)
} catch (error) {
  console.log({
    context,
    error,
    url
  });

  throw error;
}

The same code works if I remove --loader=import-in-the-middle/hook.mjs.

Happy to provide more context.

Specifications

  • Platform: Node.js v21

Cannot handle node import assertions

Expected Behavior

Import assertions should be parsed correctly.

i.e. import contracts from './package.json' assert { type: 'json' };

Actual Behavior

When using the dd-trace/loader-hook.mjs, the nodejs server is unable to start due to the import-in-the-middle package failing to parse import assertions. Here is the related issue DataDog/dd-trace-js#2221 (comment)

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///Users/james/dev/myproject/build/package.json" needs an import assertion of type "json"
    at new NodeError (node:internal/errors:372:5)
    at validateAssertions (node:internal/modules/esm/assert:82:15)
    at defaultLoad (node:internal/modules/esm/load:24:3)
    at load (file:///Users/james/dev/myproject/node_modules/import-in-the-middle/hook.mjs:154:10)
    at ESMLoader.load (node:internal/modules/esm/loader:407:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:326:22)
    at new ModuleJob (node:internal/modules/esm/module_job:66:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:345:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:304:34)
    at async Promise.all (index 0) {
  code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}

**Note: ** When running node without the dd-trace/loader-hook.mjs, it runs fine (however, this issue still remains)

Steps to Reproduce the Problem

  1. node --experimental-specifier-resolution=node --experimental-loader dd-trace/loader-hook build/src/index.js - Use the dd-trace/loader-hook in an esm environment. In your build, ensure you have an import assertion, i.e. import contracts from './package.json' assert { type: 'json' };
  2. Run the node command above ^ and

Specifications

  • Version: dd-trace -> 3.2.0, node 17.1.0 (also tried on 16.17.0)
  • Platform: MacOS 11.6.4
  • Subsystem:

Further notes

  • I tried using the latest code released today: 3d77da2 - but this doesn't change anything

SyntaxError when importing a JSON file using { type: 'json' }

Expected Behavior

The import statement should successfully import the JSON file.

Actual Behavior

A "SyntaxError" is thrown.

Steps to Reproduce the Problem

  1. Import json file using with specific syntax ( with { type: 'json'} ):
    import data from './data.json' with { type: 'json' };
  2. So I updated import-in-the-middle to 1.8.0 (Note that this works fine on version 1.7.3)
  3. After running project I gettting this error:
    { "type": "SyntaxError", "message": "Unexpected token (1:818) at SyntaxError: Unexpected token (1:818)", "stack": SyntaxError: Unexpected token (1:818) at pp$4.raise (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:3586:15) at pp$9.unexpected (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:772:10) at pp$9.semicolon (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:749:68) at Parser.parseImport (/Users/inaiat/app/node_modules/.pnpm/[email protected][email protected]/node_modules/acorn-import-attributes/lib/index.js:242:12) at pp$8.parseStatement (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:948:51) at pp$8.parseTopLevel (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:829:23) at Parser.parse (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:601:17) at Function.parse (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:651:37) at getEsmExports (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-esm-exports.js:37:23) at getExports (/Users/inaiat/app/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js:73:12) "pos": 818, "loc": { "line": 1, "column": 818 }, "raisedAt": 822 }

Specifications

  • Version: 1.8.0
  • Platform: macOS Apple Silicon / Debian
  • Subsystem: nodejs version 22.3.0

Does not handle `import *` of current file

Another strange ESM edge case!

Some files in the openapi sdk import * themselves like below.

I don't know how common this is elsewhere but these files are auto-generated which might explain this strange pattern.

batches.mjs

// File generated from our OpenAPI spec by Stainless.
import * as BatchesAPI from "./batches.mjs";
export class Batches {}
export class BatchesPage {}
(function (Batches) {
    Batches.BatchesPage = BatchesAPI.BatchesPage;
})(Batches || (Batches = {}));

If you import the above in Node, there is no error.

If the import-in-the-middle/hook.mjs is registered, you get the following error:

file:///Users/tim/Documents/Repositories/repro/more.mjs:5
    Batches.BatchesPage = BatchesAPI.BatchesPage;
                                     ^
ReferenceError: Cannot access 'BatchesPage' before initialization
    at file:///Users/tim/Documents/Repositories/repro/more.mjs:5:38
    at file:///Users/tim/Documents/Repositories/repro/more.mjs:6:3
    at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:475:24)
    at async file:///Users/tim/Documents/Repositories/repro/test.mjs:5:1

parentResolve is not a function

Expected Behavior

import-in-the-middle to work.

Actual Behavior

at 20:24:19 โฏ node --import @sentry/node/preload ./dist/bin/server.js

node:internal/modules/run_main:125
    triggerUncaughtException(
    ^
TypeError [Error]: parentResolve is not a function
    at processModule (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:154:30)
    at async processModule (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:160:23)

This is the line where it happens:

        // Bare specifiers need to be resolved relative to the parent module.
        const result = await parentResolve(modFile, { parentURL: srcUrl })

If I inject console.log({parentResolve, modFile}); before, it prints:

{
  parentResolve: [AsyncFunction: nextResolve],
  modFile: '@envelop/types'
}
{
  parentResolve: [AsyncFunction: nextResolve],
  modFile: '@graphql-yoga/logger'
}

Appears to be some sort of race condition.

async function processModule ({ srcUrl, context, parentGetSource, parentResolve, excludeDefault }) {
+  console.log({parentResolve});

  const exportNames = await getExports(srcUrl, context, parentGetSource)
  const duplicates = new Set()
  const setters = new Map()

  const addSetter = (name, setter) => {
    // When doing an `import *` duplicates become undefined, so do the same
    if (setters.has(name)) {
      duplicates.add(name)
      setters.delete(name)
    } else if (!duplicates.has(name)) {
      setters.set(name, setter)
    }
  }

  for (const n of exportNames) {
    if (n === 'default' && excludeDefault) continue

    if (isStarExportLine(n) === true) {
      const [, modFile] = n.split('* from ')

      let modUrl
      if (isBareSpecifier(modFile)) {
+        console.log({parentResolve, modFile});

        // Bare specifiers need to be resolved relative to the parent module.
        const result = await parentResolve(modFile, { parentURL: srcUrl })
{ parentResolve: [AsyncFunction: nextResolve] }
{ cachedResolve: [AsyncFunction: nextResolve] }
{ parentResolve: [AsyncFunction: nextResolve] }
{ parentResolve: undefined }
{
  parentResolve: [AsyncFunction: nextResolve],
  modFile: '@envelop/types'
}
{ parentResolve: undefined }

Steps to Reproduce the Problem

N/A

Specifications

  • Version: v22.1.0
  • Platform: macOS
  • Subsystem: N/A

tests not running when using `import-in-the-middle` with `mocha`

It seems that when using import-in-the-middle with mocha, mocha exits without running tests as require.main === module evaluates to false when it's trying to determine if it is run directly from Node.js.

Expected Behavior

mocha runs tests even if IITM is used

Actual Behavior

mocha exists with 0, not running any tests

Steps to Reproduce the Problem

I've created a reproducer to illustrate that contains a more detailed description:
https://github.com/pichlermarc/esm-test

It contains two scripts, both attempt to run a single test that always fails, one with the IITM loader, and one without.

  1. git clone https://github.com/pichlermarc/esm-test
  2. npm install
  3. npm run test
    - this does not use the IITM hook, tests do run, and mocha exits with 1 (as expected)
  4. npm run test:iitm
    - this uses the IITM hook, tests do not run, and mocha exits with 0 (1 is expected)

Specifications

Assignments to exported variables are not visible in other modules

Expected Behavior

If a module exports a variable that was defined with let and can be reassigned, other modules that import that variable should always see the current value of the variable.

E.g.

env.js:

let env = { FOO: 'baz' };   // Starts with an old value

function setEnv(newEnv) {
  // Allow callers to set a new value
  env = newEnv;
}

export { env, setEnv };

If module1 calls setEnv with a new value, and module2 references env, module2 should see the new value.

Actual Behavior

With the Node flag --experimental-loader=import-in-the-middle/hook.mjs, module2 actually sees the old value.

Steps to Reproduce the Problem

I made a repro here: https://github.com/dennisjlee/iitm-assign-module-var-repro

Specifications

  • Version: import-in-the-middle 1.4.2
  • Platform: Node 18.18.2 or 20.9.0

Acorn parser does not support import attributes

The older import assertion syntax works:

import coolFile from './something.json' assert { type: 'json' }

But acorn does not appear to support the newer import attribute syntax and it will not get added until Stage 4:

import coolFile from './something.json' with { type: 'json' }

PR #104 actually works around this issue by falling back to the parent loader when parsing fails.

Explicitly named exports should be exported over `export *` exports

@mohd-akram and I incorrectly deduced that duplicate named exports resulted in those exports being excluded.

However, this is not always the case!

With the following code:
a.mjs

export function foo() { return 'a' }

b.mjs

export function foo() { return 'b' }

dupe.mjs

// the order here doesn't matter!
export * from './a.mjs'
export { foo } from './b.mjs'

test.mjs

import { foo } from './dupe.mjs'
console.log('out:', foo())
> node test.mjs
out: b

dupe.mjs should export foo from b.mjs. This is because explicitly named exports DO override export * exports.

Unable to use iitm ESM loader with @apollo/server

Expected Behavior

Using import-in-the-middle/hook.mjs as a loader in an ESM project works when @apollo/server is used by the project.

Actual Behavior

The node application failed to start. The behavior varies based on the version of node in use.

Node v18: exit code 13
Node v20: exit code 1 with SyntaxError: Unexpected token '*'

Steps to Reproduce the Problem

  1. clone https://github.com/barryhagan/iitm-apollo-server-repro
  2. npm i
  3. npm build
  4. npm run start

SyntaxError: Unexpected token

Getting error:

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
SyntaxError: Unexpected token (32:14)
    at Parser.pp$4.raise (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:3573:15)
    at Parser.pp$9.unexpected (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:772:10)
    at Parser.pp$9.expect (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:766:28)
    at Parser.pp$8.parseImportSpecifiers (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:1896:14)
    at Parser.parseImport (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected][email protected]/node_modules/acorn-import-assertions/lib/index.js:180:32)
    at Parser.pp$8.parseStatement (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:948:51)
    at Parser.pp$8.parseTopLevel (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:829:23)
    at Parser.parse (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:601:17)
    at Function.parse (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:651:37)
    at getEsmExports (/Users/x/Developer/contra/gaia/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-esm-exports.js:51:23)

The file which it attempts to parse is:

import { Logger } from './Logger.js';
import { createPool } from '@contra/slonik';
import { Connection } from '@temporalio/client';
import { Redis } from 'ioredis';
import { setTimeout } from 'node:timers/promises';
const log = Logger.child({
    namespace: 'waitFor',
});
export const checks = {
    api: async (url) => {
        const body = await fetch(url, {
            headers: {
                accept: 'text/html',
            },
            method: 'GET',
        }).then((response) => {
            return response.text();
        });
        if (!body.includes('GraphiQL')) {
            return false;
        }
        return true;
    },
    customProfiles: async (baseUrl) => {
        const body = await fetch(baseUrl, {
            headers: {
                accept: 'text/html',
                credentials: 'omit',
            },
            method: 'GET',
        }).then((response) => {
            return response.text();
        });
        if (!body?.includes('</html>')) {
            return false;
        }
        return true;
    },
    meilisearch: async (baseUrl) => {
        const url = new URL('health', baseUrl);
        const response = await fetch(url, {
            method: 'GET',
        });
        const { status } = await response.json();
        if (status !== 'available') {
            return false;
        }
        return true;
    },
    postgres: async (dsn) => {
        const pool = await createPool(dsn);
        pool.end();
        return true;
    },
    redis: async (url) => {
        const redis = new Redis(url, {
            lazyConnect: true,
        });
        redis.on('error', () => {
            // Not providing error handler will result in ioredis logging to stdout.
        });
        await redis.connect();
        redis.disconnect();
        return true;
    },
    temporal: async (url) => {
        try {
            const connection = await Connection.connect({
                address: url,
                connectTimeout: 1000,
            });
            await connection.close();
            return true;
        }
        catch {
            return false;
        }
    },
    webApp: async (baseUrl) => {
        const url = new URL('log-in', baseUrl);
        const body = await fetch(url, {
            headers: {
                accept: 'text/html',
            },
            method: 'GET',
        }).then((response) => {
            return response.text();
        });
        if (!body.includes('</html>')) {
            return false;
        }
        return true;
    },
};
export const waitFor = async (instructions) => {
    await Promise.all(instructions.map(async ({ checkName, serviceName, url }) => {
        let ready = false;
        while (!ready) {
            try {
                ready = await checks[checkName](url);
            }
            catch {
                ready = false;
            }
            if (ready) {
                log.info('%s (%s) is ready!', serviceName, checkName);
            }
            else {
                log.warn('%s (%s) not available', serviceName, checkName);
                await setTimeout(1000);
            }
        }
    }));
};
//# sourceMappingURL=waitFor.js.map

Trying to use import-in-the-middle to fix OpenTelemetry support (open-telemetry/opentelemetry-js#4437).

How does one workaround this?

Imports from @react-email/components break with import-in-the-middle

@react-email/components is a metapackage that re-exports the individual component libraries from @react-email such as @react-email/body, @react-email/html, @react-email/tailwind, etc.

It does this using export * from <package> in its index.mjs file, and the following (compiled) code for index.js:

module.exports = __toCommonJS(src_exports);
__reExport(src_exports, require("@react-email/body"), module.exports);
__reExport(src_exports, require("@react-email/button"), module.exports);
__reExport(src_exports, require("@react-email/column"), module.exports);
// etc for the rest of the component libraries

// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ...require("@react-email/body"),
  ...require("@react-email/button"),
  ...require("@react-email/column"),
  // etc for the rest of the component libraries
});

I have created an example minimal reproduction at: https://github.com/dawnmist/import-in-the-middle-react-email-issue

Expected Behavior

The application should be able to import any of the individual components directly from the @react-email/components library.

Actual Behavior

When import-in-the-middle is used as an --experimental-loader, the import paths for the @react-email components get incorrectly mapped as subdirectories/siblings of the index.js/index.mjs files of the @react-email/components library, resulting in file not found import errors:

Yarn 4 (nodeLinker=pnpm):

node:internal/process/esm_loader:40
      internalBinding('errors').triggerUncaughtException(
                                ^
[Error: ENOENT: no such file or directory, open '/home/username/project/node_modules/.store/@react-email-components-virtual-daf4c187d9/package/dist/@react-email/body'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/home/username/project/node_modules/.store/@react-email-components-virtual-daf4c187d9/package/dist/@react-email/body'
}

Yarn 3 (nodeLinker=pnpm):

(node:1753054) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("import-in-the-middle/hook.mjs", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)

node:internal/process/esm_loader:34
      internalBinding('errors').triggerUncaughtException(
                                ^
[Error: ENOENT: no such file or directory, open '/home/username/project/node_modules/.store/@react-email-components-virtual-8a501030e1/package/dist/@react-email/body'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/home/username/project/node_modules/.store/@react-email-components-virtual-8a501030e1/package/dist/@react-email/body'
}

Steps to Reproduce the Problem

I have created an example reproduction at: https://github.com/dawnmist/import-in-the-middle-react-email-issue

  1. Clone the git repo: https://github.com/dawnmist/import-in-the-middle-react-email-issue
  2. yarn install
  3. yarn build
  4. yarn start - server will run without using import-in-the-middle
  5. Go to http://localhost:3000 and you should see the output "Hello world!"
  6. Stop the server
  7. yarn start:import - the same server will fail to run at all when using import-in-the-middle as an experimental-loader, throwing the error above where the @react-email/body library is instead attempted to be loaded as a subdirectory of the @react-email/components library instead of being treated as a separate npm library.

Specifications

  • Version: import-in-the-middle version 1.7.3
  • Platform: Debian 12 linux, x86_64
  • NodeJS: 18.19.0 & 20.11.0
  • Yarn: 3.7.0 + 4.0.2

Does not handle CallExpression in ExportDefaultDeclaration node

Expected Behavior

Works.

Actual Behavior

TypeError: Cannot read properties of undefined (reading 'name')
    at getEsmExports (/Users/user/code/project/node_modules/import-in-the-middle/lib/get-esm-exports.js:71:59)
    at getExports (/Users/user/code/project/node_modules/import-in-the-middle/lib/get-exports.js:80:12)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:134:23)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:160:20)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:160:20)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:160:20)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:160:20)
    at async processModule (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:160:20)
    at async getSource (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:269:60)
    at async load (/Users/user/code/project/node_modules/import-in-the-middle/hook.js:335:26)

Steps to Reproduce the Problem

I'm getting this after updating Sentry to 8.x. I'm not sure where exactly import-in-the-middle is being called from, but the gist of it is that it is crashing on this file (node_modules/adminjs/lib/frontend/components/app/sort-link.js) because it has a function call in the export.

  1. require('import-in-the-middle/lib/get-esm-exports')({moduleSource: 'export default parseInt("1");', defaultAs: 'SortLink'})

Specifications

  • Version: 1.7.4
  • Platform: macOS
  • Subsystem: ?

Can it be used to hook imports in react app

Expected Behavior

Integerate it in a react app to hook axios but the logic is not being executed. Expected "in Hooks" to logged.

import Hook from "import-in-the-middle"
Hook(["axios"], (exports, name, baseDir) => {
  console.log("in hook of axios")
})
import axios from 'axios';

Actual Behavior

Nothing is logged.

Steps to Reproduce the Problem

Specifications

  • Version:
  • Platform: React.js
  • Subsystem:

Are sequential patched supported?

As far as i understand and according to some testing being done if there are multiple multiple hooks attached to the same package the last one will be eventually used in application. Am i right? If so are there any methods to apply sequential patches to the package. The case is that i'm using library that's patched the package i want to patch.

Library version: 1.8.0
Node version: 20.11.1

test/hook/v18-static-import-assert.mjs fails with `SyntaxError: Unexpected identifier 'assert'` with Node.js 22

Expected Behavior

Test suite passes with Node.js 22.

Actual Behavior

not ok 21 test/hook/v18-static-import-assert.mjs
   ---
   stdout: ''
   stderr: >-
     file:///home/iojs/tmp/citgm_tmp/57e69ae9-aca1-49ae-aae0-acf7ab1693e1/import-in-the-middle/test/fixtures/json.mjs:5
     import coolFile from './something.json' assert { type: 'json' }
                                             ^^^^^^
     SyntaxError: Unexpected identifier 'assert'
         at compileSourceTextModule (node:internal/modules/esm/utils:337:16)
         at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:166:18)
         at callTranslator (node:internal/modules/esm/loader:416:14)
         at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:422:30)
     Node.js v22.0.0
   ...

Steps to Reproduce the Problem

This is seen in CITGM runs for Node.js 22 (and main) where support for import assertions has been removed, replaced by import attributes.
e.g. https://ci.nodejs.org/job/citgm-smoker/3421/nodes=rhel8-x64/testReport/junit/(root)/citgm/import_in_the_middle_v1_7_3/

This appears to be coming from the fixture:

import coolFile from './something.json' assert { type: 'json' }

At first glance it looks fairly simple to update the fixture (replace assert with with) but it seems like the GitHub Actions here test on a selection of older Node.js 18 and 20 releases which do not have support for import attributes.

FTR support for import attributes landed in Node.js 18 in 18.20.0 and Node.js 20 in 20.10.0. There are no plans to remove import assertions from Node.js 18 or 20.

Specifications

  • Version:
  • Platform:
  • Subsystem:

Handle ExportAllDeclaration without node.exported

Expected Behavior

Given a module like:

export * from './other-file.mjs'

When IITM-ing this module I should get no errors when intercepting the import.

Actual Behavior

This error is thrown:

let $* = namespace.*
^
SyntaxError: Unexpected token '*'

This is because the ExportAllDeclaration case where node.exported is null is explicitly handled by pushing the exported name as "*".

Specifications

  • Version: 1.7.1
  • Platform: OSX
  • Subsystem: Node 18.19

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.