Введение: почему вопрос про @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" // Явно указано здесь!
}
}