Чего вы не знали о Babel preset-env

TL;DR

Независимо от того, являетесь ли вы разработчиком с полным стеком или разработчиком JavaScript, вы должны изучить конфигурацию preset-env и оптимизировать ее для браузеров, которые на самом деле используют ваши клиенты, а также оптимизировать размер пакета, чтобы настроить core-js в соответствии с вашими потребностями. Это позволит вам поделиться конкретным набором поддерживаемых браузеров для вашего приложения, одновременно используя возможности новейших функций JavaScript.

Фон

Здесь, в PerimeterX, мы поддерживаем сторонний JS-скрипт как часть нашего решения Bot Defender.

Если вы когда-либо работали над каким-либо сторонним скриптом, вы знаете, насколько сложно поддерживать скрипт, который работает на веб-сайтах ваших клиентов, в миллионах различных браузеров и на самых разных платформах. Поэтому, когда я взялся за обновление библиотек нашей системы сборки и перенос BabelJS с Preset-2015 на Preset-env, я был очень осторожен. Я нашел много интересного под капотом babelJS и почерпнул информацию о том, как он работает с preset-env.

Что именно сделали годовые пресеты

Годовые пресеты Babel — пресет-2015, 2016 и т. д. — были сосредоточены на преобразовании функций JavaScript ES6 в ES5. Это позволило разработчикам писать код JavaScript с новейшими функциями JavaScript, такими как обещания, let и const, а также стрелочные функции, в соответствии с их стадией утверждения.

Каждый год пресет добавлял все больше и больше функций. Любые другие функции должны были быть добавлены в виде дополнительных библиотек полифилла. Babel полагался на библиотеку babel-polyfill для предустановленных полифилов.

Также важно отметить, что некоторые встроенные функции были заменены альтернативным кодом, например, const и let перенесены в var. Другим требовался дополнительный код, например промисы. Весь вспомогательный код был добавлен в ваш пакет независимо от фактического использования вами функций, возможно добавление неработающего кода и увеличение размера пакета без уважительной причины.

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

Основные изменения с предустановленной оболочкой

Основная идея preset-env заключалась в том, чтобы перестать создавать ежегодные пресеты и иметь только одну библиотеку, которая будет периодически обновляться. Но это изменение вместе с изменениями в самой библиотеке babel привело к следующим сдвигам:

Переход с библиотеки babel-polyfill на core-js

Чтобы предоставить модули для поддержки новых функций JS, Babel отказался от собственной библиотеки полифиллов в пользу core-js. Когда я впервые запустил новую конфигурацию в своем коде, я был удивлен, увидев код, решающий проблемы браузера с такими API, как array.reduce, setTimeout и array.indexOf. Поскольку документации для каждого модуля в core-js практически не существует, я клонировал репозиторий и изучил код и историю коммитов, чтобы понять, что на самом деле делают эти модули. Вскоре я понял, что core-js не только выполняет преобразования ES6 → ES5 (описываемые как плагины), но также добавляет модули для исправления неподдерживаемого синтаксиса или частичных реализаций старых функций JavaScript. Он также добавляет поддержку новых функций, таких как символы в старых функциях, описанных как полифиллы. У каждого модуля есть код, который он запускает в браузере, чтобы определить, должен ли он работать, заменяя некоторые функции в зависимости от типа и версии браузера.

Флаг preset-env useBuiltins определяет, должен ли Babel всегда добавлять полифиллы corejs, добавлять их один раз для каждого файла или для каждого использования конкретной функции в вашем коде. Это рекомендуемый способ: добавлять только то, что поддерживает ваш код. Используя флаг preset-env debug:true, я заметил, что babel указывает, какой мой файл требует каждого модуля в core-js при использовании
useBuiltins: usage. Читая библиотеку core-js, я узнал, что ее логика определения того, действительно ли вы используете функцию в своем коде или нет, ненадежна, иногда полагаясь на появление определенных ключевых слов, а в некоторых случаях добавляя ненужный код.

Прочтите это, если вы разработчик JavaScript. Важно отметить, что
модули core-js или полифиллы во многом влияют на прототипы браузеров. Поскольку мы поддерживаем сторонний скрипт, мы хотим избежать изменения реализации JavaScript браузера на веб-сайтах наших клиентов, что может привести к непредсказуемому поведению и ошибкам. Имея это в виду, мы фактически использовали
useBuiltins: false для отключения всех полифилов, оставив только встроенные преобразования. В нашем случае core-js добавил около 90 модулей по умолчанию, что значительно повлияло на размер пакета. Я рекомендую проверять, что он добавляет, и включать или исключать соответственно. См. свойства exclude\include. В PerimeterX мы добавляем любые встроенные плагины, которые нам нужны, в рамках нашего стороннего скрипта, не влияя на реализацию браузера JS.

Цели браузера

Preset-env также представил функцию targets, позволяющую нам сообщить Babel, какие браузеры мы намерены поддерживать. Эта возможность основана на пакете browserslist для определения наших целей. Цели будут определять, какие функции нам нужно преобразовать или добавить вспомогательный код, поскольку больше кода означает больший пакет. Спецификация Browserslist позволяет нам определить наши цели несколькими способами. Вот некоторые возможные части запроса:

  • Установите минимальную поддерживаемую версию браузера, например Firefox > 20, или конкретную версию, например ie 6–8.
  • Last X versions каждого или конкретного браузера last 2 versions
  • Согласно общемировому проценту использования браузеров > 5%

Поскольку для меня было важно сохранить небольшой размер пакета, я сравнил различные проценты и узнал, что существует большая разница в размере пакета при добавлении поддержки старых браузеров, таких как IE10. Однако, как только вы поддерживаете старые браузеры с незначительной поддержкой ES6, размер пакета существенно не изменится, если вы решите поддерживать очень старые браузеры, такие как IE9, или браузеры с минимальным использованием 0,01%, как это сделал я. Установив пакет npm browserslist, вы можете запустить browserslist cmd в корне проекта, чтобы получить минимальную поддержку браузеров для разных типов браузеров.

Два варианта

На мой взгляд, есть два способа пойти с вашей конфигурацией:

Безопасный способ —настройте useBuiltIns на false, отключив любой добавленный код или плагины, оставив только встроенную транспиляцию ES6 в ES5. Таким образом вы убедитесь, что ваш код перенесен для поддержки старых браузеров, но не воспользуетесь преимуществами других исправлений браузеров, которые может предложить corejs. Я рекомендую этот конфиг для сторонних разработчиков скриптов. Вот пример:

Поддерживающий способ — настройте useBuiltIns на usage, добавляя плагины по мере использования и включая или исключая плагины в соответствии с вашими потребностями. Таким образом, вы можете поддерживать старые ошибки браузера, а также транспилировать свой код. Вы также будете контролировать влияние corejs на размер вашего пакета. Я рекомендую эту конфигурацию для разработчиков полного стека, работающих над собственным веб-приложением.

Заключение

Независимо от того, являетесь ли вы разработчиком полного стека или разработчиком javascript, важно знать конфигурацию вашего babel и предустановленного окружения, поскольку это может повлиять на поведение, а также на размер пакета и совместимость с браузером. Удачи!