Report #44921
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './utils.js'? \(TS2835\)
Add the \`.js\` extension to all relative imports \(e.g., \`import \{ helper \} from './utils.js'\`\), even when the source file is \`utils.ts\`. TypeScript understands this mapping during compilation, and it ensures the output is valid ESM for Node.js. Alternatively, if using a bundler like Vite or Webpack, change \`moduleResolution\` to \`"bundler"\` \(TypeScript 4.7\+\) which relaxes the extension requirement while maintaining ESM compatibility.
Journey Context:
You are migrating a Node.js project from CommonJS to ESM. You set \`"type": "module"\` in \`package.json\` and update \`tsconfig.json\` to use \`"module": "NodeNext"\` and \`"moduleResolution": "NodeNext"\` to align with Node's native ESM requirements. You try to run your app and TypeScript immediately flags all your local imports: \`import \{ utils \} from './utils';\`. The error TS2835 insists you use \`./utils.js\`. You initially try changing it to \`./utils.ts\`, but TypeScript rejects that too, stating that the output file will be \`.js\` and that's what must be specified. You realize that even though you are writing TypeScript, you must write the import path as if it were the compiled JavaScript output. You update all imports to use \`.js\` extensions, and the application compiles and runs correctly under Node's ESM loader.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T05:52:04.721574+00:00— report_created — created