IIFE (Immediately Invoked Function Expression) — формат сборки, при котором весь код приложения оборачивается в самовызывающуюся функцию. После загрузки браузером такой скрипт немедленно выполняется и не требует дополнительного механизма запуска.
Типичный результат выглядит следующим образом:
(() => {
// код приложения
})();
Главная задача IIFE — изолировать внутренние переменные и функции от глобальной области видимости браузера. Такой подход широко использовался задолго до появления модулей ES Modules и до сих пор остаётся востребованным при создании автономных браузерных скриптов.
В Esbuild формат IIFE предназначен прежде всего для случаев, когда
необходимо получить один готовый JavaScript-файл, подключаемый через
обычный тег <script>.
Формат особенно полезен в следующих ситуациях:
Например, компания предоставляет код виджета для вставки на сторонние сайты:
<script src="widget.js"></script>
В этом случае невозможно требовать от владельцев сайтов поддержку ES-модулей или настройку сборщика. Один готовый IIFE-файл оказывается самым простым вариантом.
Во многих проектах отсутствует современная инфраструктура сборки:
<script src="app.js"></script>
IIFE позволяет распространять приложение в виде единственного файла без использования:
Хотя Esbuild умеет транспилировать код под различные версии браузеров, формат IIFE особенно удобен для окружений, где ES Modules могут работать нестабильно или вовсе отсутствовать.
Иногда библиотека должна предоставлять глобальный объект:
MyLibrary.doSomething();
В этом случае код библиотеки собирается в IIFE и экспортируется через глобальную переменную.
Esbuild поддерживает несколько форматов вывода:
| Формат | Назначение |
|---|---|
| iife | Автономный браузерный скрипт |
| esm | ES Modules |
| cjs | CommonJS |
| neutral | Нейтральный формат |
Сравнение IIFE и ESM:
<script src="bundle.js"></script>
Преимущества:
Недостатки:
<script type="module" src="bundle.js"></script>
Преимущества:
Недостатки:
Исходный файл:
// 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");
})();
Полученный файл подключается стандартным способом:
<!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);
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();
Для продакшена обычно включается минификация.
Команда:
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");})();
Размер файла существенно уменьшается.
При отладке минифицированного кода полезны карты исходников.
esbuild src/index.js \
--bundle \
--format=iife \
--sourcemap \
--outfile=dist/app.js
Будут созданы:
app.js
app.js.map
Браузер сможет отображать оригинальные файлы вместо сгенерированного бандла.
Формат 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.
Следующая команда вызовет ошибку:
esbuild src/index.js \
--bundle \
--splitting \
--format=iife
Причина заключается в архитектуре формата.
IIFE предполагает:
Для разделения кода следует использовать формат ESM.
Пример настройки:
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"]
});
Здесь одновременно выполняются:
Для режима разработки:
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>
От пользователя требуется лишь вставка одного тега.
Пример использования:
<script src="sdk.js"></script>
<script>
SDK.init({
apiKey: "123"
});
</script>
В этом случае Esbuild собирает SDK в IIFE и экспортирует его через
globalName.
Несмотря на универсальность, формат обладает рядом особенностей:
Поэтому IIFE обычно применяется для:
Для современных фронтенд-приложений чаще используется формат ESM, тогда как IIFE остаётся одним из наиболее удобных способов распространения полностью самодостаточных браузерных скриптов.