Avatar ·

Ошибка TypeScript: "File name differs from already included file name only in casing" на Windows

Привет всем! Я новичок в TypeScript и столкнулся со странной ошибкой при компиляции проекта на Windows. У меня есть проект, структура папок которого выглядит так: ``` src/ Components/ Header.tsx header.ts App.tsx index.ts ``` Я импортирую компонент Header в App.tsx: ```typescript // App.tsx import Header from './Components/Header'; ``` Но при запуске tsc получаю ошибку: ``` error TS1149: File name 'C:/project/src/Components/header.ts' differs from already included file name 'C:/project/src/Components/Header.tsx' only in casing. ``` Я понимаю, что на Windows файловая система не чувствительна к регистру, но я точно уверен, что у меня есть файл Header.tsx (с большой буквы), а header.ts — это другой файл. Почему TypeScript путает их? И как это исправить? Пробовал менять регистр в импорте — не помогает. Пробовал переименовывать файлы — тоже. В чем может быть проблема? Может быть, это баг TypeScript на Windows? Использую TypeScript 5.3, Windows 10, Visual Studio Code. <<>>

Причина ошибки

Ошибка возникает из-за особенностей работы TypeScript на Windows. Дело в том, что Windows использует файловую систему NTFS, которая по умолчанию не чувствительна к регистру (case-insensitive). Это означает, что для Windows файлы Header.tsx и header.ts — это один и тот же файл.

Однако TypeScript при компиляции отслеживает регистр имён файлов, чтобы избежать путаницы при переносе кода на Linux или macOS (где файловые системы чувствительны к регистру). Когда TypeScript находит два файла, различающихся только регистром, он выдаёт ошибку, так как на Windows это может привести к неоднозначности.

Как это исправить

Есть несколько способов решения проблемы:

1. Переименуйте один из файлов

Самый простой способ — дать файлам разные имена, которые отличаются не только регистром. Например:

  • Header.tsx → HeaderComponent.tsx
  • header.ts → headerUtils.ts

2. Используйте опцию forceConsistentCasingInFileNames

Включите эту опцию в tsconfig.json:

{
  "compilerOptions": {
    "forceConsistentCasingInFileNames": true
  }
}

Хотя это не решит саму ошибку, но поможет TypeScript более строго проверять регистр имён файлов и выдавать предупреждения на ранних этапах.

3. Используйте Git с настройкой регистра

Если вы используете Git, настройте его на чувствительность к регистру:

git config core.ignorecase false

Это поможет избежать проблем при клонировании репозитория на разные ОС.

4. Проверьте, нет ли дублирующихся импортов

Иногда ошибка возникает из-за того, что в разных файлах проекта используются разные варианты написания одного и того же файла. Проверьте все импорты в проекте:

// Плохо (разный регистр)
import Header from './Components/Header';
import header from './Components/header';

// Хорошо (единый регистр)
import Header from './Components/Header';
import headerUtils from './Components/headerUtils';

5. Используйте ESLint с правилом casing

Установите плагин eslint-plugin-import и включите правило:

{
  "rules": {
    "import/extensions": ["error", "never"],
    "import/no-unresolved": "error"
  }
}

Это поможет автоматически выявлять проблемы с регистром имён файлов.

Почему это происходит именно на Windows?

На Linux и macOS файловые системы (ext4, APFS) по умолчанию чувствительны к регистру, поэтому там файлы Header.tsx и header.ts будут считаться разными. На Windows же NTFS не различает регистр, что и приводит к подобным ошибкам.

Лучшая практика — всегда использовать единый стиль именования файлов в проекте. Рекомендую придерживаться PascalCase для компонентов (например, Header.tsx) и camelCase для утилит (например, headerUtils.ts).

<<>>

Альтернативное решение: настройка tsconfig.json

Если переименование файлов невозможно из-за уже существующей архитектуры, можно использовать опцию preserveSymlinks в tsconfig.json:

{
  "compilerOptions": {
    "preserveSymlinks": true
  }
}

Эта опция заставляет TypeScript использовать физический путь к файлу, а не символическую ссылку. Однако это может замедлить компиляцию и не всегда решает проблему.

Дополнительный совет: используйте TypeScript 5.0+ с moduleResolution: "bundler"

Начиная с TypeScript 5.0, появился новый режим разрешения модулей — bundler. Он лучше обрабатывает регистр имён файлов:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "module": "ESNext"
  }
}

Этот режим имитирует поведение современных сборщиков (Webpack, Vite) и менее строг к регистру имён файлов. Однако убедитесь, что ваш проект использует сборщик, поддерживающий этот режим.

Также рекомендую установить TypeScript-сервер в VSCode (команда TypeScript: Select TypeScript Version) и выбрать последнюю версию — это может решить проблему на уровне IDE.