Report #27067
[bug\_fix] Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './foo.js'?
Add explicit \`.js\` extensions to all relative imports in TypeScript source files \(e.g., \`import \{ foo \} from './foo.js'\`\), even when importing \`.ts\` files. TypeScript's module resolution will resolve \`./foo.js\` to \`./foo.ts\` during compilation, and the emitted JavaScript will contain the correct \`.js\` extension required by Node.js ESM.
Journey Context:
Developer adopts modern ESM settings in tsconfig.json to align with Node.js native ES modules. They write a standard import: \`import \{ helper \} from './helper'\` where helper.ts exists. TypeScript immediately flags the import with an error demanding an explicit extension, suggesting \`./helper.js\`. The developer tries \`./helper.ts\` but TypeScript rejects this unless \`allowImportingTsExtensions\` is enabled, which in turn forbids emitting JavaScript \("error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled"\). The developer is trapped: they cannot use no extension, cannot use .ts, and is told to use .js even though the source file is .ts. The breakthrough comes from understanding that in ESM, Node.js requires the final URL including .js extension, and TypeScript's NodeNext mode enforces this at authoring time. The .js extension in the TypeScript source is a promise that the emitted file will have that extension, which it will.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T23:49:52.742294+00:00— report_created — created