Предупреждение THIS_IS_UNDEFINED

THIS_IS_UNDEFINED — это предупреждение Rollup, возникающее в момент анализа модулей, когда сборщик обнаруживает использование this в контексте ES-модуля, где значение this гарантированно равно undefined. Это предупреждение относится не к синтаксической ошибке, а к потенциальному расхождению поведения между различными форматами модулей и окружениями исполнения.

В классическом JavaScript поведение this зависит от способа вызова функции и режима выполнения. Однако ES-модули вводят фиксированное поведение: в верхнеуровневом контексте значение this всегда равно undefined.

// ES module
console.log(this); // undefined

Это отличается от CommonJS-модулей, где this на верхнем уровне указывает на module.exports:

// CommonJS
console.log(this === module.exports); // true

Именно это различие является источником проблем при сборке через Rollup, когда код из разных модульных систем объединяется в единый бандл.

Когда возникает предупреждение THIS_IS_UNDEFINED

Rollup генерирует предупреждение THIS_IS_UNDEFINED, если:

  • обнаружено использование this в верхнеуровневом контексте ES-модуля
  • или внутри функции, где this не может быть корректно восстановлен после транспиляции
  • или при преобразовании CommonJS-кода в ESM без сохранения контекста

Типичный пример:

// module.js
console.log(this.value);

После интерпретации как ES module this становится undefined, и обращение к this.value приводит к ошибке выполнения.

Почему Rollup предупреждает заранее

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

  • в исходном коде this может иметь осмысленное значение (CommonJS, legacy-код)
  • после преобразования в ESM или строгий режим значение становится undefined
  • итоговое поведение становится некорректным без явных признаков ошибки на этапе компиляции

Таким образом предупреждение сигнализирует о потенциально сломанной логике после сборки.

Контекст выполнения и строгий режим

ESM всегда исполняются в strict mode. Это ключевой момент:

'use strict';

function test() {
  console.log(this); // undefined
}

Даже если в исходном коде ожидалось глобальное this, в модульной системе оно отсутствует.

Rollup учитывает это поведение и считает любые неявные обращения к this потенциально опасными.

Частые источники проблемы

1. CommonJS-библиотеки

Многие старые библиотеки используют this как ссылку на module.exports:

this.exportedValue = 123;

После преобразования в ES module через Rollup:

  • this становится undefined
  • запись приводит к ошибке

2. Глобальный контекст

Код, рассчитанный на браузер без модулей:

this.alertMessage = "hello";

В модуле такое поведение ломается.

3. Некорректная транспиляция

При использовании Babel или plugin-commonjs возможны ситуации, когда:

  • контекст не был явно привязан
  • this не заменён на корректную переменную

Механика предупреждения в Rollup

Rollup анализирует AST и определяет:

  • наличие MemberExpression вида this.something
  • контекст, в котором используется this
  • формат вывода (ESM, CJS, IIFE)

Если формат гарантирует this === undefined, генерируется предупреждение:

(!) THIS_IS_UNDEFINED
src/module.js
`this` has been rewritten to `undefined`

Влияние формата вывода

ES Module (esm)

Наиболее строгий вариант:

  • this всегда undefined
  • предупреждение почти всегда критично

CommonJS (cjs)

Поведение ближе к Node.js:

  • this может указывать на module.exports
  • предупреждение возникает реже или игнорируется

IIFE / UMD

Зависит от обёртки:

  • иногда this привязывается к глобальному объекту
  • поведение может быть нестабильным

Как Rollup переписывает this

В некоторых случаях Rollup может автоматически заменить this:

// исходник
this.value

// результат
undefined.value

или

var _this = undefined;
_this.value;

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

Практические способы устранения проблемы

1. Устранение зависимости от this

Наиболее надёжный подход — исключение this из логики модуля:

export const value = 123;

вместо:

this.value = 123;

2. Явная передача контекста

Если this использовался как объект состояния:

function createModule(ctx) {
  ctx.value = 123;
}

3. Использование function вместо стрелочных функций

Стрелочные функции не имеют собственного this, что часто усиливает проблему:

const fn = () => {
  console.log(this);
};

Замена:

function fn() {
  console.log(this);
}

Но в ES module это всё равно будет undefined, поэтому важно не полагаться на this вообще.

4. Настройка preserveModuleContext

Rollup позволяет сохранить контекст модуля:

export default {
  output: {
    preserveModules: true
  }
}

или через moduleContext:

export default {
  moduleContext: (id) => {
    if (id.includes('legacy')) {
      return 'window';
    }
  }
}

Это позволяет явно задать значение this для конкретных модулей.

5. Использование plugin-commonjs

Плагин преобразования CommonJS может частично эмулировать this:

  • require()-модули сохраняют поведение module.exports
  • но не всегда корректно восстанавливают динамический this

Особенности взаимодействия с TypeScript и Babel

При использовании транспиляции:

  • TypeScript переводит модули в ESM с this = undefined
  • Babel в режиме modules: false сохраняет оригинальную структуру, но не гарантирует контекст

В результате предупреждение может появляться на этапе Rollup даже при корректном исходном TypeScript-коде.

Типовые сценарии, приводящие к ошибке в продакшене

1. Инициализация singleton через this

this.instance = new Service();

После сборки:

  • this теряет значение
  • singleton не создаётся

2. Расширение объекта экспорта

this.api = {};
this.api.method = fn;

В ESM становится невалидным.

3. Ленивая инициализация

this.cache ||= compute();

При this = undefined ломается вся логика кеширования.

Диагностика в больших проектах

Rollup позволяет включить подробные предупреждения:

onwarn(warning, warn) {
  if (warning.code === 'THIS_IS_UNDEFINED') {
    console.log(warning.id, warning.frame);
  }
  warn(warning);
}

Это помогает локализовать места, где this используется неявно.

Архитектурная причина возникновения проблемы

Основная причина появления THIS_IS_UNDEFINED — смешение двух моделей модулей:

  • CommonJS с динамическим this
  • ES modules со статическим контекстом

Rollup строго придерживается ES module semantics, поэтому любые попытки использовать старую модель поведения приводят к предупреждениям.

Поведенческое различие как источник ошибок

Ключевое различие:

  • CommonJS: this динамический, зависит от вызова
  • ESM: this фиксирован и равен undefined

Rollup не пытается «угадать» поведение, а явно сигнализирует о несовместимости.

Итоговая модель интерпретации предупреждения

THIS_IS_UNDEFINED следует воспринимать как индикатор:

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

В рамках Rollup это не косметическое предупреждение, а сигнал о потере семантики исполнения после сборки.