datadog / import-in-the-middle Goto Github PK
View Code? Open in Web Editor NEWLike `require-in-the-middle`, but for ESM import
Home Page: https://www.npmjs.com/package/import-in-the-middle
License: Other
Like `require-in-the-middle`, but for ESM import
Home Page: https://www.npmjs.com/package/import-in-the-middle
License: Other
Given a module like:
export * from './other-file.mjs'
When IITM-ing this module I should get no errors when intercepting the import.
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 "*"
.
@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.
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.
With the Node flag --experimental-loader=import-in-the-middle/hook.mjs
, module2 actually sees the old value.
I made a repro here: https://github.com/dennisjlee/iitm-assign-module-var-repro
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?
See https://nodejs.org/en/blog/release/v20.6.0#new-nodemodule-api-register-for-module-customization-hooks-new-initialize-hook which appears to have changed --experimental-loader
chaining such that it no longer works correctly.
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'
$ node load-sub.mjs
loaded submodule
bar
$ node --loader=./esm-loader.mjs load-sub.mjs
loaded submodule
bar
$ 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
Shown above.
@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.
@AbhiPrasad suggested we use es-module-lexer
father than acorn
:
acorn
(4KiB vs 32KiB gzipped)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
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
.
Due to WASM and https://github.com/bmeck/proposal-arbitrary-module-namespace-identifiers JS can export names that are not identifiers, this generally only affects WASM though
See potential fix by using index rather than name like in https://github.com/nodejs/node/blob/255b1db9320df7b2e17a248ba4ab659d3f25853d/test/fixtures/es-module-loaders/mock-loader.mjs#L232
import-in-the-middle
to work.
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 }
N/A
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?
import-in-the-middle/lib/get-exports.js
Lines 20 to 30 in a8da141
@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
The application should be able to import any of the individual components directly from the @react-email/components
library.
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'
}
I have created an example reproduction at: https://github.com/dawnmist/import-in-the-middle-react-email-issue
yarn install
yarn build
yarn start
- server will run without using import-in-the-middleyarn 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.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
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';
Nothing is logged.
Test suite passes with Node.js 22.
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
...
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:
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.
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)
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'
.
Here is the reproduction repo.
There are three scripts:
register
callLibrary version: 1.8.0
Node version: 20.11.1
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.
mocha
runs tests even if IITM is used
mocha
exists with 0
, not running any tests
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.
git clone https://github.com/pichlermarc/esm-test
npm install
npm run test
1
(as expected)npm run test:iitm
mocha
exits with 0
(1
is expected)[email protected]
[email protected]
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.
No security warning
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
.
Without the patch, MITM does not recognize TypeScript extensions (i.e. .ts
, .mts
, .cts
).
npm i --ignore-scripts
npm test
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();
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
{
"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"
}
}
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
console.log('hi');
node --experimental-loader=import-in-the-middle/hook.mjs foo.js
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)
}
% 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"?
When running an application that has circular deps with import-in-the-middle
, it should handle this accordingly.
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)
git clone https://github.com/bizob2828/cyclic-loader-issue
npm i && npm run start
works with modules that export a then()
method
export function then() {}
crash
use indirection like a data URL to load the module namespace rather than directly when using import()
: https://github.com/DataDog/import-in-the-middle/blob/main/hook.mjs#L40
The import statement should successfully import the JSON file.
A "SyntaxError" is thrown.
import data from './data.json' with { type: 'json' };
{ "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 }
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.
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.
The error Cannot find module '.prisma/client/default' is thrown.
npm i
.cd packages/a/test/prisma/
npm i
within this directory.cd ../../../../
node --loader ./hook.mjs packages/a/test/prisma/app.mjs
node --import ./register packages/a/test/prisma/app.mjs
If exports cannot be parsed from source, iitm should fail gracefully and fall back to not wrapping modules.
iitm can fail to parse the source for a number of reasons:
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:
package.json#engines
to the Node versions where it's requiredoptionalDependency
of import-in-the-middle
import('import-in-the-middle-parser')
in hook.js
in a try/catchWhen 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
Using Hook
, the module passed to hookFn
includes @@toStringTag
property as was the case up until [email protected]
Using Hook
, the module passed to hookFn
does not include the @@toStringTag
property.
npm init
npm install --save-exact [email protected]
(used as an example)npm install --save-exact [email protected]
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');
node --loader=import-in-the-middle/hook.mjs index.mjs
This failsHowever, doing the same with [email protected]
works as expected:
npm install --save-exact [email protected]
node --loader=import-in-the-middle/hook.mjs index.mjs
This succeedsI'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]
}
Using import-in-the-middle/hook.mjs as a loader in an ESM project works when @apollo/server is used by the project.
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 '*'
Works.
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)
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.
require('import-in-the-middle/lib/get-esm-exports')({moduleSource: 'export default parseInt("1");', defaultAs: 'SortLink'})
The loader does not throw when using node internals in Node 16.17.0.
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
import * as fs from 'fs';
in an ESM project.--loader
point to the hook.mjs
file.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
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.
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.
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";
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
Import assertions should be parsed correctly.
i.e. import contracts from './package.json' assert { type: 'json' };
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)
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' };
When hooking a module that exports a function, the exported object passed to the hook will also be a function.
When hooking a module that exports a function, the exported object passed to the hook is not a function.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.