Module Resolution: node vs bundler, esModuleInterop
Болезненная тема: «у меня всё работает, у тебя — нет» обычно про moduleResolution и расширения. Красный флаг — не знать, что nodenext требует расширений в импортах, и путать esModuleInterop с allowSyntheticDefaultImports.
Какую стратегию выбрать:
// Сводка// 'node' — классика TS до 4.7, ищет 'pkg' через node_modules без поддержки exports map// 'node16' / 'nodenext' — современная Node ESM/CJS:// полная поддержка package.json#exports, conditional exports,// ОБЯЗАТЕЛЬНЫ расширения './util.js' даже из .ts// 'bundler' — TS 5.0+: поведение, как у Vite/esbuild/webpack:// exports map поддержан, расширения НЕ обязательны// tsconfig.json для современного веб-проекта (Vite/Next):{ "compilerOptions": { "module": "ESNext", "moduleResolution": "bundler", "esModuleInterop": true, // import fs from 'fs' для cjs-модулей "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "isolatedModules": true }}
// tsconfig.json для Node-библиотеки на ESM:{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2022", "esModuleInterop": true }}Что меняет esModuleInterop:
// без esModuleInterop:import * as fs from 'fs';fs.readFileSync('a');
// с esModuleInterop:import fs from 'fs';fs.readFileSync('a'); // CJS-модуль импортируется как defaultИтог: Веб с бандлером — moduleResolution: bundler; чистая Node ESM-библиотека — nodenext с расширениями .js в импортах.