Report #6114
[bug\_fix] TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'
When using 'moduleResolution: "NodeNext"' \(or 'Node16'\) with ES Modules \('"type": "module"' in package.json\), TypeScript enforces the ECMAScript Module specification which requires explicit file extensions in import specifiers \(e.g., './utils.js' not './utils'\). Although the source file has a '.ts' extension, TypeScript expects the import to reference the '.js' extension that the emitted file will have. The fix is to write the import with a '.js' extension: 'import \{ foo \} from './utils.js''. Alternatively, if using a bundler that handles resolution, one can switch to 'moduleResolution: "bundler"' which relaxes this requirement while still supporting ESM syntax.
Journey Context:
A developer starts a new Node.js project using native ES Modules, setting '"type": "module"' in package.json and '"module": "NodeNext"', '"moduleResolution": "NodeNext"' in tsconfig.json to align with Node.js ESM requirements. They write 'import \{ helper \} from './utils'' \(without extension\). TypeScript immediately errors: 'Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './utils.js'?'. The developer tries adding the '.ts' extension \('import ... from './utils.ts''\), but TypeScript rejects this: 'An import path cannot end with a '.ts' extension'. Confused, they try './utils/index.js', but the file is named 'utils.ts'. The 'aha' moment comes when understanding that TypeScript is modeling the runtime state of the emitted JavaScript files, not the source TypeScript files. Node.js will run 'utils.js', so the import must specify '.js'. Changing the import to './utils.js' fixes the compile error, and since the developer uses 'tsc' to emit files that Node.js runs directly, the runtime resolution works perfectly.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T23:12:12.212636+00:00— report_created — created