moduleResolution визначає, як TypeScript шукає файл за рядком в import - і тим самим, чи збігатиметься його уявлення з тим, як код справді виконається.
bundler - для коду, який збирає Vite, webpack, esbuild чи виконує Bun:
import { formatPrice } from './utils'; // без розширення - гаразд
import Button from '@/components/Button.vue';
- розширення файлів можна не вказувати;
- підтримує поле
exportsуpackage.jsonзалежностей; - поєднується з
module: "esnext"або"preserve"(з TypeScript 6.0 - і зcommonjs).
nodenext - для коду, який напряму виконує Node.js (сервер, CLI, бібліотеки для Node):
import { formatPrice } from './utils.js'; // розширення обов'язкове
- точно моделює Node.js: ES-модуль чи CommonJS визначається полем
"type"уpackage.jsonі розширенням (.mts,.cts); - розширення обов'язкові у відносних імпортах ES-модулів, як вимагає Node.js;
- іде в парі з
module: "nodenext".
Чому розширення .js, якщо файл .ts: TypeScript не переписує шляхи в імпортах, а після компіляції поруч буде utils.js. Для коду, що виконується через вбудоване зняття типів Node.js, використовують .ts в імпортах разом з опцією rewriteRelativeImportExtensions (для генерації .js) або allowImportingTsExtensions (якщо JavaScript не генерується).
Що обрати:
| Код | moduleResolution |
|---|---|
| фронтенд на Vite (Laravel + Vue/React) | bundler |
| сервер чи скрипти, що запускає Node.js | nodenext |
| бібліотека для npm | nodenext - так перевірка суворіша й результат працює всюди |
Застарілі варіанти: node (він же node10) моделював Node.js 10 і не знав про exports; classic - алгоритм з часів до Node.js. Обидва в TypeScript 7 видалено - помилка «Option 'moduleResolution=node10' has been removed».
Ознака неправильного вибору: TypeScript без помилок перевіряє імпорт, який падає під час виконання (або навпаки). bundler для коду, що запускає Node.js, - типова причина.