Полифилы
JavaScript – динамично развивающийся язык программирования. Регулярно появляются предложения о добавлении в JS новых возможностей, они анализируются, и, если предложения одобряются, то описания новых возможностей языка переносятся в черновик https://tc39.github.io/ecma262/, а затем публикуются в спецификации.
Разработчики JavaScript-движков сами решают, какие предложения реализовывать в первую очередь. Они могут заранее добавить в браузеры поддержку функций, которые всё ещё находятся в черновике, и отложить разработку функций, которые уже перенесены в спецификацию, потому что они менее интересны разработчикам или более сложные в реализации.
Таким образом, довольно часто реализуется только часть стандарта.
Можно проверить текущее состояние поддержки различных возможностей JavaScript на странице https://kangax.github.io/compat-table/es6/ (нам ещё предстоит изучить многое из этого списка).
Babel
Когда мы используем современные возможности JavaScript, некоторые движки могут не поддерживать их. Как было сказано выше, не везде реализованы все функции.
И тут приходит на помощь Babel.
Babel – это транспилер. Он переписывает современный JavaScript-код в предыдущий стандарт.
На самом деле, есть две части Babel:
Во-первых, транспилер, который переписывает код. Разработчик запускает Babel на своём компьютере. Он переписывает код в старый стандарт. И после этого код отправляется на сайт. Современные сборщики проектов, такие как webpack или brunch, предоставляют возможность запускать транспилер автоматически после каждого изменения кода, что позволяет экономить время.
Новые возможности языка могут включать встроенные функции и синтаксические конструкции. Транспилер переписывает код, преобразовывая новые синтаксические конструкции в старые. Но что касается новых встроенных функций, нам нужно их как-то реализовать. JavaScript является высокодинамичным языком, скрипты могут добавлять/изменять любые функции, чтобы они вели себя в соответствии с современным стандартом.
Термин «полифил» означает, что скрипт «заполняет» пробелы и добавляет современные функции.
Два интересных хранилища полифилов:
-
поддерживает много функций, можно подключать только нужные. – сервис, который автоматически создаёт скрипт с полифилом в зависимости от необходимых функций и браузера пользователя.
Таким образом, чтобы современные функции поддерживались в старых движках, нам надо установить транспилер и добавить полифил.
Примеры в учебнике
Большинство примеров можно запустить «на месте», как этот:
Примеры, в которых используются современные возможности JS, будут работать, если ваш браузер их поддерживает.
Google Chrome обычно поддерживает современные функции, можно запускать новейшие примеры без каких-либо транспилеров, но и другие современные браузеры тоже хорошо работают.
@babel/polyfill
As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features):
If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0 , you must also load the regenerator runtime package. It is automatically loaded when using @babel/preset-env ‘s useBuiltIns: "usage" option or @babel/plugin-transform-runtime .
Babel includes a polyfill that includes a custom regenerator runtime and core-js.
This will emulate a full ES2015+ environment (no < Stage 4 proposals) and is intended to be used in an application rather than a library/tool. (this polyfill is automatically loaded when using babel-node ).
This means you can use new built-ins like Promise or WeakMap , static methods like Array.from or Object.assign , instance methods like Array.prototype.includes , and generator functions (provided you use the regenerator plugin). The polyfill adds to the global scope as well as native prototypes like String in order to do this.
Because this is a polyfill (which will run before your source code), we need it to be a dependency , not a devDependency
The polyfill is provided as a convenience but you should use it with @babel/preset-env and the useBuiltIns option so that it doesn’t include the whole polyfill which isn’t always needed. Otherwise, we would recommend you import the individual polyfills manually.
If you need to use a proposal that is not Stage 4, @babel/polyfill will not automatically import those for you. You will have to import those from another polyfill like core-js individually. We may work towards including this as separate files in @babel/polyfill soon.
Usage in Node / Browserify / Webpack
To include the polyfill you need to require it at the top of the entry point to your application.
Make sure it is called before all other code/require statements!
If you are using ES6’s import syntax in your application’s entry point, you should instead import the polyfill at the top of the entry point to ensure the polyfills are loaded first:
With webpack, there are multiple ways to include the polyfills:
If useBuiltIns: ‘usage’ is specified in .babelrc then do not include @babel/polyfill in either webpack.config.js entry array nor source. Note, @babel/polyfill still needs to be installed.
If useBuiltIns: ‘entry’ is specified in .babelrc then include @babel/polyfill at the top of the entry point to your application via require or import as discussed above.
If useBuiltIns key is not specified or it is explicitly set with useBuiltIns: false in your .babelrc, add @babel/polyfill directly to the entry array in your webpack.config.js .
- If @babel/preset-env is not used then add @babel/polyfill to webpack entry array as discussed above. It can still be added at the top of the entry point to application via import or require , but this is not recommended.
We do not recommend that you import the whole polyfill directly: either try the useBuiltIns options or import only the polyfills you need manually (either from this package or somewhere else).
Usage in Browser
Available from the dist/polyfill.js file within a @babel/polyfill npm release. This needs to be included before all your compiled Babel code. You can either prepend it to your compiled code or include it in a <script> before it.
NOTE: Do not require this via browserify etc, use @babel/polyfill .
Babel + core-js + IE = .
Сегодня будет рассказ про фронтендерский зоопарк. Начну издалека.
Если вы фронт, то вы знаете, что наш код читается многими браузерами. Вы также знаете, что разные браузеры реализуют разные части стандарта языка, а одинаковые части реализуют по-разному. Одно время такая разница в прочтении превращала разработку в ад. Но довольно быстро появились инструменты, “уравнивающие” ваш код таким образом, чтобы во всех браузерах он читался одинаково.
Мы во ВКонтакте местами поддерживаем IE 11, поэтому для нас вопрос кроссбраузерности стоит остро.
Полифиллы
В сети можно найти бесконечное количество полифиллов для почти всего, что придумано в стандарте: Array.prototype.map , Object.keys , Map , Promise , etc.
Полифилл — это кусочек рантайм-кода, который реализует недостающий функционал так, чтобы он во всех браузерах работал одинаково.
Представим, что у нас есть такой код:
И нам не повезло так, что приходится поддерживать пользователей на IE.
Сейчас самая популярная библиотека полифиллов — это core-js . Чтобы использовать её, что называется, втупую, можно просто сделать импорт всего пакета в самом начале вашего модуля:
core-js весит очень много, поэтому умнее будет импортнуть только то, что вы планируете использовать:
Вроде прикольно. Но далеко от совершенства.
Ваше приложение усложняется, команда растёт и в какой-то момент вы начинаете испытывать дискомфорт от того, что на ревью кто-то в очередной раз не заметил, что в код завезли новый модный метод массива, забыв дописать импорт полифилла. Итог — АНДЕЙФАЙНД ИЗ НОТ Э ФАНКШН.
Транспиляторы
Machines must suffer (c)
Омар ХайямАндрей Ситник
Переложить заботу о стабильности кода на машины — отличная затея. Почти всегда. Почти. Babel — прекрасный инструмент. Он позволяет нам писать на современном стандарте языка, делая за нас все необходимые преобразования в ту версию, которую мы ему укажем.
Даже не так. Мы можем сказать бабелю: “мы поддерживаем вот такой набор браузеров, список найдёшь в .browserslistrc , всё, давай!”
Даже не так. Бабель сам посмотрит в этот файл, если он есть в корне проекта.
Причём в первую очередь бабель решает не задачу поиска полифилла для всяких модных методов. Его основная фича — ТРАНСПИЛЯЦИЯ кода. Это когда вы пишете ваш любимый < . user, last_name: 'Doe' >(оператор, появившийся в es2015), а эта хрень работает в IE 11, который вышел в 2013.
Как так получается? С помощью транспиляции. Всё, что нам нужно сделать — это поставить пару пакетиков:
И создать пару конфигов:
Допустим, наш файл выглядит так:
При запуске npx babel script.js в консоли мы увидим следующее:
То есть Babel понял, что в нашем списке поддерживаемых браузеров затесался отсталый, который не знает о таком операторе. Поэтому он его ТРАНСПИЛИРУЕТ. В транспилированном коде уже нет спреда, есть какой-то свой спред, собранный из говна из es5.
Ещё раз
Транспиляция — это преобразование одной синтаксической конструкции в другую на этапе билда кода.
Полифилл — кусочек рантайм-кода, который докидывает недостающие методы, функции, конструкторы и т.д.
С помощью полифилла нельзя сделать так, чтобы ?? завёлся в IE 11. Потому что это незнакомая синтаксическая конструкция, которую никаким рантайм-кодом не сделать интерпретируемой. Её можно только заменить на другую перед тем, как она попадёт в браузер. Этим и занимается Babel.
Как это работает?
Бабель состоит из плагинов и пресетов.
Каждый плагин отвечает за преобразование конкретной конструкции языка. Есть, например, плагин для преобразования JSX в React.createElement . Или преобразователь любимого всеми ?? в обычный тернарник.
Пресеты — это тупо набор плагинов, объединенных по какому-то признаку. Есть, например, пресет для реакта. Или для тайпскрипта.
Но самый прикольный пресет — это тот, который мы установили.
@babel/preset-env — это умный пресет, который подключает только те плагины, которые нужны, основываясь на браузерах, которые поддерживает конкретный проект. Собсно, для этого мы и положили файлик .browserslistrc рядом с .babelrc .
Давайте для эксперимента уберем IE 11 из браузерлиста и снова запустим бабель.
Смотрите, что получается:
В коде пресета есть знание о том, что последний хром поддерживает спред оператор. Поэтому, раз мы хотим поддерживать только этот браузер, нет никакого смысла транспилировать этот код.
Это знание получается из пакета caniuse-lite , который находится в зависимостях browserslist , который находится в зависимостях у @babel/preset-env .
Примечание @rock: @babel/preset-env берёт данные не из caniuse-lite, а из собственной базы @babel/compat-data
Круто? Я считаю, что круто.
Проблемы
С использованием современных конструкций языка вроде разобрались. Бабель будет решать проблему несовместимости за нас.
Но если мы напишем нечто такое:
и запустим бабель, то мы увидим, что Object.values остался нетронутым:
Хотя этот метод появился в es2015 и IE 11 его не поддерживает. То есть этот код в IE упадёт с ошибкой! Как же нам быть?
Babel + core-js = ❤️
В @babel/preset-env есть решение этой проблемы. Давайте чутка подпилим наш конфиг:
Если переводить на русский, то написано следующее: “дорогой пресет энв, если ты в коде заметишь современные конструкции, которые можно заполифиллить, то так пожалуйста и сделай. Используй для этого core-js 3-й версии. Спасибо”
Что за дьявольщина, спросите вы? Я вчера задавался тем же вопросом.
Но давайте успокоимся и хладнокровно посмотрим на то, что натворил бабель.
Мы видим, что теперь для Object.values был импортирован полифилл из core-js . То есть этот код будет работать.
Точнее так. Этот код будет работать, когда мы все эти реквайры соберем в один большой JS-файл. Об этом чуть позже.
Но какого чёрта он добавил столько казалось бы лишних полифиллов? Например, полифилл для Object.keys . Это метод из стандарта es5. На сайте caniuse.com написано, что IE 11 его поддерживает. Пресет сошёл с ума?
На самом деле нет.
Дело в том, что core-js , который мы присобачили к пресет энву, имеет собственный мап, в котором чётко описано, с каких версий та и или иная фича поддержана в браузере. И прикол в том, что хоть Object.keys и работает в IE, с точки зрения создателей core-js , он работает там неправильно. Об этом мне поведал мейнтенер бабеля.
То есть сам пресет энв берет инфу о поддержке той или иной конструкции из caniuse-lite , а core-js — из собственного мапа. Веселуха!
Измерения
Ща мы будем собирать этот код вебпаком и смотреть, как меняется размер итогового файла в зависимости от настроек бабеля и наших браузерных предпочтений.
Конфиг вебпака as simple as possible:
После минимизации такой код весит 64 байта. Запускаем npx webpack
Без автополифиллов
То есть вебпак сделал x2, обмазав код своим бойлерплейтом. Терпимо.
Желание поддерживать IE 11 увеличивает размер бандла в 10 раз. Стерпим и это.
С автополифиллами
134 bytes. Логично, так как все перечисленные браузеры ни в каких полифиллах не нуждаются.
22.7 KiB (x170). Ну вы поняли. Самая мерзость в том, что даже дефолтные настройки браузерлиста генерят такой огромный кусок JS.
Визуализация

Результат выполнения команды npx webpack —analyze
Наш index.js (справа) весит 2 KB, а полифиллы для него — больше 20. И всё ради одного браузера.
Итоги
При использовании @babel/preset-env обязательно позаботьтесь о том, чтобы указать список интересующих лично вас браузеров. Иначе ваши пользователи будут скачивать огромную кучу скорее всего ненужного кода.