#838·tsx

CJS hook re-evaluates ESM dependencies on require(), creating duplicate module instances (Node ≥22.12)

Author: DanTskCreated Sep 3, 2026Updated Sep 3, 2026

Acknowledgements

  • I read the documentation and searched existing issues to avoid duplicates
  • I understand this is a bug tracker and anything other than a proven bug will be closed
  • I understand this is a free project and relies on community contributions
  • I read and understood the Contribution guide

Minimal reproduction URL

https://github.com/DanTsk/tsx-require-esm-dual-instance

Problem & expected behavior (under 200 words)

What happens. On Node ≥ 22.12, when a CommonJS dependency does require("graphql") (graphql 17 ships .mjs via exports), tsx's CJS hook transpiles graphql/index.mjs to CJS with esbuild and module._compile()s it — recursively, for the whole package (168 files land in require.cache). The result is a second graphql instance, distinct from the one import "graphql" produced. graphql brands schemas with a per-instance Symbol('Schema'), so isSchema() from one copy rejects objects from the other.

Real-world form: mercurius (CJS) + graphql@17 + @graphql-tools/schema → startup fails with Expected {…} to be a GraphQL schema. on a perfectly valid schema.

What I expected. Plain node already handles this: native require(esm) (loadESMFromCJS) goes through the ESM module map and returns the same instance (import === requiretrue). node --import tsx/esm also behaves correctly — only the CJS hook diverges.

Cause. module-extensions.ts transforms any ESM-syntax file unconditionally. shouldApplyRequireEsmInterop and isRequireEsmCandidate exist but only pick an error code (L214–223) and emulate module.exports interop post-hoc (L245–258) — they never defer to Node.

Proposed fix (verified against the real app via pnpm patch): after isEsmSyntax,

typescript
if (shouldApplyRequireEsmInterop && isEsmSyntax && isRequireEsmCandidate(cleanFilePath)) {
	return defaultLoader(module, cleanFilePath);
}

TS files are unaffected (isEsmSyntax is .js/.mjs-only); Node < 22.12 unchanged. Full analysis and CI matrix (Node 22 & 24) in the repo README.

Bugs are expected to be fixed by those affected by it

  • I'm interested in working on this issue

Compensating engineering work will speed up resolution and support the project

  • I'm willing to offer $10 for financial support