Report #77490
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports. Did you mean './utils.js'?
TypeScript's NodeNext/Node16 module resolution enforces Node.js native ESM requirements, which mandate explicit file extensions \(including .js for .ts source files\) for relative imports. TypeScript does not rewrite import paths during emit. The fix is to append .js to all relative imports in TypeScript source files: change import \{ foo \} from './foo' to import \{ foo \} from './foo.js', even though the source file is foo.ts. For directory imports \(index files\), use ./folder/index.js.
Journey Context:
Developer converts a Node.js project to ESM by setting 'type': 'module' in package.json. They update tsconfig.json to 'module': 'NodeNext' and 'moduleResolution': 'NodeNext' to align with Node's module resolution. Immediately, all relative imports light up red with TS2835: 'Relative import paths need explicit file extensions...'. Developer tries adding .ts extensions \('./foo.ts'\), but TypeScript errors that TS extensions are not allowed in JS imports. Confused, they look at the emitted JS in dist/ and see the import paths are exactly as written in TS. They realize Node.js ESM loader \(not TypeScript\) requires the exact file path including .js extension because Node doesn't do extension resolution like CJS. The 'Did you mean...' message from TS is the hint. Developer begrudgingly adds .js to every import in their .ts files, feeling counter-intuitive, but the code compiles and runs perfectly in Node.js ESM.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T12:40:08.511980+00:00— report_created — created