W3docs

Полифиллы и транспиляторы JavaScript

Узнайте, как современный JavaScript работает в старых движках с помощью транспиляторов Babel и полифиллов core-js для добавления отсутствующих методов.

JavaScript получает новые возможности каждый год. Каждый ежегодный выпуск — ES2015 (ES6), ES2020, ES2022 и так далее — добавляет новый синтаксис и встроенные методы. Проблема в том, что ваш код выполняется не в одном фиксированном движке: он работает в том браузере или среде выполнения, которую используют ваши посетители, а некоторые из них устарели на несколько лет. Код, который отлично работает в последней версии Chrome, может вызвать SyntaxError в более старой, или тихо завершиться с ошибкой, потому что метод вроде Array.prototype.includes попросту не существует.

Есть два разных пробела, которые нужно устранить, и для каждого из них нужен свой инструмент. Новый синтаксис, который старый движок не может даже разобрать, требует транспилятора. Новые встроенные API, которые старый движок никогда не поддерживал, требуют полифилла. В этой главе объясняются оба инструмента, показывается их различие и описывается, как они вписываются в современную цепочку сборки.

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

Прежде чем браться за инструмент, полезно понять, почему одного инструмента недостаточно.

  • Синтаксис — это грамматика языка: стрелочные функции, class, опциональная цепочка (?.), оператор нулевого слияния (??), шаблонные строки. Если движок не понимает грамматику, скрипт не удаётся разобрать и ничего не выполняется. Исправить это во время выполнения невозможно, так как файл отклоняется ещё до запуска какого-либо кода.
  • API — это встроенные функции и объекты, доступные во время выполнения вашего кода: Promise, Array.prototype.includes, String.prototype.padStart, Object.fromEntries, fetch. Это просто значения, хранящиеся на глобальных объектах и прототипах. Если какого-то из них нет, вы можете добавить его самостоятельно до того, как ваш код его использует.

Это и есть вся суть: переписать грамматику заранее или предоставить недостающие значения во время выполнения.

Транспиляторы: новый синтаксис в старый

Транспилятор (также называемый компилятором исходного кода в исходный код) читает ваш современный JavaScript и переписывает его в эквивалентный старый JavaScript, который понимает больше движков. Самый известный транспилятор — Babel. Это происходит на этапе сборки — до того, как ваш код будет отправлен, — поэтому браузер получает только тот синтаксис, который умеет разбирать.

Вот небольшой пример «до» и «после». Вы пишете современную стрелочную функцию:

// Source — modern syntax
const double = x => x * 2;

Транспилятор, нацеленный на старые движки, переписывает её в классическое функциональное выражение:

// Output — down-leveled to ES5
var double = function (x) {
  return x * 2;
};

Поведение идентично; изменилась только грамматика. Babel делает то же самое для объявлений class, деструктуризации, параметров по умолчанию, опциональной цепочки и многого другого. Например, user?.address?.city превращается в серию проверок &&, с которыми старые движки справляются без проблем.

@babel/preset-env и browserslist

Вы редко настраиваете каждую возможность вручную. Вместо этого вы используете @babel/preset-env — пресет, который решает, какие преобразования применять, исходя из сред, которые вы хотите поддерживать. Эти среды описываются с помощью запроса browserslist — краткого общего способа указать целевые браузеры:

{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead"
  ]
}

С этим списком @babel/preset-env транспилирует только то, чего реально не хватает этим браузерам. Сузьте список до современных браузеров — и почти ничего не будет преобразовано; расширьте до устаревших — и преобразований будет гораздо больше. Главная идея: транспилятор — это шаг сборки, а browserslist говорит ему, насколько усердно работать.

Полифиллы: предоставление отсутствующих API во время выполнения

Полифилл — это фрагмент кода, который добавляет отсутствующий встроенный элемент, чтобы старый движок получил нужный API во время выполнения. Транспилятор может переписать синтаксис ?., но он не может создать объект Promise, который движок никогда не поддерживал, — это задача полифилла. Наиболее широко используемая библиотека полифиллов — core-js, которая предоставляет реализации для Promise, Array.from, Object.fromEntries, String.prototype.padStart и сотен других.

Вы также можете написать небольшой полифилл вручную. Основной шаблон — это защита с обнаружением возможностей: проверка if, которая устанавливает вашу версию только в том случае, если встроенная версия отсутствует:

if (!String.prototype.padStart) {
  String.prototype.padStart = function (targetLength, padString) {
    targetLength = Math.floor(targetLength) || 0;
    if (targetLength < this.length) {
      return String(this);
    }

    padString = padString ? String(padString) : ' ';

    let pad = '';
    const len = targetLength - this.length;
    let i = 0;
    while (pad.length < len) {
      if (!padString[i]) {
        i = 0;
      }
      pad += padString[i];
      i++;
    }

    return pad + String(this).slice(0);
  };
}

Строка if (!String.prototype.padStart) — самое важное. Без неё вы бы перезаписывали встроенную реализацию движка каждый раз, заменяя быстрый, хорошо протестированный встроенный код своим собственным. Защита говорит: «вмешаться только тогда, когда возможность действительно отсутствует», — поэтому современные движки сохраняют свою оптимизированную версию, и только старые движки переходят на вашу.

Пример ниже обнаруживает и использует padStart так же, как это делал бы полифилл. В современном браузере выполняется встроенный метод; в устаревшем защищённый запасной вариант выше был бы предоставлен заранее.

javascript— editable
Внимание

Изменение встроенных прототипов (например, String.prototype) допустимо только для полифиллов, и только с защитой обнаружения возможностей. В собственном коде приложения избегайте добавления методов к встроенным прототипам — это может конфликтовать с другими библиотеками и будущими возможностями языка.

Транспилятор и полифилл: сравнение

Эти два инструмента легко перепутать, поскольку оба существуют для поддержки старых движков. Следующее сравнение поможет их разграничить:

АспектТранспилятор (например, Babel)Полифилл (например, core-js)
ИсправляетНовый синтаксис, который движок не может разобратьОтсутствующие встроенные API
Когда выполняетсяЭтап сборки (до отправки)Время выполнения (в браузере)
Пример входных данных?., ??, стрелочные функции, classPromise, fetch, Array.prototype.includes
Как работаетПереписывает код в старую грамматикуДобавляет отсутствующую функцию/объект
Может ли закрыть другой пробел?Нет — не может добавить отсутствующие APIНет — не может исправить непарсируемый синтаксис

Простое правило: если движок не может прочитать ваш код, нужен транспилятор; если может прочитать, но функция не определена — нужен полифилл. Большинство реальных проектов используют оба инструмента одновременно.

Место в современной цепочке сборки

На практике вы не запускаете эти инструменты вручную. Бандлер или инструмент сборки — например, Vite, webpack или esbuild — управляет ими за вас. Типичная настройка работает следующим образом:

  1. Вы один раз объявляете целевые среды в browserslist.
  2. Инструмент сборки запускает Babel с @babel/preset-env, который понижает уровень только того синтаксиса, которого не хватает вашим целевым браузерам.
  3. Та же конфигурация внедряет полифиллы core-js для API, которых не хватает этим целевым браузерам, — и при useBuiltIns: 'usage' только те, на которые ваш код действительно ссылается.

Результатом является бандл, настроенный под вашу реальную аудиторию: ничто не преобразуется и не полифиллируется там, где браузеры ваших посетителей уже поддерживают нужное.

Информация

Современные вечнозелёные браузеры — Chrome, Edge, Firefox и Safari — обновляются автоматически и уже поддерживают подавляющее большинство современных возможностей ES6 и более поздних версий. Активная транспиляция и широкое применение полифиллов сегодня гораздо менее необходимы, чем прежде. Установите реалистичный целевой список browserslist для вашей аудитории и позвольте цепочке сборки понижать уровень и полифиллировать только то, что действительно нужно.

Игнорирование этого совета имеет реальную цену. Избыточные полифиллы раздувают бандл кодом, который каждый современный пользователь скачивает, разбирает и выбрасывает неиспользованным. Слишком агрессивное понижение уровня также производит более объёмный и медленный результат. Цель не в том, чтобы «поддерживать всё», а в том, чтобы «поддерживать то, что реально используют ваши пользователи». Чтобы разобраться, какие возможности поддерживает конкретный браузер, полезным дополнением будет глава о совместимости DOM и браузеров.

Проверьте свои знания

Практика
Какой инструмент переписывает современный синтаксис JavaScript в эквивалентный старый синтаксис на этапе сборки?
Какой инструмент переписывает современный синтаксис JavaScript в эквивалентный старый синтаксис на этапе сборки?
Практика
Зачем написанный вручную полифилл начинается с проверки вроде 'if (!String.prototype.padStart)'?
Зачем написанный вручную полифилл начинается с проверки вроде 'if (!String.prototype.padStart)'?
Практика
Какой пробел может закрыть полифилл, но не может транспилятор?
Какой пробел может закрыть полифилл, но не может транспилятор?
Was this page helpful?