Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
💻
Технологии
Опубликовано:
14.09.2026
Обновлено:
14.09.2026

Microfrontends с Module Federation: как настроить строгую типизацию Remote-модулей в TypeScript

Тимофей Ищенко

Коротко: Разбираем настройку строгой типизации в microfrontends на Webpack Module Federation: ручные .d.ts, автоматическая синхронизация и плагины.

Архитектура микрофронтендов на базе Webpack 5 Module Federation решает задачу независимого развертывания и изоляции команд. Однако при переходе на TypeScript разработчики неизбежно сталкиваются с архитектурным противоречием. Webpack загружает remote-модули динамически во время выполнения приложения (runtime), а компилятор TypeScript требует информацию о типах на этапе сборки (compile-time).

Без выстроенного процесса доставки типов импорт компонентов из других микрофронтендов либо приводит к ошибке компиляции TS2307: Cannot find module, либо заставляет разработчиков прибегать к директивам @ts-ignore и типу any. В результате разрушается сквозная безопасность типов: изменение сигнатуры пропсов в remote-модуле не вызывает ошибок в host-приложении на этапе билда и проявляется только в виде сбоев у пользователей.

Разберем, почему возникает этот разрыв, как устроены механизмы синхронизации .d.ts и какие подходы позволяют выстроить надежный типобезопасный контракт между микрофронтендами.


Анатомия проблемы: конфликт между Compile-Time и Runtime

Чтобы понять корень проблемы, рассмотрим, как взаимодействуют Webpack и TypeScript при сборке микрофронтенда.

Конфигурация типового ModuleFederationPlugin разделяет микрофронтенды на две роли:

  • Remote (поставщик): публикует наружу JS-модули через поле exposes.
  • Host (потребитель): подключает удаленные точки входа через поле remotes.
// host/webpack.config.js
new ModuleFederationPlugin({
  name: 'hostApp',
  remotes: {
    remoteApp: 'remoteApp@https://cdn.example.com/remoteEntry.js',
  },
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});

Когда разработчик в Host-приложении пишет:

import RemoteButton from 'remoteApp/Button';

происходят два независимых процесса:

  1. Компиляция TypeScript (tsc): компилятор пытается найти файл декларации по физическому или сконфигурированному пути (node_modules, paths в tsconfig.json). Поскольку модуля remoteApp/Button физически нет на диске в момент сборки, tsc генерирует ошибку TS2307: Cannot find module 'remoteApp/Button' or its corresponding type declarations.
  2. Сборка Webpack: бандлер заменяет импорт на рантайм-вызов загрузки скрипта через remoteEntry.js и контейнер Module Federation.

Webpack отвечает исключительно за сборку JavaScript и разделение зависимостей через shareScope. Он не генерирует и не передает метаданные типов. Задача доставки определений .d.ts полностью ложится на плечи разработчиков инфраструктуры.


Подход 1. Ambient Declarations: ручное описание контрактов

Самый простой способ устранить ошибки компилятора — объявить сигнатуру удаленного модуля вручную в файле глобальных деклараций Host-приложения.

Реализация

В Host-приложении создается файл declarations.d.ts:

// host/src/declarations.d.ts
declare module 'remoteApp/Button' {
  export interface ButtonProps {
    variant: 'primary' | 'secondary';
    onClick: () => void;
    children: React.ReactNode;
  }

  const Button: React.FC<ButtonProps>;
  export default Button;
}

Чтобы TypeScript учитывал этот файл, необходимо убедиться, что он попадает в секцию include в tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "moduleResolution": "NodeNext"
  },
  "include": ["src/**/*"]
}

Плюсы и минусы подхода

Преимущества Недостатки
Нулевые требования к инфраструктуре сборки Человеческий фактор: типы пишутся вручную
Быстрый старт на этапе PoC (Proof of Concept) Рассинхронизация: remote меняет контракт, host не знает об этом
Не требует изменений в конфигурации Remote-сборки Накопление скрытого техдолга и ложное чувство безопасности

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


Подход 2. Автоматическая генерация и синхронизация типов через плагины

Для устранения человеческого фактора процесс генерации и передачи типов автоматизируют. Архитектурный паттерн строится по следующей схеме:

  1. Remote при сборке компилирует .d.ts для файлов, указанных в exposes, и упаковывает их в архив (tarball/zip) или сохраняет в публичную директорию рядом с remoteEntry.js.
  2. Host на этапе предсборки или через Webpack-плагин запрашивает архив с типами по HTTP, распаковывает его в локальную папку @types/remotes и мапит пути через tsconfig.json.

Популярный стек для реализации этой схемы: dts-loader / @module-federation/typescript на стороне Remote и webpack-remote-types-plugin на стороне Host.

Шаг 1: Конфигурация Remote

Remote должен генерировать типы именно для экспортируемых модулей. При сборке создается директория с .d.ts, которая отдается dev-сервером или загружается на CDN вместе с JS-бандлами.

// remote/webpack.config.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

Для сборки типов в архив используется скрипт генерации dts (через tsc --emitDeclarationOnly) и упаковки полученной папки dist/types в архив remoteApp-dts.tgz.

Шаг 2: Конфигурация Host через WebpackRemoteTypesPlugin

В Host-приложении плагин на этапе компиляции скачивает актуальный архив типов:

// host/webpack.config.js
const WebpackRemoteTypesPlugin = require('webpack-remote-types-plugin').default;

module.exports = {
  plugins: [
    new WebpackRemoteTypesPlugin({
      remotes: {
        remoteApp: 'remoteApp@https://cdn.example.com/remoteApp-dts.tgz',
      },
      outputDir: './src/@types/remotes',
      remoteFileName: '[name]-dts.tgz',
    }),
  ],
};

Шаг 3: Настройка путей в tsconfig.json

Чтобы IDE и компилятор видели скачанные типы без явного указания путей в импортах, настраивается секция paths:

// host/tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "remoteApp/*": ["src/@types/remotes/remoteApp/*"]
    }
  }
}

В результате при вызове import Button from 'remoteApp/Button' компилятор подхватывает оригинальные интерфейсы пропсов, созданные авторами remote-компонента.


Подход 3. Module Federation 2.0 и экосистема @module-federation/enhanced

С развитием спецификации Module Federation и появлением пакета @module-federation/enhanced (развиваемого в рамках экосистемы Module Federation 2.0) типизация микрофронтендов получила нативную поддержку без необходимости писать кастомные скрипты архивации.

Плагин @module-federation/enhanced берет на себя:

  • Автоматический анализ блока exposes;
  • Генерацию .d.ts и бандлинг их в формат .d.ts.tar.gz;
  • Скачивание и резолв типов на стороне Host во время работы dev-сервера и сборки.

Настройка Remote

// remote/webpack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button.tsx',
      },
      dts: {
        generateTypes: {
          extractThirdParty: true,
          compileInChildProcess: true,
        },
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

Настройка Host

// host/webpack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      dts: {
        consumeTypes: {
          consumeAPITypes: true,
        },
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

При запуске сборки плагин автоматически скачивает типы с dev-сервера Remote, сохраняет их в локальную директорию и прозрачно регистрирует их в TypeScript. Это исключает ручную настройку paths в tsconfig.json.


Организация версионирования и CI/CD в кросс-командной разработке

Автоматизация доставки типов решает техническую сторону проблемы, но не исключает организационных рисков. В микрофронтендной архитектуре Remote и Host деплоятся асинхронно. Если команда Remote удалит обязательный пропс или переименует событие, Host-приложение скомпилируется со старыми типами из кэша, но упадет в рантайме после деплоя Remote.

Принципы стабильности контрактов

  1. Правило расширения (Open-Closed): при изменении интерфейса Remote-модуля новые поля должны быть опциональными (optional props), а устаревшие свойства помечаются @deprecated и сохраняются минимум на один релизный цикл:
    export interface RemoteButtonProps {
      label: string;
      onClick: () => void;
      /** @deprecated Используйте variant="accent" */
      isPrimary?: boolean;
      variant?: 'default' | 'accent';
    }
    
  2. Контрактное тестирование в CI: в пайплайн Host-приложения внедряется шаг валидации типов против актуального CDN/Staging окружения Remote. Если Remote выкатил ломающие изменения в контракт, CI Host-приложения должен упасть на этапе проверки типов (tsc --noEmit).
  3. Версионирование точек входа: для критических изменений API создается новая точка входа в exposes (например, './Button/v2'), что позволяет хостам мигрировать независимо.

Сравнение подходов и чек-лист для Production

Выбор стратегии типизации зависит от размера кодовой базы и используемого инструментария.

Критерий Ambient Declarations (ручные) Плагины архивации (dts-loader / webpack-remote-types-plugin) @module-federation/enhanced
Сложность внедрения Минимальная Средняя Низкая (при переходе на MF 2.0)
Синхронизация типов Отсутствует Автоматическая (по URL/архиву) Автоматическая (встроена в билд)
Поддержка IDE Требует ручных правок Через tsconfig.paths Из коробки
Риск расхождения версий Критический Низкий Минимальный
Где применять Прототипы, POC Webpack 5 production-проекты Новые проекты и стек с MF 2.0

Чек-лист готовности к production:

  • [ ] В кодовой базе Host нет конструкций // @ts-ignore и any на federated-импортах.
  • [ ] В пайплайне Remote настроена автоматическая генерация .d.ts при релизной сборке.
  • [ ] В CI Host-приложения включен шаг проверки типов tsc --noEmit с подтягиванием свежих деклараций.
  • [ ] Настроены таймауты и fallback-значения на случай недоступности сервера с типами при локальной разработке.
  • [ ] Команды следуют соглашению об обратной совместимости интерфейсов.

FAQ

Почему TypeScript не видит типы из Remote-приложения по умолчанию?

TypeScript — статический анализатор кода. Он работает исключительно с локальной файловой системой во время сборки. Webpack Module Federation связывает код динамически по сети в браузере. Так как в файловой структуре Host-приложения физически отсутствуют исходники Remote, компилятор считает путь несуществующим.

Стоит ли выносить типы микрофронтендов в отдельный npm-пакет?

Вынос типов в npm-пакет решает проблему валидации, но разрушает ключевое преимущество микрофронтендов — независимость релизного цикла. Команда Remote будет вынуждена публиковать пакет, а команда Host — обновлять зависимости в package.json при каждом изменении контракта. Синхронизация типов через CDN/URL вместе со сборкой устраняет эту задержку.

Как локально разрабатывать Host и Remote с автоматическим обновлением типов?

При локальной разработке Remote-приложение запускается на локальном порту (например, localhost:3001). Плагины синхронизации типов настраиваются на опрос локального URL http://localhost:3001/remoteApp-dts.tgz. При сохранении изменений в Remote и пересборке Host подтягивает обновленный файл определений.

Что делать, если сборка Host падает в CI из-за недоступности dev-сервера с типами?

Для сборки в CI типы Remote-приложений должны забираться со стабильного окружения (Staging/Production CDN или S3-хранилища с версионированными артефактами), а не с временных dev-серверов. Также рекомендуется кэшировать последнюю стабильную версию скачанных типов в артефактах CI.

Можно ли использовать Module Federation с разными версиями TypeScript в микрофронтендах?

Да. Поскольку передаются только скомпилированные .d.ts файлы, внутренние версии компиляторов могут различаться. Главное требование — сгенерированные декларации типов должны соответствовать синтаксису, поддерживаемому версией TypeScript в Host-приложении.


Заключение

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

Внедрение автоматической синхронизации типов через плагины генерации деклараций или переход на @module-federation/enhanced позволяет сохранить преимущества изолированного деплоя микрофронтендов без потери преимуществ строгой типизации. Контракт между командами становится самодокументируемым и проверяемым на всех этапах жизненного цикла приложения.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также