Как правильно размещать @types/* пакеты: в dependencies или devDependencies?
Основное правило: типы — это инструмент разработки
В TypeScript файлы с расширением .d.ts (декларации типов) не используются в рантайме. Они нужны только компилятору TypeScript для проверки типов во время разработки и сборки. После компиляции в JavaScript все типы стираются. Поэтому в подавляющем большинстве случаев @types/* пакеты должны быть в devDependencies.
Давайте разберём на примере. Допустим, у вас есть код:
import _ from 'lodash';
const result: number[] = _.map([1, 2, 3], (n) => n * 2);
После компиляции TypeScript превратит это в:
const _ = require('lodash');
const result = _.map([1, 2, 3], (n) => n * 2);
Как видите, типов number[] и _.map больше нет. @types/lodash не нужен для выполнения этого кода — он нужен только чтобы TypeScript мог проверить, что вы правильно используете _.map.
Когда @types/* всё же нужны в dependencies?
Есть единственное исключение: если вы создаёте npm-пакет (библиотеку), который сам экспортирует типы. В этом случае:
- Ваш пакет компилируется и публикуется в npm.
- Потребители вашего пакета устанавливают его и хотят видеть типы.
- Если ваш пакет реэкспортирует типы из другой библиотеки (например,
@types/express), то этот пакет с типами должен быть вdependencies, чтобы он установился у пользователей вашей библиотеки.
Пример: вы пишете библиотеку my-express-utils, которая использует express и его типы:
// my-express-utils/src/index.ts
import { Request, Response } from 'express';
export function logRequest(req: Request, res: Response): void {
console.log(req.method, req.url);
}
Если вы поместите @types/express в devDependencies, то пользователи вашей библиотеки при установке my-express-utils не получат типы для Request и Response. Их TypeScript не сможет проверить использование вашей функции. Поэтому в этом случае @types/express должен быть в dependencies.
Как проверить для своего проекта?
Задайте себе вопрос: «Будет ли мой проект использоваться как зависимость другого проекта?»
- Если нет (вы пишете приложение, а не библиотеку) → смело помещайте @types/* в
devDependencies. На продакшене они не нужны. - Если да (вы пишете библиотеку) → проверьте, экспортируете ли вы типы из @types/* пакетов. Если да — в
dependencies. Если нет — вdevDependencies.
Что насчёт вашего страха «проект сломается»?
Не волнуйтесь. Если вы используете Webpack, то на продакшене запускается только скомпилированный JavaScript. TypeScript и его типы не участвуют в выполнении. Более того, на продакшн-сервере вы можете вообще не устанавливать devDependencies с помощью флага npm install --production. Это уменьшит размер node_modules и ускорит развёртывание.
Итоговая рекомендация для вашего проекта:
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.0.0",
"webpack": "^5.75.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/lodash": "^4.14.182"
}
}
Всё работает, типы проверяются в разработке, а на продакшене их нет — и это нормально.
<<Альтернативный подход: используйте bundledDependencies или утилиты
Если вы всё же беспокоитесь, что типы могут понадобиться на продакшене (например, для динамических импортов или плагинов), есть ещё один вариант — использовать bundledDependencies или специальные утилиты вроде tsc --build с настройкой declaration: true.
Но на практике это избыточно для большинства проектов. Вместо этого рекомендую:
- Для приложений: всегда
devDependencies. - Для библиотек: используйте
peerDependenciesдля @types/*, если типы не обязательны, но желательны. Это позволит пользователю самому решать, устанавливать их или нет.
Пример для библиотеки:
{
"peerDependencies": {
"@types/express": "^4.17.0"
},
"peerDependenciesMeta": {
"@types/express": {
"optional": true
}
}
}
Так вы не заставляете пользователя устанавливать типы, но даёте возможность их подключить. Этот подход особенно популярен в больших экосистемах, где типы могут конфликтовать с версиями TypeScript.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги