Опция engines: точное указание целевых сред

В системе сборки esbuild определение целевой среды выполнения напрямую влияет на то, какой JavaScript-код будет сгенерирован в результате трансформации. Под целевой средой понимается набор характеристик рантайма: версия ECMAScript, браузер или Node.js, наличие встроенных API и уровень поддержки синтаксиса.

Ключевая идея заключается в том, что esbuild не просто транспилирует код «вниз», а адаптирует результат под заранее заданные ограничения, минимизируя лишний полифил и сохраняя максимально современный синтаксис там, где это возможно.


Основной механизм: target

Главный инструмент управления целевыми средами в esbuild — параметр target.

Он задаёт минимально поддерживаемый уровень JavaScript-движка, под который будет компилироваться код.

Примеры значений:

  • es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023
  • chrome80, chrome90, chrome120
  • firefox70, firefox110
  • safari13, safari16
  • edge90
  • node12, node14, node18, node20

Пример конфигурации:

import { build } from 'esbuild';

build({
  entryPoints: ['src/index.js'],
  bundle: true,
  target: ['es2019']
});

Здесь esbuild будет избегать синтаксиса и API, появившихся после ES2019.


Поведение трансформации под разные targets

Установка target влияет сразу на несколько уровней компиляции:

1. Синтаксические конструкции

Если целевая среда не поддерживает современный синтаксис, он будет преобразован:

  • optional chaining (?.)
  • nullish coalescing (??)
  • private class fields
  • top-level await (в зависимости от окружения)

Например:

const value = obj?.user?.name ?? 'default';

Может быть преобразовано в цепочку проверок:

var value = obj == null ? void 0 : obj.user == null ? void 0 : obj.user.name;
if (value == null) value = 'default';

2. Генерация классов

Для старых target-версий классы превращаются в функции-конструкторы с прототипами, если это необходимо.


3. Модули

Хотя target не управляет системой модулей напрямую (это делает format), он влияет на вспомогательный код вокруг импортов/экспортов, особенно при генерации вспомогательных функций.


Разница между target и «версией Node.js»

Указание nodeXX не означает только уровень ECMAScript. Оно включает:

  • особенности V8-движка конкретной версии Node
  • поддержку встроенных возможностей языка
  • ограничения на синтаксис

Пример:

target: ['node14']

Это означает, что esbuild может оставить:

  • некоторые современные конструкции ES2020
  • async/await без полной деградации
  • более современные class features

но исключит возможности, отсутствующие в Node 14.


Расширенное управление через supported

Если target задаёт общий уровень среды, то supported позволяет более точно управлять доступными возможностями.

Это словарь флагов вида:

supported: {
  'arrow': true,
  'bigint': false,
  'const-and-let': true,
  'template-literal': true
}

Такой подход используется для сценариев, где:

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

Комбинация target и supported

При совместном использовании действует правило приоритета:

  • target задаёт базовый уровень возможностей
  • supported переопределяет конкретные фичи

Пример:

build({
  target: ['es2020'],
  supported: {
    bigint: false
  }
});

Даже если ES2020 поддерживает BigInt, он будет отключён.


Практика определения целевой среды

Выбор значений для target зависит от реальной аудитории приложения.

Веб-приложения

Обычно используется стратегия «минимально необходимой поддержки»:

  • современные браузеры → es2020 или выше
  • широкая поддержка → es2018 или es2017

Пример:

target: ['chrome80', 'firefox78', 'safari13']

Node.js-библиотеки

Часто выбирается минимальная LTS-версия:

target: ['node18']

или более консервативно:

target: ['node16']

Влияние на размер и производительность

Чем старше target:

  • тем больше трансформаций
  • тем больше вспомогательного кода
  • тем выше размер бандла
  • тем ниже эффективность оптимизаций

Чем новее target:

  • меньше преобразований
  • ближе к исходному коду
  • меньше runtime-обвязки

Частые ошибки при настройке целевых сред

1. Смешивание слишком разных target

target: ['es2015', 'chrome120']

В результате esbuild вынужден выбирать общий минимум, что снижает эффективность.


2. Игнорирование реальной среды исполнения

Указание слишком нового стандарта приводит к падению на старых браузерах без явной ошибки на этапе сборки.


3. Использование только ES-уровня без учёта runtime

target: ['es2022']

Такой подход не учитывает различия между Node.js и браузерами, особенно в отношении встроенных API.


Поведение при отсутствии target

Если target не указан, esbuild использует достаточно современный уровень ECMAScript по умолчанию, ориентируясь на текущие возможности движка, что приводит к минимальной транспиляции.


Роль target в tree-shaking и оптимизациях

Хотя tree-shaking в первую очередь зависит от структуры модулей, target влияет на:

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

Совместимость с другими настройками esbuild

target тесно взаимодействует с:

  • platform (browser / node / neutral)
  • format (esm / cjs / iife)
  • jsx (runtime и трансформация)
  • minify (оптимизация, зависящая от синтаксиса)

Особенно важно учитывать, что platform определяет окружение API, а target — уровень языка.


Стратегии выбора target в крупных проектах

В монорепозиториях часто используется несколько уровней сборки:

  • базовый target для библиотек
  • отдельный target для production bundle
  • отдельный target для legacy-совместимости

Пример:

const targets = {
  modern: ['es2022'],
  legacy: ['es2017'],
  node: ['node18']
};

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