Поле output.interop

При сборке модулей, где одновременно используются ESM (ES Modules) и CommonJS, возникает задача согласования разных систем экспорта. Rollup решает её через слой межмодульной совместимости, который управляется настройкой output.interop.

Этот параметр определяет, как именно будут обрабатываться default-импорты из CommonJS-модулей и каким образом Rollup будет генерировать вспомогательный код для согласования типов экспорта.


Причина появления interop-логики

ESM и CommonJS по-разному трактуют экспорт:

  • ESM

    • поддерживает именованные экспорты
    • имеет явный default экспорт
    • статически анализируем
  • CommonJS

    • экспорт представляет собой объект module.exports
    • default не существует как концепция
    • структура динамическая

При импорте CommonJS-модуля в ESM возникает неоднозначность:

import foo from './cjs-module.cjs';

Что именно считать foo:

  • весь объект module.exports?
  • или module.exports.default?
  • или попытаться эмулировать ESModule?

Rollup решает это через interop-обёртки.


Роль output.interop в процессе сборки

Параметр output.interop управляет тем, будет ли Rollup добавлять обёртки совместимости и в каком объёме.

Он влияет на:

  • генерацию helper-функций (__toESM, __toCommonJS)
  • поведение default импорта из CommonJS
  • структуру экспортируемых namespace-объектов
  • наличие __esModule проверки

Основные режимы output.interop

auto (значение по умолчанию)

Наиболее интеллектуальный режим. Rollup анализирует модуль и добавляет interop только при необходимости.

Поведение:

  • для ESM модулей interop не добавляется
  • для CommonJS добавляется минимально необходимая обёртка
  • учитываются статические признаки __esModule

Пример поведения:

import foo from 'cjs-lib';

Может быть преобразовано в:

var foo = __toESM(require('cjs-lib'));

или без обёртки, если Rollup уверен в совместимости.

Ключевая особенность:

  • минимальный объём генерируемого кода
  • оптимизация под tree-shaking
  • адаптивное поведение

true (устаревшая форма)

Исторически означал включение interop-логики.

Фактически эквивалентен:

  • auto

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


false

Полностью отключает interop-обработку.

Поведение:

  • CommonJS модули не оборачиваются
  • default импорт может стать некорректным
  • отсутствует защита от несовместимых структур

Пример результата:

import foo from 'cjs-lib';

может превратиться в:

var foo = require('cjs-lib');

Без дополнительной нормализации.

Риски:

  • нарушение ожиданий ESM-кода
  • потеря согласованности default-экспорта

default

Режим, в котором Rollup всегда трактует CommonJS как имеющий default экспорт.

Поведение:

  • module.exports считается default
  • именованные экспорты не эмулируются
  • упрощённая модель совместимости

Пример:

import foo from 'cjs-lib';

превращается в:

var foo = require('cjs-lib').default ?? require('cjs-lib');

(логика может варьироваться в зависимости от окружения сборки)

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

  • предсказуемое поведение default-импорта
  • игнорирование named exports из CommonJS

esModule

Наиболее строгий режим интеропа.

Поведение:

  • все импорты трактуются как ESModule
  • ожидается наличие __esModule: true
  • Rollup не пытается эмулировать CommonJS default-логику
  • namespace остаётся ES-сущностью

Пример:

import foo from 'cjs-lib';

может привести к:

var foo = require('cjs-lib');

без дополнительной нормализации, либо с минимальной обёрткой для соответствия ESM-модели.

Используется в случаях, когда:

  • окружение гарантирует ESM-совместимость
  • CJS-модули уже транспилированы с __esModule
  • требуется строгая семантика модулей

Внутренние helper-функции Rollup

При включённом interop Rollup генерирует вспомогательные функции.

__toESM

Используется для преобразования CommonJS в ESM-совместимую структуру.

Типичное поведение:

  • копирует свойства объекта
  • устанавливает default
  • добавляет __esModule

Пример:

var foo = __toESM(require('cjs-lib'));

__toCommonJS

Обратное преобразование:

  • создаёт объект module.exports
  • переносит named exports

Влияние output.interop на tree-shaking

Interop напрямую влияет на эффективность tree-shaking.

При auto:

  • Rollup пытается минимизировать обёртки
  • сохраняется статический анализ
  • возможна частичная элиминация импортов

При false:

  • отсутствуют лишние обёртки
  • но теряется корректная семантика ESM/CJS

При default:

  • упрощается модель импортов
  • tree-shaking может ухудшаться из-за fallback-логики

При esModule:

  • наиболее строгая модель
  • оптимальна для чистого ESM-окружения

Совместимость с различными типами модулей

ESM → ESM

Interop не применяется.

ESM → CJS

Может применяться __toCommonJS.

CJS → ESM

Основная зона применения output.interop.


Типичные сценарии использования

Библиотека с поддержкой Node.js и браузера

Рекомендуется:

  • auto

Позволяет сохранить баланс между совместимостью и размером бандла.


Полностью ESM-проект

Рекомендуется:

  • esModule

Минимизирует лишнюю трансформацию.


Старые CJS-зависимости без корректного interop

Рекомендуется:

  • default

Обеспечивает стабильный доступ к require()-экспорту.


Отладочные или минимальные сборки

Возможен вариант:

  • false

Используется для анализа поведения модулей без обёрток.


Связь с другими параметрами output

output.interop тесно связан с:

  • output.format (esm, cjs, iife)
  • output.exports
  • output.generatedCode
  • output.externalLiveBindings

Особенно сильная связь наблюдается с форматом esm, где interop влияет на корректность импортов из CommonJS-зависимостей.


Поведение при динамическом require

При наличии динамических require():

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

Итоговая модель принятия решений внутри Rollup

При сборке Rollup фактически выбирает стратегию:

  1. определить тип модуля (ESM или CJS)
  2. проверить наличие __esModule
  3. применить output.interop
  4. сгенерировать wrapper (если требуется)
  5. оптимизировать через tree-shaking

Практическое значение параметра

output.interop определяет не только синтаксическую совместимость, но и:

  • стабильность runtime-поведения
  • корректность default-импортов
  • размер итогового бандла
  • возможность предсказуемой работы смешанных зависимостей

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