Именование глобальных переменных в UMD/IIFE

Глобальное имя в форматах UMD и IIFE в контексте сборки Rollup определяет точку доступа к библиотеке после подключения через <script> без модульной системы. От корректного выбора имени зависит предсказуемость использования, отсутствие конфликтов в глобальном пространстве и совместимость с экосистемами, где отсутствует ESM.

Формат IIFE (Immediately Invoked Function Expression) в Rollup генерирует самовызывающуюся функцию, которая изолирует внутренний код модуля, но при этом может экспортировать результат в глобальную область видимости.

Типичный результат сборки:

var MyLib = (function () {
  'use strict';

  function add(a, b) {
    return a + b;
  }

  return {
    add
  };
}());

Ключевой момент заключается в том, что переменная MyLib становится глобальной только если бандл подключён напрямую в браузере без модульного загрузчика. Именно это имя определяет, как библиотека будет доступна пользователю.

В современных средах глобальный объект может быть разным:

  • window в браузере
  • global в Node.js (при не-модульной сборке)
  • globalThis как универсальная абстракция

Rollup при генерации IIFE опирается на конфигурацию output.name, которая и задаёт глобальный идентификатор.

UMD как универсальный формат экспорта

UMD (Universal Module Definition) расширяет IIFE, добавляя поддержку AMD и CommonJS. Это делает имя глобальной переменной частью более сложной системы ветвления:

(function (global, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports !== 'undefined') {
    module.exports = factory();
  } else {
    global.MyLib = factory();
  }
}(this, (function () {
  'use strict';

  function add(a, b) {
    return a + b;
  }

  return {
    add
  };
})));

Здесь MyLib становится ключевой точкой привязки в глобальном пространстве. Ошибка в выборе имени приводит к конфликтам или недоступности библиотеки в legacy-окружениях.

Конфигурация Rollup и параметр output.name

В Rollup имя глобальной переменной задаётся через output.name:

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.umd.js',
    format: 'umd',
    name: 'MyLib'
  }
};

Это значение используется:

  • как глобальная переменная в IIFE
  • как экспорт в UMD fallback-ветке
  • как идентификатор в AMD define (в некоторых конфигурациях)

Критичность имени

Имя становится частью публичного API при использовании через <script>:

<script src="bundle.umd.js"></script>
<script>
  MyLib.add(2, 3);
</script>

Если имя выбрано неудачно, возникают следующие проблемы:

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

Правила формирования глобального имени

1. Соответствие имени пакета

Чаще всего глобальное имя выводится из имени npm-пакета. Например:

  • lodash_
  • momentmoment
  • chart.jsChart

Rollup не выполняет автоматическую нормализацию в большинстве случаев, поэтому явное задание output.name обязательно для предсказуемости.

2. CamelCase и PascalCase

Для глобальных объектов предпочтителен PascalCase:

  • MyLibrary
  • HttpClient
  • DateUtils

CamelCase также допустим, но чаще используется для внутренних API:

  • myLibrary (менее предпочтительно для глобала)

Причина выбора PascalCase связана с традицией конструкторов и классов в JavaScript, что снижает риск конфликтов с примитивными значениями.

3. Избежание общих слов

Использование слишком общих идентификаторов создаёт риск коллизий:

  • Utils
  • Helper
  • App
  • Lib

Такие имена часто уже присутствуют в глобальной области других библиотек или приложений.

4. Пространства имён

Для сложных библиотек используется объектное пространство имён:

output: {
  name: 'MyCompany.MyLib',
  format: 'umd'
}

Результат:

global.MyCompany.MyLib = factory();

Это снижает вероятность конфликтов, но увеличивает длину обращения.

Использование такого подхода оправдано для корпоративных SDK и крупных систем.

Поведение при вложенных именах

Rollup поддерживает точечную нотацию, которая превращается в цепочку объектов:

name: 'Platform.Services.Http'

Генерация:

global.Platform = global.Platform || {};
global.Platform.Services = global.Platform.Services || {};
global.Platform.Services.Http = factory();

Такой подход обеспечивает безопасное расширение глобального пространства без перезаписи существующих значений.

Влияние минификации на глобальные имена

Минификация кода внутри IIFE не влияет на имя глобальной переменной, если оно задано через output.name. Однако внутренние свойства объекта могут быть сокращены:

return {
  add,
  subtract,
  multiply
};

После минификации:

return { a, b, c };

Глобальное имя при этом остаётся неизменным, поскольку оно является частью контракта внешнего API.

Конфликты глобальных имён

Основные сценарии конфликтов:

Перезапись глобальной переменной

window.Utils = ...

Если другая библиотека уже использует Utils, происходит скрытая замена функциональности.

Неявная зависимость

Код может случайно полагаться на существующее глобальное имя:

if (window.Utils) {
  window.Utils.format();
}

При подключении другой версии библиотеки поведение становится нестабильным.

Порядок загрузки скриптов

UMD/IIFE-бандлы зависят от порядка подключения:

<script src="core.js"></script>
<script src="plugin.js"></script>

Если plugin.js ожидает глобальное имя Core, оно должно быть гарантированно создано ранее.

Различия поведения UMD и IIFE в Rollup

Характеристика IIFE UMD
Глобальная переменная Да Да
AMD поддержка Нет Да
CommonJS Нет Да
Использование name output.name output.name
Сложность кода Ниже Выше

UMD требует более строгого подхода к именованию, поскольку имя используется в нескольких контекстах сразу.

Использование globalThis как базового объекта

Современные бандлы часто опираются на globalThis:

globalThis.MyLib = factory();

Это устраняет необходимость различать window, global и self. Однако имя переменной остаётся тем же самым ключевым элементом API.

Паттерны безопасного именования

Префикс компании

name: 'AcmeHttpClient'

Снижает риск конфликта в публичных окружениях.

Доменная структура

name: 'Acme.Network.HttpClient'

Используется в больших экосистемах.

Сокращённые стабильные имена

Иногда допускаются короткие имена, если библиотека становится стандартом де-факто:

  • Vue
  • React
  • Rx

В таких случаях имя становится частью индустриального соглашения.

Влияние имени на DX и интеграцию

Глобальное имя влияет на:

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

Неудачное имя приводит к необходимости дополнительных обёрток:

const Http = window.SomeVeryLongAndInconsistentName.Http;

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

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

declare global {
  const MyLib: {
    add(a: number, b: number): number;
  };
}

Несоответствие между output.name и декларацией приводит к ошибкам времени выполнения при использовании UMD-бандла.

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

Процесс выбора глобального идентификатора обычно включает:

  • анализ существующих глобальных объектов в браузере
  • проверку npm-экосистемы на совпадения
  • оценку длины имени и удобства использования
  • определение необходимости пространства имён
  • фиксацию имени как части публичного API

Имя, заданное в Rollup, становится контрактом между сборкой и средой выполнения, поэтому любые изменения требуют версионирования и миграционной стратегии.