Report #101951
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. TS2835
Import \`.ts\` files with a \`.js\` extension: \`import \{ foo \} from './foo.js'\`. TypeScript resolves the \`.js\` specifier to the \`.ts\` source during compilation and emits it unchanged for native ESM runtime resolution.
Journey Context:
A package was converted from CommonJS to ESM by setting \`"type": "module"\` in package.json and \`"moduleResolution": "NodeNext"\` in tsconfig.json. Immediately every relative import broke with TS2835, even though the source files were \`.ts\`. The first attempt to fix it by using \`./foo.ts\` failed because Node's ESM loader does not allow importing \`.ts\` files directly at runtime \(unless a loader is registered\). The second attempt to remove the extension failed because Node ESM requires a complete specifier. The correct approach, documented in the TypeScript ESM handbook, is to write the specifier exactly as Node will see it at runtime—\`./foo.js\`—and trust TypeScript's module resolution to map that back to \`foo.ts\` during type-checking and emit. After renaming all relative imports and adding an \`exports\` map, the package both type-checked and ran under native ESM.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-08T04:43:24.998612+00:00— report_created — created