Расширения файлов и resolve.extensions

Параметр resolve.extensions в конфигурации Vite управляет списком расширений файлов, которые система автоматически пытается определить при импорте модулей без явного указания расширения.

По умолчанию Vite поддерживает распространённые расширения JavaScript, TypeScript и модулей:

resolve: {
  extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
}

Механизм работает аналогично поведению Node.js и Webpack: если в коде указан импорт без расширения, Vite перебирает список расширений по порядку и пытается найти подходящий файл.

Пример:

import App from './App'

Во время обработки Vite последовательно проверяет:

./App.mjs
./App.js
./App.mts
./App.ts
./App.jsx
./App.tsx
./App.json

Первый найденный файл используется как результат импорта.


Базовая настройка

Конфигурация задаётся в vite.config.js:

import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx']
  }
})

После этого Vite будет искать только указанные расширения.


Почему параметр важен

Автоматическое определение расширений влияет на:

  • удобство написания импортов;
  • совместимость со старыми проектами;
  • миграцию с Webpack;
  • производительность поиска модулей;
  • приоритет файлов;
  • корректность разрешения импортов.

Импорт без расширения

Стандартный импорт

import Button from './Button'

Если существует файл:

Button.jsx

то Vite автоматически подключит его.


Импорт с явным расширением

import Button from './Button.jsx'

В этом случае resolve.extensions уже не используется, потому что путь указан полностью.


Порядок расширений

Порядок элементов внутри массива критически важен.

Пример:

resolve: {
  extensions: ['.ts', '.js']
}

При наличии двух файлов:

utils.ts
utils.js

и импорте:

import utils from './utils'

Vite выберет:

utils.ts

Потому что .ts находится раньше.


Конфликт одинаковых имён

Частая проблема крупных проектов:

Button.js
Button.ts
Button.jsx
Button.tsx

При коротком импорте:

import Button from './Button'

результат зависит только от порядка в extensions.

Это может вызывать:

  • неожиданные импорты;
  • сложные баги;
  • путаницу между серверным и клиентским кодом;
  • проблемы при рефакторинге.

Расширения React

Для React-проектов обычно используются:

resolve: {
  extensions: ['.js', '.jsx', '.ts', '.tsx']
}

JSX и TSX

Если проект использует React + TypeScript:

Component.tsx

то импорт:

import Component from './Component'

будет успешно работать только при наличии .tsx в списке расширений.


Поддержка Vue

Для Vue-файлов:

Component.vue

можно добавить:

resolve: {
  extensions: ['.js', '.ts', '.vue']
}

Теперь станет возможным:

import Component from './Component'

вместо:

import Component from './Component.vue'

Особенности Vue

Для Vue-проектов рекомендуется указывать .vue явно:

import Component from './Component.vue'

Причины:

  • лучшая читаемость;
  • корректная работа IDE;
  • более предсказуемое разрешение модулей;
  • совместимость с линтерами;
  • упрощение навигации по проекту.

Поддержка собственных расширений

Vite позволяет добавлять нестандартные расширения.

Пример:

resolve: {
  extensions: ['.js', '.custom']
}

Теперь импорт:

import module from './example'

может разрешаться в:

example.custom

Использование с собственными загрузчиками

Подобная схема применяется:

  • в плагинах;
  • генераторах кода;
  • DSL-языках;
  • markdown-компонентах;
  • MDX;
  • системах шаблонов.

Работа с TypeScript

Автоматический поиск .ts

При настройке:

resolve: {
  extensions: ['.ts', '.js']
}

можно писать:

import api from './api'

вместо:

import api from './api.ts'

Особенности ESM

В обычном Node.js ESM требуется явное указание расширений:

import './file.js'

Но Vite использует собственную систему обработки модулей, поэтому разрешает сокращённые импорты.


Отличие от TypeScript compiler

tsconfig.json также содержит настройки разрешения модулей:

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

Однако TypeScript и Vite работают независимо.

Даже если TypeScript успешно проверяет импорт, Vite может не найти файл при неправильном resolve.extensions.


Производительность

Избыточное количество расширений

Каждый импорт без расширения инициирует последовательный поиск файлов.

Пример:

extensions: [
  '.js',
  '.jsx',
  '.ts',
  '.tsx',
  '.vue',
  '.json',
  '.mjs',
  '.mts'
]

Для одного импорта Vite может выполнить множество проверок файловой системы.


Влияние на большие проекты

В проектах с тысячами импортов:

  • увеличивается количество filesystem lookup;
  • возрастает нагрузка на watcher;
  • ухудшается скорость cold start;
  • замедляется HMR.

Рекомендации

Оптимальный подход:

resolve: {
  extensions: ['.ts', '.tsx', '.js']
}

Следует избегать:

  • лишних расширений;
  • устаревших форматов;
  • дублирующих типов файлов.

Разница между Vite и Webpack

Webpack

Webpack традиционно активно использует resolve.extensions.

Типичная конфигурация:

resolve: {
  extensions: ['.js', '.jsx', '.ts', '.tsx']
}

Vite

В Vite философия немного отличается.

Рекомендуется:

  • явно указывать расширения;
  • минимизировать магию;
  • делать импорты предсказуемыми.

Особенно это касается:

  • .vue;
  • .css;
  • .scss;
  • .svg;
  • .json.

Явные расширения против автоматических

Подход с автоматическим поиском

import Header from './Header'

Плюсы:

  • меньше текста;
  • компактные импорты;
  • совместимость со старыми проектами.

Минусы:

  • неоднозначность;
  • скрытые конфликты;
  • ухудшение читаемости.

Подход с явными расширениями

import Header from './Header.tsx'

Преимущества:

  • прозрачность;
  • предсказуемость;
  • меньше ошибок;
  • проще навигация;
  • лучше совместимость с ESM.

Импорт директорий

resolve.extensions участвует и при поиске индексных файлов.

Пример:

import utils from './utils'

Vite проверит:

utils.js
utils.ts
utils/index.js
utils/index.ts

в зависимости от конфигурации.


Использование вместе с alias

Пример alias

resolve: {
  alias: {
    '@': '/src'
  },
  extensions: ['.js', '.ts']
}

Импорт:

import api from '@/services/api'

будет автоматически разрешён в:

/src/services/api.ts

или:

/src/services/api.js

Проблемы IDE

Некоторые IDE хуже работают с неявными расширениями.

Возможные проблемы:

  • некорректный autocomplete;
  • ошибки навигации;
  • ложные предупреждения;
  • неправильный auto-import.

Особенно это проявляется:

  • в монорепозиториях;
  • при alias;
  • в mixed JS/TS проектах.

Расширения и SSR

В SSR-проектах особенно важно избегать неоднозначности.

Например:

api.server.ts
api.client.ts
api.ts

Неявный импорт:

import api from './api'

может привести к загрузке неправильной версии.


Практика минимализма

Во многих современных проектах используется минимальная конфигурация:

resolve: {
  extensions: ['.js', '.ts']
}

или вообще значение по умолчанию.


Когда стоит изменять resolve.extensions

Настройка действительно полезна при:

  • миграции с Webpack;
  • поддержке старого кода;
  • использовании нестандартных расширений;
  • работе с генераторами файлов;
  • интеграции собственных плагинов.

Когда лучше оставить настройки по умолчанию

Изменение параметра не требуется, если:

  • проект использует стандартные JS/TS расширения;
  • импорты указываются явно;
  • отсутствуют кастомные типы файлов;
  • нет конфликтующих модулей.

Типичные ошибки

Отсутствует нужное расширение

Конфигурация:

resolve: {
  extensions: ['.js']
}

Импорт:

import App from './App'

Файл:

App.tsx

Результат:

Failed to resolve import "./App"

Неправильный приоритет

extensions: ['.js', '.ts']

При наличии:

config.js
config.ts

будет выбран config.js, даже если ожидался TypeScript-файл.


Слишком большое количество расширений

extensions: [
  '.js',
  '.jsx',
  '.ts',
  '.tsx',
  '.vue',
  '.mjs',
  '.mts',
  '.json',
  '.custom'
]

Подобные конфигурации ухудшают производительность и усложняют сопровождение.


Практический пример

Конфигурация

import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    extensions: [
      '.ts',
      '.tsx',
      '.js',
      '.jsx'
    ]
  }
})

Структура проекта

src/
├── components/
│   ├── Button.tsx
│   └── Modal.jsx
├── utils/
│   └── api.ts

Импорты

import Button from './components/Button'
import Modal from './components/Modal'
import api from './utils/api'

Все модули будут корректно разрешены без явного указания расширений.


Рекомендации для современных проектов

TypeScript

extensions: ['.ts', '.tsx', '.js']

React

extensions: ['.tsx', '.ts', '.jsx', '.js']

Vue

Чаще используется явное указание:

import App from './App.vue'

Библиотеки

Для библиотек рекомендуется:

  • минимизировать автоматическое разрешение;
  • использовать явные расширения;
  • избегать неоднозначных импортов;
  • учитывать совместимость с Node.js ESM.