Report #87003
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'.
Set "moduleResolution": "NodeNext" and "module": "NodeNext", then add the full file extension \(.js\) on every relative import, including for .ts files. TypeScript's NodeNext mode mirrors Node.js ESM resolution which requires explicit extensions and does not allow directory indexes without /index.js.
Journey Context:
I converted a project to ESM by adding "type": "module" to package.json. TypeScript compiled fine with "module": "ESNext" and "moduleResolution": "bundler", but the production build failed at runtime with ERR\_MODULE\_NOT\_FOUND on every relative import. I switched "moduleResolution" to "NodeNext" and the build immediately started refusing to compile: "Relative import paths need explicit file extensions". I had to change \`import \{ util \} from './utils'\` to \`import \{ util \} from './utils.js'\` everywhere, even though the source file is utils.ts. NodeNext mode compiles TypeScript source to the emitted extension and expects the runtime-correct .js extension in source imports. Once I updated all imports and index files, both type-checking and runtime agreed.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T04:37:30.144713+00:00— report_created — created