Постепенная миграция проекта с Webpack на Rollup

Миграция с Webpack на Rollup представляет собой процесс постепенного переноса сборочной инфраструктуры с универсального, ориентированного на приложения бандлера к более специализированному инструменту, оптимизированному для библиотек и модульных пакетов. Rollup строится вокруг концепции максимально чистого ESM-графа и статического анализа импортов, что требует пересмотра архитектуры сборки, разделения ответственности между инструментами и точной настройки выходных форматов.


Первым шагом становится разбор текущей Webpack-сборки. Webpack часто выполняет несколько задач одновременно:

  • транспиляция кода через Babel или ts-loader
  • обработка CSS и ассетов
  • code splitting
  • полифиллы и runtime-обвязка
  • tree-shaking (частично)
  • HMR и dev-сервер

Rollup не предназначен для всех этих задач в одном инструменте, поэтому необходимо выделить зоны ответственности.

Типичная Webpack-конфигурация содержит:

  • entry points (часто несколько)
  • output с bundle и chunking
  • loaders для разных типов файлов
  • plugins (DefinePlugin, ProvidePlugin и др.)
  • optimization (splitChunks, runtimeChunk)

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


Разделение сборки: библиотека vs приложение

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

Рекомендуемая модель:

  • Rollup → сборка библиотеки (ESM/CJS/UMD)
  • Webpack или Vite → демо-приложение (опционально)
  • отдельные конфигурации для тестов и документации

Такое разделение снижает сложность миграции и позволяет постепенно заменять части Webpack.


Подготовка проекта к ESM-ориентированной сборке

Rollup опирается на ES-модули как на основной формат входного кода. Поэтому важно устранить препятствия:

1. Замена CommonJS зависимостей

Webpack спокойно обрабатывает CommonJS, но Rollup требует плагины:

  • @rollup/plugin-commonjs

Однако лучше постепенно переходить на ESM-версии зависимостей.

2. Устранение динамических require

Конструкции вида:

require(someVariable)

затрудняют статический анализ и могут ломать tree-shaking.

3. Упрощение side effects

В package.json важно определить:

{
  "sideEffects": false
}

или перечислить исключения (CSS, polyfills).


Базовая конфигурация Rollup

Минимальная конфигурация для замены Webpack-бандла:

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

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'es'
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs'
    }
  ],
  plugins: [
    resolve(),
    commonjs(),
    babel({ babelHelpers: 'bundled' })
  ]
};

Webpack-аналоги:

  • entry → input
  • output → output
  • loaders → plugins
  • module.rules → plugin pipeline

Постепенная замена Webpack в монорепозитории

Если проект содержит Webpack и несколько пакетов, миграция выполняется по слоям.

Этап 1: параллельная сборка

Webpack продолжает работать как основной билд, Rollup добавляется как дополнительный:

webpack.config.js → приложение
rollup.config.js → библиотека

Цель этапа:

  • проверить корректность output
  • сравнить размеры бандлов
  • убедиться в совместимости API

Этап 2: перенос экспортируемых модулей

Начинается с публичного API библиотеки.

Структура:

src/
  index.js   → главный экспорт
  utils/
  core/

Rollup должен собирать только публичный entry point, без внутренних Webpack-расширений.


Этап 3: отказ от Webpack-специфичных возможностей

Webpack часто скрыто влияет на код через:

  • DefinePlugin (глобальные переменные)
  • alias
  • polyfill injection
  • require.context

В Rollup это заменяется явно:

Alias

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

DefinePlugin

Используется:

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

Обработка стилей и ассетов

Webpack позволяет импортировать CSS и изображения напрямую. Rollup требует явных плагинов.

CSS

import postcss from 'rollup-plugin-postcss';

Варианты:

  • инлайн CSS в JS
  • отдельный CSS файл
  • минификация через PostCSS

Assets

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

Используется для:

  • изображений
  • шрифтов
  • медиа

Разрешение зависимостей и tree-shaking

Одно из ключевых отличий Rollup — агрессивный tree-shaking.

Webpack может оставлять:

  • неиспользуемые экспорты
  • побочные эффекты модулей

Rollup строит более строгий граф:

  • анализ import/export
  • удаление dead code на уровне AST

Важно:

  • использовать ESM-зависимости
  • избегать CommonJS-библиотек без необходимости
  • контролировать sideEffects

Переход от code splitting Webpack к Rollup output chunks

Webpack использует splitChunks автоматически, Rollup требует явной настройки.

output: {
  dir: 'dist',
  format: 'esm',
  chunkFileNames: '[name]-[hash].js'
}

Динамические import() создают чанки:

import('./module.js').then(m => m.run());

В Rollup это основной механизм разделения кода.


Замена dev-сервера и HMR

Webpack Dev Server не имеет прямого аналога в Rollup.

Обычно используется связка:

  • Rollup + rollup-plugin-serve
  • Rollup + rollup-plugin-livereload

Однако в миграции важно разделить:

  • билд библиотеки (Rollup)
  • dev-среду приложения (Vite или Webpack)

Rollup редко используется как полноценный dev-server для UI-приложений.


Обработка TypeScript при миграции

Если Webpack использует ts-loader, в Rollup переходят на:

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

или

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

Выбор зависит от архитектуры:

  • TypeScript plugin → быстрее и проще
  • Babel → больше контроля и совместимости

Работа с CommonJS-переходным слоем

Во время миграции часто остаются CommonJS-модули.

Порядок обработки:

  1. @rollup/plugin-node-resolve
  2. @rollup/plugin-commonjs
  3. трансформационные плагины

Важно учитывать:

  • named exports из CJS могут быть нестабильны
  • возможны конфликты с default export

Обработка глобальных переменных и окружений

Webpack часто использует:

process.env.NODE_ENV

В Rollup это заменяется через:

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

Пример:

replace({
  preventAssignment: true,
  'process.env.NODE_ENV': JSON.stringify('production')
});

Переход от Webpack alias к Rollup alias

Webpack:

resolve: {
  alias: {
    '@core': path.resolve(__dirname, 'src/core')
  }
}

Rollup:

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

Это важный шаг, поскольку многие проекты сильно зависят от alias-структуры.


Управление выходными форматами

Webpack обычно генерирует один bundle. Rollup позволяет гибко формировать несколько артефактов:

  • ESM (modern bundlers)
  • CJS (Node.js)
  • UMD/IIFE (браузер)

Типичная стратегия миграции:

  1. сначала ESM
  2. затем CJS
  3. затем UMD (если нужен legacy)

Проверка эквивалентности сборки

После переноса необходимо сравнить:

  • размер бандлов
  • структуру экспорта
  • tree-shaking эффект
  • поведение runtime

Инструменты анализа:

  • rollup-plugin-visualizer
  • source-map explorer

Типичные проблемы при миграции

1. Различие поведения import/export

Webpack иногда интерпретирует CJS как ESM, Rollup — строже.

2. Побочные эффекты модулей

Rollup может удалить код, который Webpack оставлял.

3. Несовместимость плагинов

Webpack loader ≠ Rollup plugin. Прямая замена невозможна.

4. require.context

Полностью отсутствует в Rollup, заменяется явным импортом или glob-подходами.


Финальная стабилизация сборки

После переноса всех модулей важно закрепить конфигурацию:

  • фиксировать версии плагинов
  • включить строгий sideEffects контроль
  • минимизировать количество трансформаций
  • избегать смешивания CommonJS и ESM без необходимости

Rollup-сборка должна становиться максимально предсказуемой и линейной, без скрытых трансформаций, характерных для Webpack.