Report #4039
[bug\_fix] The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
Align the project with Node.js ESM/CJS interop rules. Set \`module: "NodeNext"\` and \`moduleResolution: "NodeNext"\` in tsconfig.json. Ensure package.json contains \`"type": "module"\` to enable ESM mode, or use explicit \`.mts\` \(ESM\) and \`.cts\` \(CJS\) file extensions to disambiguate module types. In ESM mode, use full relative import paths including file extensions \(e.g., \`./foo.js\` even when importing \`./foo.ts\`\). The root cause is Node.js's strict enforcement that ESM files cannot be loaded via synchronous \`require\(\)\`, and TypeScript's NodeNext mode enforces these constraints at compile time to prevent runtime interop failures.
Journey Context:
Developer starts a new Node.js 20 project and wants to use modern ESM. They run \`npm init -y\` and install TypeScript. They create \`tsconfig.json\` with default \`module: "commonjs"\`. They write \`import express from 'express';\` in \`src/index.ts\`. Everything works. Then they read about ESM benefits and change \`tsconfig.json\` to \`module: "ESNext"\` and \`moduleResolution: "bundler"\`. They add \`"type": "module"\` to package.json. Suddenly, TypeScript reports: "The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module..." The developer is confused: "I set it to ESNext, why is it calling require?" Down the rabbit hole, they learn that \`module: "ESNext"\` with \`moduleResolution: "node"\` \(or default\) causes a mismatch where TS emits ESM syntax but resolves modules using CommonJS assumptions. They discover \`module: "NodeNext"\` and \`moduleResolution: "NodeNext"\`, which are specifically designed to match Node.js's strict ESM/CJS resolution algorithm. Now TypeScript insists they must use \`.js\` extensions on all imports: \`import \{ foo \} from './foo.js'\` even though the source file is \`foo.ts\`. They resist, but learn this is required by the ESM specification that Node.js enforces. They also discover that \`.mts\` and \`.cts\` extensions can force the module type per-file regardless of package.json. The fix works because \`module: "NodeNext"\` and \`moduleResolution: "NodeNext"\` tell TypeScript to enforce the exact same module resolution and interop rules as Node.js, including the prohibition on requiring ESM from CJS and the requirement for file extensions in ESM imports, thus catching interop errors at compile time instead of runtime.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T18:43:25.874149+00:00— report_created — created