Кеширование через поле cache

Кеширование в Rollup реализовано через передачу ранее собранного состояния бандла в последующие сборки. Это состояние передаётся через поле cache в объекте входных опций и позволяет существенно ускорить повторные сборки за счёт переиспользования уже построенного графа модулей и результатов их анализа.

Основная задача кеширования в Rollup — минимизация повторной работы при инкрементальных сборках. В процессе первой сборки Rollup:

  • строит граф зависимостей модулей;
  • парсит исходный код и формирует AST;
  • выполняет плагины на каждом этапе трансформации;
  • вычисляет связи между модулями, экспортами и импортами;
  • проводит tree-shaking и оптимизацию.

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

Как работает cache в Rollup

При первом вызове rollup.rollup() кеш отсутствует, и создаётся полный внутренний state сборщика. После завершения сборки Rollup формирует объект бандла, внутри которого присутствует поле cache.

Этот объект содержит:

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

При следующем вызове rollup.rollup({ cache }) этот объект передаётся обратно в сборщик, что позволяет:

  • пропустить повторный парсинг неизменённых модулей;
  • переиспользовать результаты AST;
  • избежать повторного вызова многих хуков плагинов;
  • ускорить пересборку графа зависимостей.

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

Формат объекта cache

Объект cache не является стабильным публичным API в смысле строгой структуры, но концептуально он включает:

  • modules — список модулей с их состоянием;
  • moduleIds — идентификаторы модулей в графе;
  • pluginCache — кеш, используемый плагинами через их cacheKey;
  • watchFiles — список файлов, участвующих в сборке;
  • внутренние структуры резолвера и трансформеров.

Каждый модуль в кеше содержит:

  • исходный код;
  • AST (если сохранён плагинами или внутренними механизмами);
  • список импортов и экспортов;
  • зависимости от других модулей;
  • флаги изменённости.

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

Повторное использование кеша

При повторной сборке с переданным cache Rollup выполняет дифференциальный анализ:

  1. Проверяет, изменился ли исходный файл модуля.
  2. Сравнивает хеши или timestamps (в зависимости от среды).
  3. Помечает изменённые модули как invalid.
  4. Переиспользует неизменённые модули из кеша.
  5. Пересчитывает только затронутую часть графа.

Таким образом, при небольших изменениях перерасчёт ограничивается локальной областью зависимостей.

Особенно эффективно кеширование работает в проектах с большим количеством модулей, где изменение одного файла не требует пересборки всего дерева.

Влияние на watch режим

В режиме наблюдения (watch) кеш играет центральную роль. Каждый цикл пересборки:

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

Это создаёт непрерывную цепочку инкрементальных состояний.

В watch-режиме кеш:

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

Однако при некорректной работе плагинов кеш может приводить к «залипанию» состояния, когда изменения не отражаются из-за неправильной инвалидации.

Инвалидация кеша

Ключевой аспект кеширования — корректная инвалидация. Rollup автоматически инвалидирует модули при:

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

Плагины могут влиять на кеш через:

  • transform — изменение содержимого модуля;
  • resolveId — изменение структуры графа;
  • load — подмена содержимого модуля.

Если плагин возвращает разные результаты для одного и того же входа, но не сообщает Rollup о необходимости пересборки, кеш может стать источником неконсистентного состояния.

Взаимодействие с плагинами

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

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

Типичный сценарий — сохранение результатов дорогих вычислений, например:

  • парсинга JSON схем;
  • анализа AST;
  • генерации промежуточных представлений;
  • резолвинга внешних ресурсов.

Важно, что плагинный кеш и общий cache Rollup — разные уровни абстракции: первый локальный, второй глобальный для всей сборки.

Производительность и эффект кеша

Использование cache влияет на производительность в нескольких направлениях:

  • уменьшение времени парсинга исходного кода;
  • снижение нагрузки на резолвер модулей;
  • сокращение количества вызовов плагинов;
  • уменьшение работы garbage collector за счёт повторного использования структур.

В крупных проектах прирост скорости повторной сборки может быть кратным, особенно если:

  • количество модулей превышает сотни или тысячи;
  • используются тяжёлые трансформации (TypeScript, JSX, SASS через плагины);
  • граф зависимостей глубокий и сложный.

Ограничения кеширования

Кеш в Rollup имеет ряд ограничений, которые необходимо учитывать:

  • не гарантируется стабильность структуры объекта cache между версиями;
  • не все плагины корректно поддерживают инкрементальную сборку;
  • некоторые трансформации не кэшируются из-за своей недетерминированной природы;
  • изменение конфигурации может полностью инвалидировать кеш;
  • внешние зависимости могут обходить механизмы инвалидации.

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

Практическое использование cache в API

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

  • первая сборка выполняется без кеша;
  • результат сохраняется;
  • последующие сборки используют bundle.cache.

Это особенно характерно для кастомных build-систем, где Rollup используется как движок:

  • сборка при изменении файлов;
  • интеграция с dev-серверами;
  • гибридные пайплайны с другими инструментами.

Передача кеша выглядит как часть входных опций:

const bundle = await rollup.rollup({
  input: 'src/index.js',
  cache: previousCache
});

После завершения:

previousCache = bundle.cache;

Типичные ошибки при работе с cache

Неправильное использование кеша часто приводит к нестабильным результатам:

  • сохранение и повторное использование кеша между несовместимыми конфигурациями;
  • попытка модификации объекта cache вручную;
  • использование кеша при радикально изменённой структуре проекта;
  • игнорирование влияния плагинов на инвалидацию;
  • ожидание полного ускорения при наличии тяжёлых transform-операций, которые не кешируются.

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