Как правильно размещать @types/* пакеты: в dependencies или devDependencies?
Ключевое правило: приложение vs библиотека
Главный принцип, который нужно усвоить: @types/* пакеты почти всегда должны быть в devDependencies, за исключением случая, когда вы публикуете библиотеку, и её пользователям нужны эти типы для работы с вашим кодом.
Давайте разберёмся, почему. TypeScript типы (.d.ts файлы) — это исключительно информация для компилятора. Они нужны на этапе разработки и сборки, но в скомпилированном JavaScript коде их нет. Поэтому в продакшене, когда ваше приложение уже работает, типы не нужны.
Случай 1: Вы пишете конечное приложение (веб-сервер, CLI, desktop app)
В этом случае всегда кладите @types/* в devDependencies.
Ваш package.json должен выглядеть так:
{
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.3.3",
"ts-node": "^10.9.2",
"@types/express": "^4.17.21",
"@types/node": "^20.11.5",
"@types/lodash": "^4.17.0",
"@types/jest": "^29.5.11"
}
}
Почему? Потому что при развёртывании вашего приложения на сервере (или при сборке Docker-образа) вы запускаете npm install --production (или npm ci --only=production). Эта команда устанавливает только dependencies, игнорируя devDependencies.
Если вы положите @types/express в dependencies, то в продакшене будут установлены десятки мегабайт ненужных файлов, которые замедлят развёртывание и увеличат размер образа. Более того, некоторые типы могут содержать ошибки или несовместимости, которые в продакшене вообще не должны проявляться.
Случай 2: Вы публикуете библиотеку (npm-пакет)
Здесь правило меняется. Если ваша библиотека использует типы из другого пакета, и эти типы "протекают" в публичный API вашей библиотеки, то их нужно указывать в dependencies.
Пример: вы пишете библиотеку my-express-utils, которая принимает Request и Response из Express.
// my-express-utils/index.ts
import { Request, Response } from 'express';
export function logRequest(req: Request): void {
console.log(req.method, req.url);
}
export function sendJson(res: Response, data: object): void {
res.json(data);
}
Пользователи вашей библиотеки будут импортировать logRequest и sendJson, и их TypeScript-компилятор должен знать типы Request и Response. Если вы положите @types/express в devDependencies, то при установке вашей библиотеки пользователь не получит эти типы, и его код не скомпилируется.
В этом случае @types/express должен быть в dependencies:
{
"name": "my-express-utils",
"dependencies": {
"express": "^4.18.2",
"@types/express": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.3.3",
"@types/node": "^20.11.5"
}
}
Практические рекомендации
- Для приложений: все
@types/*— вdevDependencies. Это 99% случаев. - Для библиотек:
@types/*— вdependencies, только если типы экспортируются или используются в публичном API. Иначе — вdevDependencies. - Исключение:
@types/node— обычно всегда вdevDependencies, даже для библиотек, потому что пользователи сами устанавливают Node.js типы для своей среды. - Проверка: используйте
npm ls @types/expressчтобы увидеть, откуда приходит пакет. Если он появляется в деревеdependencies— значит, он установится в продакшене.
В вашем случае (приложение на Express) просто переместите все @types/* в devDependencies. Это уменьшит размер продакшен-сборки и ускорит развёртывание.
Альтернативный подход: используйте peerDependencies для библиотек
Некоторые разработчики предпочитают не включать @types/* в dependencies даже для библиотек, а указывать их в peerDependencies. Это даёт пользователю гибкость в выборе версии типов.
{
"name": "my-express-utils",
"peerDependencies": {
"@types/express": "^4.17.0"
},
"devDependencies": {
"@types/express": "^4.17.21"
}
}
Плюс: пользователь сам решает, какую версию @types/express установить, и она не дублируется в его проекте.
Минус: если пользователь забудет установить @types/express, его код не скомпилируется, и он может не понять, в чём дело. Поэтому этот подход требует хорошей документации.
Для приложений этот метод не применяется — там по-прежнему используйте devDependencies.
Итог: для новичка самый безопасный путь — запомнить: "приложение → devDependencies, библиотека → dependencies (только для типов публичного API)". А когда наберётесь опыта, можно экспериментировать с peerDependencies.