shared: управление общими зависимостями

Механизм shared в Module Federation решает задачу централизованного и согласованного использования общих зависимостей между независимыми сборками (host и remote). Основная цель — избежать дублирования библиотек, конфликтов версий и множественной инициализации глобального состояния, сохраняя при этом автономность микрофронтендов.

Shared-зависимости работают поверх runtime-слоя Webpack и опираются на концепцию share scope — именованного пространства, в котором регистрируются и резолвятся общие модули.


Share Scope: механизм рантайм-изоляции и совместного доступа

Webpack использует runtime-слой для управления общими модулями через глобальные области видимости.

Основная логика:

  • каждый контейнер (host или remote) может публиковать зависимости в share scope
  • каждый контейнер может потреблять зависимости из share scope
  • зависимости хранятся не как обычные import-ы, а как runtime-реестр

Ключевые характеристики:

  • share scope существует в рантайме браузера
  • инициализация происходит через __webpack_init_sharing__
  • загрузка модулей выполняется асинхронно
  • возможен fallback на локальную версию

Конфигурация shared в ModuleFederationPlugin

Базовая структура:

new ModuleFederationPlugin({
  name: 'app',
  remotes: {},
  exposes: {},
  shared: {}
})

shared может задаваться в двух формах:

Строковая форма

shared: {
  react: '18.2.0',
  'react-dom': '18.2.0'
}

Это упрощённый вариант, где версия интерпретируется как минимально требуемая.


Объектная форма

shared: {
  react: {
    singleton: true,
    requiredVersion: '^18.2.0',
    eager: false,
    strictVersion: true
  }
}

Позволяет управлять поведением зависимости на уровне runtime-резолва.


Ключевые параметры shared-зависимостей

singleton

Гарантирует, что в runtime будет использоваться только одна копия модуля.

react: {
  singleton: true
}

Поведение:

  • первая загруженная версия становится активной
  • остальные запросы переиспользуют её
  • критично для библиотек с глобальным состоянием

Типичные кандидаты:

  • react
  • react-dom
  • vue (в некоторых архитектурах)
  • state managers (redux, zustand)

requiredVersion

Определяет допустимый диапазон версий.

requiredVersion: '^18.0.0'

Webpack сравнивает:

  • версию, предоставленную remote
  • версию, доступную в host

Если несовместимость — поведение зависит от strictVersion.


strictVersion

strictVersion: true

При включении:

  • несовпадение версии приводит к runtime-ошибке
  • fallback отключается

При выключении:

  • Webpack может использовать ближайшую подходящую версию
  • допускается деградация

eager

eager: true

Заставляет загрузить зависимость синхронно на этапе инициализации контейнера.

Особенности:

  • уменьшает количество async boundary
  • увеличивает размер initial bundle
  • может вызвать конфликт, если dependency не доступна сразу

Используется редко, в основном для критических библиотек.


import

Позволяет задать альтернативный модуль, который будет использоваться как fallback:

react: {
  import: false
}

или:

react: {
  import: 'preact/compat'
}

Применяется для замены реализаций библиотек.


shareKey

Позволяет переопределить имя ключа в share scope:

'@lib/utils': {
  shareKey: 'utils'
}

Используется для унификации разных путей к одному пакету.


shareScope

Указывает область совместного использования:

shareScope: 'default'

По умолчанию все используют default, но возможно создание изолированных scope для:

  • A/B тестов
  • multi-tenant приложений
  • версионной изоляции

Алгоритм разрешения shared-зависимостей

Runtime Webpack выполняет несколько шагов:

  1. Проверка наличия share scope
  2. Поиск зарегистрированных версий модуля
  3. Сравнение версий через semver
  4. Проверка singleton-ограничений
  5. Выбор наиболее подходящей версии
  6. При отсутствии — загрузка локального fallback

Псевдологика:

  • если singleton → выбрать первую и закрепить
  • если strictVersion → только точное совпадение
  • иначе → выбрать максимальную совместимую версию

Поведение host и remote при shared

Host

  • инициализирует share scope
  • регистрирует локальные зависимости
  • может переопределять remote зависимости

Remote

  • подключается к существующему share scope
  • пытается использовать уже загруженные зависимости
  • при отсутствии — загружает собственные версии

Проблемы множественных копий библиотек

Наиболее частая ошибка в Module Federation связана с дублированием React.

Пример проблемы:

  • host использует React 18.2
  • remote загружает React 18.1
  • в результате создаются две runtime-копии React

Последствия:

  • ломаются hooks
  • контекст React перестаёт работать
  • появляется ошибка invalid hook call

Решение:

shared: {
  react: { singleton: true, requiredVersion: '^18.2.0' },
  'react-dom': { singleton: true }
}

Поведение версий и semver

Webpack использует semver-сравнение:

  • ^18.2.0 → разрешает 18.x.x
  • ~18.2.0 → разрешает только патчи
  • exact version → строгое совпадение

При конфликте:

  • выбирается highest compatible version
  • либо fallback на local module

Оптимизация загрузки shared-зависимостей

Предзагрузка критических библиотек

shared: {
  react: { eager: true }
}

Снижает latency при первом render, но увеличивает initial payload.


Lazy resolution

По умолчанию:

  • shared modules загружаются асинхронно
  • подгружаются только при первом использовании

Tree-shaking взаимодействие

Shared-зависимости могут ухудшать tree-shaking:

  • модуль загружается как единый chunk
  • оптимизация внутри него ограничена

Monorepo и shared-зависимости

В монорепозиториях shared часто пересекается с workspace-резолвингом.

Особенности:

  • версии библиотек могут быть физически одинаковыми
  • Webpack всё равно выполняет runtime проверку
  • singleton становится обязательным для UI-библиотек

Конфликты и их причины

1. Несовпадение React экземпляров

Причина:

  • разные node_modules у host и remote

2. Разные версии peerDependencies

Причина:

  • несогласованность package.json

3. eager + async remote

Причина:

  • попытка синхронного доступа к ещё не загруженному remote

Продвинутые сценарии использования shared

Замена библиотек (alias через import)

shared: {
  lodash: {
    import: 'lodash-es'
  }
}

Позволяет централизованно менять реализацию.


Изоляция по shareScope

shareScope: 'admin'
shareScope: 'public'

Используется для разделения сред внутри одного приложения.


Версионирование микрофронтендов

Shared может использоваться как механизм мягкой миграции:

  • старая версия остаётся fallback
  • новая версия постепенно захватывает scope
  • переход без полного деплоя всех remote

Runtime-инварианты shared-модели

  • один share scope может содержать несколько версий одного пакета
  • singleton гарантирует только одну активную версию
  • strictVersion управляет жёсткостью проверки
  • eager изменяет фазу загрузки, но не поведение резолва
  • final выбор версии всегда происходит в runtime, а не в build time

Производственные ограничения

  • увеличение complexity runtime логики
  • сложность дебага зависимостей
  • необходимость синхронизации версий между командами
  • зависимость от корректного semver в экосистеме

Типовые конфигурации shared

Базовая стабильная конфигурация

shared: {
  react: { singleton: true, requiredVersion: '^18.0.0' },
  'react-dom': { singleton: true },
  axios: {}
}

Строгая корпоративная конфигурация

shared: {
  react: { singleton: true, strictVersion: true },
  'react-dom': { singleton: true, strictVersion: true },
  '@company/ui': { singleton: true }
}

Производительная конфигурация с eager

shared: {
  react: { singleton: true, eager: true },
  'react-dom': { singleton: true, eager: true }
}