Опция define в Esbuild предназначена для замены
идентификаторов на заданные значения во время сборки проекта.
По своей сути она работает как механизм препроцессорных констант: каждое
вхождение указанного идентификатора подменяется соответствующим
выражением ещё до выполнения кода в браузере или среде Node.js.
Наиболее распространённые сценарии применения:
Благодаря тому, что замена происходит на этапе сборки, Esbuild может дополнительно выполнять оптимизации, включая удаление недостижимого кода (tree shaking и dead code elimination).
esbuild src/index.js \
--bundle \
--define:DEBUG=true
Исходный код:
if (DEBUG) {
console.log("Отладочный режим");
}
После обработки:
if (true) {
console.log("Отладочный режим");
}
import * as esbuild from "esbuild";
await esbuild.build({
entryPoints: ["src/index.js"],
bundle: true,
outfile: "dist/app.js",
define: {
DEBUG: "true"
}
});
В объекте define все значения задаются строками.
Например:
define: {
DEBUG: "true"
}
не означает передачу строки "true".
Esbuild воспринимает содержимое как JavaScript-выражение и вставляет его напрямую в код.
Результат:
if (DEBUG)
станет
if (true)
Для передачи строк необходимо дополнительно использовать кавычки внутри строки.
Пример:
define: {
API_URL: '"https://api.example.com"'
}
Исходный код:
fetch(API_URL);
После сборки:
fetch("https://api.example.com");
Можно подставлять числовые литералы.
Настройка:
define: {
VERSION: "5"
}
Код:
console.log(VERSION);
Результат:
console.log(5);
Один из наиболее частых вариантов использования.
Конфигурация:
define: {
DEV: "false"
}
Код:
if (DEV) {
console.log("Development mode");
}
После сборки:
if (false) {
console.log("Development mode");
}
Во время минификации такой блок обычно будет полностью удалён.
process.env.NODE_ENVЭто самый распространённый сценарий использования
define.
Многие библиотеки определяют режим работы через:
process.env.NODE_ENV
Конфигурация Esbuild:
define: {
"process.env.NODE_ENV": '"production"'
}
Исходный код:
if (process.env.NODE_ENV === "development") {
console.log("Debug");
}
После замены:
if ("production" === "development") {
console.log("Debug");
}
Минификатор затем удалит недостижимый код.
Часто создаются отдельные конфигурации для разработки и продакшена.
define: {
__DEV__: "true"
}
define: {
__DEV__: "false"
}
Код приложения:
if (__DEV__) {
enableDebugTools();
}
В продакшн-сборке вызов будет полностью исключён.
Опция define особенно полезна для удаления
логирования.
Исходный код:
if (DEBUG_LOGS) {
console.log(user);
}
Конфигурация:
define: {
DEBUG_LOGS: "false"
}
После сборки:
if (false) {
console.log(user);
}
После минификации:
/* удалено */
Таким образом можно избавляться от большого объёма служебного кода без дополнительных инструментов.
Допустимо определять сразу множество значений.
define: {
__DEV__: "false",
__VERSION__: '"2.1.0"',
MAX_RETRY_COUNT: "5"
}
Использование:
console.log(__VERSION__);
for (let i = 0; i < MAX_RETRY_COUNT; i++) {
retry();
}
После замены:
console.log("2.1.0");
for (let i = 0; i < 5; i++) {
retry();
}
В качестве значения можно передавать не только литералы, но и полноценные выражения JavaScript.
Настройка:
define: {
IS_BROWSER: "typeof window !== 'undefined'"
}
Код:
if (IS_BROWSER) {
initBrowserFeatures();
}
После обработки:
if (typeof window !== "undefined") {
initBrowserFeatures();
}
Во многих проектах создаются специальные глобальные константы.
Пример:
define: {
__FEATURE_CHAT__: "true",
__FEATURE_PAYMENTS__: "false"
}
Код:
if (__FEATURE_CHAT__) {
loadChat();
}
if (__FEATURE_PAYMENTS__) {
loadPayments();
}
После сборки:
if (true) {
loadChat();
}
if (false) {
loadPayments();
}
Неиспользуемый функционал может быть полностью удалён из бандла.
Подход с feature flags позволяет выпускать разные версии приложения из одной кодовой базы.
Конфигурация:
define: {
ENABLE_NEW_UI: "true"
}
Код:
const page = ENABLE_NEW_UI
? renderNewPage()
: renderOldPage();
После сборки:
const page = true
? renderNewPage()
: renderOldPage();
После оптимизации:
const page = renderNewPage();
Часто значения читаются из среды выполнения Node.js.
define: {
API_URL: JSON.stringify(process.env.API_URL),
APP_NAME: JSON.stringify(process.env.APP_NAME)
}
Если:
API_URL=https://api.company.com
APP_NAME=Portal
то после сборки:
fetch("https://api.company.com");
console.log("Portal");
Рассмотрим код:
const DEBUG = false;
if (DEBUG) {
console.log("debug");
}
и вариант:
if (__DEBUG__) {
console.log("debug");
}
с конфигурацией:
define: {
__DEBUG__: "false"
}
Во втором случае замена выполняется до анализа кода. Это даёт больше возможностей для оптимизации, особенно когда флаг используется в разных файлах и модулях.
defineРаботает:
define: {
DEBUG: "true"
}
Код:
if (DEBUG)
Не работает для динамически сформированных строк:
window["DEBUG"]
или
globalThis[name]
Поскольку здесь отсутствует прямое обращение к идентификатору.
Следует помнить, что Esbuild не создаёт переменные.
Конфигурация:
define: {
VALUE: "1 + 2"
}
Код:
console.log(VALUE * 10);
Результат:
console.log((1 + 2) * 10);
Происходит именно подстановка выражения.
Если идентификатор используется в разных местах программы, замена произойдёт везде.
Пример:
define: {
VERSION: '"1.0.0"'
}
Код:
console.log(VERSION);
После обработки:
console.log("1.0.0");
Поэтому рекомендуется использовать специальные имена констант:
__APP_VERSION__
__DEV__
__TEST__
__BUILD_DATE__
Так снижается риск конфликта с обычными переменными приложения.
import * as esbuild from "esbuild";
const production = process.argv.includes("--production");
await esbuild.build({
entryPoints: ["src/main.js"],
bundle: true,
minify: production,
outfile: "dist/app.js",
define: {
__DEV__: String(!production),
__VERSION__: '"3.4.1"',
"process.env.NODE_ENV": production
? '"production"'
: '"development"'
}
});
Использование в приложении:
if (__DEV__) {
console.log("Debug mode");
}
console.log("Version:", __VERSION__);
if (process.env.NODE_ENV === "production") {
enableOptimizations();
}
Во время сборки все значения будут подставлены напрямую, а неиспользуемые ветви кода смогут быть удалены оптимизатором.
Максимальная эффективность достигается при совместном использовании
define и minify.
Конфигурация:
define: {
DEBUG: "false"
},
minify: true
Исходный код:
if (DEBUG) {
console.log("debug");
}
startApplication();
После замены:
if (false) {
console.log("debug");
}
startApplication();
После минификации:
startApplication();
Таким образом, define служит не только средством
передачи конфигурации в приложение, но и важным инструментом
оптимизации, позволяющим исключать целые ветви логики из итогового
бандла ещё на этапе сборки.