Настройка interopRequireDefault и interopRequireWildcard

В системах сборки и трансформации кода SWC особое внимание уделяется совместимости модулей CommonJS и ES Modules. Именно в этом слое появляются настройки interopRequireDefault и interopRequireWildcard, которые определяют, каким образом транспайлер формирует обвязку импортов при переходе между разными системами модулей.


При компиляции ESModule-кода в CommonJS возникает фундаментальная проблема различий:

  • CommonJS использует module.exports и require()
  • ESModules используют export default, export const, import * as

Несовпадение этих моделей требует промежуточного слоя — interop-обёрток, которые нормализуют структуру импортируемых объектов.

SWC решает это через две ключевые стратегии:

  • interopRequireDefault — управление доступом к default-экспорту
  • interopRequireWildcard — управление импортом всего модуля как пространства имён

interopRequireDefault: логика и поведение

Суть механизма

interopRequireDefault определяет, будет ли SWC автоматически оборачивать CommonJS-модуль в объект вида:

{
  default: exports
}

Это поведение критично для корректной работы ESModule-импорта:

import foo from "foo";

Поведение при включении

При активном interopRequireDefault:

const foo = require("foo");

трансформируется в:

const _foo = require("foo");
const foo = _foo && _foo.__esModule ? _foo.default : _foo;

Ключевая логика:

  • Если модуль помечен __esModule: true, берётся .default
  • Иначе используется весь экспорт как значение

Поведение при отключении

Если interopRequireDefault: false, SWC делает более прямолинейную трансформацию:

const foo = require("foo");

Без дополнительной проверки и без доступа к .default.

Это может приводить к несовместимости с ESM-библиотеками, ожидающими default-поведение.


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

interopRequireDefault влияет на:

  • импорт библиотек, собранных как ESM
  • поведение Babel-совместимых пакетов
  • корректность работы export default

interopRequireWildcard: импорт пространства имён

Основная идея

interopRequireWildcard определяет, как SWC обрабатывает:

import * as mod from "mod";

или:

const mod = require("mod");

в контексте ESM-CJS совместимости.


Поведение при включении

При включённой опции SWC создаёт полноценный объект-обёртку:

const _mod = require("mod");

const mod = _mod && _mod.__esModule
  ? _mod
  : Object.defineProperties({}, {
      ...Object.getOwnPropertyNames(_mod).reduce((acc, key) => {
        if (key !== "default") {
          acc[key] = {
            enumerable: true,
            get: () => _mod[key]
          };
        }
        return acc;
      }, {}),
      default: {
        enumerable: true,
        value: _mod
      }
    });

Смысл такой трансформации:

  • сохраняется доступ ко всем named exports
  • добавляется default
  • создаётся ленивый доступ через getter’ы
  • обеспечивается ESM-поведение поверх CommonJS

Поведение при отключении

При interopRequireWildcard: false результат упрощается:

const mod = require("mod");

Без нормализации структуры и без добавления default.

Это быстрее, но менее совместимо с ESM-семантикой.


Взаимодействие двух настроек

Обе опции часто работают совместно и определяют разные аспекты одной задачи:

Сценарий импорта interopRequireDefault interopRequireWildcard
import x from критически важен не используется
import * as x косвенно влияет ключевой механизм
CommonJS require влияет на обёртку влияет на структуру

Конфигурация SWC

Настройки задаются в .swcrc:

{
  "module": {
    "type": "commonjs",
    "interop": "auto"
  }
}

В SWC поведение interop часто агрегируется через режимы:

  • “none” — без interop-обёрток
  • “auto” — автоматическое определение необходимости
  • “esModule” — принудительная ESM-совместимость

Детальная настройка через transforms

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

{
  "jsc": {
    "transform": {
      "legacyDecorator": false
    }
  },
  "module": {
    "type": "commonjs"
  }
}

Хотя напрямую флаги interopRequireDefault и interopRequireWildcard обычно не выставляются вручную в SWC-конфигурации как в Babel, их логика присутствует внутри генерации кода при выборе режима module.type.


Внутренняя модель __esModule

Обе настройки опираются на маркер:

__esModule: true

Он добавляется SWC при транспиляции ESM-модулей в CommonJS:

exports.__esModule = true;
exports.default = something;

Роль маркера:

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

Сложные кейсы взаимодействия

  1. CommonJS библиотека внутри ESM-кода

import express from "express";

Без interop:

  • express может оказаться объектом модуля
  • express.default отсутствует

С interop:

  • автоматически выбирается корректный экспорт

  1. ESM библиотека в CommonJS окружении

const lib = require("esm-lib");

С interopRequireWildcard:

  • сохраняется доступ к named exports
  • добавляется default

Без него:

  • структура теряется

  1. Двойной interop

При цепочке:

  • ESM → CommonJS → ESM

возникает риск двойной обёртки, который решается проверкой __esModule.


Оптимизация и стоимость выполнения

interopRequireDefault

  • минимальный overhead
  • одна проверка __esModule

interopRequireWildcard

  • создание нового объекта
  • ленивые getters
  • потенциально дорого при больших модулях

Практическое влияние на bundle

В крупных приложениях выбор режима interop влияет на:

  • размер итогового bundle
  • количество вспомогательных функций
  • скорость выполнения require
  • совместимость с tree-shaking

Связь с другими трансформами SWC

Interop-механизмы тесно связаны с:

  • генерацией importrequire
  • hoisting import declarations
  • namespace import wrapping
  • helper-функциями runtime

При отключении interop некоторые из этих слоёв становятся избыточными и удаляются из выходного кода.


Поведение в различных target-режимах

target: ES5 + CommonJS

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

target: ES2020+

  • interop может быть минимальным
  • сохраняется ближе к исходному ESM

Влияние на типичные фреймворки

Node.js окружение

  • требует стабильного require-interop
  • важен interopRequireDefault

React-проекты

  • критичен interopRequireWildcard при import * as React

Библиотеки

  • часто требуют отключения или тонкой настройки interop для предсказуемого API