Совместимость с ES5: что поддерживается, а что нет

Базовая модель совместимости и ограничения таргетирования

esbuild ориентирован на современный JavaScript-ландшафт и работает по принципу транспиляции «из современного в современный», а не «из современного в устаревший ES5». Это ключевое отличие от классических транспайлеров вроде Babel в режиме legacy.

Минимальная целевая планка генерации кода в esbuild — ES2015 (ES6). Поддержка полноценного ES5-вывода отсутствует как архитектурное ограничение.

Это означает:

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

Параметр target и его роль

Основной механизм управления совместимостью — поле target в конфигурации сборки.

Поддерживаемые значения начинаются с ES2015:

  • es2015
  • es2016
  • es2017
  • es2018
  • es2019
  • es2020
  • es2021
  • es2022
  • es2023
  • esnext

Отсутствие ES5 среди допустимых целей означает принципиальную границу: генерация кода ниже ES2015 не предусмотрена.

Что esbuild преобразует из ES6+ в ES2015

Несмотря на отсутствие ES5-таргета, значительная часть современного синтаксиса всё же преобразуется до уровня ES2015.

Стрелочные функции

const add = (a, b) => a + b;

Преобразуется в function expression:

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

Это уже ES5-совместимый уровень, так как используется function и var.

const и let

const и let трансформируются в var:

const x = 1;
let y = 2;

Результат:

var x = 1;
var y = 2;

Однако семантика блочной области видимости частично теряется, что может приводить к отличиям от исходного поведения ES6.

Шаблонные строки

const msg = `Hello ${name}`;

Преобразуются в конкатенацию:

var msg = "Hello " + name;

Частично поддерживаемые конструкции

Классы (class)

Классы трансформируются в ES5-подобные функции с прототипами:

class A {
  constructor(x) {
    this.x = x;
  }
  getX() {
    return this.x;
  }
}

Преобразуется в:

function A(x) {
  this.x = x;
}
A.prototype.getX = function() {
  return this.x;
};

При этом:

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

Деструктуризация

Поддержка частичная и зависит от контекста.

Пример:

const { a, b } = obj;

Может быть преобразован в доступ к свойствам:

var a = obj.a;
var b = obj.b;

Но сложные паттерны (вложенные структуры, параметры функций) могут преобразовываться не полностью или требовать дополнительной обработки.

Spread/rest оператор

Объекты

const newObj = { ...a, b: 1 };

Может быть преобразован в Object.assign:

var newObj = Object.assign({}, a, { b: 1 });

Это уже ES5-совместимо при наличии Object.assign или его полифилла.

Массивы

const arr2 = [...arr1, 2];

Преобразуется в:

var arr2 = arr1.concat([2]);

Что не поддерживается на уровне ES5-совместимости

Отсутствие полного ES5-вывода

Ключевое ограничение: невозможно гарантировать генерацию кода, полностью соответствующего ES5-рантайму без дополнительных инструментов.

Причины:

  • esbuild не реализует глубокую эмуляцию устаревших спецификаций;
  • часть преобразований ориентирована на современный runtime (ES2015+);
  • отсутствует слой полифиллов.

Async/await

async function load() {
  const res = await fetch("/api");
  return res.json();
}

Преобразуется в генератороподобную конструкцию или state machine, но:

  • требует поддержки runtime-хелперов;
  • не гарантирует работу в чистом ES5 без дополнительных полифиллов;
  • зависит от target и настроек.

В ряде случаев результат всё равно ориентирован на ES2015+ окружение.

Promise и встроенные API

esbuild не добавляет полифиллы для:

  • Promise
  • Map, Set
  • Object.assign (в некоторых конфигурациях требуется вручную)
  • Array.from
  • Symbol

Это критическое ограничение для ES5-окружений.

Proxy, Reflect и подобные API

Эти конструкции:

  • не транспилируются;
  • не имеют ES5-эквивалента;
  • требуют поддержки среды выполнения.

Генераторы

function* gen() {
  yield 1;
}

Могут быть трансформированы, но результат не является нативным ES5-кодом и часто требует runtime поддержки генераторов.

Отличие esbuild от Babel в контексте ES5

Babel

  • поддерживает @babel/preset-env;
  • может целиться в ES5;
  • подключает полифиллы через core-js;
  • имеет точную настройку targets browserslist.

esbuild

  • ориентирован на скорость;
  • ограничен ES2015 как нижней границей;
  • не управляет полифиллами;
  • не строит полный legacy pipeline.

Поведение при target: es2015

При установке:

target: "es2015"

происходит:

  • удаление большинства ES6-синтаксических конструкций;
  • сохранение ES2015-совместимого кода;
  • отсутствие попытки деградации до ES5.

Это означает, что итоговый код:

  • может использовать Promise;
  • может использовать Map/Set;
  • не рассчитан на IE11 и аналогичные среды.

Практические последствия для ES5-окружений

Неполная совместимость браузеров

ES5-ориентированные среды включают:

  • Internet Explorer 11
  • старые Android WebView
  • устаревшие embedded-движки

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

Необходимость внешних полифиллов

Для приближения к ES5 требуется:

  • отдельное подключение core-js или аналогов;
  • ручное управление поддержкой API;
  • дополнительные шаги сборки вне esbuild.

Ограничения на архитектурном уровне

esbuild сознательно избегает:

  • сложной системы совместимости браузеров;
  • runtime-инъекций;
  • глубокой реконструкции семантики ES6→ES5.

Фокус смещён в сторону:

  • скорости сборки;
  • современного JavaScript;
  • минимального количества преобразований.

Поведение при комбинировании с другими инструментами

Использование вместе с Babel

Типичный подход:

  1. esbuild выполняет:

    • бандлинг;
    • минификацию;
    • базовую трансформацию.
  2. Babel выполняет:

    • downlevel до ES5;
    • подключение полифиллов;
    • управление browserslist.

Такое разделение компенсирует ограничения esbuild.

Использование через loader pipeline

В некоторых сборках:

  • TypeScript → esbuild → Babel → legacy bundle

или

  • esbuild → post-processing step

Итоговые ограничения ES5-совместимости

  • отсутствует нативный ES5 target;
  • нет встроенных полифиллов;
  • async/await не гарантирует ES5 runtime;
  • современные API не эмулируются;
  • результат ориентирован минимум на ES2015.

Поддерживаемые преобразования как частичный ES5-слой

Несмотря на ограничения, значительная часть синтаксиса упрощается до ES5-эквивалентов:

  • стрелочные функции → function
  • let/const → var
  • классы → прототипы
  • шаблонные строки → конкатенация
  • spread (частично) → Object.assign/concat

Эта трансформация даёт «псевдо-ES5 слой», но не полноценную спецификационную совместимость с устаревшими средами выполнения.