Настройка cacheDirectory и cacheLocation

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

Ключевую роль в файловом кэшировании играют параметры:

  • cacheDirectory
  • cacheLocation

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


Эволюция кэширования в Webpack

Кэширование до Webpack 5

В Webpack 4 и ранних версиях полноценного встроенного persistent cache не существовало. Основной механизм ускорения сборки обеспечивали:

  • cache-loader
  • кэш внутри babel-loader
  • сторонние плагины
  • временные каталоги в node_modules/.cache

Именно в этот период широкое распространение получил параметр cacheDirectory.

Пример:

{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true
    }
}

В этом случае babel-loader создавал каталог кэша и сохранял туда результаты трансформации Babel.


Появление persistent cache в Webpack 5

Webpack 5 внедрил полноценную файловую систему кэширования:

module.exports = {
    cache: {
        type: 'filesystem'
    }
};

Теперь Webpack самостоятельно сохраняет:

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

В рамках этой системы появился параметр cacheLocation.


Параметр cacheDirectory

Где используется cacheDirectory

cacheDirectory чаще всего относится не к самому Webpack, а к отдельным инструментам:

  • babel-loader
  • eslint-loader
  • postcss-loader
  • terser-webpack-plugin
  • некоторые кастомные loader’ы

Наиболее распространённый пример — babel-loader.


Базовая настройка cacheDirectory

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                options: {
                    cacheDirectory: true
                }
            }
        ]
    }
};

При значении true Babel создаёт директорию автоматически.

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

node_modules/.cache/babel-loader

Явное указание директории

Вместо true можно указать путь:

options: {
    cacheDirectory: path.resolve(__dirname, '.babel-cache')
}

Теперь кэш будет храниться в:

.babel-cache

Что сохраняется в cacheDirectory

babel-loader кэширует:

  • результат транспиляции
  • source maps
  • вычисленные AST
  • параметры конфигурации

Это избавляет Babel от повторной обработки неизменённых файлов.


Пример ускорения

Без кэша:

Initial build: 18s
Rebuild: 16s

С cacheDirectory:

Initial build: 19s
Rebuild: 2s

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


Как работает cacheDirectory

Формирование ключа кэша

Loader вычисляет специальный hash на основе:

  • содержимого файла
  • версии Babel
  • конфигурации Babel
  • версии loader’а
  • environment variables

Если hash совпадает, результат берётся из кэша.


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

Кэш автоматически пересоздаётся при изменении:

{
    "presets": ["@babel/preset-env"]
}

Например, добавление нового preset:

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

приведёт к полной перестройке кэша.


Ограничения cacheDirectory

Кэширует только конкретный loader

cacheDirectory не ускоряет:

  • анализ графа модулей
  • chunk splitting
  • tree shaking
  • оптимизацию Webpack

Он ускоряет только работу конкретного инструмента.


Возможен рост размера кэша

Большие проекты могут создавать гигабайты временных файлов:

node_modules/.cache/

Особенно при:

  • множестве entry points
  • monorepo
  • большом количестве Babel plugins

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

Если узким местом является:

  • Terser
  • source maps
  • SplitChunksPlugin
  • медленный filesystem
  • giant dependency graph

то cacheDirectory почти не поможет.


Параметр cacheLocation

Назначение

cacheLocation относится уже к встроенному filesystem cache Webpack 5.

Пример:

module.exports = {
    cache: {
        type: 'filesystem',
        cacheLocation: path.resolve(__dirname, '.webpack-cache')
    }
};

Webpack будет сохранять persistent cache в:

.webpack-cache

Базовая настройка filesystem cache

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

module.exports = {
    cache: {
        type: 'filesystem'
    }
};

По умолчанию Webpack создаёт каталог:

node_modules/.cache/webpack

Явная настройка cacheLocation

const path = require('path');

module.exports = {
    cache: {
        type: 'filesystem',
        cacheLocation: path.resolve(
            __dirname,
            '.cache/webpack'
        )
    }
};

Что хранится в filesystem cache

В отличие от cacheDirectory, persistent cache Webpack значительно шире.

Кэшируются:

  • module graph
  • dependency graph
  • transformed modules
  • generated chunks
  • serialization data
  • build dependencies
  • snapshots
  • resolver cache

Это полноценное состояние сборки.


Структура filesystem cache

После сборки можно увидеть:

.cache/
└── webpack/
    ├── default-development/
    ├── default-production/
    └── index.pack

Webpack создаёт отдельные сегменты кэша для:

  • production
  • development
  • разных конфигураций
  • различных target

Как работает cacheLocation

Сериализация данных

Webpack сериализует внутренние структуры:

  • Module instances
  • ChunkGraph
  • Resolver snapshots
  • BuildInfo

После этого данные записываются в бинарные pack-файлы.


Восстановление состояния

При следующем запуске Webpack:

  1. проверяет snapshot файлов
  2. сравнивает timestamps
  3. анализирует зависимости
  4. восстанавливает сериализованные данные

Если файлы не изменились, пересборка не требуется.


Связь cacheLocation и name

Webpack может использовать несколько независимых кэшей.

Пример:

cache: {
    type: 'filesystem',
    name: 'client-cache',
    cacheLocation: path.resolve(
        __dirname,
        '.cache/client'
    )
}

Для server bundle:

cache: {
    type: 'filesystem',
    name: 'server-cache',
    cacheLocation: path.resolve(
        __dirname,
        '.cache/server'
    )
}

Это особенно важно при:

  • SSR
  • multi-compiler
  • monorepo
  • microfrontend architecture

Практические схемы размещения кэша

Стандартная схема

node_modules/.cache/

Преимущества:

  • привычная структура
  • совместимость с инструментами
  • автоматическая очистка при удалении node_modules

Недостатки:

  • удаление node_modules уничтожает кэш
  • медленная повторная установка зависимостей

Отдельный .cache

project/
├── .cache/
│   └── webpack/
├── src/
└── package.json

Преимущества:

  • независимость от node_modules
  • удобно исключать из Git
  • проще использовать в CI

Временная RAM-директория

Linux:

cacheLocation: '/tmp/webpack-cache'

macOS:

cacheLocation: '/private/tmp/webpack-cache'

Преимущества:

  • максимальная скорость IO

Недостатки:

  • кэш исчезает после перезагрузки
  • не подходит для CI persistence

Настройка кэша в CI/CD

Проблема холодных сборок

В CI каждый pipeline часто стартует с пустого окружения.

Без сохранения cache:

Build #1: 9m
Build #2: 9m
Build #3: 9m

Сохранение cache directory

Например:

.cache/webpack

можно сохранять между pipeline.

GitHub Actions:

- uses: actions/cache@v3
  with:
    path: .cache/webpack
    key: webpack-cache

Build dependencies

Webpack умеет автоматически инвалидировать кэш при изменении конфигурации.

Пример:

cache: {
    type: 'filesystem',
    buildDependencies: {
        config: [__filename]
    }
}

Если меняется webpack.config.js, кэш сбрасывается.


Snapshot system

Webpack 5 использует snapshot-механизм.

Snapshot содержит:

  • timestamps
  • hashes
  • metadata файлов

Это позволяет быстро понимать:

  • изменился ли файл
  • нужно ли пересобрать модуль
  • можно ли восстановить данные из cache

Влияние SSD и HDD

Filesystem cache сильно зависит от скорости диска.

HDD

Проблемы:

  • медленный random access
  • высокий latency
  • медленное чтение pack-файлов

SSD/NVMe

Преимущества:

  • быстрый random IO
  • быстрое восстановление сериализованных данных
  • ускорение incremental build

На NVMe разница особенно заметна в крупных monorepo.


Проблемы больших cache directory

Переполнение кэша

Крупные проекты могут создавать:

5-20 GB cache

Причины:

  • source maps
  • огромный dependency graph
  • multiple targets
  • persistent snapshots

Замедление файловой системы

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

  • Docker volumes
  • WSL2
  • network filesystem
  • HDD

Очистка кэша

Полная очистка

rm -rf node_modules/.cache

Очистка только Webpack cache

rm -rf node_modules/.cache/webpack

Очистка Babel cache

rm -rf node_modules/.cache/babel-loader

Когда кэш становится вредным

Частые изменения конфигурации

Если постоянно меняются:

  • Babel plugins
  • aliases
  • loaders
  • environment variables

то invalidation может происходить почти на каждой сборке.


Нестабильные loader’ы

Некоторые loader’ы:

  • используют случайные значения
  • зависят от внешних ресурсов
  • имеют side effects

Это делает кэш малоэффективным.


Комбинирование cacheDirectory и cacheLocation

Оба механизма могут использоваться одновременно.

Пример:

module.exports = {
    cache: {
        type: 'filesystem',
        cacheLocation: path.resolve(
            __dirname,
            '.cache/webpack'
        )
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                options: {
                    cacheDirectory: path.resolve(
                        __dirname,
                        '.cache/babel'
                    )
                }
            }
        ]
    }
};

Что происходит в такой конфигурации

Babel cache

Кэширует:

  • transpilation result
  • AST
  • source maps

Webpack filesystem cache

Кэширует:

  • module graph
  • dependency graph
  • chunk graph
  • serialization data

Производительность incremental build

Типичный сценарий:

Конфигурация Rebuild
Без cache 20s
Только Babel cache 8s
Только filesystem cache 4s
Оба cache 1.5s

Лучшие практики

Использование filesystem cache в Webpack 5

Современный стандарт:

cache: {
    type: 'filesystem'
}

Размещение cache вне Git

.gitignore:

.cache
node_modules/.cache

Разделение cache по окружениям

cache: {
    type: 'filesystem',
    name: process.env.NODE_ENV
}

Разделение cache для client/server

.cache/client
.cache/server

Использование стабильных путей

Нежелательно:

cacheLocation: `/tmp/${Date.now()}`

Это полностью ломает повторное использование кэша.


Типичные ошибки

Кэш внутри Docker layer

Плохой вариант:

RUN npm install
RUN npm run build

При изменении любого файла весь слой инвалидируется.


Общий cache для разных проектов

Опасный вариант:

cacheLocation: '/tmp/webpack-cache'

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

Возможны:

  • конфликты
  • повреждение кэша
  • неверные rebuild

Кэш в network filesystem

Например:

  • NFS
  • SMB
  • удалённые Docker volumes

Filesystem cache может работать медленнее, чем без кэша.


Сравнение cacheDirectory и cacheLocation

Параметр cacheDirectory cacheLocation
Относится к loader Webpack
Основная задача кэш трансформаций persistent build cache
Появился до Webpack 5 Webpack 5
Масштаб кэша локальный глобальный
Тип данных transpilation results build graph
Чаще всего используется Babel Webpack filesystem cache
Влияет на rebuild частично значительно
Ускоряет resolver нет да
Ускоряет chunk graph нет да
Поддерживает snapshots нет да