Встраивание полифилов

Встраивание полифилов в сборку Rollup является ключевым этапом при подготовке JavaScript-библиотек и приложений к выполнению в средах с различным уровнем поддержки стандартов ECMAScript и Node.js API. Основная задача полифилов заключается в эмуляции отсутствующих возможностей среды исполнения без изменения исходного кода, при этом Rollup, как ES-модульный бандлер с агрессивной оптимизацией, требует особого подхода к их подключению.

Особенности работы Rollup с полифилами

Rollup ориентирован на статический анализ модулей и удаление неиспользуемого кода (tree-shaking). Это напрямую влияет на стратегию подключения полифилов:

  • динамические require не анализируются корректно без дополнительных плагинов;
  • глобальные сайд-эффекты могут быть ошибочно удалены;
  • ESM-полифилы предпочтительнее CommonJS;
  • дублирование полифилов часто возникает при некорректной настройке зависимостей.

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

Базовые стратегии подключения полифилов

Явный импорт в входной точке

Наиболее прозрачный подход заключается в явном подключении необходимых полифилов в entry-файле:

import 'core-js/actual/array/from';
import 'core-js/actual/promise';
import 'regenerator-runtime/runtime';

Данная стратегия обеспечивает полный контроль над набором полифилов и их версионированием. При этом Rollup способен учитывать такие импорты при tree-shaking, если полифилы реализованы как ESM.

Использование core-js модульной структуры

Современные версии core-js предоставляют модульный импорт:

  • core-js/es/... — полные полифилы;
  • core-js/actual/... — актуальные реализации;
  • core-js/stable/... — стабильные API.

Такой подход минимизирует размер бандла, так как подключаются только используемые части.

Полифилы для окружения Node.js API

При сборке библиотек для браузера часто требуется эмуляция Node.js модулей:

  • path
  • buffer
  • stream
  • crypto
  • process

Для этого используется плагин:

@rollup/plugin-polyfill-node

Он предоставляет ESM-совместимые реализации Node.js стандартной библиотеки.

Конфигурация:

import nodePolyfills from '@rollup/plugin-polyfill-node';

export default {
  input: 'src/index.js',
  output: {
    format: 'esm',
    dir: 'dist'
  },
  plugins: [
    nodePolyfills()
  ]
};

Особенность данного подхода заключается в том, что Rollup не пытается самостоятельно интерпретировать Node.js API, а полностью делегирует это плагину.

Автоматическая подстановка глобальных переменных

Некоторые библиотеки ожидают наличие глобальных переменных:

  • process
  • Buffer
  • global

Для их внедрения используется @rollup/plugin-inject:

import inject from '@rollup/plugin-inject';

export default {
  plugins: [
    inject({
      process: 'process/browser',
      Buffer: ['buffer', 'Buffer']
    })
  ]
};

Этот механизм подменяет идентификаторы на соответствующие импорты, не изменяя исходный код модулей.

Совмещение с CommonJS-модулями

Многие полифилы поставляются в формате CommonJS. Для корректной интеграции требуется:

@rollup/plugin-commonjs
@rollup/plugin-node-resolve

Конфигурация:

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  plugins: [
    resolve({
      browser: true,
      preferBuiltins: false
    }),
    commonjs()
  ]
};

Ключевой момент заключается в том, что без commonjs многие полифилы не будут корректно интерпретированы Rollup.

Управление дублированием полифилов

Дублирование возникает в нескольких случаях:

  • разные пакеты импортируют разные версии core-js;
  • одновременно используется ручной импорт и Babel;
  • подключены частичные и полные сборки полифилов.

Для контроля используются следующие техники:

Единая точка подключения

Создание отдельного модуля:

// polyfills.js
import 'core-js/stable';
import 'regenerator-runtime/runtime';

И импорт его только один раз в entry.

Настройка sideEffects

В package.json:

{
  "sideEffects": [
    "polyfills.js"
  ]
}

Это предотвращает удаление полифилов tree-shaking-ом.

Взаимодействие с Babel

Babel часто используется совместно с Rollup для автоматической подстановки полифилов через @babel/preset-env и useBuiltIns.

Режимы:

  • usage — импортирует только используемые полифилы;
  • entry — требует явного подключения core-js в entry;
  • false — отключает автоматические полифилы.

Конфигурация:

presets: [
  ['@babel/preset-env', {
    useBuiltIns: 'usage',
    corejs: 3
  }]
]

Важно учитывать, что Babel может конфликтовать с ручной стратегией подключения полифилов в Rollup, приводя к дублированию кода.

Tree-shaking и побочные эффекты полифилов

Rollup удаляет неиспользуемые экспорты, но полифилы часто работают через side effects:

  • модификация глобальных объектов;
  • регистрация новых методов прототипов;
  • патчинг встроенных функций.

Для сохранения таких модификаций необходимо учитывать:

export default {
  treeshake: {
    moduleSideEffects: true
  }
};

Или точечная настройка для отдельных модулей.

Разделение браузерных и серверных сборок

При универсальных библиотеках часто применяется dual build:

  • browser build — включает полифилы;
  • node build — полифилы отключены.

Пример условной конфигурации:

const isBrowser = process.env.BUILD === 'browser';

export default {
  plugins: [
    isBrowser && nodePolyfills()
  ].filter(Boolean)
};

Такой подход предотвращает избыточное увеличение размера серверной сборки.

Полифилы современных ECMAScript возможностей

Наиболее часто подключаемые возможности:

  • Promise
  • Symbol
  • Array.from
  • Object.assign
  • String.prototype.includes

В современных проектах они импортируются точечно:

import 'core-js/features/promise';
import 'core-js/features/array/from';

Такой подход позволяет минимизировать итоговый бандл и избежать загрузки ненужных полифилов.

Особенности работы с регенераторами

Асинхронные функции и генераторы требуют:

import 'regenerator-runtime/runtime';

При использовании Babel с @babel/plugin-transform-runtime возможно исключение дублирования runtime-кода.

Типичные ошибки интеграции

  • подключение полифилов после основной логики;
  • использование CommonJS без commonjs плагина Rollup;
  • отсутствие node-resolve, приводящее к пропущенным зависимостям;
  • одновременное использование Babel useBuiltIns: usage и ручных импортов;
  • включение Node.js полифилов в серверную сборку.

Каждая из этих ошибок приводит либо к увеличению бандла, либо к некорректному поведению в рантайме.

Оптимизация размера бандла

Основные методы уменьшения размера:

  • точечные импорты из core-js;
  • отключение ненужных Node.js полифилов;
  • разделение сборок по платформам;
  • исключение дублирующих runtime-библиотек;
  • контроль side effects.

Эффективная стратегия всегда строится вокруг минимального набора полифилов, строго соответствующего целевой среде исполнения.