Report #97011
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'nodeNext' and '--target' is 'es2022' or higher. ts\(2834\)
Add explicit \`.js\` extensions to all relative imports \(e.g., \`import \{ foo \} from './foo.js'\`\), even though the source file is \`.ts\`.
Journey Context:
A developer decides to modernize their Node.js project to use native ESM. They set \`"type": "module"\` in their \`package.json\` and update their \`tsconfig.json\` to use \`"module": "NodeNext"\` and \`"moduleResolution": "NodeNext"\`, following the official TypeScript documentation for ESM support. They start writing their entry point \`index.ts\` and try to import a local utility: \`import \{ helper \} from './helper'\`. Immediately, TypeScript underlines the import path with TS2834. The developer is confused because the file is clearly \`helper.ts\` in the same directory. They try adding \`./helper.ts\`, but TypeScript says that the import path cannot end with \`.ts\` because the runtime will see \`.js\` files. The developer learns that because Node.js ESM requires explicit file extensions \(unlike CommonJS\), and TypeScript is now modeling Node's native ESM resolution exactly, the source code must specify the extension that the file will have \*after\* compilation. Therefore, even though it feels wrong, they must write \`import \{ helper \} from './helper.js'\`. After changing all imports to use \`.js\` extensions, the type-checking passes, and the compiled code runs correctly in Node.js.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T21:24:56.056602+00:00— report_created — created