@types/* в dependencies или devDependencies: главные ошибки

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему вопрос про @types/* мучает даже опытных разработчиков?

Вы когда-нибудь тратили час на дебаг ошибки, которая в итоге сводилась к неправильному расположению пакета @types/* в package.json? Если да — вы не одиноки. Вопрос «How do I decide whether @types/* goes into dependencies or devDependencies?» собрал тысячи просмотров на Stack Overflow и стал настоящей головной болью для TypeScript-сообщества.

Казалось бы, что сложного? Кинул типы в devDependencies — и дело с концом. Но не тут-то было. Когда ваш проект начинает расти, подключаются сторонние библиотеки, монорепозитории и CI/CD пайплайны, неправильное размещение типов может привести к фатальным ошибкам сборки, неожиданным падениям в production или, что ещё хуже, к тихой потере типизации.

В этой статье мы разберём топ-5 самых частых ошибок при работе с @types/*, научимся принимать правильное решение на основе реальных сценариев и составим чек-лист, который убережёт вас от проблем. Готовы? Поехали!

Ошибка №1: «Все @types/* всегда в devDependencies» — миф, который ломает сборки

Самый популярный совет в интернете звучит так: «Типы — это не runtime-код, поэтому они всегда должны быть в devDependencies». И это в 90% случаев действительно так. Но есть важное исключение, о котором забывают.

Когда devDependencies — правильный выбор

Если вы пишете приложение (веб-сервер на Express, React-фронтенд, утилиту командной строки), то все @types/* смело отправляйте в devDependencies. Ваш конечный пользователь не компилирует TypeScript — он запускает уже скомпилированный JavaScript. Типы нужны только вам во время разработки.

// Пример для приложения на Express
{
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.0",
    "typescript": "^5.3.3"
  }
}

Когда devDependencies — фатальная ошибка

А теперь представьте: вы разрабатываете библиотеку, которая экспортирует TypeScript-типы для внешних потребителей. Если типы вашей библиотеки зависят от @types/express, а вы положили его в devDependencies, то пользователь, установивший вашу библиотеку, не получит эти типы автоматически. И его IDE начнёт показывать красные подчёркивания.

// ❌ Неправильно для библиотеки, экспортирующей типы
{
  "devDependencies": {
    "@types/express": "^4.17.21" // Пользователь не увидит эти типы!
  }
}

// ✅ Правильно
{
  "dependencies": {
    "@types/express": "^4.17.21" // Типы будут доступны потребителям
  }
}

Золотое правило: Если ваша библиотека реэкспортирует типы из @types/* пакета или использует их в публичном API, то этот пакет должен быть в dependencies. Если типы используются только внутри — devDependencies.

Ошибка №2: Игнорирование разницы между «использую в коде» и «использую в типах»

Многие разработчики путают: «Я же импортирую Request из express, значит express должен быть в dependencies». Это верно для самого пакета express, но не для его типов.

Давайте разберём на конкретном примере. Есть три сценария использования пакета @types/lodash:

  • Сценарий А: Вы пишете import _ from 'lodash' и используете _.chunk(). Типы нужны только для компиляции — devDependencies.
  • Сценарий Б: Вы создаёте функцию function mySort(arr: T[]): T[] и внутри используете _.orderBy. Типы не выходят наружу — devDependencies.
  • Сценарий В: Вы пишете export function process(items: _.List<number>) — тип List из lodash становится частью вашего публичного API. Это значит, что @types/lodash должен быть в dependencies.

Как проверить себя: откройте сгенерированные .d.ts файлы вашей библиотеки. Если там есть import('@types/lodash') или /// <reference types="@types/lodash" /> — значит, типы должны быть в dependencies.

Ошибка №3: Забыть про @types/node в библиотеках

Классическая ситуация: вы пишете библиотеку для Node.js, используете Buffer, URL или process.env. В TypeScript эти типы предоставляются пакетом @types/node. Куда его ставить?

Ответ зависит от того, экспортируете ли вы что-то, что использует эти типы. Если ваша функция возвращает Buffer — типы Node.js становятся публичным контрактом. Значит, @types/node должен быть в dependencies.

// ❌ Ошибка: типы Buffer не дойдут до пользователя
{
  "devDependencies": {
    "@types/node": "^20.11.0"
  }
}

// ✅ Правильно: Buffer — часть публичного API
{
  "dependencies": {
    "@types/node": "^20.11.0"
  }
}

// Ваша библиотека
export function readConfig(path: string): Buffer {
  return fs.readFileSync(path);
}

А если вы используете Buffer только внутри (например, для временного хранения данных) и не экспортируете его — смело оставляйте в devDependencies.

Ошибка №4: Путаница с монорепозиториями и workspace

Когда вы работаете с монорепозиторием (Lerna, Nx, Turborepo), правила немного меняются. Пакет @types/*, установленный в корневом node_modules, может «случайно» подхватиться всеми воркспейсами. Это создаёт иллюзию, что всё работает.

Но как только вы попытаетесь опубликовать один из пакетов отдельно — сборка упадёт с ошибкой Cannot find module '@types/express'.

Как избежать: всегда явно указывайте @types/* в package.json того пакета, который их использует. Не надейтесь на «общие» зависимости из корня.

// packages/my-lib/package.json
{
  "name": "@my-org/my-lib",
  "dependencies": {
    "@types/express": "^4.17.21" // Явно указано здесь!
  }
}

Ошибка №5: Неправильное использование @

Блоги

Рекомендаций по книгам