Совместная работа с Babel

Rollup изначально ориентирован на работу с современным JavaScript и ESM-модулями, предполагая максимально статический анализ кода. Однако реальный промышленный код почти всегда требует транспиляции: поддержки старых браузеров, преобразования синтаксиса TypeScript/JSX, использования полифилов и трансформаций возможностей ECMAScript, которые ещё не везде реализованы.

Babel выступает промежуточным этапом трансформации кода, расширяя возможности Rollup за счёт преобразования синтаксиса до того уровня, который требуется целевой среде выполнения.

Архитектурная модель совместной работы

Связка Rollup и Babel строится на разделении ответственности:

  • Rollup отвечает за модульную систему, связывание зависимостей и tree-shaking
  • Babel отвечает за синтаксические трансформации исходного кода

Ключевая особенность заключается в том, что Babel не должен разрушать ESM-структуру до этапа анализа Rollup. Иначе теряется возможность эффективного удаления неиспользуемого кода.

Типовой порядок работы плагинов:

  1. Загрузка модулей Rollup
  2. Применение Babel-трансформаций к каждому модулю
  3. Построение графа зависимостей
  4. Tree-shaking
  5. Генерация бандла

Базовая интеграция через @rollup/plugin-babel

Основной способ подключения Babel в Rollup осуществляется через официальный плагин @rollup/plugin-babel.

Установка зависимостей:

npm install --save-dev @rollup/plugin-babel @babel/core

Минимальная конфигурация Rollup:

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

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

Проблема порядка применения трансформаций

Критически важный момент — порядок плагинов. Babel должен применяться до большинства трансформаций, но после чтения исходного кода.

Типичная ошибка — преобразование модулей до анализа Rollup. Это приводит к следующим проблемам:

  • ломается tree-shaking
  • теряется статическая структура import/export
  • ухудшается производительность сборки

Правильный подход — применять Babel как трансформатор синтаксиса, сохраняя ESM-структуру максимально долго.

Настройка Babel-конфига

Babel конфиг обычно задаётся через .babelrc или babel.config.json.

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

{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "targets": {
        "browsers": "> 0.25%, not dead"
      }
    }]
  ]
}

Ключевая настройка modules: false предотвращает преобразование ES-модулей в CommonJS. Это необходимо для корректной работы Rollup.

Взаимодействие с preset-env

@babel/preset-env управляет трансформацией синтаксиса в зависимости от целевых окружений.

В связке с Rollup важно учитывать:

  • отключение преобразования модулей
  • контроль полифилов
  • использование useBuiltIns при необходимости

Пример более продвинутой конфигурации:

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

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

Babel Helpers и проблема дублирования кода

При трансформациях Babel вставляет вспомогательные функции (helpers), например для классов, async/await, spread-операторов.

Без дополнительной настройки это приводит к:

  • дублированию helper-кода в каждом модуле
  • увеличению размера бандла
  • ухудшению кешируемости

Режим bundled

babel({
  babelHelpers: 'bundled'
})

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

Режим runtime

Более оптимальный вариант:

babel({
  babelHelpers: 'runtime',
  plugins: ['@babel/plugin-transform-runtime']
})

Этот режим выносит вспомогательные функции в отдельный runtime и переиспользует их.

Дополнительная зависимость:

npm install --save-dev @babel/plugin-transform-runtime
npm install @babel/runtime

Оптимизация tree-shaking при использовании Babel

Babel может негативно влиять на tree-shaking, если:

  • преобразует ESM в CommonJS
  • добавляет обёртки вокруг модулей
  • инлайнит лишние helper-функции

Чтобы сохранить эффективность Rollup:

  • всегда использовать modules: false
  • избегать лишних плагинов трансформации
  • минимизировать side effects

В package.json библиотеки важно явно указывать:

{
  "sideEffects": false
}

или перечислять исключения.

Обработка TypeScript и JSX через Babel

Babel часто используется как альтернатива tsc или @rollup/plugin-typescript.

Для этого добавляются пресеты:

npm install --save-dev @babel/preset-typescript @babel/preset-react

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

{
  "presets": [
    "@babel/preset-typescript",
    "@babel/preset-react"
  ]
}

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

  • не выполняет type-checking
  • только удаляет типы TypeScript
  • требует отдельного tsc --noEmit для проверки типов

Интеграция с Rollup плагинами

Типичный production pipeline включает несколько плагинов:

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.js',
    format: 'esm'
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({
      babelHelpers: 'runtime',
      exclude: 'node_modules/**'
    })
  ]
};

Здесь важен порядок:

  • node-resolve — поиск модулей
  • commonjs — конвертация CJS зависимостей
  • babel — финальная трансформация синтаксиса

Исключение node_modules из Babel

Обычно node_modules не проходят через Babel:

babel({
  exclude: 'node_modules/**'
})

Это ускоряет сборку и предотвращает конфликты с уже транспилированными библиотеками.

Однако иногда требуется обратное — транспиляция отдельных пакетов, если они поставляются в современном синтаксисе:

babel({
  include: ['src/**', 'node_modules/some-modern-lib/**']
})

Производительность сборки

Babel является одним из самых дорогих этапов в Rollup pipeline.

Основные факторы замедления:

  • большое количество модулей
  • глубокие деревья AST-трансформаций
  • использование множества плагинов Babel
  • отсутствие кэширования

Оптимизационные подходы:

  • ограничение include/exclude
  • минимизация preset-ов
  • использование @rollup/plugin-babel с cache
  • разделение dev/prod конфигураций

Разделение конфигураций dev и prod

Dev-конфигурация может быть более мягкой:

  • без полифилов
  • без runtime helper оптимизаций
  • с более быстрыми пресетами

Production-конфигурация ориентирована на размер и совместимость:

  • preset-env с target browsers
  • babelHelpers: runtime
  • включён core-js

Пример разделения:

const isProd = !process.env.ROLLUP_WATCH;

export default {
  plugins: [
    babel({
      babelHelpers: isProd ? 'runtime' : 'bundled'
    })
  ]
};

Влияние Babel на ESM-экосистему Rollup

Rollup получает максимальную выгоду, когда исходный код остаётся в формате ESM до финальной стадии. Babel, в свою очередь, должен работать как «слой синтаксической адаптации», не вмешиваясь в модульную структуру.

Нарушение этого принципа приводит к:

  • деградации tree-shaking
  • увеличению размера бандла
  • потере предсказуемости зависимостей

Правильная интеграция сохраняет преимущества обеих систем: статический анализ Rollup и универсальность Babel.

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

Часто встречающиеся проблемы:

  • включён modules: 'commonjs' в Babel preset-env
  • отсутствие exclude: node_modules
  • одновременное использование TypeScript plugin и Babel без разграничения зон ответственности
  • неправильный порядок commonjs и babel

Каждая из этих ошибок влияет либо на корректность сборки, либо на её эффективность.

Использование Babel только для части кода

В сложных проектах часто применяется частичная трансформация:

  • библиотечный код проходит через Babel
  • вспомогательные скрипты — нет
  • тестовый код обрабатывается отдельно

Это достигается через include и exclude:

babel({
  include: 'src/**',
  exclude: ['node_modules/**', 'src/vendor/**']
})

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

Совместимость с современными фичами ECMAScript

Babel используется для поддержки:

  • optional chaining
  • nullish coalescing
  • class properties
  • decorators (в зависимости от версии)
  • async/await в старых окружениях

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

Итоговая модель взаимодействия слоёв

Логическая цепочка сборки в связке Rollup и Babel выглядит как последовательность независимых этапов:

  • исходный ESM-код
  • Babel AST трансформации
  • анализ графа модулей Rollup
  • tree-shaking
  • бандлинг
  • генерация итогового файла

Эта модель позволяет сохранять баланс между современным синтаксисом разработки и требованиями целевых сред выполнения.