Полифилы (polyfills) — это реализации возможностей JavaScript, отсутствующих в старых браузерах или окружениях выполнения. Их задача заключается в обеспечении совместимости современного кода с платформами, не поддерживающими определённые API, синтаксис или встроенные объекты.
Полифил может:
Наиболее распространённые примеры:
Promise
fetch
Array.prototype.flat
Object.fromEntries
URL
Symbol
Map
Set
Webpack напрямую не создаёт полифилы, однако тесно интегрируется с инструментами их автоматического и ручного подключения.
Очень важно различать:
| Инструмент | Назначение |
|---|---|
| Babel | Преобразование синтаксиса |
| Полифилы | Реализация отсутствующих API |
Пример:
const fn = async () => {
await fetch('/api');
};
Babel способен преобразовать async/await в совместимый
код:
function fn() {
return regeneratorRuntime.async(function fn$() {
// ...
});
}
Но Babel не реализует:
fetch
Promise
URL
Map
Set
Если браузер не поддерживает эти API, требуется полифил.
Существует два базовых подхода:
| Подход | Описание |
|---|---|
| Автоматический | Полифилы подключаются автоматически на основе кода и целевых браузеров |
| Ручной | Разработчик самостоятельно импортирует необходимые полифилы |
Большинство современных инструментов используют
Browserslist.
Пример:
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
Webpack сам по себе не анализирует совместимость API, но:
используют Browserslist для определения необходимых преобразований.
core-js — крупнейшая библиотека полифилов для
JavaScript.
Поддерживает:
Установка:
npm install core-js
Для Babel:
npm install core-js regenerator-runtime
Основной механизм автоматического подключения:
npm install @babel/preset-env babel-loader --save-dev
Конфигурация:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3
}
]
]
}
}
}
]
}
};
Существует три режима.
Полифилы не подключаются автоматически.
{
useBuiltIns: false
}
Только синтаксические преобразования.
Babel анализирует импортированный общий полифил и заменяет его набором необходимых модулей.
Точка входа:
import 'core-js';
import 'regenerator-runtime/runtime';
Babel преобразует это в набор конкретных импортов.
Наиболее популярный режим.
Babel анализирует используемые API и подключает только нужные полифилы.
Исходный код:
const arr = [1, 2, 3];
arr.flat();
После обработки:
import "core-js/modules/es.array.flat.js";
import "core-js/modules/es.array.unscopables.flat.js";
Преимущества:
Недостатки:
Для:
async/await
generators
необходим runtime:
npm install regenerator-runtime
При использовании:
useBuiltIns: 'usage'
Babel подключает runtime автоматически.
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: '> 0.5%, not dead',
useBuiltIns: 'usage',
corejs: 3
}
]
]
}
}
}
]
}
};
Babel:
Например:
Promise.any()
может привести к подключению:
core-js/modules/es.promise.any.js
core-js разбит на модули.
Это позволяет:
Однако глобальные полифилы часто считаются side effects, поэтому полностью удалить их невозможно.
Во многих проектах предпочтителен ручной контроль.
import 'core-js/features/promise';
import 'core-js/features/array/flat';
import 'core-js/modules/es.promise';
import 'core-js/modules/es.array.flat';
import 'core-js/stable';
Подключает практически весь ECMAScript polyfill layer.
Недостатки:
Некоторые проекты избегают глобального загрязнения среды.
Используется:
npm install @babel/plugin-transform-runtime
npm install @babel/runtime
Конфигурация:
{
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3
}
]
]
}
Добавляет полифилы в глобальную область:
Promise
Array.prototype.flat
становятся глобально доступными.
Не загрязняет глобальную область.
Babel импортирует helper-функции локально:
import _Promise from "@babel/runtime-corejs3/core-js/promise";
Подходит для:
Библиотеки обычно не должны:
Поэтому библиотекам рекомендуется:
@babel/plugin-transform-runtime
вместо:
useBuiltIns
Webpack 4 автоматически добавлял polyfills для Node.js-модулей:
Buffer
process
crypto
stream
path
Webpack 5 отказался от автоматических полифилов.
Теперь требуется ручная настройка.
Пример ошибки:
Module not found: Error: Can't resolve 'crypto'
Решение:
npm install crypto-browserify
module.exports = {
resolve: {
fallback: {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
buffer: require.resolve('buffer')
}
}
};
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser'
})
]
};
Причины:
Иногда полифил подключается только при необходимости.
Пример:
if (!window.Promise) {
await import('core-js/features/promise');
}
Можно загружать полифилы динамически:
async function loadPolyfills() {
if (!('IntersectionObserver' in window)) {
await import('intersection-observer');
}
}
Преимущества:
Сервис автоматической выдачи полифилов:
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
Сервис определяет браузер и отправляет только необходимые полифилы.
Проблемы:
Babel не полифилит DOM API.
Например:
IntersectionObserver
ResizeObserver
fetch
AbortController
требуют отдельных библиотек.
Популярный вариант:
npm install whatwg-fetch
Импорт:
import 'whatwg-fetch';
npm install abortcontroller-polyfill
npm install intersection-observer
Некоторые старые браузеры не поддерживают:
URL
URLSearchParams
Полифил:
npm install core-js
или специализированные библиотеки.
Старые браузеры:
не поддерживают Promise.
Полифил:
import 'core-js/features/promise';
Symbol крайне сложен для полной эмуляции.
Некоторые возможности невозможно воспроизвести полностью:
Проблемы чрезмерного количества полифилов:
Эффективные подходы:
useBuiltIns: 'usage'
Избыточная поддержка старых браузеров резко увеличивает размер bundle.
Современный подход:
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>
Можно собирать:
| Bundle | Назначение |
|---|---|
| modern | Современные браузеры |
| legacy | Старые браузеры |
Современные браузеры получают минимальный bundle без лишних полифилов.
Позволяет увидеть подключаемые полифилы.
{
debug: true
}
Пример вывода:
Using polyfills:
es.array.flat
es.promise
Крайне важно явно указывать версию:
corejs: 3
Без указания версии Babel может работать некорректно.
Некоторые полифилы:
Особенно опасны:
Array.prototype
Object.prototype
String.prototype
Полифилы почти всегда имеют side effects.
Например:
Array.prototype.flat = function () {}
Это глобальное изменение среды выполнения.
Часто используется feature detection:
if (!Array.prototype.flat) {
// polyfill
}
или:
if (!window.fetch) {
// polyfill
}
TypeScript не добавляет полифилы.
Даже если код успешно компилируется:
Promise.any()
это не означает наличие поддержки в браузере.
tslib содержит helper-функции TypeScript, но не полифилы
ECMAScript API.
SWC поддерживает:
Но экосистема автоматических полифилов у SWC менее зрелая по сравнению с Babel.
Часто используется комбинированный подход:
SWC + core-js
Esbuild не занимается автоматическим полифиллингом API.
Он:
Но не добавляет:
Promise
fetch
Map
Set
Наиболее проблемные браузеры:
| Браузер | Особенности |
|---|---|
| Internet Explorer 11 | отсутствие Promise, fetch, Map |
| Старые Safari | частичная поддержка ES features |
| Android 4 WebView | множество ограничений |
| Старые Samsung Internet | неполная реализация API |
Используются инструменты:
npx browserslist
и:
npx core-js-compat
Полифилы могут занимать значительную часть сборки.
Полезные инструменты:
webpack-bundle-analyzer
source-map-explorer
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: '>0.25%, not dead',
useBuiltIns: 'usage',
corejs: 3,
bugfixes: true
}
]
]
};
Подход хорошо подходит для:
Ручной контроль часто выбирается для:
Во многих крупных проектах используется смешанная стратегия:
preset-env для ECMAScript;Такой подход обеспечивает: