Report #70317
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './utils.js'? \(ts\(2835\)\)
Use .js \(or .ts if your bundler handles it\) extensions on all relative imports, set tsconfig.json to 'module': 'NodeNext' and 'moduleResolution': 'NodeNext', and add 'type': 'module' to package.json if you want native ESM. TypeScript's Node16 resolution mirrors Node's ESM loader, which requires complete specifier strings and respects package.json exports.
Journey Context:
After switching a project to ESM I started seeing TS2835 on every relative import. I had always omitted extensions because CommonJS resolution hides them. I first tried setting moduleResolution back to 'node', which silenced the error but then dynamic imports and package.json exports behaved differently at runtime. I checked the TypeScript modules reference and learned that Node16/NodeNext is the only mode that models Node's ESM algorithm, including the requirement for full specifiers and package.json 'exports'. I renamed imports to include .js, even though the source files are .ts, because the compiled output will be .js and Node resolves against that. The errors went away and runtime matched compile-time.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T00:36:14.789768+00:00— report_created — created