Avatar ·

Как правильно размещать @types/* пакеты: в dependencies или devDependencies?

Привет, коллеги! Я новичок в TypeScript и пытаюсь разобраться с правильной структурой package.json. У меня есть проект на React + TypeScript. Я устанавливаю типы для различных библиотек, например, для lodash, express и react. Я не понимаю, куда их помещать: в `dependencies` или `devDependencies`? Вот пример моего package.json: ```json { "name": "my-app", "version": "1.0.0", "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "lodash": "^4.17.21", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@types/lodash": "^4.14.182" }, "devDependencies": { "typescript": "^5.0.0", "webpack": "^5.75.0" } } ``` Я вижу, что многие в интернете помещают @types/* в devDependencies, но я не уверен, почему это правильно. Ведь если я использую lodash с типами в коде, то типы нужны для работы сборки? Или это не так? Мой проект собирается с помощью Webpack, и я запускаю `npm install` на продакшн-сервере. Я боюсь, что если я помещу @types/* в 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.