Елена Голубева
Настройка типов с declare module в TypeScript
Введение
TypeScript предоставляет разработчикам мощный механизм типизации, который помогает выявлять ошибки в коде еще до этапа компиляции. Однако далеко не всегда все сторонние библиотеки имеют свои объявления типов, и иногда требуется описывать их самостоятельно. Для решения этой задачи предназначена конструкция declare module. Она позволяет настраивать типы для сторонних зависимостей, а также расширять типы ваших собственных модулей и даже глобальных объектов.
В этой статье вы узнаете, как использовать declare module в различных сценариях: от добавления поддержки типизации для JS-библиотек без типов, до расширения уже существующих модулей. Я покажу, как грамотно структурировать такие определения, на что обращать внимание при их настройке и приведу примеры кода с подробными пояснениями.
Использование сторонних библиотек в TypeScript проектах часто требует описания типов для этих библиотек. declare module предоставляет мощный механизм для создания таких описаний, позволяя TypeScript понимать структуру и типы данных, используемые в библиотеках. Для более глубокого изучения работы с типами, их расширениями и декларациями, приглашаем вас на наш курс TypeScript с нуля. На курсе 192 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.
Когда и зачем использовать 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 увидит все определения внутри этого файла.
declare module - это лишь один из инструментов в арсенале TypeScript разработчика. Чтобы создавать масштабируемые и поддерживаемые приложения, важно знать как устроен Typescript под капотом. На нашем курсе TypeScript с нуля вы узнаете как использовать все возможности TypeScript. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в TypeScript прямо сегодня.
Постройте личный план изучения Typescript до уровня Middle — бесплатно!
Typescript — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Typescript
Лучшие курсы по теме

TypeScript с нуля
Антон Ларичев
CSS Flexbox
Антон Ларичев