Report #17243
[bug\_fix] TS2835: Relative import paths need explicit file extensions in ECMAScript imports.
When using TypeScript with ESM \(\`"module": "NodeNext"\` or \`"ESNext"\` in Node.js\), the compiler enforces Node.js's native ESM resolution rules. Node.js ESM requires explicit file extensions \(\`.js\`, \`.mjs\`\) for relative imports; it does not support automatic extension resolution like CommonJS. You must write \`import \{ foo \} from './utils.js'\` even though the source file is \`utils.ts\`. TypeScript will resolve \`./utils.js\` to \`utils.ts\` during compilation but emit the \`.js\` extension into the output.
Journey Context:
Developer migrates a project to ESM by setting \`"type": "module"\` in \`package.json\` and updates \`tsconfig.json\` to use \`"module": "NodeNext"\` and \`"moduleResolution": "NodeNext"\` \(the recommended combination for Node ESM\). They attempt to import a local utility: \`import \{ helper \} from './utils';\` where \`utils.ts\` exists. TypeScript immediately reports \`TS2835: Relative import paths need explicit file extensions in ECMAScript imports. Did you mean './utils.js'?\`. The developer is confused because the file is \`.ts\`, not \`.js\`. They try writing \`./utils.ts\`, but TypeScript errors with \`TS2691: An import path cannot end with a '.ts' extension\` \(unless using specific TS 5.0\+ flags with noEmit\). After reading the error message carefully and checking the TypeScript ESM handbook, they realize that for Node.js ESM compatibility, they must write the import with a \`.js\` extension: \`import \{ helper \} from './utils.js'\`. TypeScript's module resolution will map that \`.js\` specifier back to the \`.ts\` source file during compilation, and the emitted JavaScript will contain the correct \`.js\` extension that Node.js requires at runtime.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T04:50:43.746928+00:00— report_created — created