логотип PurpleSchool
логотип PurpleSchool

Настройка типов с declare module в TypeScript

Автор

Елена Голубева

Введение

TypeScript предоставляет разработчикам мощный механизм типизации, который помогает выявлять ошибки в коде еще до этапа компиляции. Однако далеко не всегда все сторонние библиотеки имеют свои объявления типов, и иногда требуется описывать их самостоятельно. Для решения этой задачи предназначена конструкция declare module. Она позволяет настраивать типы для сторонних зависимостей, а также расширять типы ваших собственных модулей и даже глобальных объектов.

В этой статье вы узнаете, как использовать declare module в различных сценариях: от добавления поддержки типизации для JS-библиотек без типов, до расширения уже существующих модулей. Я покажу, как грамотно структурировать такие определения, на что обращать внимание при их настройке и приведу примеры кода с подробными пояснениями.

Когда и зачем использовать declare module

Давайте разберем, зачем вообще может понадобиться явно объявлять модуль в вашем TypeScript-проекте:

  • Сторонний модуль без типов
    Вы используете npm-пакет, у которого нет официальной поддержки типов (@types/packagename тоже отсутствует).
  • Собственная JS-библиотека
    У вас есть собственный JS-модуль или один из членов команды написал библиотеку на чистом JS, и вы хотите использовать его в TypeScript-проекте с типовой поддержкой.
  • Расширение типов existentes пакета
    Иногда нужно добавить новые методы, свойства или интерфейсы существующему модулю, не изменяя сам пакет.
  • Работа с динамическими импортами и legacy-кодом
    Используются редкие способы импорта или нестандартные структуры, которые TypeScript не распознает из коробки.

Вот почему знание и умение правильно использовать declare module — неотъемлемая часть эффективной работы с TypeScript.

Синтаксис и ключевые особенности

Базовый синтаксис

Такое объявление выглядит следующим образом:

declare module 'название-модуля' {
  // Описание типов, интерфейсов, переменных и классов внутри модуля
}

Название модуля в кавычках должно точно совпадать с тем, как вы его импортируете в проекте. Внутри секции можно определять интерфейсы, типы, функции, классы и переменные, которые предоставляет модуль.

Где размещать такие объявления

Объявления модулей чаще всего помещаются в файлы с расширением .d.ts (declaration files), чтобы они не попадали в итоговый JS-код.

  • src/types/my-custom-module.d.ts — для монорепозиториев и крупных проектов рекомендуется хранить собственные объявления типов в отдельной папке types.
  • Также можно создать объявление прямо в global.d.ts, если расширяете существующий глобальный модуль.

Важно помнить: TypeScript автоматически находит файлы с расширением .d.ts, если они лежат в папке, указанной в настройках tsconfig.json (например, в "typeRoots").

Различие между declare module и обычным module

declare module — это конструкция только для объявления типов, она не создает новый модуль или код в JS-файле, а сообщает компилятору, какой API у модуля. В обычном режимe module участвует в создании кода, а не только типов.

Примеры реального использования

Давайте рассмотрим несколько практических сценариев, чтобы вы могли видеть, как это выглядит на практике.

Сценарий 1: Использование JS-библиотеки без типов

Проблема:
В проекте используется библиотека awesome-lib, в которой нет объявления типов. При попытке импорта TypeScript выбрасывает ошибку "Cannot find module 'awesome-lib' or its corresponding type declarations".

Решение:
Создаем файл awesome-lib.d.ts с таким содержимым:

// Объявляем, что есть модуль 'awesome-lib'
declare module 'awesome-lib' {
  // Определяем, что модуль экспортирует функцию по умолчанию
  export default function greet(name: string): string;

  // И еще одну функцию с именованным экспортом
  export function add(x: number, y: number): number;
}

Теперь модуль можно использовать с полной поддержкой типов:

import greet, { add } from 'awesome-lib';

const message = greet('Anna'); // string
const sum = add(4, 9); // number

Пояснение:
В объявлении мы сказали TypeScript, что модуль существует и указали сигнатуры экспортируемых функций. Теперь редактор подсказывает типы и предупреждает об ошибках при некорректном использовании API.

Сценарий 2: Типизация для собственных JS-файлов

Проблема:
У вас есть файл utils.js, который экспортирует несколько функций, а проект написан на TypeScript.

Решение:
Создайте рядом типовой файл utils.d.ts:

// Предположим, что utils.js экспортирует эти функции
declare module './utils' {
  export function multiply(a: number, b: number): number;
  export function isEven(n: number): boolean;
}

Использование:

import { multiply, isEven } from './utils';

const result = multiply(2, 8); // number
const isMultipleOf2 = isEven(result); // boolean

Пояснение:
Такой подход позволяет вам пользоваться всеми преимуществами TypeScript даже с исходным JS-кодом.

Сценарий 3: Расширение типов существующего модуля

Иногда хочется добавить дополнительные свойства или методы в уже существующий модуль без модификации оригинальных типов.

Допустим, у вас есть:

  • пакет express, и вы хотите добавить новое свойство в тип Request.

Реализация:

// my-express-extensions.d.ts
import 'express';

declare module 'express-serve-static-core' {
  interface Request {
    userId?: string; // Теперь везде в req.userId будет подсказка типа
  }
}

Пояснение:
Этот способ называется declaration merging — TypeScript объединит ваши дополнения с официальными типами пакета.

Сценарий 4: Объявление модулей с неизвестным содержимым

Если структура модуля неизвестна или переменчива (например, импортируются статические ресурсы — картинки или стили), это тоже можно типизировать.

Например, для SVG-файлов:

// svg-modules.d.ts
declare module '*.svg' {
  // Экспортируется строка с путем до файла
  const content: string;
  export default content;
}

Теперь можно импортировать SVG-файлы так:

import logoPath from './logo.svg'; // logoPath: string

Пояснение:
Такой подход применяется для любых не-TS/JS-ресурсов: JSON, CSS, JPG и т.д.

Сценарий 5: Модули с вложенной структурой

Иногда пакеты имеют вложенные подмодули (например, 'lodash/fp'), и если у пользователя нет официальной типизации — их тоже следует объявлять явно.

declare module 'lodash/fp' {
  export function map<T, U>(
    fn: (item: T) => U,
    arr: T[]
  ): U[];
  // другие методы по аналогии
}

Best Practices: полезные советы и нюансы

Автоматизация поиска объявлений

Если используете редко встречающийся пакет, поищите типы на DefinitelyTyped — возможно, кто-то уже сделал нужный вам файл. Подключайте их через npm как @types/название-пакета. Если такого пакета нет — пишите свой declare module.

Где хранить свои типы

Лучше складывать все самописные объявления модулей в отдельную папку (src/types или @types). Не забывайте добавить её в "typeRoots" или "include" вашего tsconfig.json, чтобы компилятор всегда видел ваши описания.

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "typeRoots": ["./node_modules/@types", "./src/types"]
  }
}

Частичная типизация

Если у модуля большой API, а вы используете только часть функций, типизируйте только их — остальные можно не описывать. Позже можно будет добавить новые определения.

Использование any для быстрого прототипирования

В случаях, когда структура модуля вам неизвестна или вы только начинаете интеграцию, используйте следующий шаблон:

declare module 'legacy-module' {
  const whatever: any;
  export = whatever;
}

Но лучше со временем заменить все any на конкретные типы.

Не трогайте чужие файлы в node_modules

Всегда добавляйте собственные объявления типов вне node_modules, чтобы избежать конфликтов и упростить поддержку кода.

Заключение

declare module — это удобный и гибкий инструмент для настройки типизации в TypeScript, который позволяет работать с любыми внешними JS-модулями, даже если у них нет официальной поддержки типов. Благодаря грамотному использованию этой конструкции можно максимально обезопасить и структурировать свой проект, получать поддержку автодополнения и видеть возможные ошибки уже на этапе разработки.

Модульные объявления типов позволяют интегрировать сторонний код, делать проект независимым от специфики конкретных библиотек и легче поддерживать его в будущем. Запомните основные правила: храните объявления в удобном месте, старайтесь типизировать только нужную часть, а если расширяете существующие типы — используйте возможности declaration merging.


Частозадаваемые технические вопросы по теме и ответы на них

Как типизировать экспорт через module.exports в CommonJS-модуле?

Если библиотека использует экспорты "в стиле Node" (module.exports = ...), объявление должно выглядеть так:

declare module 'имя-модуля' {
  const whatever: any;
  export = whatever; // Экспорт через assignment
}

Теперь можно использовать import whatever = require('имя-модуля').

Почему объявление типа не работает и TypeScript продолжает ругаться на "Cannot find module"?

Проверьте:

  • Расширение файла — .d.ts, а не .ts.
  • Описание находится в папке, указанной в typeRoots или входит в "include" вашего tsconfig.json.
  • Название модуля внутри кавычек полностью соответствует строке импорта.

Как расширить типы для глобальных переменных или window?

Используйте объявление модуля без имени:

declare global {
  interface Window {
    myCustomVar: string;
  }
}

И добавьте этот файл в проект.

Как добавить типизацию для css/scss-модулей?

Объявите файл по маске, например:

declare module '*.module.scss' {
  const classes: { [key: string]: string };
  export default classes;
}

Теперь import styles from './styles.module.scss' будет работать с типами.

Можно ли объявлять несколько разных модулей в одном .d.ts файле?

Да, вы можете разместить несколько declare module ... {} подряд в одном файле деклараций, главное — чтобы каждый был самостоятельным и имел уникальное имя. TypeScript увидит все определения внутри этого файла.

Стрелочка влевоПеременные и константы в TypeScriptПреобразование к типу в TypeScript (Type Assertion)Стрелочка вправо

Постройте личный план изучения Typescript до уровня Middle — бесплатно!

Typescript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Typescript

Открыть базу знаний

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

CSS Flexbox

Антон Ларичев
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
бесплатно
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее