IIFE: скрипты для браузера

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

Типичный результат выглядит следующим образом:

(() => {
  // код приложения
})();

Главная задача IIFE — изолировать внутренние переменные и функции от глобальной области видимости браузера. Такой подход широко использовался задолго до появления модулей ES Modules и до сих пор остаётся востребованным при создании автономных браузерных скриптов.

В Esbuild формат IIFE предназначен прежде всего для случаев, когда необходимо получить один готовый JavaScript-файл, подключаемый через обычный тег <script>.


Когда используется формат IIFE

Формат особенно полезен в следующих ситуациях:

Встраиваемые виджеты

Например, компания предоставляет код виджета для вставки на сторонние сайты:

<script src="widget.js"></script>

В этом случае невозможно требовать от владельцев сайтов поддержку ES-модулей или настройку сборщика. Один готовый IIFE-файл оказывается самым простым вариантом.


Подключение через обычный script

Во многих проектах отсутствует современная инфраструктура сборки:

<script src="app.js"></script>

IIFE позволяет распространять приложение в виде единственного файла без использования:

  • import;
  • export;
  • module loaders;
  • дополнительных зависимостей.

Поддержка старых браузеров

Хотя Esbuild умеет транспилировать код под различные версии браузеров, формат IIFE особенно удобен для окружений, где ES Modules могут работать нестабильно или вовсе отсутствовать.


Создание библиотек для браузера

Иногда библиотека должна предоставлять глобальный объект:

MyLibrary.doSomething();

В этом случае код библиотеки собирается в IIFE и экспортируется через глобальную переменную.


Отличия IIFE от других форматов сборки

Esbuild поддерживает несколько форматов вывода:

Формат Назначение
iife Автономный браузерный скрипт
esm ES Modules
cjs CommonJS
neutral Нейтральный формат

Сравнение IIFE и ESM:

IIFE

<script src="bundle.js"></script>

Преимущества:

  • работает практически везде;
  • автоматически запускается;
  • не требует модульной системы.

Недостатки:

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

ESM

<script type="module" src="bundle.js"></script>

Преимущества:

  • современный стандарт;
  • поддержка import/export;
  • динамические импорты.

Недостатки:

  • требуется поддержка модулей браузером;
  • иногда неудобен для распространения сторонних скриптов.

Создание IIFE-сборки

Исходный файл:

// src/index.js

function greet(name) {
  console.log(`Hello, ${name}`);
}

greet("World");

Команда сборки:

esbuild src/index.js \
  --bundle \
  --format=iife \
  --outfile=dist/app.js

Параметры:

Параметр Назначение
–bundle объединение модулей
–format=iife выбор формата IIFE
–outfile итоговый файл

После выполнения создаётся файл:

(() => {
  function greet(name) {
    console.log(`Hello, ${name}`);
  }

  greet("World");
})();

Подключение IIFE в браузере

Полученный файл подключается стандартным способом:

<!DOCTYPE html>
<html>
<head>
  <title>Esbuild IIFE</title>
</head>
<body>

<script src="app.js"></script>

</body>
</html>

После загрузки страницы код сразу выполнится.


Работа с импортами

Esbuild объединяет все импортированные модули в один файл.

Исходная структура:

// math.js

export function sum(a, b) {
  return a + b;
}
// app.js

import { sum } from "./math.js";

console.log(sum(2, 3));

Сборка:

esbuild app.js \
  --bundle \
  --format=iife \
  --outfile=bundle.js

Результат:

(() => {

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

  console.log(sum(2, 3));

})();

Модульная структура исчезает, а весь код помещается внутрь самовызывающейся функции.


Изоляция глобальной области видимости

Одно из важнейших преимуществ IIFE — защита от загрязнения глобального объекта браузера.

Без IIFE:

var counter = 0;

function increment() {
  counter++;
}

После загрузки:

window.counter
window.increment

становятся глобальными.

С IIFE:

(() => {
  let counter = 0;

  function increment() {
    counter++;
  }
})();

Внешний код не получает доступ к внутренним переменным:

window.counter // undefined

Это значительно снижает риск конфликтов между сторонними скриптами.


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

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

Esbuild позволяет задать имя глобального объекта через параметр globalName.

Пример через JavaScript API:

await esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  format: "iife",
  globalName: "MyLibrary",
  outfile: "dist/library.js"
});

Исходный код

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

export function multiply(a, b) {
  return a * b;
}

После сборки появится глобальный объект:

MyLibrary.add(1, 2);
MyLibrary.multiply(3, 4);

Использование globalName в командной строке

esbuild src/index.js \
  --bundle \
  --format=iife \
  --global-name=MyLibrary \
  --outfile=dist/library.js

После подключения:

<script src="library.js"></script>

станет доступно:

console.log(MyLibrary);

Экспорт нескольких сущностей

Исходный код:

export const version = "1.0.0";

export function init() {
  console.log("Initialized");
}

export function destroy() {
  console.log("Destroyed");
}

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

MyLibrary.version
MyLibrary.init()
MyLibrary.destroy()

Все экспортируемые элементы становятся свойствами глобального объекта.


Вложенные пространства имён

Esbuild поддерживает создание иерархических объектов.

Настройка:

await esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  format: "iife",
  globalName: "Company.Tools.Widget",
  outfile: "widget.js"
});

После загрузки:

Company.Tools.Widget

будет автоматически создано.

Использование:

Company.Tools.Widget.start();

Минификация IIFE-сборки

Для продакшена обычно включается минификация.

Команда:

esbuild src/index.js \
  --bundle \
  --format=iife \
  --minify \
  --outfile=dist/app.min.js

Обычный код:

function greet(name) {
  console.log("Hello " + name);
}

greet("World");

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

(()=>{function o(l){console.log("Hello "+l)}o("World");})();

Размер файла существенно уменьшается.


Генерация source maps

При отладке минифицированного кода полезны карты исходников.

esbuild src/index.js \
  --bundle \
  --format=iife \
  --sourcemap \
  --outfile=dist/app.js

Будут созданы:

app.js
app.js.map

Браузер сможет отображать оригинальные файлы вместо сгенерированного бандла.


Поддержка TypeScript

Формат IIFE полностью совместим с TypeScript.

Исходный файл:

interface User {
  name: string;
}

function greet(user: User) {
  console.log(user.name);
}

greet({
  name: "Alex"
});

Сборка:

esbuild src/index.ts \
  --bundle \
  --format=iife \
  --outfile=dist/app.js

Типы будут удалены:

(() => {
  function greet(user) {
    console.log(user.name);
  }

  greet({
    name: "Alex"
  });
})();

Использование внешних зависимостей

Иногда библиотека уже присутствует на странице.

Например:

<script src="react.production.min.js"></script>

Можно исключить React из бандла:

esbuild src/index.jsx \
  --bundle \
  --format=iife \
  --external:react \
  --outfile=dist/app.js

В этом случае React не попадёт в итоговый файл.


IIFE и code splitting

Механизм разделения кода несовместим с форматом IIFE.

Следующая команда вызовет ошибку:

esbuild src/index.js \
  --bundle \
  --splitting \
  --format=iife

Причина заключается в архитектуре формата.

IIFE предполагает:

  • один файл;
  • один момент запуска;
  • отсутствие внутренней системы загрузки модулей.

Для разделения кода следует использовать формат ESM.


Использование через JavaScript API

Пример настройки:

import * as esbuild from "esbuild";

await esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  format: "iife",
  outfile: "dist/app.js",
  minify: true,
  sourcemap: true,
  target: ["es2018"]
});

Здесь одновременно выполняются:

  • объединение модулей;
  • генерация IIFE;
  • минификация;
  • создание source maps;
  • транспиляция под ES2018.

Использование через Build Context

Для режима разработки:

import * as esbuild from "esbuild";

const context = await esbuild.context({
  entryPoints: ["src/index.js"],
  bundle: true,
  format: "iife",
  outfile: "dist/app.js"
});

await context.watch();

После изменения файлов Esbuild автоматически пересобирает IIFE-бандл.


Типичные сценарии применения

Виджет онлайн-чата

Подключение:

<script src="chat-widget.js"></script>

После загрузки виджет самостоятельно создаёт интерфейс и начинает работу.


Счётчики аналитики

Подобным образом работают многие аналитические системы:

<script src="analytics.js"></script>

Скрипт выполняется сразу после загрузки страницы.


Баннеры и рекламные модули

Рекламные сети часто распространяют код именно в формате IIFE:

<script src="banner.js"></script>

От пользователя требуется лишь вставка одного тега.


SDK для сторонних сайтов

Пример использования:

<script src="sdk.js"></script>

<script>
  SDK.init({
    apiKey: "123"
  });
</script>

В этом случае Esbuild собирает SDK в IIFE и экспортирует его через globalName.


Ограничения формата IIFE

Несмотря на универсальность, формат обладает рядом особенностей:

  • отсутствует динамический импорт модулей;
  • не поддерживается code splitting;
  • весь код загружается сразу;
  • размер бандла может быстро расти;
  • хуже подходит для крупных SPA-приложений;
  • не использует преимущества нативных ES Modules.

Поэтому IIFE обычно применяется для:

  • библиотек;
  • виджетов;
  • SDK;
  • внедряемых скриптов;
  • небольших браузерных приложений;
  • автономных клиентских модулей.

Для современных фронтенд-приложений чаще используется формат ESM, тогда как IIFE остаётся одним из наиболее удобных способов распространения полностью самодостаточных браузерных скриптов.