В монорепозиториях
declare module в .d.ts файлах часто считают удобным способом типизировать динамически загружаемые модули. Однако на production это приводит к трудноуловимым багам, когда глобальные декларации из разных пакетов склеиваются, а не заменяются, создавая пересечения типов, которые не совпадают с реальным runtime-поведением.Проблема склейки в пересечение
Когда два пакета в монорепозитории объявляют
declare module 'lib' с разными версиями опций, TypeScript не выбирает одну — он объединяет их в пересечение. Результат: { mode: never }, если типы конфликтуют. Локально тесты проходят, а в production код падает из-за несовместимости. Типичная ошибка — считать, что declare module изолирован.Динамический импорт и runtime-разрыв
При динамической загрузке через
import('lib').then(...) TypeScript использует глобальные ambient-типы, а не фактическую сигнатуру модуля в рантайме. Если модуль загружается по условию (например, A/B-тест или фича-флаг), статически верный тип может не совпадать с реально загруженной версией. Результат — ошибки в рантайме, которые не ловятся статическим анализатором.Стратегии для монорепозиториев
* Изоляция через tsconfig paths — явно указывай пути к реальным файлам модуля и исключай лишние
.d.ts. Минус: накладные расходы на настройку каждого пакета.* Wrapper-пакет с type guard — создай прослойку, которая делает динамический импорт и экспортирует явный тип. Контролируешь, что отдаёшь наружу, но добавляешь лишний слой.
* Маркер версии в declare module — добавь
export const __version: '2.0.0' в декларацию. В коде видно, какая версия предполагалась, и можно проверить совпадение.Практический совет
Периодически прогоняй
tsc --noEmit --traceResolution в каждом пакете отдельно. Это покажет, откуда TypeScript берёт типы для declare module, и выявит случайные пересечения из node_modules или соседних пакетов. В монорепозиториях это обязательно, иначе баги с типами уйдут на production.Вывод: Ambient type augmentation в
declare module — опасный компромисс между удобством и надёжностью типов в монорепозиториях, требующий явной изоляции через tsconfig paths или wrapper-пакеты для предотвращения runtime-ошибок.